+2014-03-07 Carl Hetherington <cth@carlh.net>
+
+ * Add subtitle view.
+
2014-05-19 Carl Hetherington <cth@carlh.net>
* Version 1.69.9 released.
-# Doxyfile 1.8.2
+# Doxyfile 1.8.6
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
#
-# All text after a hash (#) is considered a comment and will be ignored.
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
# The format is:
-# TAG = value [value, ...]
-# For lists items can also be appended using:
-# TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (" ").
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
# This tag specifies the encoding used for all characters in the config file
-# that follow. The default is UTF-8 which is also the encoding used for all
-# text before the first occurrence of this tag. Doxygen uses libiconv (or the
-# iconv built into libc) for the transcoding. See
-# http://www.gnu.org/software/libiconv for the list of possible encodings.
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
DOXYFILE_ENCODING = UTF-8
-# The PROJECT_NAME tag is a single word (or sequence of words) that should
-# identify the project. Note that if you do not use Doxywizard you need
-# to put quotes around the project name if it contains spaces.
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
PROJECT_NAME = DCP-o-matic
-# The PROJECT_NUMBER tag can be used to enter a project or revision number.
-# This could be handy for archiving the generated documentation or
-# if some version control system is used.
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
PROJECT_NUMBER =
# Using the PROJECT_BRIEF tag one can provide an optional one line description
-# for a project that appears at the top of each page and should give viewer
-# a quick idea about the purpose of the project. Keep the description short.
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
PROJECT_BRIEF =
-# With the PROJECT_LOGO tag one can specify an logo or icon that is
-# included in the documentation. The maximum height of the logo should not
-# exceed 55 pixels and the maximum width should not exceed 200 pixels.
-# Doxygen will copy the logo to the output directory.
+# With the PROJECT_LOGO tag one can specify an logo or icon that is included in
+# the documentation. The maximum height of the logo should not exceed 55 pixels
+# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
+# to the output directory.
PROJECT_LOGO =
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
-# base path where the generated documentation will be put.
-# If a relative path is entered, it will be relative to the location
-# where doxygen was started. If left blank the current directory will be used.
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
OUTPUT_DIRECTORY = build/doc
-# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
-# 4096 sub-directories (in 2 levels) under the output directory of each output
-# format and will distribute the generated files over these directories.
-# Enabling this option can be useful when feeding doxygen a huge amount of
-# source files, where putting all generated files in the same directory would
-# otherwise cause performance problems for the file system.
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
CREATE_SUBDIRS = NO
# The OUTPUT_LANGUAGE tag is used to specify the language in which all
# documentation generated by doxygen is written. Doxygen will use this
# information to generate all constant output in the proper language.
-# The default language is English, other supported languages are:
-# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
-# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
-# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
-# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
-# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
-# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
OUTPUT_LANGUAGE = English
-# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
-# include brief member descriptions after the members that are listed in
-# the file and class documentation (similar to JavaDoc).
-# Set to NO to disable this.
+# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
BRIEF_MEMBER_DESC = YES
-# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
-# the brief description of a member or function before the detailed description.
-# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
# brief descriptions will be completely suppressed.
+# The default value is: YES.
REPEAT_BRIEF = YES
-# This tag implements a quasi-intelligent brief description abbreviator
-# that is used to form the text in various listings. Each string
-# in this list, if found as the leading text of the brief description, will be
-# stripped from the text and the result after processing the whole list, is
-# used as the annotated text. Otherwise, the brief description is used as-is.
-# If left blank, the following values are used ("$name" is automatically
-# replaced with the name of the entity): "The $name class" "The $name widget"
-# "The $name file" "is" "provides" "specifies" "contains"
-# "represents" "a" "an" "the"
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
ABBREVIATE_BRIEF =
# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# Doxygen will generate a detailed section even if there is only a brief
+# doxygen will generate a detailed section even if there is only a brief
# description.
+# The default value is: NO.
ALWAYS_DETAILED_SEC = NO
# inherited members of a class in the documentation of that class as if those
# members were ordinary class members. Constructors, destructors and assignment
# operators of the base classes will not be shown.
+# The default value is: NO.
INLINE_INHERITED_MEMB = NO
-# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
-# path before files name in the file list and in the header files. If set
-# to NO the shortest path that makes the file name unique will be used.
+# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
FULL_PATH_NAMES = YES
-# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
-# can be used to strip a user-defined part of the path. Stripping is
-# only done if one of the specified strings matches the left-hand part of
-# the path. The tag can be used to show relative paths in the file list.
-# If left blank the directory from which doxygen is run is used as the
-# path to strip. Note that you specify absolute paths here, but also
-# relative paths, which will be relative from the directory where doxygen is
-# started.
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
STRIP_FROM_PATH =
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
-# the path mentioned in the documentation of a class, which tells
-# the reader which header file to include in order to use a class.
-# If left blank only the name of the header file containing the class
-# definition is used. Otherwise one should specify the include paths that
-# are normally passed to the compiler using the -I flag.
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
STRIP_FROM_INC_PATH =
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
-# (but less readable) file names. This can be useful if your file system
-# doesn't support long names like on DOS, Mac, or CD-ROM.
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
SHORT_NAMES = NO
-# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
-# will interpret the first line (until the first dot) of a JavaDoc-style
-# comment as the brief description. If set to NO, the JavaDoc
-# comments will behave just like regular Qt-style comments
-# (thus requiring an explicit @brief command for a brief description.)
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
JAVADOC_AUTOBRIEF = NO
-# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
-# interpret the first line (until the first dot) of a Qt-style
-# comment as the brief description. If set to NO, the comments
-# will behave just like regular Qt-style comments (thus requiring
-# an explicit \brief command for a brief description.)
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
QT_AUTOBRIEF = NO
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
-# treat a multi-line C++ special comment block (i.e. a block of //! or ///
-# comments) as a brief description. This used to be the default behaviour.
-# The new default is to treat a multi-line C++ comment block as a detailed
-# description. Set this tag to YES if you prefer the old behaviour instead.
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
MULTILINE_CPP_IS_BRIEF = NO
-# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
-# member inherits the documentation from any documented member that it
-# re-implements.
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
INHERIT_DOCS = YES
-# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
-# a new page for each member. If set to NO, the documentation of a member will
-# be part of the file/class/namespace that contains it.
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a
+# new page for each member. If set to NO, the documentation of a member will be
+# part of the file/class/namespace that contains it.
+# The default value is: NO.
SEPARATE_MEMBER_PAGES = NO
-# The TAB_SIZE tag can be used to set the number of spaces in a tab.
-# Doxygen uses this value to replace tabs by spaces in code fragments.
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
TAB_SIZE = 8
-# This tag can be used to specify a number of aliases that acts
-# as commands in the documentation. An alias has the form "name=value".
-# For example adding "sideeffect=\par Side Effects:\n" will allow you to
-# put the command \sideeffect (or @sideeffect) in the documentation, which
-# will result in a user-defined paragraph with heading "Side Effects:".
-# You can put \n's in the value part of an alias to insert newlines.
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
ALIASES =
# This tag can be used to specify a number of word-keyword mappings (TCL only).
-# A mapping has the form "name=value". For example adding
-# "class=itcl::class" will allow you to use the command class in the
-# itcl::class meaning.
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
TCL_SUBST =
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
-# sources only. Doxygen will then generate output that is more tailored for C.
-# For instance, some of the names that are used will be different. The list
-# of all members will be omitted, etc.
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
OPTIMIZE_OUTPUT_FOR_C = NO
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
-# sources only. Doxygen will then generate output that is more tailored for
-# Java. For instance, namespaces will be presented as packages, qualified
-# scopes will look different, etc.
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
OPTIMIZE_OUTPUT_JAVA = NO
# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
-# sources only. Doxygen will then generate output that is more tailored for
-# Fortran.
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
OPTIMIZE_FOR_FORTRAN = NO
# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
-# sources. Doxygen will then generate output that is tailored for
-# VHDL.
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
OPTIMIZE_OUTPUT_VHDL = NO
# Doxygen selects the parser to use depending on the extension of the files it
# parses. With this tag you can assign which parser to use for a given
# extension. Doxygen has a built-in mapping, but you can override or extend it
-# using this tag. The format is ext=language, where ext is a file extension,
-# and language is one of the parsers supported by doxygen: IDL, Java,
-# Javascript, CSharp, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL, C,
-# C++. For instance to make doxygen treat .inc files as Fortran files (default
-# is PHP), and .f files as C (default is Fortran), use: inc=Fortran f=C. Note
-# that for custom extensions you also need to set FILE_PATTERNS otherwise the
-# files are not read by doxygen.
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C.
+#
+# Note For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
EXTENSION_MAPPING =
-# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all
-# comments according to the Markdown format, which allows for more readable
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
# documentation. See http://daringfireball.net/projects/markdown/ for details.
-# The output of markdown processing is further processed by doxygen, so you
-# can mix doxygen, HTML, and XML commands with Markdown formatting.
-# Disable only in case of backward compatibilities issues.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
MARKDOWN_SUPPORT = YES
-# When enabled doxygen tries to link words that correspond to documented classes,
-# or namespaces to their corresponding documentation. Such a link can be
-# prevented in individual cases by by putting a % sign in front of the word or
-# globally by setting AUTOLINK_SUPPORT to NO.
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by by putting a % sign in front of the word
+# or globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
AUTOLINK_SUPPORT = YES
# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
-# to include (a tag file for) the STL sources as input, then you should
-# set this tag to YES in order to let doxygen match functions declarations and
-# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
-# func(std::string) {}). This also makes the inheritance and collaboration
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
BUILTIN_STL_SUPPORT = NO
# If you use Microsoft's C++/CLI language, you should set this option to YES to
# enable parsing support.
+# The default value is: NO.
CPP_CLI_SUPPORT = NO
-# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
-# Doxygen will parse them like normal C++ but will assume all classes use public
-# instead of private inheritance when no explicit protection keyword is present.
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
SIP_SUPPORT = NO
-# For Microsoft's IDL there are propget and propput attributes to indicate getter and setter methods for a property. Setting this option to YES (the default) will make doxygen replace the get and set methods by a property in the documentation. This will only work if the methods are indeed getting or setting a simple type. If this is not the case, or you want to show the methods anyway, you should set this option to NO.
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
IDL_PROPERTY_SUPPORT = YES
# tag is set to YES, then doxygen will reuse the documentation of the first
# member in the group (if any) for the other members of the group. By default
# all members of a group must be documented explicitly.
+# The default value is: NO.
DISTRIBUTE_GROUP_DOC = NO
-# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
-# the same type (for instance a group of public functions) to be put as a
-# subgroup of that type (e.g. under the Public Functions section). Set it to
-# NO to prevent subgrouping. Alternatively, this can be done per class using
-# the \nosubgrouping command.
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
SUBGROUPING = YES
-# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
-# unions are shown inside the group in which they are included (e.g. using
-# @ingroup) instead of on a separate page (for HTML and Man pages) or
-# section (for LaTeX and RTF).
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
INLINE_GROUPED_CLASSES = NO
-# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and
-# unions with only public data fields will be shown inline in the documentation
-# of the scope in which they are defined (i.e. file, namespace, or group
-# documentation), provided this scope is documented. If set to NO (the default),
-# structs, classes, and unions are shown on a separate page (for HTML and Man
-# pages) or section (for LaTeX and RTF).
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
INLINE_SIMPLE_STRUCTS = NO
-# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
-# is documented as struct, union, or enum with the name of the typedef. So
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
# with name TypeT. When disabled the typedef will appear as a member of a file,
-# namespace, or class. And the struct will be named TypeS. This can typically
-# be useful for C code in case the coding convention dictates that all compound
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
TYPEDEF_HIDES_STRUCT = NO
-# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
-# determine which symbols to keep in memory and which to flush to disk.
-# When the cache is full, less often used symbols will be written to disk.
-# For small to medium size projects (<1000 input files) the default value is
-# probably good enough. For larger projects a too small cache size can cause
-# doxygen to be busy swapping symbols to and from disk most of the time
-# causing a significant performance penalty.
-# If the system has enough physical memory increasing the cache will improve the
-# performance by keeping more symbols in memory. Note that the value works on
-# a logarithmic scale so increasing the size by one will roughly double the
-# memory usage. The cache size is given by this formula:
-# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
-# corresponding to a cache size of 2^16 = 65536 symbols.
-
-SYMBOL_CACHE_SIZE = 0
-
-# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be
-# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given
-# their name and scope. Since this can be an expensive process and often the
-# same symbol appear multiple times in the code, doxygen keeps a cache of
-# pre-resolved symbols. If the cache is too small doxygen will become slower.
-# If the cache is too large, memory is wasted. The cache size is given by this
-# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0,
-# corresponding to a cache size of 2^16 = 65536 symbols.
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
LOOKUP_CACHE_SIZE = 0
#---------------------------------------------------------------------------
# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
-# documentation are documented, even if no documentation was available.
-# Private class members and static file members will be hidden unless
-# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
EXTRACT_ALL = NO
-# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
-# will be included in the documentation.
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
EXTRACT_PRIVATE = NO
# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
# scope will be included in the documentation.
+# The default value is: NO.
EXTRACT_PACKAGE = NO
-# If the EXTRACT_STATIC tag is set to YES all static members of a file
-# will be included in the documentation.
+# If the EXTRACT_STATIC tag is set to YES all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
EXTRACT_STATIC = NO
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
-# defined locally in source files will be included in the documentation.
-# If set to NO only classes defined in header files are included.
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
EXTRACT_LOCAL_CLASSES = YES
-# This flag is only useful for Objective-C code. When set to YES local
-# methods, which are defined in the implementation section but not in
-# the interface are included in the documentation.
-# If set to NO (the default) only methods in the interface are included.
+# This flag is only useful for Objective-C code. When set to YES local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO only methods in the interface are
+# included.
+# The default value is: NO.
EXTRACT_LOCAL_METHODS = NO
# If this flag is set to YES, the members of anonymous namespaces will be
# extracted and appear in the documentation as a namespace called
-# 'anonymous_namespace{file}', where file will be replaced with the base
-# name of the file that contains the anonymous namespace. By default
-# anonymous namespaces are hidden.
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
EXTRACT_ANON_NSPACES = NO
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
-# undocumented members of documented classes, files or namespaces.
-# If set to NO (the default) these members will be included in the
-# various overviews, but no documentation section is generated.
-# This option has no effect if EXTRACT_ALL is enabled.
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
HIDE_UNDOC_MEMBERS = NO
-# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy.
-# If set to NO (the default) these classes will be included in the various
-# overviews. This option has no effect if EXTRACT_ALL is enabled.
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO these classes will be included in the various overviews. This option has
+# no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
HIDE_UNDOC_CLASSES = NO
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
-# friend (class|struct|union) declarations.
-# If set to NO (the default) these declarations will be included in the
-# documentation.
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO these declarations will be
+# included in the documentation.
+# The default value is: NO.
HIDE_FRIEND_COMPOUNDS = NO
-# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
-# documentation blocks found inside the body of a function.
-# If set to NO (the default) these blocks will be appended to the
-# function's detailed documentation block.
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
HIDE_IN_BODY_DOCS = NO
-# The INTERNAL_DOCS tag determines if documentation
-# that is typed after a \internal command is included. If the tag is set
-# to NO (the default) then the documentation will be excluded.
-# Set it to YES to include the internal documentation.
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
INTERNAL_DOCS = NO
-# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
-# file names in lower-case letters. If set to YES upper-case letters are also
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES upper-case letters are also
# allowed. This is useful if you have classes or files whose names only differ
# in case and if your file system supports case sensitive file names. Windows
# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
CASE_SENSE_NAMES = YES
-# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
-# will show members with their full class and namespace scopes in the
-# documentation. If set to YES the scope will be hidden.
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES the
+# scope will be hidden.
+# The default value is: NO.
HIDE_SCOPE_NAMES = NO
-# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
-# will put a list of the files that are included by a file in the documentation
-# of that file.
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
SHOW_INCLUDE_FILES = YES
-# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
-# will list include files with double quotes in the documentation
-# rather than with sharp brackets.
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
FORCE_LOCAL_INCLUDES = NO
-# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
-# is inserted in the documentation for inline members.
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
INLINE_INFO = YES
-# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
-# will sort the (detailed) documentation of file and class members
-# alphabetically by member name. If set to NO the members will appear in
-# declaration order.
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order.
+# The default value is: YES.
SORT_MEMBER_DOCS = YES
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
-# brief documentation of file, namespace and class members alphabetically
-# by member name. If set to NO (the default) the members will appear in
-# declaration order.
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
SORT_BRIEF_DOCS = NO
-# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
-# will sort the (brief and detailed) documentation of class members so that
-# constructors and destructors are listed first. If set to NO (the default)
-# the constructors will appear in the respective orders defined by
-# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
-# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
-# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
SORT_MEMBERS_CTORS_1ST = NO
-# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
-# hierarchy of group names into alphabetical order. If set to NO (the default)
-# the group names will appear in their defined order.
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
SORT_GROUP_NAMES = NO
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
-# sorted by fully-qualified names, including namespaces. If set to
-# NO (the default), the class list will be sorted only by class name,
-# not including the namespace part.
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the
-# alphabetical list.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
SORT_BY_SCOPE_NAME = NO
-# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
-# do proper type resolution of all parameters of a function it will reject a
-# match between the prototype and the implementation of a member function even
-# if there is only one candidate or it is obvious which candidate to choose
-# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
-# will still accept a match between prototype and implementation in such cases.
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
STRICT_PROTO_MATCHING = NO
-# The GENERATE_TODOLIST tag can be used to enable (YES) or
-# disable (NO) the todo list. This list is created by putting \todo
-# commands in the documentation.
+# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the
+# todo list. This list is created by putting \todo commands in the
+# documentation.
+# The default value is: YES.
GENERATE_TODOLIST = YES
-# The GENERATE_TESTLIST tag can be used to enable (YES) or
-# disable (NO) the test list. This list is created by putting \test
-# commands in the documentation.
+# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the
+# test list. This list is created by putting \test commands in the
+# documentation.
+# The default value is: YES.
GENERATE_TESTLIST = YES
-# The GENERATE_BUGLIST tag can be used to enable (YES) or
-# disable (NO) the bug list. This list is created by putting \bug
-# commands in the documentation.
+# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
GENERATE_BUGLIST = YES
-# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
-# disable (NO) the deprecated list. This list is created by putting
-# \deprecated commands in the documentation.
+# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
GENERATE_DEPRECATEDLIST= YES
-# The ENABLED_SECTIONS tag can be used to enable conditional
-# documentation sections, marked by \if sectionname ... \endif.
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
ENABLED_SECTIONS =
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
-# the initial value of a variable or macro consists of for it to appear in
-# the documentation. If the initializer consists of more lines than specified
-# here it will be hidden. Use a value of 0 to hide initializers completely.
-# The appearance of the initializer of individual variables and macros in the
-# documentation can be controlled using \showinitializer or \hideinitializer
-# command in the documentation regardless of this setting.
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
MAX_INITIALIZER_LINES = 30
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
-# at the bottom of the documentation of classes and structs. If set to YES the
-# list will mention the files that were used to generate the documentation.
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES the list
+# will mention the files that were used to generate the documentation.
+# The default value is: YES.
SHOW_USED_FILES = YES
-# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
-# This will remove the Files entry from the Quick Index and from the
-# Folder Tree View (if specified). The default is YES.
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
SHOW_FILES = YES
-# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
-# Namespaces page.
-# This will remove the Namespaces entry from the Quick Index
-# and from the Folder Tree View (if specified). The default is YES.
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
SHOW_NAMESPACES = YES
# The FILE_VERSION_FILTER tag can be used to specify a program or script that
# doxygen should invoke to get the current version for each file (typically from
# the version control system). Doxygen will invoke the program by executing (via
-# popen()) the command <command> <input-file>, where <command> is the value of
-# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
-# provided by doxygen. Whatever the program writes to standard output
-# is used as the file version. See the manual for examples.
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
FILE_VERSION_FILTER =
# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
# by doxygen. The layout file controls the global structure of the generated
# output files in an output format independent way. To create the layout file
-# that represents doxygen's defaults, run doxygen with the -l option.
-# You can optionally specify a file name after the option, if omitted
-# DoxygenLayout.xml will be used as the name of the layout file.
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
LAYOUT_FILE =
-# The CITE_BIB_FILES tag can be used to specify one or more bib files
-# containing the references data. This must be a list of .bib files. The
-# .bib extension is automatically appended if omitted. Using this command
-# requires the bibtex tool to be installed. See also
-# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style
-# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this
-# feature you need bibtex and perl available in the search path.
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. Do not use file names with spaces, bibtex cannot handle them. See
+# also \cite for info how to create references.
CITE_BIB_FILES =
#---------------------------------------------------------------------------
-# configuration options related to warning and progress messages
+# Configuration options related to warning and progress messages
#---------------------------------------------------------------------------
-# The QUIET tag can be used to turn on/off the messages that are generated
-# by doxygen. Possible values are YES and NO. If left blank NO is used.
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
QUIET = YES
# The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated by doxygen. Possible values are YES and NO. If left blank
-# NO is used.
+# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
WARNINGS = YES
-# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
-# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
-# automatically be disabled.
+# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
WARN_IF_UNDOCUMENTED = YES
-# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some
-# parameters in a documented function, or documenting parameters that
-# don't exist or using markup commands wrongly.
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
WARN_IF_DOC_ERROR = YES
-# The WARN_NO_PARAMDOC option can be enabled to get warnings for
-# functions that are documented, but have no documentation for their parameters
-# or return value. If set to NO (the default) doxygen will only warn about
-# wrong or incomplete parameter documentation, but not about the absence of
-# documentation.
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO doxygen will only warn about wrong or incomplete parameter
+# documentation, but not about the absence of documentation.
+# The default value is: NO.
WARN_NO_PARAMDOC = NO
-# The WARN_FORMAT tag determines the format of the warning messages that
-# doxygen can produce. The string should contain the $file, $line, and $text
-# tags, which will be replaced by the file and line number from which the
-# warning originated and the warning text. Optionally the format may contain
-# $version, which will be replaced by the version of the file (if it could
-# be obtained via FILE_VERSION_FILTER)
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
WARN_FORMAT = "$file:$line: $text"
-# The WARN_LOGFILE tag can be used to specify a file to which warning
-# and error messages should be written. If left blank the output is written
-# to stderr.
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
WARN_LOGFILE =
#---------------------------------------------------------------------------
-# configuration options related to the input files
+# Configuration options related to the input files
#---------------------------------------------------------------------------
-# The INPUT tag can be used to specify the files and/or directories that contain
-# documented source files. You may enter file names like "myfile.cpp" or
-# directories like "/usr/src/myproject". Separate the files or directories
-# with spaces.
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces.
+# Note: If this tag is empty the current directory is searched.
-INPUT = src/lib src/wx src/tools \
+INPUT = src/lib \
+ src/wx \
+ src/tools \
+ test \
doc/mainpage.txt
# This tag can be used to specify the character encoding of the source files
-# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
-# also the default input encoding. Doxygen uses libiconv (or the iconv built
-# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
-# the list of possible encodings.
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
INPUT_ENCODING = UTF-8
# If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank the following patterns are tested:
-# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
-# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
-# *.f90 *.f *.for *.vhd *.vhdl
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank the
+# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,
+# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,
+# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,
+# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
+# *.qsf, *.as and *.js.
FILE_PATTERNS =
-# The RECURSIVE tag can be used to turn specify whether or not subdirectories
-# should be searched for input files as well. Possible values are YES and NO.
-# If left blank NO is used.
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
RECURSIVE = NO
# The EXCLUDE tag can be used to specify files and/or directories that should be
# excluded from the INPUT source files. This way you can easily exclude a
# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
# Note that relative paths are relative to the directory from which doxygen is
# run.
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded
# from the input.
+# The default value is: NO.
EXCLUDE_SYMLINKS = NO
# If the value of the INPUT tag contains directories, you can use the
# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories. Note that the wildcards are matched
-# against the file with absolute path, so to exclude all test directories
-# for example use the pattern */test/*
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
EXCLUDE_PATTERNS =
# output. The symbol name can be a fully qualified name, a word, or if the
# wildcard * is used, a substring. Examples: ANamespace, AClass,
# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
EXCLUDE_SYMBOLS =
-# The EXAMPLE_PATH tag can be used to specify one or more files or
-# directories that contain example code fragments that are included (see
-# the \include command).
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
EXAMPLE_PATH =
# If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank all files are included.
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
EXAMPLE_PATTERNS =
# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
-# searched for input files to be used with the \include or \dontinclude
-# commands irrespective of the value of the RECURSIVE tag.
-# Possible values are YES and NO. If left blank NO is used.
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
EXAMPLE_RECURSIVE = NO
-# The IMAGE_PATH tag can be used to specify one or more files or
-# directories that contain image that are included in the documentation (see
-# the \image command).
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
IMAGE_PATH =
# The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command <filter> <input-file>, where <filter>
-# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
-# input file. Doxygen will then use the output that the filter program writes
-# to standard output.
-# If FILTER_PATTERNS is specified, this tag will be
-# ignored.
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
INPUT_FILTER =
# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
-# basis.
-# Doxygen will compare the file name with each pattern and apply the
-# filter if there is a match.
-# The filters are a list of the form:
-# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
-# info on how filters are used. If FILTER_PATTERNS is empty or if
-# non of the patterns match the file name, INPUT_FILTER is applied.
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
FILTER_PATTERNS =
# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER) will be used to filter the input files when producing source
-# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# INPUT_FILTER ) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
FILTER_SOURCE_FILES = NO
# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
-# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
-# and it is also possible to disable source filtering for a specific pattern
-# using *.ext= (so without naming a filter). This option only has effect when
-# FILTER_SOURCE_FILES is enabled.
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
FILTER_SOURCE_PATTERNS =
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
#---------------------------------------------------------------------------
-# configuration options related to source browsing
+# Configuration options related to source browsing
#---------------------------------------------------------------------------
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will
-# be generated. Documented entities will be cross-referenced with these sources.
-# Note: To get rid of all source code in the generated output, make sure also
-# VERBATIM_HEADERS is set to NO.
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
SOURCE_BROWSER = NO
-# Setting the INLINE_SOURCES tag to YES will include the body
-# of functions and classes directly in the documentation.
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
INLINE_SOURCES = NO
-# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
-# doxygen to hide any special comment blocks from generated source code
-# fragments. Normal C, C++ and Fortran comments will always remain visible.
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
STRIP_CODE_COMMENTS = YES
-# If the REFERENCED_BY_RELATION tag is set to YES
-# then for each documented function all documented
-# functions referencing it will be listed.
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
REFERENCED_BY_RELATION = NO
-# If the REFERENCES_RELATION tag is set to YES
-# then for each documented function all documented entities
-# called/used by that function will be listed.
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
REFERENCES_RELATION = NO
-# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
-# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
-# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
-# link to the source code.
-# Otherwise they will link to the documentation.
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES, then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
REFERENCES_LINK_SOURCE = YES
-# If the USE_HTAGS tag is set to YES then the references to source code
-# will point to the HTML generated by the htags(1) tool instead of doxygen
-# built-in source browser. The htags tool is part of GNU's global source
-# tagging system (see http://www.gnu.org/software/global/global.html). You
-# will need version 4.8.6 or higher.
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
USE_HTAGS = NO
-# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
-# will generate a verbatim copy of the header file for each class for
-# which an include is specified. Set to NO to disable this.
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
VERBATIM_HEADERS = YES
#---------------------------------------------------------------------------
-# configuration options related to the alphabetical class index
+# Configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
-# of all compounds will be generated. Enable this if the project
-# contains a lot of classes, structs, unions or interfaces.
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
ALPHABETICAL_INDEX = YES
-# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
-# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
-# in which this list will be split (can be a number in the range [1..20])
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
COLS_IN_ALPHA_INDEX = 5
-# In case all classes in a project start with a common prefix, all
-# classes will be put under the same header in the alphabetical index.
-# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
-# should be ignored while generating the index headers.
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
IGNORE_PREFIX =
#---------------------------------------------------------------------------
-# configuration options related to the HTML output
+# Configuration options related to the HTML output
#---------------------------------------------------------------------------
-# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
-# generate HTML output.
+# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output
+# The default value is: YES.
GENERATE_HTML = YES
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `html' will be used as the default path.
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_OUTPUT = html
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
-# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
-# doxygen will generate files with .html extension.
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_FILE_EXTENSION = .html
-# The HTML_HEADER tag can be used to specify a personal HTML header for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard header. Note that when using a custom header you are responsible
-# for the proper inclusion of any scripts and style sheets that doxygen
-# needs, which is dependent on the configuration options used.
-# It is advised to generate a default header using "doxygen -w html
-# header.html footer.html stylesheet.css YourConfigFile" and then modify
-# that header. Note that the header is subject to change so you typically
-# have to redo this when upgrading to a newer version of doxygen or when
-# changing the value of configuration settings such as GENERATE_TREEVIEW!
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_HEADER =
-# The HTML_FOOTER tag can be used to specify a personal HTML footer for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard footer.
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_FOOTER =
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
-# style sheet that is used by each HTML page. It can be used to
-# fine-tune the look of the HTML output. If left blank doxygen will
-# generate a default style sheet. Note that it is recommended to use
-# HTML_EXTRA_STYLESHEET instead of this one, as it is more robust and this
-# tag will in the future become obsolete.
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_STYLESHEET =
-# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional
-# user-defined cascading style sheet that is included after the standard
-# style sheets created by doxygen. Using this option one can overrule
-# certain style aspects. This is preferred over using HTML_STYLESHEET
-# since it does not replace the standard style sheet and is therefor more
-# robust against future updates. Doxygen will copy the style sheet file to
-# the output directory.
+# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user-
+# defined cascading style sheet that is included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefor more robust against future updates.
+# Doxygen will copy the style sheet file to the output directory. For an example
+# see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET =
# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the HTML output directory. Note
# that these files will be copied to the base HTML output directory. Use the
-# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
-# files. In the HTML_STYLESHEET file, use the file name only. Also note that
-# the files will be copied as-is; there are no commands or markers available.
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_FILES =
-# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
-# Doxygen will adjust the colors in the style sheet and background images
-# according to this color. Hue is specified as an angle on a colorwheel,
-# see http://en.wikipedia.org/wiki/Hue for more information.
-# For instance the value 0 represents red, 60 is yellow, 120 is green,
-# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
-# The allowed range is 0 to 359.
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the stylesheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE_HUE = 220
-# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
-# the colors in the HTML output. For a value of 0 the output will use
-# grayscales only. A value of 255 will produce the most vivid colors.
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE_SAT = 100
-# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
-# the luminance component of the colors in the HTML output. Values below
-# 100 gradually make the output lighter, whereas values above 100 make
-# the output darker. The value divided by 100 is the actual gamma applied,
-# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
-# and 100 does not change the gamma.
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE_GAMMA = 80
# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
-# page will contain the date and time when the page was generated. Setting
-# this to NO can help when comparing the output of multiple runs.
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_TIMESTAMP = YES
# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
# documentation will contain sections that can be hidden and shown after the
# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_DYNAMIC_SECTIONS = NO
-# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of
-# entries shown in the various tree structured indices initially; the user
-# can expand and collapse entries dynamically later on. Doxygen will expand
-# the tree to such a level that at most the specified number of entries are
-# visible (unless a fully collapsed tree already exceeds this amount).
-# So setting the number of entries 1 will produce a full collapsed tree by
-# default. 0 is a special value representing an infinite number of entries
-# and will result in a full expanded tree by default.
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_INDEX_NUM_ENTRIES = 100
-# If the GENERATE_DOCSET tag is set to YES, additional index files
-# will be generated that can be used as input for Apple's Xcode 3
-# integrated development environment, introduced with OSX 10.5 (Leopard).
-# To create a documentation set, doxygen will generate a Makefile in the
-# HTML output directory. Running make will produce the docset in that
-# directory and running "make install" will install the docset in
-# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
-# it at startup.
-# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_DOCSET = NO
-# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
-# feed. A documentation feed provides an umbrella under which multiple
-# documentation sets from a single provider (such as a company or product suite)
-# can be grouped.
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
DOCSET_FEEDNAME = "Doxygen generated docs"
-# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
-# should uniquely identify the documentation set bundle. This should be a
-# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
-# will append .docset to the name.
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
DOCSET_BUNDLE_ID = org.doxygen.Project
-# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely
-# identify the documentation publisher. This should be a reverse domain-name
-# style string, e.g. com.mycompany.MyDocSet.documentation.
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
DOCSET_PUBLISHER_ID = org.doxygen.Publisher
-# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
DOCSET_PUBLISHER_NAME = Publisher
-# If the GENERATE_HTMLHELP tag is set to YES, additional index files
-# will be generated that can be used as input for tools like the
-# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
-# of the generated HTML documentation.
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_HTMLHELP = NO
-# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
-# be used to specify the file name of the resulting .chm file. You
-# can add a path in front of the file if the result should not be
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
CHM_FILE =
-# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
-# be used to specify the location (absolute path including file name) of
-# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
-# the HTML help compiler on the generated index.hhp.
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler ( hhc.exe). If non-empty
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
HHC_LOCATION =
-# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
-# controls if a separate .chi index file is generated (YES) or that
-# it should be included in the master .chm file (NO).
+# The GENERATE_CHI flag controls if a separate .chi index file is generated (
+# YES) or that it should be included in the master .chm file ( NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
GENERATE_CHI = NO
-# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
-# is used to encode HtmlHelp index (hhk), content (hhc) and project file
-# content.
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
CHM_INDEX_ENCODING =
-# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
-# controls whether a binary table of contents is generated (YES) or a
-# normal table of contents (NO) in the .chm file.
+# The BINARY_TOC flag controls whether a binary table of contents is generated (
+# YES) or a normal table of contents ( NO) in the .chm file.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
BINARY_TOC = NO
-# The TOC_EXPAND flag can be set to YES to add extra items for group members
-# to the contents of the HTML help documentation and to the tree view.
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
TOC_EXPAND = NO
# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
-# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
-# that can be used as input for Qt's qhelpgenerator to generate a
-# Qt Compressed Help (.qch) of the generated HTML documentation.
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_QHP = NO
-# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
-# be used to specify the file name of the resulting .qch file.
-# The path specified is relative to the HTML output folder.
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
QCH_FILE =
-# The QHP_NAMESPACE tag specifies the namespace to use when generating
-# Qt Help Project output. For more information please see
-# http://doc.trolltech.com/qthelpproject.html#namespace
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_NAMESPACE = org.doxygen.Project
-# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
-# Qt Help Project output. For more information please see
-# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_VIRTUAL_FOLDER = doc
-# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
-# add. For more information please see
-# http://doc.trolltech.com/qthelpproject.html#custom-filters
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_CUST_FILTER_NAME =
-# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
-# custom filter to add. For more information please see
-# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
-# Qt Help Project / Custom Filters</a>.
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_CUST_FILTER_ATTRS =
# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
-# project's
-# filter section matches.
-# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
-# Qt Help Project / Filter Attributes</a>.
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_SECT_FILTER_ATTRS =
-# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
-# be used to specify the location of Qt's qhelpgenerator.
-# If non-empty doxygen will try to run qhelpgenerator on the generated
-# .qhp file.
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
QHG_LOCATION =
-# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
-# will be generated, which together with the HTML files, form an Eclipse help
-# plugin. To install this plugin and make it available under the help contents
-# menu in Eclipse, the contents of the directory containing the HTML and XML
-# files needs to be copied into the plugins directory of eclipse. The name of
-# the directory within the plugins directory should be the same as
-# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
-# the help appears.
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_ECLIPSEHELP = NO
-# A unique identifier for the eclipse help plugin. When installing the plugin
-# the directory name containing the HTML and XML files should also have
-# this name.
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
ECLIPSE_DOC_ID = org.doxygen.Project
-# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs)
-# at top of each HTML page. The value NO (the default) enables the index and
-# the value YES disables it. Since the tabs have the same information as the
-# navigation tree you can set this option to NO if you already set
-# GENERATE_TREEVIEW to YES.
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
DISABLE_INDEX = NO
# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
-# structure should be generated to display hierarchical information.
-# If the tag value is set to YES, a side panel will be generated
-# containing a tree-like index structure (just like the one that
-# is generated for HTML Help). For this to work a browser that supports
-# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
-# Windows users are probably better off using the HTML help feature.
-# Since the tree basically has the same information as the tab index you
-# could consider to set DISABLE_INDEX to NO when enabling this option.
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_TREEVIEW = NO
-# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
-# (range [0,1..20]) that doxygen will group on one line in the generated HTML
-# documentation. Note that a value of 0 will completely suppress the enum
-# values from appearing in the overview section.
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
ENUM_VALUES_PER_LINE = 4
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
-# used to set the initial width (in pixels) of the frame in which the tree
-# is shown.
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
TREEVIEW_WIDTH = 250
-# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
-# links to external symbols imported via tag files in a separate window.
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
EXT_LINKS_IN_WINDOW = NO
-# Use this tag to change the font size of Latex formulas included
-# as images in the HTML documentation. The default is 10. Note that
-# when you change the font size after a successful doxygen run you need
-# to manually remove any form_*.png images from the HTML output directory
-# to force them to be regenerated.
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
FORMULA_FONTSIZE = 10
# Use the FORMULA_TRANPARENT tag to determine whether or not the images
-# generated for formulas are transparent PNGs. Transparent PNGs are
-# not supported properly for IE 6.0, but are supported on all modern browsers.
-# Note that when changing this option you need to delete any form_*.png files
-# in the HTML output before the changes have effect.
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
FORMULA_TRANSPARENT = YES
-# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
-# (see http://www.mathjax.org) which uses client side Javascript for the
-# rendering instead of using prerendered bitmaps. Use this if you do not
-# have LaTeX installed or if you want to formulas look prettier in the HTML
-# output. When enabled you may also need to install MathJax separately and
-# configure the path to it using the MATHJAX_RELPATH option.
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using prerendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
USE_MATHJAX = NO
-# When MathJax is enabled you need to specify the location relative to the
-# HTML output directory using the MATHJAX_RELPATH option. The destination
-# directory should contain the MathJax.js script. For instance, if the mathjax
-# directory is located at the same level as the HTML output directory, then
-# MATHJAX_RELPATH should be ../mathjax. The default value points to
-# the MathJax Content Delivery Network so you can quickly see the result without
-# installing MathJax.
-# However, it is strongly recommended to install a local
-# copy of MathJax from http://www.mathjax.org before deployment.
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH = http://www.mathjax.org/mathjax
-# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension
-# names that should be enabled during MathJax rendering.
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_EXTENSIONS =
-# When the SEARCHENGINE tag is enabled doxygen will generate a search box
-# for the HTML output. The underlying search engine uses javascript
-# and DHTML and should work on any modern browser. Note that when using
-# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
-# (GENERATE_DOCSET) there is already a search function so this one should
-# typically be disabled. For large projects the javascript based search engine
-# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
SEARCHENGINE = YES
# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
-# implemented using a PHP enabled web server instead of at the web client
-# using Javascript. Doxygen will generate the search PHP script and index
-# file to put on the web server. The advantage of the server
-# based approach is that it scales better to large projects and allows
-# full text search. The disadvantages are that it is more difficult to setup
-# and does not have live searching capabilities.
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavours of web server based searching depending on the
+# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for
+# searching and an index file used by the script. When EXTERNAL_SEARCH is
+# enabled the indexing and searching needs to be provided by external tools. See
+# the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
SERVER_BASED_SEARCH = NO
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
#---------------------------------------------------------------------------
-# configuration options related to the LaTeX output
+# Configuration options related to the LaTeX output
#---------------------------------------------------------------------------
-# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
-# generate Latex output.
+# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.
+# The default value is: YES.
GENERATE_LATEX = YES
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `latex' will be used as the default path.
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_OUTPUT = latex
# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
-# invoked. If left blank `latex' will be used as the default command name.
-# Note that when enabling USE_PDFLATEX this option is only used for
-# generating bitmaps for formulas in the HTML output, but not in the
-# Makefile that is written to the output directory.
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_CMD_NAME = latex
-# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
-# generate index for LaTeX. If left blank `makeindex' will be used as the
-# default command name.
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
MAKEINDEX_CMD_NAME = makeindex
-# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
-# LaTeX documents. This may be useful for small projects and may help to
-# save some trees in general.
+# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
COMPACT_LATEX = NO
-# The PAPER_TYPE tag can be used to set the paper type that is used
-# by the printer. Possible values are: a4, letter, legal and
-# executive. If left blank a4wide will be used.
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
PAPER_TYPE = a4
-# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
-# packages that should be included in the LaTeX output.
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. To get the times font for
+# instance you can specify
+# EXTRA_PACKAGES=times
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
EXTRA_PACKAGES =
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
-# the generated latex document. The header should contain everything until
-# the first chapter. If it is left blank doxygen will generate a
-# standard header. Notice: only use this tag if you know what you are doing!
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber. Doxygen will
+# replace them by respectively the title of the page, the current date and time,
+# only the current date, the version number of doxygen, the project name (see
+# PROJECT_NAME), or the project number (see PROJECT_NUMBER).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_HEADER =
-# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
-# the generated latex document. The footer should contain everything after
-# the last chapter. If it is left blank doxygen will generate a
-# standard footer. Notice: only use this tag if you know what you are doing!
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_FOOTER =
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
-# is prepared for conversion to pdf (using ps2pdf). The pdf file will
-# contain links (just like the HTML output) instead of page references
-# This makes the output suitable for online browsing using a pdf viewer.
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
PDF_HYPERLINKS = YES
-# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
-# plain latex in the generated Makefile. Set this option to YES to get a
+# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES to get a
# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
USE_PDFLATEX = YES
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
-# command to the generated LaTeX files. This will instruct LaTeX to keep
-# running if errors occur, instead of asking the user for help.
-# This option is also used when generating formulas in HTML.
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_BATCHMODE = NO
-# If LATEX_HIDE_INDICES is set to YES then doxygen will not
-# include the index chapters (such as File Index, Compound Index, etc.)
-# in the output.
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_HIDE_INDICES = NO
-# If LATEX_SOURCE_CODE is set to YES then doxygen will include
-# source code with syntax highlighting in the LaTeX output.
-# Note that which sources are shown also depends on other settings
-# such as SOURCE_BROWSER.
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_SOURCE_CODE = NO
# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
-# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See
-# http://en.wikipedia.org/wiki/BibTeX for more info.
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_BIB_STYLE = plain
#---------------------------------------------------------------------------
-# configuration options related to the RTF output
+# Configuration options related to the RTF output
#---------------------------------------------------------------------------
-# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
-# The RTF output is optimized for Word 97 and may not look very pretty with
-# other RTF readers or editors.
+# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
GENERATE_RTF = NO
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `rtf' will be used as the default path.
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_OUTPUT = rtf
-# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
-# RTF documents. This may be useful for small projects and may help to
-# save some trees in general.
+# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
COMPACT_RTF = NO
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
-# will contain hyperlink fields. The RTF file will
-# contain links (just like the HTML output) instead of page references.
-# This makes the output suitable for online browsing using WORD or other
-# programs which support those fields.
-# Note: wordpad (write) and others do not support links.
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_HYPERLINKS = NO
-# Load style sheet definitions from file. Syntax is similar to doxygen's
-# config file, i.e. a series of assignments. You only have to provide
-# replacements, missing definitions are set to their default value.
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_STYLESHEET_FILE =
-# Set optional variables used in the generation of an rtf document.
-# Syntax is similar to doxygen's config file.
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_EXTENSIONS_FILE =
#---------------------------------------------------------------------------
-# configuration options related to the man page output
+# Configuration options related to the man page output
#---------------------------------------------------------------------------
-# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
-# generate man pages
+# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
GENERATE_MAN = NO
-# The MAN_OUTPUT tag is used to specify where the man pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `man' will be used as the default path.
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
MAN_OUTPUT = man
-# The MAN_EXTENSION tag determines the extension that is added to
-# the generated man pages (default is the subroutine's section .3)
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
MAN_EXTENSION = .3
-# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
-# then it will generate one additional man file for each entity
-# documented in the real man page(s). These additional files
-# only source the real man page, but without them the man command
-# would be unable to find the correct page. The default is NO.
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
MAN_LINKS = NO
#---------------------------------------------------------------------------
-# configuration options related to the XML output
+# Configuration options related to the XML output
#---------------------------------------------------------------------------
-# If the GENERATE_XML tag is set to YES Doxygen will
-# generate an XML file that captures the structure of
-# the code including all documentation.
+# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
GENERATE_XML = NO
-# The XML_OUTPUT tag is used to specify where the XML pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `xml' will be used as the default path.
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
XML_OUTPUT = xml
-# The XML_SCHEMA tag can be used to specify an XML schema,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
+# The XML_SCHEMA tag can be used to specify a XML schema, which can be used by a
+# validating XML parser to check the syntax of the XML files.
+# This tag requires that the tag GENERATE_XML is set to YES.
XML_SCHEMA =
-# The XML_DTD tag can be used to specify an XML DTD,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
+# The XML_DTD tag can be used to specify a XML DTD, which can be used by a
+# validating XML parser to check the syntax of the XML files.
+# This tag requires that the tag GENERATE_XML is set to YES.
XML_DTD =
-# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
-# dump the program listings (including syntax highlighting
-# and cross-referencing information) to the XML output. Note that
-# enabling this will significantly increase the size of the XML output.
+# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
XML_PROGRAMLISTING = YES
#---------------------------------------------------------------------------
-# configuration options for the AutoGen Definitions output
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
-# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
-# generate an AutoGen Definitions (see autogen.sf.net) file
-# that captures the structure of the code including all
-# documentation. Note that this feature is still experimental
-# and incomplete at the moment.
+# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen
+# Definitions (see http://autogen.sf.net) file that captures the structure of
+# the code including all documentation. Note that this feature is still
+# experimental and incomplete at the moment.
+# The default value is: NO.
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
-# configuration options related to the Perl module output
+# Configuration options related to the Perl module output
#---------------------------------------------------------------------------
-# If the GENERATE_PERLMOD tag is set to YES Doxygen will
-# generate a Perl module file that captures the structure of
-# the code including all documentation. Note that this
-# feature is still experimental and incomplete at the
-# moment.
+# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
GENERATE_PERLMOD = NO
-# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
-# the necessary Makefile rules, Perl scripts and LaTeX code to be able
-# to generate PDF and DVI output from the Perl module output.
+# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
PERLMOD_LATEX = NO
-# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
-# nicely formatted so it can be parsed by a human reader.
-# This is useful
-# if you want to understand what is going on.
-# On the other hand, if this
-# tag is set to NO the size of the Perl module output will be much smaller
-# and Perl will parse it just the same.
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
PERLMOD_PRETTY = YES
-# The names of the make variables in the generated doxyrules.make file
-# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
-# This is useful so different doxyrules.make files included by the same
-# Makefile don't overwrite each other's variables.
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
PERLMOD_MAKEVAR_PREFIX =
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
-# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
-# evaluate all C-preprocessor directives found in the sources and include
-# files.
+# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
ENABLE_PREPROCESSING = YES
-# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
-# names in the source code. If set to NO (the default) only conditional
-# compilation will be performed. Macro expansion can be done in a controlled
-# way by setting EXPAND_ONLY_PREDEF to YES.
+# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names
+# in the source code. If set to NO only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
MACRO_EXPANSION = NO
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
-# then the macro expansion is limited to the macros specified with the
-# PREDEFINED and EXPAND_AS_DEFINED tags.
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
EXPAND_ONLY_PREDEF = NO
-# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
-# pointed to by INCLUDE_PATH will be searched when a #include is found.
+# If the SEARCH_INCLUDES tag is set to YES the includes files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
SEARCH_INCLUDES = YES
# The INCLUDE_PATH tag can be used to specify one or more directories that
-# contain include files that are not input files but should be processed by
-# the preprocessor.
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
INCLUDE_PATH =
# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
# patterns (like *.h and *.hpp) to filter out the header-files in the
-# directories. If left blank, the patterns specified with FILE_PATTERNS will
-# be used.
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
INCLUDE_FILE_PATTERNS =
-# The PREDEFINED tag can be used to specify one or more macro names that
-# are defined before the preprocessor is started (similar to the -D option of
-# gcc). The argument of the tag is a list of macros of the form: name
-# or name=definition (no spaces). If the definition and the = are
-# omitted =1 is assumed. To prevent a macro definition from being
-# undefined via #undef or recursively expanded use the := operator
-# instead of the = operator.
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
PREDEFINED =
-# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
-# this tag can be used to specify a list of macro names that should be expanded.
-# The macro definition that is found in the sources will be used.
-# Use the PREDEFINED tag if you want to use a different macro definition that
-# overrules the definition found in the source code.
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
EXPAND_AS_DEFINED =
-# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
-# doxygen's preprocessor will remove all references to function-like macros
-# that are alone on a line, have an all uppercase name, and do not end with a
-# semicolon, because these will confuse the parser if not removed.
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all refrences to function-like macros that are alone on a line, have an
+# all uppercase name, and do not end with a semicolon. Such function macros are
+# typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
SKIP_FUNCTION_MACROS = YES
#---------------------------------------------------------------------------
-# Configuration::additions related to external references
+# Configuration options related to external references
#---------------------------------------------------------------------------
-# The TAGFILES option can be used to specify one or more tagfiles. For each
-# tag file the location of the external documentation should be added. The
-# format of a tag file without this location is as follows:
-#
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
# TAGFILES = file1 file2 ...
# Adding location for the tag files is done as follows:
-#
# TAGFILES = file1=loc1 "file2 = loc2" ...
-# where "loc1" and "loc2" can be relative or absolute paths
-# or URLs. Note that each tag file must have a unique name (where the name does
-# NOT include the path). If a tag file is not located in the directory in which
-# doxygen is run, you must also specify the path to the tagfile here.
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have an unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
TAGFILES =
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create
-# a tag file that is based on the input files it reads.
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
GENERATE_TAGFILE =
-# If the ALLEXTERNALS tag is set to YES all external classes will be listed
-# in the class index. If set to NO only the inherited external classes
-# will be listed.
+# If the ALLEXTERNALS tag is set to YES all external class will be listed in the
+# class index. If set to NO only the inherited external classes will be listed.
+# The default value is: NO.
ALLEXTERNALS = NO
-# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
-# in the modules index. If set to NO, only the current project's groups will
-# be listed.
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in
+# the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
EXTERNAL_GROUPS = YES
+# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of `which perl').
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
PERL_PATH = /usr/bin/perl
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
-# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
-# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
-# or super classes. Setting the tag to NO turns the diagrams off. Note that
-# this option also works with HAVE_DOT disabled, but it is recommended to
-# install and use dot, since it yields more powerful graphs.
+# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
CLASS_DIAGRAMS = YES
# You can define message sequence charts within doxygen comments using the \msc
-# command. Doxygen will then run the mscgen tool (see
-# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
# documentation. The MSCGEN_PATH tag allows you to specify the directory where
# the mscgen tool resides. If left empty the tool is assumed to be found in the
# default search path.
MSCGEN_PATH =
-# If set to YES, the inheritance and collaboration graphs will hide
-# inheritance and usage relations if the target is undocumented
-# or is not a class.
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
HIDE_UNDOC_RELATIONS = YES
# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
-# available from the path. This tool is part of Graphviz, a graph visualization
-# toolkit from AT&T and Lucent Bell Labs. The other options in this section
-# have no effect if this option is set to NO (the default)
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
HAVE_DOT = NO
-# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
-# allowed to run in parallel. When set to 0 (the default) doxygen will
-# base this on the number of processors available in the system. You can set it
-# explicitly to a value larger than 0 to get control over the balance
-# between CPU load and processing speed.
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_NUM_THREADS = 0
-# By default doxygen will use the Helvetica font for all dot files that
-# doxygen generates. When you want a differently looking font you can specify
-# the font name using DOT_FONTNAME. You need to make sure dot is able to find
-# the font, which can be done by putting it in a standard location or by setting
-# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
-# directory containing the font.
+# When you want a differently looking font n the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTNAME = Helvetica
-# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
-# The default size is 10pt.
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTSIZE = 10
-# By default doxygen will tell dot to use the Helvetica font.
-# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to
-# set the path where dot can find it.
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTPATH =
-# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect inheritance relations. Setting this tag to YES will force the
-# CLASS_DIAGRAMS tag to NO.
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
CLASS_GRAPH = YES
-# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect implementation dependencies (inheritance, containment, and
-# class references variables) of the class with other documented classes.
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
COLLABORATION_GRAPH = YES
-# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for groups, showing the direct groups dependencies
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
GROUP_GRAPHS = YES
# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
# collaboration diagrams in a style similar to the OMG's Unified Modeling
# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
UML_LOOK = NO
-# If the UML_LOOK tag is enabled, the fields and methods are shown inside
-# the class node. If there are many fields or methods and many nodes the
-# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS
-# threshold limits the number of items for each type to make the size more
-# managable. Set this to 0 for no limit. Note that the threshold may be
-# exceeded by 50% before the limit is enforced.
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
UML_LIMIT_NUM_FIELDS = 10
-# If set to YES, the inheritance and collaboration graphs will show the
-# relations between templates and their instances.
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
TEMPLATE_RELATIONS = NO
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
-# tags are set to YES then doxygen will generate a graph for each documented
-# file showing the direct and indirect include dependencies of the file with
-# other documented files.
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
INCLUDE_GRAPH = YES
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
-# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
-# documented header file showing the documented files that directly or
-# indirectly include this file.
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
INCLUDED_BY_GRAPH = YES
-# If the CALL_GRAPH and HAVE_DOT options are set to YES then
-# doxygen will generate a call dependency graph for every global function
-# or class method. Note that enabling this option will significantly increase
-# the time of a run. So in most cases it will be better to enable call graphs
-# for selected functions only using the \callgraph command.
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
CALL_GRAPH = NO
-# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
-# doxygen will generate a caller dependency graph for every global function
-# or class method. Note that enabling this option will significantly increase
-# the time of a run. So in most cases it will be better to enable caller
-# graphs for selected functions only using the \callergraph command.
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
CALLER_GRAPH = NO
-# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
-# will generate a graphical hierarchy of all classes instead of a textual one.
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
GRAPHICAL_HIERARCHY = YES
-# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES
-# then doxygen will show the dependencies a directory has on other directories
-# in a graphical way. The dependency relations are determined by the #include
-# relations between the files in the directories.
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
DIRECTORY_GRAPH = YES
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot. Possible values are svg, png, jpg, or gif.
-# If left blank png will be used. If you choose svg you need to set
-# HTML_FILE_EXTENSION to xhtml in order to make the SVG files
-# visible in IE 9+ (other browsers do not have this requirement).
+# generated by dot.
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif and svg.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_IMAGE_FORMAT = png
# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
# enable generation of interactive SVG images that allow zooming and panning.
-# Note that this requires a modern browser other than Internet Explorer.
-# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you
-# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files
-# visible. Older versions of IE do not have SVG support.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
INTERACTIVE_SVG = NO
-# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_PATH =
# The DOTFILE_DIRS tag can be used to specify one or more directories that
-# contain dot files that are included in the documentation (see the
-# \dotfile command).
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
DOTFILE_DIRS =
# The MSCFILE_DIRS tag can be used to specify one or more directories that
-# contain msc files that are included in the documentation (see the
-# \mscfile command).
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
MSCFILE_DIRS =
-# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
-# nodes that will be shown in the graph. If the number of nodes in a graph
-# becomes larger than this value, doxygen will truncate the graph, which is
-# visualized by representing a node as a red box. Note that doxygen if the
-# number of direct children of the root node in a graph is already larger than
-# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
-# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_GRAPH_MAX_NODES = 50
-# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
-# graphs generated by dot. A depth value of 3 means that only nodes reachable
-# from the root by following a path via at most 3 edges will be shown. Nodes
-# that lay further from the root node will be omitted. Note that setting this
-# option to 1 or 2 may greatly reduce the computation time needed for large
-# code bases. Also note that the size of a graph can be further restricted by
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
MAX_DOT_GRAPH_DEPTH = 0
# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is disabled by default, because dot on Windows does not
-# seem to support this out of the box. Warning: Depending on the platform used,
-# enabling this option may lead to badly anti-aliased labels on the edges of
-# a graph (i.e. they become hard to read).
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_TRANSPARENT = NO
# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This
-# makes dot run faster, but since only newer versions of dot (>1.8.10)
-# support this, this feature is disabled by default.
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_MULTI_TARGETS = YES
-# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
-# generate a legend page explaining the meaning of the various boxes and
-# arrows in the dot generated graphs.
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
GENERATE_LEGEND = YES
-# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
-# remove the intermediate dot files that are used to generate
-# the various graphs.
+# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
DOT_CLEANUP = YES
def dependencies(target):
return (('ffmpeg-cdist', 'bba68a5'),
- ('libdcp', '060348d'))
+ ('libdcp', '1.0'))
def build(target, options):
cmd = './waf configure --prefix=%s' % target.work_dir_cscript()
\documentclass{article}
+\usepackage{amsmath}
\begin{document}
Here is what resampling we need to do. Content video is at $C_V$ fps, audio at $C_A$.
\textbf{Resample $C_A$ to the DCI rate.}
\section{Hard case 1}
+\label{sec:hard1}
$C_V$ is not a DCI rate, $C_A$ is, e.g.\ if $C_V = 25$, $C_A =
48\times{}10^3$. We will run the video at a nearby DCI rate $F_V$,
\medskip
\textbf{Resample $C_A$ to $C_V C_A / F_V$}
+\section{Hard case 2}
+
+Neither $C_V$ nor $C_A$ is not a DCI rate, e.g.\ if $C_V = 25$, $C_A =
+44.1\times{}10^3$. We will run the video at a nearby DCI rate $F_V$,
+meaning that it will run faster or slower than it should. We first
+resample the audio to a DCI rate $F_A$, then perform as with
+Section~\ref{sec:hard1} above.
+
+\medskip
+\textbf{Resample $C_A$ to $C_V F_A / F_V$}
+
+
+\section{The general case}
+
+Given a DCP running at $F_V$ and $F_A$ and a piece of content at $C_V$
+and $C_A$, resample the audio to $R_A$ where
+\begin{align*}
+R_A &= \frac{C_V F_A}{F_V}
+\end{align*}
\end{document}
/** @mainpage DCP-o-matic
*
* DCP-o-matic is a tool to create digital cinema packages (DCPs) from
- * video files, or from sets of TIFF image files. It is written in C++
+ * video files, or from sets of image files. It is written in C++
* and distributed under the GPL.
*
* Video files are decoded using FFmpeg (http://ffmpeg.org), so any video
* for debugging purposes.
*
* Thanks are due to the authors and communities of all DCP-o-matic's dependencies.
- *
- * DCP-o-matic is distributed in the hope that there are still cinemas with projectionists
- * who might want to use it. As Mark Kermode says, "if it doesn't have a projectionist
- * it's not a cinema - it's a sweetshop with a video-screen."
*
- * Email correspondance is welcome to cth@carlh.net
+ * Email correspondance is welcome to carl@dcpomatic.com
*
- * More details can be found at http://carlh.net/software/dcpomatic
+ * More details can be found at http://dcpomatic.com/
*/
*/
#include "audio_analysis.h"
+#include "audio_buffers.h"
#include "analyse_audio_job.h"
#include "compose.hpp"
#include "film.h"
shared_ptr<Playlist> playlist (new Playlist);
playlist->add (content);
shared_ptr<Player> player (new Player (_film, playlist));
- player->disable_video ();
- player->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1, _2));
-
- _samples_per_point = max (int64_t (1), _film->time_to_audio_frames (_film->length()) / _num_points);
+ int64_t const len = _film->length().frames (_film->audio_frame_rate());
+ _samples_per_point = max (int64_t (1), len / _num_points);
_current.resize (_film->audio_channels ());
_analysis.reset (new AudioAnalysis (_film->audio_channels ()));
_done = 0;
- OutputAudioFrame const len = _film->time_to_audio_frames (_film->length ());
- while (!player->pass ()) {
- set_progress (double (_done) / len);
+ DCPTime const block = DCPTime::from_seconds (1.0 / 8);
+ for (DCPTime t; t < _film->length(); t += block) {
+ analyse (player->get_audio (t, block, false));
+ set_progress (t.seconds() / _film->length().seconds());
}
_analysis->write (content->audio_analysis_path ());
}
void
-AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, Time)
+AnalyseAudioJob::analyse (shared_ptr<const AudioBuffers> b)
{
for (int i = 0; i < b->frames(); ++i) {
for (int j = 0; j < b->channels(); ++j) {
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/lib/analyse_audio_job.h
+ * @brief AnalyseAudioJob class.
+ */
+
#include "job.h"
#include "audio_analysis.h"
#include "types.h"
+#include "dcpomatic_time.h"
class AudioBuffers;
class AudioContent;
+/** @class AnalyseAudioJob
+ * @brief A job to analyse the audio of a piece of AudioContent and make a note of its
+ * broad peak and RMS levels.
+ *
+ * After computing the peak and RMS levels over the length of the content, the job
+ * will write a file to Content::audio_analysis_path.
+ */
class AnalyseAudioJob : public Job
{
public:
void run ();
private:
- void audio (boost::shared_ptr<const AudioBuffers>, Time);
+ void analyse (boost::shared_ptr<const AudioBuffers>);
boost::weak_ptr<AudioContent> _content;
- OutputAudioFrame _done;
+ int64_t _done;
int64_t _samples_per_point;
std::vector<AudioPoint> _current;
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/lib/audio_analysis.h
+ * @brief AudioAnalysis and AudioPoint classes.
+ */
+
#ifndef DCPOMATIC_AUDIO_ANALYSIS_H
#define DCPOMATIC_AUDIO_ANALYSIS_H
#include <list>
#include <boost/filesystem.hpp>
+/** @class AudioPoint
+ * @brief A single point of an audio analysis for one portion of one channel.
+ */
class AudioPoint
{
public:
float _data[COUNT];
};
+/** @class AudioAnalysis
+ * @brief An analysis of the audio data in a piece of AudioContent.
+ *
+ * This is a set of AudioPoints for each channel. The AudioPoints
+ * each represent some measurement of the audio over a portion of the
+ * content. For example each AudioPoint may give the RMS level of
+ * a 1-minute portion of the audio.
+ */
class AudioAnalysis : public boost::noncopyable
{
public:
void
AudioBuffers::allocate (int channels, int frames)
{
+ assert (frames >= 0);
+ assert (channels >= 0);
+
_channels = channels;
_frames = frames;
_allocated_frames = frames;
void
AudioBuffers::copy_from (AudioBuffers const * from, int frames_to_copy, int read_offset, int write_offset)
{
+ if (frames_to_copy == 0) {
+ /* Prevent the asserts from firing if there is nothing to do */
+ return;
+ }
+
assert (from->channels() == channels());
assert (from);
AudioBuffers::accumulate_frames (AudioBuffers const * from, int read_offset, int write_offset, int frames)
{
assert (_channels == from->channels ());
+ assert (read_offset >= 0);
+ assert (write_offset >= 0);
for (int i = 0; i < _channels; ++i) {
for (int j = 0; j < frames; ++j) {
/*
- Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
-#ifndef DVDOMATIC_AUDIO_BUFFERS_H
-#define DVDOMATIC_AUDIO_BUFFERS_H
+/** @file src/lib/audio_buffers.h
+ * @brief AudioBuffers class.
+ */
+
+#ifndef DCPOMATIC_AUDIO_BUFFERS_H
+#define DCPOMATIC_AUDIO_BUFFERS_H
#include <boost/shared_ptr.hpp>
void copy_from (AudioBuffers const * from, int frames_to_copy, int read_offset, int write_offset);
void move (int from, int to, int frames);
- void accumulate_channel (AudioBuffers const *, int, int, float gain = 1);
+ void accumulate_channel (AudioBuffers const * from, int from_channel, int to_channel, float gain = 1);
void accumulate_frames (AudioBuffers const *, int read_offset, int write_offset, int frames);
private:
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
#include <libcxml/cxml.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "audio_content.h"
#include "analyse_audio_job.h"
#include "job_manager.h"
using std::vector;
using boost::shared_ptr;
using boost::dynamic_pointer_cast;
-using libdcp::raw_convert;
+using dcp::raw_convert;
int const AudioContentProperty::AUDIO_CHANNELS = 200;
int const AudioContentProperty::AUDIO_LENGTH = 201;
int const AudioContentProperty::AUDIO_DELAY = 204;
int const AudioContentProperty::AUDIO_MAPPING = 205;
-AudioContent::AudioContent (shared_ptr<const Film> f, Time s)
+AudioContent::AudioContent (shared_ptr<const Film> f, DCPTime s)
: Content (f, s)
, _audio_gain (0)
, _audio_delay (Config::instance()->default_audio_delay ())
}
-AudioContent::AudioContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+AudioContent::AudioContent (shared_ptr<const Film> f, cxml::ConstNodePtr node)
: Content (f, node)
{
_audio_gain = node->number_child<float> ("AudioGain");
string
AudioContent::technical_summary () const
{
- return String::compose ("audio: channels %1, length %2, raw rate %3, out rate %4", audio_channels(), audio_length(), content_audio_frame_rate(), output_audio_frame_rate());
+ return String::compose (
+ "audio: channels %1, length %2, content rate %3, resampled rate %4",
+ audio_channels(),
+ audio_length().seconds(),
+ audio_frame_rate(),
+ resampled_audio_frame_rate()
+ );
+}
+
+void
+AudioContent::set_audio_mapping (AudioMapping)
+{
+ signal_changed (AudioContentProperty::AUDIO_MAPPING);
+}
+
+/** @return the frame rate that this content should be resampled to in order
+ * that it is in sync with the active video content at its start time.
+ */
+int
+AudioContent::resampled_audio_frame_rate () const
+{
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ /* Resample to a DCI-approved sample rate */
+ double t = dcp_audio_frame_rate (audio_frame_rate ());
+
+ FrameRateChange frc = film->active_frame_rate_change (position ());
+
+ /* Compensate if the DCP is being run at a different frame rate
+ to the source; that is, if the video is run such that it will
+ look different in the DCP compared to the source (slower or faster).
+ */
+
+ if (frc.change_speed) {
+ t /= frc.speed_up;
+ }
+
+ return rint (t);
}
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/lib/audio_content.h
+ * @brief AudioContent and AudioContentProperty classes.
+ */
+
#ifndef DCPOMATIC_AUDIO_CONTENT_H
#define DCPOMATIC_AUDIO_CONTENT_H
static int const AUDIO_MAPPING;
};
+/** @class AudioContent
+ * @brief Parent class for content which may contain audio data.
+ */
class AudioContent : public virtual Content
{
public:
typedef int64_t Frame;
- AudioContent (boost::shared_ptr<const Film>, Time);
+ AudioContent (boost::shared_ptr<const Film>, DCPTime);
AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path);
- AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+ AudioContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr);
AudioContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
void as_xml (xmlpp::Node *) const;
std::string technical_summary () const;
+ /** @return number of audio channels in the content */
virtual int audio_channels () const = 0;
- virtual AudioContent::Frame audio_length () const = 0;
- virtual int content_audio_frame_rate () const = 0;
- virtual int output_audio_frame_rate () const = 0;
+ /** @return the length of the audio in the content */
+ virtual ContentTime audio_length () const = 0;
+ /** @return the frame rate of the content */
+ virtual int audio_frame_rate () const = 0;
virtual AudioMapping audio_mapping () const = 0;
- virtual void set_audio_mapping (AudioMapping) = 0;
+ virtual void set_audio_mapping (AudioMapping);
virtual boost::filesystem::path audio_analysis_path () const;
+ int resampled_audio_frame_rate () const;
+
boost::signals2::connection analyse_audio (boost::function<void()>);
void set_audio_gain (float);
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "exceptions.h"
#include "log.h"
#include "resampler.h"
+#include "util.h"
+#include "film.h"
#include "i18n.h"
using std::list;
using std::pair;
using std::cout;
+using std::min;
+using std::max;
using boost::optional;
using boost::shared_ptr;
-AudioDecoder::AudioDecoder (shared_ptr<const Film> film, shared_ptr<const AudioContent> content)
- : Decoder (film)
- , _audio_content (content)
- , _audio_position (0)
+AudioDecoder::AudioDecoder (shared_ptr<const AudioContent> content)
+ : _audio_content (content)
{
+ if (content->resampled_audio_frame_rate() != content->audio_frame_rate() && content->audio_channels ()) {
+ _resampler.reset (new Resampler (content->audio_frame_rate(), content->resampled_audio_frame_rate(), content->audio_channels ()));
+ }
+ reset_decoded_audio ();
}
void
-AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame)
+AudioDecoder::reset_decoded_audio ()
{
- Audio (data, frame);
- _audio_position = frame + data->frames ();
+ _decoded_audio = ContentAudio (shared_ptr<AudioBuffers> (new AudioBuffers (_audio_content->audio_channels(), 0)), 0);
}
-/** This is a bit odd, but necessary when we have (e.g.) FFmpegDecoders with no audio.
- * The player needs to know that there is no audio otherwise it will keep trying to
- * pass() the decoder to get it to emit audio.
+shared_ptr<ContentAudio>
+AudioDecoder::get_audio (AudioFrame frame, AudioFrame length, bool accurate)
+{
+ shared_ptr<ContentAudio> dec;
+
+ AudioFrame const end = frame + length - 1;
+
+ if (frame < _decoded_audio.frame || end > (_decoded_audio.frame + length * 4)) {
+ /* Either we have no decoded data, or what we do have is a long way from what we want: seek */
+ seek (ContentTime::from_frames (frame, _audio_content->audio_frame_rate()), accurate);
+ }
+
+ /* Offset of the data that we want from the start of _decoded_audio.audio
+ (to be set up shortly)
+ */
+ AudioFrame decoded_offset = 0;
+
+ /* Now enough pass() calls will either:
+ * (a) give us what we want, or
+ * (b) hit the end of the decoder.
+ *
+ * If we are being accurate, we want the right frames,
+ * otherwise any frames will do.
+ */
+ if (accurate) {
+ /* Keep stuffing data into _decoded_audio until we have enough data, or the subclass does not want to give us any more */
+ while (!pass() && (_decoded_audio.frame > frame || (_decoded_audio.frame + _decoded_audio.audio->frames()) < end)) {}
+ decoded_offset = frame - _decoded_audio.frame;
+ } else {
+ while (!pass() && _decoded_audio.audio->frames() < length) {}
+ /* Use decoded_offset of 0, as we don't really care what frames we return */
+ }
+
+ /* The amount of data available in _decoded_audio.audio starting from `frame'. This could be -ve
+ if pass() returned true before we got enough data.
+ */
+ AudioFrame const available = _decoded_audio.audio->frames() - decoded_offset;
+
+ /* We will return either that, or the requested amount, whichever is smaller */
+ AudioFrame const to_return = max ((AudioFrame) 0, min (available, length));
+
+ /* Copy our data to the output */
+ shared_ptr<AudioBuffers> out (new AudioBuffers (_decoded_audio.audio->channels(), to_return));
+ out->copy_from (_decoded_audio.audio.get(), to_return, decoded_offset, 0);
+
+ AudioFrame const remaining = max ((AudioFrame) 0, available - to_return);
+
+ /* Clean up decoded; first, move the data after what we just returned to the start of the buffer */
+ _decoded_audio.audio->move (decoded_offset + to_return, 0, remaining);
+ /* And set up the number of frames we have left */
+ _decoded_audio.audio->set_frames (remaining);
+ /* Also bump where those frames are in terms of the content */
+ _decoded_audio.frame += decoded_offset + to_return;
+
+ return shared_ptr<ContentAudio> (new ContentAudio (out, frame));
+}
+
+/** Called by subclasses when audio data is ready.
+ *
+ * Audio timestamping is made hard by many factors, but perhaps the most entertaining is resampling.
+ * We have to assume that we are feeding continuous data into the resampler, and so we get continuous
+ * data out. Hence we do the timestamping here, post-resampler, just by counting samples.
+ *
+ * The time is passed in here so that after a seek we can set up our _audio_position. The
+ * time is ignored once this has been done.
*/
-bool
-AudioDecoder::has_audio () const
+void
+AudioDecoder::audio (shared_ptr<const AudioBuffers> data, ContentTime time)
+{
+ if (_resampler) {
+ data = _resampler->run (data);
+ }
+
+ if (!_audio_position) {
+ _audio_position = time.frames (_audio_content->resampled_audio_frame_rate ());
+ }
+
+ assert (_audio_position.get() >= (_decoded_audio.frame + _decoded_audio.audio->frames()));
+
+ /* Resize _decoded_audio to fit the new data */
+ int const new_size = _audio_position.get() + data->frames() - _decoded_audio.frame;
+ _decoded_audio.audio->ensure_size (new_size);
+ _decoded_audio.audio->set_frames (new_size);
+
+ /* Copy new data in */
+ _decoded_audio.audio->copy_from (data.get(), data->frames(), 0, _audio_position.get() - _decoded_audio.frame);
+ _audio_position = _audio_position.get() + data->frames ();
+}
+
+/* XXX: called? */
+void
+AudioDecoder::flush ()
+{
+ if (!_resampler) {
+ return;
+ }
+
+ /*
+ shared_ptr<const AudioBuffers> b = _resampler->flush ();
+ if (b) {
+ _pending.push_back (shared_ptr<DecodedAudio> (new DecodedAudio (b, _audio_position.get ())));
+ _audio_position = _audio_position.get() + b->frames ();
+ }
+ */
+}
+
+void
+AudioDecoder::seek (ContentTime, bool)
{
- return _audio_content->audio_channels () > 0;
+ _audio_position.reset ();
+ reset_decoded_audio ();
}
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "decoder.h"
#include "content.h"
#include "audio_content.h"
+#include "content_audio.h"
class AudioBuffers;
+class Resampler;
/** @class AudioDecoder.
* @brief Parent class for audio decoders.
class AudioDecoder : public virtual Decoder
{
public:
- AudioDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const AudioContent>);
-
- bool has_audio () const;
-
- /** Emitted when some audio data is ready */
- boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio;
+ AudioDecoder (boost::shared_ptr<const AudioContent>);
+
+ boost::shared_ptr<const AudioContent> audio_content () const {
+ return _audio_content;
+ }
+ /** Try to fetch some audio from a specific place in this content.
+ * @param frame Frame to start from.
+ * @param length Frames to get.
+ * @param accurate true to try hard to return frames from exactly `frame', false if we don't mind nearby frames.
+ * @return Time-stamped audio data which may or may not be from the location (and of the length) requested.
+ */
+ boost::shared_ptr<ContentAudio> get_audio (AudioFrame time, AudioFrame length, bool accurate);
+
protected:
- void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+ void seek (ContentTime time, bool accurate);
+ void audio (boost::shared_ptr<const AudioBuffers>, ContentTime);
+ void flush ();
+ void reset_decoded_audio ();
+
boost::shared_ptr<const AudioContent> _audio_content;
- AudioContent::Frame _audio_position;
+ boost::shared_ptr<Resampler> _resampler;
+ boost::optional<AudioFrame> _audio_position;
+ /** Currently-available decoded audio data */
+ ContentAudio _decoded_audio;
};
#endif
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include <libxml++/libxml++.h>
#include <libcxml/cxml.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "audio_mapping.h"
#include "util.h"
using std::min;
using boost::shared_ptr;
using boost::dynamic_pointer_cast;
-using libdcp::raw_convert;
+using dcp::raw_convert;
AudioMapping::AudioMapping ()
: _content_channels (0)
}
/** Create a default AudioMapping for a given channel count.
- * @param c Number of channels.
+ * @param channels Number of channels.
*/
-AudioMapping::AudioMapping (int c)
+AudioMapping::AudioMapping (int channels)
{
- setup (c);
+ setup (channels);
}
void
if (_content_channels == 1) {
/* Mono -> Centre */
- set (0, libdcp::CENTRE, 1);
+ set (0, dcp::CENTRE, 1);
} else {
/* 1:1 mapping */
for (int i = 0; i < min (_content_channels, MAX_DCP_AUDIO_CHANNELS); ++i) {
- set (i, static_cast<libdcp::Channel> (i), 1);
+ set (i, static_cast<dcp::Channel> (i), 1);
}
}
}
-AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node, int state_version)
+AudioMapping::AudioMapping (cxml::ConstNodePtr node, int state_version)
{
setup (node->number_child<int> ("ContentChannels"));
/* Old-style: on/off mapping */
list<cxml::NodePtr> const c = node->node_children ("Map");
for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
- set ((*i)->number_child<int> ("ContentIndex"), static_cast<libdcp::Channel> ((*i)->number_child<int> ("DCP")), 1);
+ set ((*i)->number_child<int> ("ContentIndex"), static_cast<dcp::Channel> ((*i)->number_child<int> ("DCP")), 1);
}
} else {
list<cxml::NodePtr> const c = node->node_children ("Gain");
for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
set (
(*i)->number_attribute<int> ("Content"),
- static_cast<libdcp::Channel> ((*i)->number_attribute<int> ("DCP")),
+ static_cast<dcp::Channel> ((*i)->number_attribute<int> ("DCP")),
raw_convert<float> ((*i)->content ())
);
}
}
void
-AudioMapping::set (int c, libdcp::Channel d, float g)
+AudioMapping::set (int c, dcp::Channel d, float g)
{
_gain[c][d] = g;
}
float
-AudioMapping::get (int c, libdcp::Channel d) const
+AudioMapping::get (int c, dcp::Channel d) const
{
return _gain[c][d];
}
xmlpp::Element* t = node->add_child ("Gain");
t->set_attribute ("Content", raw_convert<string> (c));
t->set_attribute ("DCP", raw_convert<string> (d));
- t->add_child_text (raw_convert<string> (get (c, static_cast<libdcp::Channel> (d))));
+ t->add_child_text (raw_convert<string> (get (c, static_cast<dcp::Channel> (d))));
}
}
}
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#define DCPOMATIC_AUDIO_MAPPING_H
#include <vector>
-#include <libdcp/types.h>
#include <boost/shared_ptr.hpp>
+#include <dcp/types.h>
+#include <libcxml/cxml.h>
namespace xmlpp {
class Node;
{
public:
AudioMapping ();
- AudioMapping (int);
- AudioMapping (boost::shared_ptr<const cxml::Node>, int);
+ AudioMapping (int channels);
+ AudioMapping (cxml::ConstNodePtr, int);
/* Default copy constructor is fine */
void make_default ();
- void set (int, libdcp::Channel, float);
- float get (int, libdcp::Channel) const;
+ void set (int, dcp::Channel, float);
+ float get (int, dcp::Channel) const;
int content_channels () const {
return _content_channels;
+++ /dev/null
-/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
-
- This program 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.
-
- This program 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 this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "audio_buffers.h"
-#include "util.h"
-
-template <class T, class F>
-class AudioMerger
-{
-public:
- AudioMerger (int channels, boost::function<F (T)> t_to_f, boost::function<T (F)> f_to_t)
- : _buffers (new AudioBuffers (channels, 0))
- , _last_pull (0)
- , _t_to_f (t_to_f)
- , _f_to_t (f_to_t)
- {}
-
- /** Pull audio up to a given time; after this call, no more data can be pushed
- * before the specified time.
- */
- TimedAudioBuffers<T>
- pull (T time)
- {
- TimedAudioBuffers<T> out;
-
- F const to_return = _t_to_f (time - _last_pull);
- out.audio.reset (new AudioBuffers (_buffers->channels(), to_return));
- /* And this is how many we will get from our buffer */
- F const to_return_from_buffers = min (to_return, _buffers->frames ());
-
- /* Copy the data that we have to the back end of the return buffer */
- out.audio->copy_from (_buffers.get(), to_return_from_buffers, 0, to_return - to_return_from_buffers);
- /* Silence any gap at the start */
- out.audio->make_silent (0, to_return - to_return_from_buffers);
-
- out.time = _last_pull;
- _last_pull = time;
-
- /* And remove the data we're returning from our buffers */
- if (_buffers->frames() > to_return_from_buffers) {
- _buffers->move (to_return_from_buffers, 0, _buffers->frames() - to_return_from_buffers);
- }
- _buffers->set_frames (_buffers->frames() - to_return_from_buffers);
-
- return out;
- }
-
- void
- push (boost::shared_ptr<const AudioBuffers> audio, T time)
- {
- assert (time >= _last_pull);
-
- F frame = _t_to_f (time);
- F after = max (_buffers->frames(), frame + audio->frames() - _t_to_f (_last_pull));
- _buffers->ensure_size (after);
- _buffers->accumulate_frames (audio.get(), 0, frame - _t_to_f (_last_pull), audio->frames ());
- _buffers->set_frames (after);
- }
-
- F min (F a, int b)
- {
- if (a < b) {
- return a;
- }
-
- return b;
- }
-
- F max (int a, F b)
- {
- if (a > b) {
- return a;
- }
-
- return b;
- }
-
- TimedAudioBuffers<T>
- flush ()
- {
- if (_buffers->frames() == 0) {
- return TimedAudioBuffers<T> ();
- }
-
- return TimedAudioBuffers<T> (_buffers, _last_pull);
- }
-
-private:
- boost::shared_ptr<AudioBuffers> _buffers;
- T _last_pull;
- boost::function<F (T)> _t_to_f;
- boost::function<T (F)> _f_to_t;
-};
using std::list;
using boost::shared_ptr;
-Cinema::Cinema (shared_ptr<const cxml::Node> node)
+Cinema::Cinema (cxml::ConstNodePtr node)
: name (node->string_child ("Name"))
, email (node->string_child ("Email"))
{
a constructor)
*/
void
-Cinema::read_screens (shared_ptr<const cxml::Node> node)
+Cinema::read_screens (cxml::ConstNodePtr node)
{
list<cxml::NodePtr> s = node->node_children ("Screen");
for (list<cxml::NodePtr>::iterator i = s.begin(); i != s.end(); ++i) {
_screens.remove (s);
}
-Screen::Screen (shared_ptr<const cxml::Node> node)
+Screen::Screen (cxml::ConstNodePtr node)
{
name = node->string_child ("Name");
- certificate = shared_ptr<libdcp::Certificate> (new libdcp::Certificate (node->string_child ("Certificate")));
+ certificate = shared_ptr<dcp::Certificate> (new dcp::Certificate (node->string_child ("Certificate")));
}
void
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/lib/cinema.h
+ * @brief Screen and Cinema classes.
+ */
+
#include <boost/enable_shared_from_this.hpp>
-#include <libdcp/certificates.h>
+#include <dcp/certificates.h>
+#include <libcxml/cxml.h>
class Cinema;
-namespace cxml {
- class Node;
-}
-
+/** @class Screen
+ * @brief A representation of a Screen for KDM generation.
+ *
+ * This is the name of the screen and the certificate of its
+ * server.
+ */
class Screen
{
public:
- Screen (std::string const & n, boost::shared_ptr<libdcp::Certificate> cert)
+ Screen (std::string const & n, boost::shared_ptr<dcp::Certificate> cert)
: name (n)
, certificate (cert)
{}
- Screen (boost::shared_ptr<const cxml::Node>);
+ Screen (cxml::ConstNodePtr);
void as_xml (xmlpp::Element *) const;
boost::shared_ptr<Cinema> cinema;
std::string name;
- boost::shared_ptr<libdcp::Certificate> certificate;
+ boost::shared_ptr<dcp::Certificate> certificate;
};
+/** @class Cinema
+ * @brief A description of a Cinema for KDM generation.
+ *
+ * This is a cinema name, contact email address and a list of
+ * Screen objects.
+ */
class Cinema : public boost::enable_shared_from_this<Cinema>
{
public:
, email (e)
{}
- Cinema (boost::shared_ptr<const cxml::Node>);
+ Cinema (cxml::ConstNodePtr);
- void read_screens (boost::shared_ptr<const cxml::Node>);
+ void read_screens (cxml::ConstNodePtr);
void as_xml (xmlpp::Element *) const;
*/
#include <libxml++/libxml++.h>
-#include <libdcp/colour_matrix.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/colour_matrix.h>
+#include <dcp/raw_convert.h>
#include <libcxml/cxml.h>
#include "config.h"
#include "colour_conversion.h"
using std::vector;
using boost::shared_ptr;
using boost::optional;
-using libdcp::raw_convert;
+using dcp::raw_convert;
ColourConversion::ColourConversion ()
: input_gamma (2.4)
{
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
- matrix (i, j) = libdcp::colour_matrix::srgb_to_xyz[i][j];
+ matrix (i, j) = dcp::colour_matrix::srgb_to_xyz[i][j];
}
}
}
#include <glib.h>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
-#include <libdcp/colour_matrix.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/colour_matrix.h>
+#include <dcp/raw_convert.h>
#include <libcxml/cxml.h>
#include "config.h"
#include "server.h"
using boost::optional;
using boost::algorithm::is_any_of;
using boost::algorithm::split;
-using libdcp::raw_convert;
+using dcp::raw_convert;
Config* Config::_instance = 0;
_allowed_dcp_frame_rates.push_back (50);
_allowed_dcp_frame_rates.push_back (60);
- _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6));
- _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6));
- _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, libdcp::colour_matrix::rec709_to_xyz, 2.6));
+ _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6));
+ _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, dcp::colour_matrix::srgb_to_xyz, 2.6));
+ _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, dcp::colour_matrix::rec709_to_xyz, 2.6));
}
void
/* Loading version 0 (before Rec. 709 was added as a preset).
Add it in.
*/
- _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, libdcp::colour_matrix::rec709_to_xyz, 2.6));
+ _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, dcp::colour_matrix::rec709_to_xyz, 2.6));
}
list<cxml::NodePtr> cin = f.node_children ("Cinema");
#include <boost/shared_ptr.hpp>
#include <boost/signals2.hpp>
#include <boost/filesystem.hpp>
-#include <libdcp/metadata.h>
+#include <dcp/metadata.h>
#include "dci_metadata.h"
#include "colour_conversion.h"
#include "server.h"
return _default_dcp_content_type;
}
- libdcp::XMLMetadata dcp_metadata () const {
+ dcp::XMLMetadata dcp_metadata () const {
return _dcp_metadata;
}
changed ();
}
- void set_dcp_metadata (libdcp::XMLMetadata m) {
+ void set_dcp_metadata (dcp::XMLMetadata m) {
_dcp_metadata = m;
changed ();
}
int _default_still_length;
Ratio const * _default_container;
DCPContentType const * _default_dcp_content_type;
- libdcp::XMLMetadata _dcp_metadata;
+ dcp::XMLMetadata _dcp_metadata;
int _default_j2k_bandwidth;
int _default_audio_delay;
std::vector<PresetColourConversion> _colour_conversions;
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/lib/content.cc
+ * @brief Content class.
+ */
+
#include <boost/thread/mutex.hpp>
#include <libxml++/libxml++.h>
#include <libcxml/cxml.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "content.h"
#include "util.h"
#include "content_factory.h"
using std::cout;
using std::vector;
using boost::shared_ptr;
-using libdcp::raw_convert;
+using dcp::raw_convert;
int const ContentProperty::PATH = 400;
int const ContentProperty::POSITION = 401;
}
-Content::Content (shared_ptr<const Film> f, Time p)
+Content::Content (shared_ptr<const Film> f, DCPTime p)
: _film (f)
, _position (p)
, _trim_start (0)
_paths.push_back (p);
}
-Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+Content::Content (shared_ptr<const Film> f, cxml::ConstNodePtr node)
: _film (f)
, _change_signals_frequent (false)
{
_paths.push_back ((*i)->content ());
}
_digest = node->string_child ("Digest");
- _position = node->number_child<Time> ("Position");
- _trim_start = node->number_child<Time> ("TrimStart");
- _trim_end = node->number_child<Time> ("TrimEnd");
+ _position = DCPTime (node->number_child<double> ("Position"));
+ _trim_start = DCPTime (node->number_child<double> ("TrimStart"));
+ _trim_end = DCPTime (node->number_child<double> ("TrimEnd"));
}
Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
, _change_signals_frequent (false)
{
for (size_t i = 0; i < c.size(); ++i) {
- if (i > 0 && c[i]->trim_start ()) {
+ if (i > 0 && c[i]->trim_start() > DCPTime()) {
throw JoinError (_("Only the first piece of content to be joined can have a start trim."));
}
- if (i < (c.size() - 1) && c[i]->trim_end ()) {
+ if (i < (c.size() - 1) && c[i]->trim_end () > DCPTime()) {
throw JoinError (_("Only the last piece of content to be joined can have an end trim."));
}
node->add_child("Path")->add_child_text (i->string ());
}
node->add_child("Digest")->add_child_text (_digest);
- node->add_child("Position")->add_child_text (raw_convert<string> (_position));
- node->add_child("TrimStart")->add_child_text (raw_convert<string> (_trim_start));
- node->add_child("TrimEnd")->add_child_text (raw_convert<string> (_trim_end));
+ node->add_child("Position")->add_child_text (raw_convert<string> (_position.get ()));
+ node->add_child("TrimStart")->add_child_text (raw_convert<string> (_trim_start.get ()));
+ node->add_child("TrimEnd")->add_child_text (raw_convert<string> (_trim_end.get ()));
}
void
}
void
-Content::set_position (Time p)
+Content::set_position (DCPTime p)
{
{
boost::mutex::scoped_lock lm (_mutex);
}
void
-Content::set_trim_start (Time t)
+Content::set_trim_start (DCPTime t)
{
{
boost::mutex::scoped_lock lm (_mutex);
}
void
-Content::set_trim_end (Time t)
+Content::set_trim_end (DCPTime t)
{
{
boost::mutex::scoped_lock lm (_mutex);
string
Content::technical_summary () const
{
- return String::compose ("%1 %2 %3", path_summary(), digest(), position());
+ return String::compose ("%1 %2 %3", path_summary(), digest(), position().seconds());
}
-Time
+DCPTime
Content::length_after_trim () const
{
return full_length() - trim_start() - trim_end();
}
-/** @param t A time relative to the start of this content (not the position).
- * @return true if this time is trimmed by our trim settings.
- */
-bool
-Content::trimmed (Time t) const
-{
- return (t < trim_start() || t > (full_length() - trim_end ()));
-}
-
/** @return string which includes everything about how this content affects
* its playlist.
*/
stringstream s;
s << Content::digest()
- << "_" << position()
- << "_" << trim_start()
- << "_" << trim_end();
+ << "_" << position().get()
+ << "_" << trim_start().get()
+ << "_" << trim_end().get();
return s.str ();
}
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/lib/content.h
+ * @brief Content class.
+ */
+
#ifndef DCPOMATIC_CONTENT_H
#define DCPOMATIC_CONTENT_H
#include <boost/thread/mutex.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
#include "types.h"
+#include "dcpomatic_time.h"
namespace cxml {
class Node;
static int const TRIM_END;
};
+/** @class Content
+ * @brief A piece of content represented by one or more files on disk.
+ */
class Content : public boost::enable_shared_from_this<Content>, public boost::noncopyable
{
public:
Content (boost::shared_ptr<const Film>);
- Content (boost::shared_ptr<const Film>, Time);
+ Content (boost::shared_ptr<const Film>, DCPTime);
Content (boost::shared_ptr<const Film>, boost::filesystem::path);
- Content (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+ Content (boost::shared_ptr<const Film>, cxml::ConstNodePtr);
Content (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
virtual ~Content () {}
virtual std::string technical_summary () const;
virtual std::string information () const = 0;
virtual void as_xml (xmlpp::Node *) const;
- virtual Time full_length () const = 0;
+ virtual DCPTime full_length () const = 0;
virtual std::string identifier () const;
boost::shared_ptr<Content> clone () const;
return _digest;
}
- void set_position (Time);
+ void set_position (DCPTime);
- /** Time that this content starts; i.e. the time that the first
+ /** DCPTime that this content starts; i.e. the time that the first
* bit of the content (trimmed or not) will happen.
*/
- Time position () const {
+ DCPTime position () const {
boost::mutex::scoped_lock lm (_mutex);
return _position;
}
- void set_trim_start (Time);
+ void set_trim_start (DCPTime);
- Time trim_start () const {
+ DCPTime trim_start () const {
boost::mutex::scoped_lock lm (_mutex);
return _trim_start;
}
- void set_trim_end (Time);
+ void set_trim_end (DCPTime);
- Time trim_end () const {
+ DCPTime trim_end () const {
boost::mutex::scoped_lock lm (_mutex);
return _trim_end;
}
- Time end () const {
- return position() + length_after_trim() - 1;
+ DCPTime end () const {
+ return position() + length_after_trim();
}
- Time length_after_trim () const;
+ DCPTime length_after_trim () const;
void set_change_signals_frequent (bool f) {
_change_signals_frequent = f;
}
- bool trimmed (Time) const;
-
boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> Changed;
protected:
boost::weak_ptr<const Film> _film;
/** _mutex which should be used to protect accesses, as examine
- jobs can update content state in threads other than the main one.
- */
+ * jobs can update content state in threads other than the main one.
+ */
mutable boost::mutex _mutex;
/** Paths of our data files */
private:
std::string _digest;
- Time _position;
- Time _trim_start;
- Time _trim_end;
+ DCPTime _position;
+ DCPTime _trim_start;
+ DCPTime _trim_end;
bool _change_signals_frequent;
};
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/lib/content_audio.h
+ * @brief ContentAudio class.
+ */
+
+#include "audio_buffers.h"
+
+/** @class ContentAudio
+ * @brief A block of audio from a piece of content, with a timestamp as a frame within that content.
+ */
+class ContentAudio
+{
+public:
+ ContentAudio ()
+ : audio (new AudioBuffers (0, 0))
+ , frame (0)
+ {}
+
+ ContentAudio (boost::shared_ptr<AudioBuffers> a, AudioFrame f)
+ : audio (a)
+ , frame (f)
+ {}
+
+ boost::shared_ptr<AudioBuffers> audio;
+ AudioFrame frame;
+};
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/lib/content_factory.cc
+ * @brief Methods to create content objects.
+ */
+
#include <libcxml/cxml.h>
#include "ffmpeg_content.h"
#include "image_content.h"
#include "sndfile_content.h"
+#include "subrip_content.h"
#include "util.h"
using std::string;
using std::list;
using boost::shared_ptr;
+/** Create a Content object from an XML node.
+ * @param film Film that the content will be in.
+ * @param node XML description.
+ * @param version XML state version.
+ * @param notes A list to which is added descriptions of any non-critial warnings / messages.
+ * @return Content object, or 0 if no content was recognised in the XML.
+ */
shared_ptr<Content>
content_factory (shared_ptr<const Film> film, cxml::NodePtr node, int version, list<string>& notes)
{
content.reset (new ImageContent (film, node, version));
} else if (type == "Sndfile") {
content.reset (new SndfileContent (film, node, version));
+ } else if (type == "SubRip") {
+ content.reset (new SubRipContent (film, node, version));
}
return content;
}
+/** Create a Content object from a file, depending on its extension.
+ * @param film Film that the content will be in.
+ * @param path File's path.
+ * @return Content object.
+ */
shared_ptr<Content>
content_factory (shared_ptr<const Film> film, boost::filesystem::path path)
{
shared_ptr<Content> content;
+
+ string ext = path.extension().string ();
+ transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
if (valid_image_file (path)) {
content.reset (new ImageContent (film, path));
} else if (SndfileContent::valid_file (path)) {
content.reset (new SndfileContent (film, path));
+ } else if (ext == ".srt") {
+ content.reset (new SubRipContent (film, path));
} else {
content.reset (new FFmpegContent (film, path));
}
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/lib/content_factory.h
+ * @brief Methods to create content objects.
+ */
+
class Film;
extern boost::shared_ptr<Content> content_factory (boost::shared_ptr<const Film>, cxml::NodePtr, int, std::list<std::string> &);
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "content_subtitle.h"
+
+ContentTime
+ContentTextSubtitle::from () const
+{
+ /* XXX: assuming we have some subs and they are all at the same time */
+ assert (!subs.empty ());
+ return ContentTime::from_seconds (double (subs.front().in().to_ticks()) / 250);
+}
+
+ContentTime
+ContentTextSubtitle::to () const
+{
+ /* XXX: assuming we have some subs and they are all at the same time */
+ assert (!subs.empty ());
+ return ContentTime::from_seconds (double (subs.front().out().to_ticks()) / 250);
+}
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_CONTENT_SUBTITLE_H
+#define DCPOMATIC_CONTENT_SUBTITLE_H
+
+#include <list>
+#include <dcp/subtitle_string.h>
+#include "dcpomatic_time.h"
+#include "rect.h"
+
+class Image;
+
+class ContentSubtitle
+{
+public:
+ virtual ContentTime from () const = 0;
+ virtual ContentTime to () const = 0;
+};
+
+class ContentImageSubtitle : public ContentSubtitle
+{
+public:
+ ContentImageSubtitle (ContentTime f, ContentTime t, boost::shared_ptr<Image> im, dcpomatic::Rect<double> r)
+ : image (im)
+ , rectangle (r)
+ , _from (f)
+ , _to (t)
+ {}
+
+ ContentTime from () const {
+ return _from;
+ }
+
+ ContentTime to () const {
+ return _to;
+ }
+
+ boost::shared_ptr<Image> image;
+ dcpomatic::Rect<double> rectangle;
+
+private:
+ ContentTime _from;
+ ContentTime _to;
+};
+
+class ContentTextSubtitle : public ContentSubtitle
+{
+public:
+ ContentTextSubtitle (std::list<dcp::SubtitleString> s)
+ : subs (s)
+ {}
+
+ ContentTime from () const;
+ ContentTime to () const;
+
+ std::list<dcp::SubtitleString> subs;
+};
+
+#endif
--- /dev/null
+/*
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_CONTENT_VIDEO_H
+#define DCPOMATIC_CONTENT_VIDEO_H
+
+class ImageProxy;
+
+/** @class ContentVideo
+ * @brief A frame of video straight out of some content.
+ */
+class ContentVideo
+{
+public:
+ ContentVideo ()
+ : eyes (EYES_BOTH)
+ {}
+
+ ContentVideo (boost::shared_ptr<const ImageProxy> i, Eyes e, Part p, VideoFrame f)
+ : image (i)
+ , eyes (e)
+ , part (p)
+ , frame (f)
+ {}
+
+ boost::shared_ptr<const ImageProxy> image;
+ Eyes eyes;
+ Part part;
+ VideoFrame frame;
+};
+
+#endif
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/lib/cross.h
+ * @brief Cross-platform compatibility code.
+ */
+
#ifndef DCPOMATIC_CROSS_H
#define DCPOMATIC_CROSS_H
extern FILE * fopen_boost (boost::filesystem::path, std::string);
extern int dcpomatic_fseek (FILE *, int64_t, int);
-/** A class which tries to keep the computer awake on various operating systems.
+/** @class Waker
+ * @brief A class which tries to keep the computer awake on various operating systems.
+ *
* Create a Waker to prevent sleep, and call ::nudge every so often (every minute or so).
* Destroy the Waker to allow sleep again.
*/
#include <iostream>
#include <libcxml/cxml.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "dci_metadata.h"
#include "i18n.h"
using std::string;
using boost::shared_ptr;
-using libdcp::raw_convert;
+using dcp::raw_convert;
-DCIMetadata::DCIMetadata (shared_ptr<const cxml::Node> node)
+DCIMetadata::DCIMetadata (cxml::ConstNodePtr node)
{
content_version = node->number_child<int> ("ContentVersion");
audio_language = node->string_child ("AudioLanguage");
#include <string>
#include <libxml++/libxml++.h>
-
-namespace cxml {
- class Node;
-}
+#include <libcxml/cxml.h>
class DCIMetadata
{
: content_version (1)
{}
- DCIMetadata (boost::shared_ptr<const cxml::Node>);
+ DCIMetadata (cxml::ConstNodePtr);
void as_xml (xmlpp::Node *) const;
void read_old_metadata (std::string, std::string);
vector<DCPContentType const *> DCPContentType::_dcp_content_types;
-DCPContentType::DCPContentType (string p, libdcp::ContentKind k, string d)
+DCPContentType::DCPContentType (string p, dcp::ContentKind k, string d)
: _pretty_name (p)
, _libdcp_kind (k)
, _dci_name (d)
void
DCPContentType::setup_dcp_content_types ()
{
- _dcp_content_types.push_back (new DCPContentType (_("Feature"), libdcp::FEATURE, N_("FTR")));
- _dcp_content_types.push_back (new DCPContentType (_("Short"), libdcp::SHORT, N_("SHR")));
- _dcp_content_types.push_back (new DCPContentType (_("Trailer"), libdcp::TRAILER, N_("TLR")));
- _dcp_content_types.push_back (new DCPContentType (_("Test"), libdcp::TEST, N_("TST")));
- _dcp_content_types.push_back (new DCPContentType (_("Transitional"), libdcp::TRANSITIONAL, N_("XSN")));
- _dcp_content_types.push_back (new DCPContentType (_("Rating"), libdcp::RATING, N_("RTG")));
- _dcp_content_types.push_back (new DCPContentType (_("Teaser"), libdcp::TEASER, N_("TSR")));
- _dcp_content_types.push_back (new DCPContentType (_("Policy"), libdcp::POLICY, N_("POL")));
- _dcp_content_types.push_back (new DCPContentType (_("Public Service Announcement"), libdcp::PUBLIC_SERVICE_ANNOUNCEMENT, N_("PSA")));
- _dcp_content_types.push_back (new DCPContentType (_("Advertisement"), libdcp::ADVERTISEMENT, N_("ADV")));
+ _dcp_content_types.push_back (new DCPContentType (_("Feature"), dcp::FEATURE, N_("FTR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Short"), dcp::SHORT, N_("SHR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Trailer"), dcp::TRAILER, N_("TLR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Test"), dcp::TEST, N_("TST")));
+ _dcp_content_types.push_back (new DCPContentType (_("Transitional"), dcp::TRANSITIONAL, N_("XSN")));
+ _dcp_content_types.push_back (new DCPContentType (_("Rating"), dcp::RATING, N_("RTG")));
+ _dcp_content_types.push_back (new DCPContentType (_("Teaser"), dcp::TEASER, N_("TSR")));
+ _dcp_content_types.push_back (new DCPContentType (_("Policy"), dcp::POLICY, N_("POL")));
+ _dcp_content_types.push_back (new DCPContentType (_("Public Service Announcement"), dcp::PUBLIC_SERVICE_ANNOUNCEMENT, N_("PSA")));
+ _dcp_content_types.push_back (new DCPContentType (_("Advertisement"), dcp::ADVERTISEMENT, N_("ADV")));
}
DCPContentType const *
#include <string>
#include <vector>
-#include <libdcp/dcp.h>
+#include <dcp/dcp.h>
/** @class DCPContentType
* @brief A description of the type of content for a DCP (e.g. feature, trailer etc.)
class DCPContentType : public boost::noncopyable
{
public:
- DCPContentType (std::string, libdcp::ContentKind, std::string);
+ DCPContentType (std::string, dcp::ContentKind, std::string);
/** @return user-visible `pretty' name */
std::string pretty_name () const {
return _pretty_name;
}
- libdcp::ContentKind libdcp_kind () const {
+ dcp::ContentKind libdcp_kind () const {
return _libdcp_kind;
}
private:
std::string _pretty_name;
- libdcp::ContentKind _libdcp_kind;
+ dcp::ContentKind _libdcp_kind;
std::string _dci_name;
/** All available DCP content types */
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
+#include <boost/lexical_cast.hpp>
#include <openssl/md5.h>
-#include <libdcp/rec709_linearised_gamma_lut.h>
-#include <libdcp/srgb_linearised_gamma_lut.h>
-#include <libdcp/gamma_lut.h>
-#include <libdcp/xyz_frame.h>
-#include <libdcp/rgb_xyz.h>
-#include <libdcp/colour_matrix.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/gamma_lut.h>
+#include <dcp/xyz_frame.h>
+#include <dcp/rgb_xyz.h>
+#include <dcp/colour_matrix.h>
+#include <dcp/raw_convert.h>
#include <libcxml/cxml.h>
#include "film.h"
#include "dcp_video_frame.h"
using std::stringstream;
using std::cout;
using boost::shared_ptr;
-using libdcp::Size;
-using libdcp::raw_convert;
+using boost::lexical_cast;
+using dcp::Size;
+using dcp::raw_convert;
#define DCI_COEFFICENT (48.0 / 52.37)
shared_ptr<EncodedData>
DCPVideoFrame::encode_locally ()
{
- shared_ptr<libdcp::LUT> in_lut;
- if (_frame->colour_conversion().input_gamma_linearised) {
- in_lut = libdcp::SRGBLinearisedGammaLUT::cache.get (12, _frame->colour_conversion().input_gamma);
- } else {
- in_lut = libdcp::GammaLUT::cache.get (12, _frame->colour_conversion().input_gamma);
- }
-
+ shared_ptr<dcp::GammaLUT> in_lut = dcp::GammaLUT::cache.get (
+ 12, _frame->colour_conversion().input_gamma, _frame->colour_conversion().input_gamma_linearised
+ );
+
/* XXX: libdcp should probably use boost */
double matrix[3][3];
}
}
- shared_ptr<libdcp::XYZFrame> xyz = libdcp::rgb_to_xyz (
+ shared_ptr<dcp::XYZFrame> xyz = dcp::rgb_to_xyz (
_frame->image(),
in_lut,
- libdcp::GammaLUT::cache.get (16, 1 / _frame->colour_conversion().output_gamma),
+ dcp::GammaLUT::cache.get (16, 1 / _frame->colour_conversion().output_gamma, false),
matrix
);
}
void
-EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, libdcp::FrameInfo fin) const
+EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, dcp::FrameInfo fin) const
{
boost::filesystem::path const info = film->info_path (frame, eyes);
FILE* h = fopen_boost (info, "w");
*/
-#include <openjpeg.h>
-#include <libdcp/picture_asset.h>
-#include <libdcp/picture_asset_writer.h>
+#include <dcp/picture_mxf_writer.h>
#include "util.h"
/** @file src/dcp_video_frame.h
void send (boost::shared_ptr<Socket> socket);
void write (boost::shared_ptr<const Film>, int, Eyes) const;
- void write_info (boost::shared_ptr<const Film>, int, Eyes, libdcp::FrameInfo) const;
+ void write_info (boost::shared_ptr<const Film>, int, Eyes, dcp::FrameInfo) const;
/** @return data */
uint8_t* data () const {
{
public:
DCPVideoFrame (boost::shared_ptr<const PlayerVideoFrame>, int, int, int, Resolution, boost::shared_ptr<Log>);
- DCPVideoFrame (boost::shared_ptr<const PlayerVideoFrame>, boost::shared_ptr<const cxml::Node>, boost::shared_ptr<Log>);
+ DCPVideoFrame (boost::shared_ptr<const PlayerVideoFrame>, cxml::ConstNodePtr, boost::shared_ptr<Log>);
boost::shared_ptr<EncodedData> encode_locally ();
boost::shared_ptr<EncodedData> encode_remotely (ServerDescription);
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "dcpomatic_time.h"
+
+using std::ostream;
+
+ContentTime::ContentTime (DCPTime d, FrameRateChange f)
+ : Time (rint (d.get() * f.speed_up))
+{
+
+}
+
+DCPTime min (DCPTime a, DCPTime b)
+{
+ if (a < b) {
+ return a;
+ }
+
+ return b;
+}
+
+ostream &
+operator<< (ostream& s, ContentTime t)
+{
+ s << "[CONT " << t.get() << " " << t.seconds() << "s]";
+ return s;
+}
+
+ostream &
+operator<< (ostream& s, DCPTime t)
+{
+ s << "[DCP " << t.get() << " " << t.seconds() << "s]";
+ return s;
+}
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_TIME_H
+#define DCPOMATIC_TIME_H
+
+#include <cmath>
+#include <ostream>
+#include <stdint.h>
+#include "frame_rate_change.h"
+
+class dcpomatic_round_up_test;
+
+class Time;
+
+/** A time in seconds, expressed as a number scaled up by Time::HZ. */
+class Time
+{
+public:
+ Time ()
+ : _t (0)
+ {}
+
+ explicit Time (int64_t t)
+ : _t (t)
+ {}
+
+ virtual ~Time () {}
+
+ int64_t get () const {
+ return _t;
+ }
+
+ double seconds () const {
+ return double (_t) / HZ;
+ }
+
+ template <typename T>
+ int64_t frames (T r) const {
+ return rint (_t * r / HZ);
+ }
+
+protected:
+ friend class dcptime_round_up_test;
+
+ int64_t _t;
+ static const int HZ = 96000;
+};
+
+class DCPTime;
+
+class ContentTime : public Time
+{
+public:
+ ContentTime () : Time () {}
+ explicit ContentTime (int64_t t) : Time (t) {}
+ ContentTime (int64_t n, int64_t d) : Time (n * HZ / d) {}
+ ContentTime (DCPTime d, FrameRateChange f);
+
+ bool operator< (ContentTime const & o) const {
+ return _t < o._t;
+ }
+
+ bool operator<= (ContentTime const & o) const {
+ return _t <= o._t;
+ }
+
+ bool operator== (ContentTime const & o) const {
+ return _t == o._t;
+ }
+
+ bool operator!= (ContentTime const & o) const {
+ return _t != o._t;
+ }
+
+ bool operator>= (ContentTime const & o) const {
+ return _t >= o._t;
+ }
+
+ bool operator> (ContentTime const & o) const {
+ return _t > o._t;
+ }
+
+ ContentTime operator+ (ContentTime const & o) const {
+ return ContentTime (_t + o._t);
+ }
+
+ ContentTime & operator+= (ContentTime const & o) {
+ _t += o._t;
+ return *this;
+ }
+
+ ContentTime operator- () const {
+ return ContentTime (-_t);
+ }
+
+ ContentTime operator- (ContentTime const & o) const {
+ return ContentTime (_t - o._t);
+ }
+
+ ContentTime & operator-= (ContentTime const & o) {
+ _t -= o._t;
+ return *this;
+ }
+
+ /** Round up to the nearest sampling interval
+ * at some sampling rate.
+ * @param r Sampling rate.
+ */
+ ContentTime round_up (float r) {
+ int64_t const n = rint (HZ / r);
+ int64_t const a = _t + n - 1;
+ return ContentTime (a - (a % n));
+ }
+
+ static ContentTime from_seconds (double s) {
+ return ContentTime (s * HZ);
+ }
+
+ template <class T>
+ static ContentTime from_frames (int64_t f, T r) {
+ assert (r > 0);
+ return ContentTime (f * HZ / r);
+ }
+
+ static ContentTime max () {
+ return ContentTime (INT64_MAX);
+ }
+};
+
+std::ostream& operator<< (std::ostream& s, ContentTime t);
+
+class DCPTime : public Time
+{
+public:
+ DCPTime () : Time () {}
+ explicit DCPTime (int64_t t) : Time (t) {}
+ DCPTime (ContentTime t, FrameRateChange c) : Time (rint (t.get() / c.speed_up)) {}
+
+ bool operator< (DCPTime const & o) const {
+ return _t < o._t;
+ }
+
+ bool operator<= (DCPTime const & o) const {
+ return _t <= o._t;
+ }
+
+ bool operator== (DCPTime const & o) const {
+ return _t == o._t;
+ }
+
+ bool operator!= (DCPTime const & o) const {
+ return _t != o._t;
+ }
+
+ bool operator>= (DCPTime const & o) const {
+ return _t >= o._t;
+ }
+
+ bool operator> (DCPTime const & o) const {
+ return _t > o._t;
+ }
+
+ DCPTime operator+ (DCPTime const & o) const {
+ return DCPTime (_t + o._t);
+ }
+
+ DCPTime & operator+= (DCPTime const & o) {
+ _t += o._t;
+ return *this;
+ }
+
+ DCPTime operator- () const {
+ return DCPTime (-_t);
+ }
+
+ DCPTime operator- (DCPTime const & o) const {
+ return DCPTime (_t - o._t);
+ }
+
+ DCPTime & operator-= (DCPTime const & o) {
+ _t -= o._t;
+ return *this;
+ }
+
+ /** Round up to the nearest sampling interval
+ * at some sampling rate.
+ * @param r Sampling rate.
+ */
+ DCPTime round_up (float r) {
+ int64_t const n = rint (HZ / r);
+ int64_t const a = _t + n - 1;
+ return DCPTime (a - (a % n));
+ }
+
+ DCPTime abs () const {
+ return DCPTime (std::abs (_t));
+ }
+
+ static DCPTime from_seconds (double s) {
+ return DCPTime (s * HZ);
+ }
+
+ template <class T>
+ static DCPTime from_frames (int64_t f, T r) {
+ assert (r > 0);
+ return DCPTime (f * HZ / r);
+ }
+
+ static DCPTime delta () {
+ return DCPTime (1);
+ }
+
+ static DCPTime max () {
+ return DCPTime (INT64_MAX);
+ }
+};
+
+DCPTime min (DCPTime a, DCPTime b);
+std::ostream& operator<< (std::ostream& s, DCPTime t);
+
+#endif
+++ /dev/null
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program 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.
-
- This program 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 this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/decoder.cc
- * @brief Parent class for decoders of content.
- */
-
-#include "film.h"
-#include "decoder.h"
-
-#include "i18n.h"
-
-using boost::shared_ptr;
-
-/** @param f Film.
- * @param o Decode options.
- */
-Decoder::Decoder (shared_ptr<const Film> f)
- : _film (f)
-{
-
-}
*/
/** @file src/decoder.h
- * @brief Parent class for decoders of content.
+ * @brief Decoder class.
*/
#ifndef DCPOMATIC_DECODER_H
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/utility.hpp>
+#include "types.h"
+#include "dcpomatic_time.h"
+class Decoded;
class Film;
/** @class Decoder.
class Decoder : public boost::noncopyable
{
public:
- Decoder (boost::shared_ptr<const Film>);
virtual ~Decoder () {}
- /** Perform one decode pass of the content, which may or may not
- * cause the object to emit some data.
+protected:
+ /** Seek so that the next pass() will yield the next thing
+ * (video/sound frame, subtitle etc.) at or after the requested
+ * time. Pass accurate = true to try harder to get close to
+ * the request. Note that seeking to time t may mean that
+ * the next pass() yields, for example, audio at time t and then
+ * video before it.
*/
- virtual void pass () = 0;
- virtual bool done () const = 0;
-
-protected:
-
- virtual void flush () {};
-
- /** The Film that we are decoding in */
- boost::weak_ptr<const Film> _film;
+ virtual void seek (ContentTime time, bool accurate) = 0;
+ virtual bool pass () = 0;
};
#endif
, _video_frames_out (0)
, _terminate (false)
{
- _have_a_real_frame[EYES_BOTH] = false;
- _have_a_real_frame[EYES_LEFT] = false;
- _have_a_real_frame[EYES_RIGHT] = false;
+
}
Encoder::~Encoder ()
}
void
-Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf, bool same)
+Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf)
{
_waker.nudge ();
if (_writer->can_fake_write (_video_frames_out)) {
_writer->fake_write (_video_frames_out, pvf->eyes ());
- _have_a_real_frame[pvf->eyes()] = false;
- frame_done ();
- } else if (same && _have_a_real_frame[pvf->eyes()]) {
- /* Use the last frame that we encoded. */
- _writer->repeat (_video_frames_out, pvf->eyes());
frame_done ();
} else {
/* Queue this new frame for encoding */
TIMING ("adding to queue of %1", _queue.size ());
_queue.push_back (shared_ptr<DCPVideoFrame> (
new DCPVideoFrame (
- pvf, _video_frames_out, _film->video_frame_rate(),
- _film->j2k_bandwidth(), _film->resolution(), _film->log()
+ pvf,
+ _video_frames_out,
+ _film->video_frame_rate(),
+ _film->j2k_bandwidth(),
+ _film->resolution(),
+ _film->log()
)
));
_condition.notify_all ();
- _have_a_real_frame[pvf->eyes()] = true;
}
if (pvf->eyes() != EYES_LEFT) {
void process_begin ();
/** Call with a frame of video.
- * @param pvf Video frame image.
- * @param same true if pvf is the same as the last time we were called.
+ * @param f Video frame.
*/
- void process_video (boost::shared_ptr<PlayerVideoFrame> pvf, bool same);
+ void process_video (boost::shared_ptr<PlayerVideoFrame> f);
/** Call with some audio data */
void process_audio (boost::shared_ptr<const AudioBuffers>);
/** Number of video frames written for the DCP so far */
int _video_frames_out;
- bool _have_a_real_frame[EYES_COUNT];
bool _terminate;
std::list<boost::shared_ptr<DCPVideoFrame> > _queue;
std::list<boost::thread *> _threads;
/*
- Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
}
-PixelFormatError::PixelFormatError (std::string o, AVPixelFormat f)
+PixelFormatError::PixelFormatError (string o, AVPixelFormat f)
: StringError (String::compose (_("Cannot handle pixel format %1 during %2"), f, o))
{
}
+
+SubRipError::SubRipError (string saw, string expecting, boost::filesystem::path f)
+ : FileError (String::compose (_("Error in SubRip file: saw %1 while expecting %2"), saw, expecting), f)
+{
+
+}
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
-#ifndef DCPOMATIC_EXCEPTIONS_H
-#define DCPOMATIC_EXCEPTIONS_H
-
-/** @file src/exceptions.h
+/** @file src/lib/exceptions.h
* @brief Our exceptions.
*/
+#ifndef DCPOMATIC_EXCEPTIONS_H
+#define DCPOMATIC_EXCEPTIONS_H
+
#include <stdexcept>
#include <cstring>
#include <boost/exception/all.hpp>
{}
};
-/** @class NetworkError.
+/** @class NetworkError
* @brief Indicates some problem with communication on the network.
*/
class NetworkError : public StringError
{}
};
+/** @class KDMError
+ * @brief A problem with a KDM.
+ */
class KDMError : public StringError
{
public:
{}
};
+/** @class PixelFormatError
+ * @brief A problem with an unsupported pixel format.
+ */
class PixelFormatError : public StringError
{
public:
PixelFormatError (std::string o, AVPixelFormat f);
};
-/** A parent class for classes which have a need to catch and
- * re-throw exceptions. This is intended for classes
- * which run their own thread; they should do something like
+/** @class SubRipError
+ * @brief An error that occurs while parsing a SubRip file.
+ */
+class SubRipError : public FileError
+{
+public:
+ SubRipError (std::string, std::string, boost::filesystem::path);
+};
+
+/** @class ExceptionStore
+ * @brief A parent class for classes which have a need to catch and
+ * re-throw exceptions.
+
+ * This is intended for classes which run their own thread; they should do
+ * something like
*
* void my_thread ()
* try {
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "ffmpeg.h"
#include "ffmpeg_content.h"
#include "exceptions.h"
using std::cout;
using std::stringstream;
using boost::shared_ptr;
-using libdcp::raw_convert;
+using dcp::raw_convert;
boost::mutex FFmpeg::_mutex;
AVCodecContext *
FFmpeg::audio_codec_context () const
{
+ if (!_ffmpeg_content->audio_stream ()) {
+ return 0;
+ }
+
return _ffmpeg_content->audio_stream()->stream(_format_context)->codec;
}
}
-FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version, list<string>& notes)
+FFmpegContent::FFmpegContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version, list<string>& notes)
: Content (f, node)
, VideoContent (f, node, version)
, AudioContent (f, node)
}
if (_first_video) {
- node->add_child("FirstVideo")->add_child_text (raw_convert<string> (_first_video.get ()));
+ node->add_child("FirstVideo")->add_child_text (raw_convert<string> (_first_video.get().get()));
}
}
Content::examine (job);
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this ()));
+ take_from_video_examiner (examiner);
- VideoContent::Frame video_length = 0;
- video_length = examiner->video_length ();
- film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length));
+ ContentTime video_length = examiner->video_length ();
+
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+ film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length.frames (video_frame_rate ())));
{
boost::mutex::scoped_lock lm (_mutex);
_first_video = examiner->first_video ();
}
- take_from_video_examiner (examiner);
-
signal_changed (ContentProperty::LENGTH);
signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
string
FFmpegContent::information () const
{
- if (video_length() == 0 || video_frame_rate() == 0) {
+ if (video_length() == ContentTime (0) || video_frame_rate() == 0) {
return "";
}
stringstream s;
- s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine(), video_frame_rate()) << "\n";
+ s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine().frames (video_frame_rate()), video_frame_rate()) << "\n";
s << VideoContent::information ();
return s.str ();
signal_changed (FFmpegContentProperty::AUDIO_STREAM);
}
-AudioContent::Frame
+ContentTime
FFmpegContent::audio_length () const
{
- int const cafr = content_audio_frame_rate ();
- float const vfr = video_frame_rate ();
- VideoContent::Frame const vl = video_length_after_3d_combine ();
-
- boost::mutex::scoped_lock lm (_mutex);
- if (!_audio_stream) {
- return 0;
+ if (!audio_stream ()) {
+ return ContentTime ();
}
-
- return video_frames_to_audio_frames (vl, cafr, vfr);
+
+ return video_length ();
}
int
}
int
-FFmpegContent::content_audio_frame_rate () const
+FFmpegContent::audio_frame_rate () const
{
boost::mutex::scoped_lock lm (_mutex);
return _audio_stream->frame_rate;
}
-int
-FFmpegContent::output_audio_frame_rate () const
-{
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- /* Resample to a DCI-approved sample rate */
- double t = dcp_audio_frame_rate (content_audio_frame_rate ());
-
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
-
- /* Compensate if the DCP is being run at a different frame rate
- to the source; that is, if the video is run such that it will
- look different in the DCP compared to the source (slower or faster).
- skip/repeat doesn't come into effect here.
- */
-
- if (frc.change_speed) {
- t *= video_frame_rate() * frc.factor() / film->video_frame_rate();
- }
-
- return rint (t);
-}
-
bool
operator== (FFmpegStream const & a, FFmpegStream const & b)
{
return a._id != b._id;
}
-FFmpegStream::FFmpegStream (shared_ptr<const cxml::Node> node)
+FFmpegStream::FFmpegStream (cxml::ConstNodePtr node)
: name (node->string_child ("Name"))
, _id (node->number_child<int> ("Id"))
{
root->add_child("Id")->add_child_text (raw_convert<string> (_id));
}
-FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node, int version)
+FFmpegAudioStream::FFmpegAudioStream (cxml::ConstNodePtr node, int version)
: FFmpegStream (node)
, mapping (node->node_child ("Mapping"), version)
{
root->add_child("FrameRate")->add_child_text (raw_convert<string> (frame_rate));
root->add_child("Channels")->add_child_text (raw_convert<string> (channels));
if (first_audio) {
- root->add_child("FirstAudio")->add_child_text (raw_convert<string> (first_audio.get ()));
+ root->add_child("FirstAudio")->add_child_text (raw_convert<string> (first_audio.get().get()));
}
mapping.as_xml (root->add_child("Mapping"));
}
* @param t String returned from to_string().
* @param v State file version.
*/
-FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node)
+FFmpegSubtitleStream::FFmpegSubtitleStream (cxml::ConstNodePtr node)
: FFmpegStream (node)
{
FFmpegStream::as_xml (root);
}
-Time
+DCPTime
FFmpegContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
-
- FrameRateConversion frc (video_frame_rate (), film->video_frame_rate ());
- return video_length_after_3d_combine() * frc.factor() * TIME_HZ / film->video_frame_rate ();
+ return DCPTime (video_length_after_3d_combine(), FrameRateChange (video_frame_rate (), film->video_frame_rate ()));
}
AudioMapping
FFmpegContent::set_audio_mapping (AudioMapping m)
{
audio_stream()->mapping = m;
- signal_changed (AudioContentProperty::AUDIO_MAPPING);
+ AudioContent::set_audio_mapping (m);
}
string
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#define DCPOMATIC_FFMPEG_CONTENT_H
#include <boost/enable_shared_from_this.hpp>
+#include <boost/lexical_cast.hpp>
#include "video_content.h"
#include "audio_content.h"
#include "subtitle_content.h"
, _id (i)
{}
- FFmpegStream (boost::shared_ptr<const cxml::Node>);
+ FFmpegStream (cxml::ConstNodePtr);
void as_xml (xmlpp::Node *) const;
mapping.make_default ();
}
- FFmpegAudioStream (boost::shared_ptr<const cxml::Node>, int);
+ FFmpegAudioStream (cxml::ConstNodePtr, int);
void as_xml (xmlpp::Node *) const;
int frame_rate;
int channels;
AudioMapping mapping;
- boost::optional<double> first_audio;
+ boost::optional<ContentTime> first_audio;
private:
friend class ffmpeg_pts_offset_test;
: FFmpegStream (n, i)
{}
- FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>);
+ FFmpegSubtitleStream (cxml::ConstNodePtr);
void as_xml (xmlpp::Node *) const;
};
{
public:
FFmpegContent (boost::shared_ptr<const Film>, boost::filesystem::path);
- FFmpegContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int version, std::list<std::string> &);
+ FFmpegContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int version, std::list<std::string> &);
FFmpegContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
boost::shared_ptr<FFmpegContent> shared_from_this () {
std::string technical_summary () const;
std::string information () const;
void as_xml (xmlpp::Node *) const;
- Time full_length () const;
+ DCPTime full_length () const;
std::string identifier () const;
/* AudioContent */
int audio_channels () const;
- AudioContent::Frame audio_length () const;
- int content_audio_frame_rate () const;
- int output_audio_frame_rate () const;
+ ContentTime audio_length () const;
+ int audio_frame_rate () const;
AudioMapping audio_mapping () const;
void set_audio_mapping (AudioMapping);
boost::filesystem::path audio_analysis_path () const;
void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>);
void set_audio_stream (boost::shared_ptr<FFmpegAudioStream>);
- boost::optional<double> first_video () const {
+ boost::optional<ContentTime> first_video () const {
boost::mutex::scoped_lock lm (_mutex);
return _first_video;
}
boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream;
std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
boost::shared_ptr<FFmpegAudioStream> _audio_stream;
- boost::optional<double> _first_video;
+ boost::optional<ContentTime> _first_video;
/** Video filters that should be used when generating DCPs */
std::vector<Filter const *> _filters;
};
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
-#include "film.h"
#include "filter.h"
#include "exceptions.h"
#include "image.h"
using boost::shared_ptr;
using boost::optional;
using boost::dynamic_pointer_cast;
-using libdcp::Size;
+using dcp::Size;
-FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio)
- : Decoder (f)
- , VideoDecoder (f, c)
- , AudioDecoder (f, c)
- , SubtitleDecoder (f)
+FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log> log)
+ : VideoDecoder (c)
+ , AudioDecoder (c)
, FFmpeg (c)
+ , _log (log)
, _subtitle_codec_context (0)
, _subtitle_codec (0)
- , _decode_video (video)
- , _decode_audio (audio)
- , _pts_offset (0)
- , _just_sought (false)
{
setup_subtitle ();
Then we remove big initial gaps in PTS and we allow our
insertion of black frames to work.
- We will do:
- audio_pts_to_use = audio_pts_from_ffmpeg + pts_offset;
- video_pts_to_use = video_pts_from_ffmpeg + pts_offset;
+ We will do pts_to_use = pts_from_ffmpeg + pts_offset;
*/
- bool const have_video = video && c->first_video();
- bool const have_audio = audio && c->audio_stream() && c->audio_stream()->first_audio;
+ bool const have_video = c->first_video();
+ bool const have_audio = c->audio_stream () && c->audio_stream()->first_audio;
/* First, make one of them start at 0 */
/* Now adjust both so that the video pts starts on a frame */
if (have_video && have_audio) {
- double first_video = c->first_video().get() + _pts_offset;
- double const old_first_video = first_video;
-
- /* Round the first video up to a frame boundary */
- if (fabs (rint (first_video * c->video_frame_rate()) - first_video * c->video_frame_rate()) > 1e-6) {
- first_video = ceil (first_video * c->video_frame_rate()) / c->video_frame_rate ();
- }
-
- _pts_offset += first_video - old_first_video;
+ ContentTime first_video = c->first_video().get() + _pts_offset;
+ ContentTime const old_first_video = first_video;
+ _pts_offset += first_video.round_up (c->video_frame_rate ()) - old_first_video;
}
}
/* XXX: should we reset _packet.data and size after each *_decode_* call? */
- if (_decode_video) {
- while (decode_video_packet ()) {}
- }
+ while (decode_video_packet ()) {}
- if (_ffmpeg_content->audio_stream() && _decode_audio) {
+ if (_ffmpeg_content->audio_stream()) {
decode_audio_packet ();
+ AudioDecoder::flush ();
}
-
- /* Stop us being asked for any more data */
- _video_position = _ffmpeg_content->video_length_after_3d_combine ();
- _audio_position = _ffmpeg_content->audio_length ();
}
-void
+bool
FFmpegDecoder::pass ()
{
int r = av_read_frame (_format_context, &_packet);
/* Maybe we should fail here, but for now we'll just finish off instead */
char buf[256];
av_strerror (r, buf, sizeof(buf));
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
- film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
+ _log->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
}
flush ();
- return;
+ return true;
}
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
int const si = _packet.stream_index;
- if (si == _video_stream && _decode_video) {
+ if (si == _video_stream) {
decode_video_packet ();
- } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si) && _decode_audio) {
+ } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si)) {
decode_audio_packet ();
- } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si) && film->with_subtitles ()) {
+ } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si)) {
decode_subtitle_packet ();
}
av_free_packet (&_packet);
+ return false;
}
/** @param data pointer to array of pointers to buffers.
return av_get_bytes_per_sample (audio_sample_format ());
}
-void
-FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate)
+int
+FFmpegDecoder::minimal_run (boost::function<bool (optional<ContentTime>, optional<ContentTime>, int)> finished)
{
- double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
+ int frames_read = 0;
+ optional<ContentTime> last_video;
+ optional<ContentTime> last_audio;
- /* If we are doing an accurate seek, our initial shot will be 5 frames (5 being
- a number plucked from the air) earlier than we want to end up. The loop below
- will hopefully then step through to where we want to be.
- */
- int initial = frame;
+ while (!finished (last_video, last_audio, frames_read)) {
+ int r = av_read_frame (_format_context, &_packet);
+ if (r < 0) {
+ /* We should flush our decoders here, possibly yielding a few more frames,
+ but the consequence of having to do that is too hideous to contemplate.
+ Instead we give up and say that you can't seek too close to the end
+ of a file.
+ */
+ return frames_read;
+ }
+
+ ++frames_read;
+
+ double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base);
+
+ if (_packet.stream_index == _video_stream) {
+
+ avcodec_get_frame_defaults (_frame);
+
+ int got_picture = 0;
+ r = avcodec_decode_video2 (video_codec_context(), _frame, &got_picture, &_packet);
+ if (r >= 0 && got_picture) {
+ last_video = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
+ }
+
+ } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, _packet.stream_index)) {
+ AVPacket copy_packet = _packet;
+ while (copy_packet.size > 0) {
- if (accurate) {
- initial -= 5;
+ int got_frame;
+ r = avcodec_decode_audio4 (audio_codec_context(), _frame, &got_frame, &_packet);
+ if (r >= 0 && got_frame) {
+ last_audio = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
+ }
+
+ copy_packet.data += r;
+ copy_packet.size -= r;
+ }
+ }
+
+ av_free_packet (&_packet);
}
- if (initial < 0) {
- initial = 0;
+ return frames_read;
+}
+
+bool
+FFmpegDecoder::seek_overrun_finished (ContentTime seek, optional<ContentTime> last_video, optional<ContentTime> last_audio) const
+{
+ return (last_video && last_video.get() >= seek) || (last_audio && last_audio.get() >= seek);
+}
+
+bool
+FFmpegDecoder::seek_final_finished (int n, int done) const
+{
+ return n == done;
+}
+
+void
+FFmpegDecoder::seek_and_flush (ContentTime t)
+{
+ ContentTime const u = t - _pts_offset;
+ int64_t s = u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base);
+
+ if (_ffmpeg_content->audio_stream ()) {
+ s = min (
+ s, int64_t (u.seconds() / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base))
+ );
}
- /* Initial seek time in the stream's timebase */
- int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _pts_offset) / time_base;
+ /* Ridiculous empirical hack */
+ s--;
+ if (s < 0) {
+ s = 0;
+ }
- av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
+ av_seek_frame (_format_context, _video_stream, s, 0);
avcodec_flush_buffers (video_codec_context());
+ if (audio_codec_context ()) {
+ avcodec_flush_buffers (audio_codec_context ());
+ }
if (_subtitle_codec_context) {
avcodec_flush_buffers (_subtitle_codec_context);
}
+}
- /* This !accurate is piling hack upon hack; setting _just_sought to true
- even with accurate == true defeats our attempt to align the start
- of the video and audio. Here we disable that defeat when accurate == true
- i.e. when we are making a DCP rather than just previewing one.
- Ewww. This should be gone in 2.0.
+void
+FFmpegDecoder::seek (ContentTime time, bool accurate)
+{
+ VideoDecoder::seek (time, accurate);
+ AudioDecoder::seek (time, accurate);
+
+ /* If we are doing an accurate seek, our initial shot will be 2s (2 being
+ a number plucked from the air) earlier than we want to end up. The loop below
+ will hopefully then step through to where we want to be.
*/
- if (!accurate) {
- _just_sought = true;
+
+ ContentTime pre_roll = accurate ? ContentTime::from_seconds (2) : ContentTime (0);
+ ContentTime initial_seek = time - pre_roll;
+ if (initial_seek < ContentTime (0)) {
+ initial_seek = ContentTime (0);
}
-
- _video_position = frame;
-
- if (frame == 0 || !accurate) {
- /* We're already there, or we're as close as we need to be */
+
+ /* Initial seek time in the video stream's timebase */
+
+ seek_and_flush (initial_seek);
+
+ if (!accurate) {
+ /* That'll do */
return;
}
- while (1) {
- int r = av_read_frame (_format_context, &_packet);
- if (r < 0) {
- return;
- }
-
- if (_packet.stream_index != _video_stream) {
- av_free_packet (&_packet);
- continue;
- }
-
- int finished = 0;
- r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
- if (r >= 0 && finished) {
- _video_position = rint (
- (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset) * _ffmpeg_content->video_frame_rate()
- );
+ int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2));
- if (_video_position >= (frame - 1)) {
- av_free_packet (&_packet);
- break;
- }
- }
-
- av_free_packet (&_packet);
+ seek_and_flush (initial_seek);
+ if (N > 0) {
+ minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _3));
}
}
int frame_finished;
int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, ©_packet);
+
if (decode_result < 0) {
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
- film->log()->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result));
+ _log->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result));
return;
}
if (frame_finished) {
-
- if (_audio_position == 0) {
- /* Where we are in the source, in seconds */
- double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
- * av_frame_get_best_effort_timestamp(_frame) + _pts_offset;
-
- if (pts > 0) {
- /* Emit some silence */
- shared_ptr<AudioBuffers> silence (
- new AudioBuffers (
- _ffmpeg_content->audio_channels(),
- pts * _ffmpeg_content->content_audio_frame_rate()
- )
- );
-
- silence->make_silent ();
- audio (silence, _audio_position);
- }
- }
+ ContentTime const ct = ContentTime::from_seconds (
+ av_frame_get_best_effort_timestamp (_frame) *
+ av_q2d (_ffmpeg_content->audio_stream()->stream (_format_context)->time_base))
+ + _pts_offset;
int const data_size = av_samples_get_buffer_size (
0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
);
-
- audio (deinterleave_audio (_frame->data, data_size), _audio_position);
+
+ audio (deinterleave_audio (_frame->data, data_size), ct);
}
copy_packet.data += decode_result;
shared_ptr<FilterGraph> graph;
list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
- while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
+ while (i != _filter_graphs.end() && !(*i)->can_process (dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
++i;
}
if (i == _filter_graphs.end ()) {
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
+ graph.reset (new FilterGraph (_ffmpeg_content, dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
_filter_graphs.push_back (graph);
-
- film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
+ _log->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
} else {
graph = *i;
}
shared_ptr<Image> image = i->first;
if (i->second != AV_NOPTS_VALUE) {
-
- double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset;
-
- if (_just_sought) {
- /* We just did a seek, so disable any attempts to correct for where we
- are / should be.
- */
- _video_position = rint (pts * _ffmpeg_content->video_frame_rate ());
- _just_sought = false;
- }
-
- double const next = _video_position / _ffmpeg_content->video_frame_rate();
- double const one_frame = 1 / _ffmpeg_content->video_frame_rate ();
- double delta = pts - next;
-
- while (delta > one_frame) {
- /* This PTS is more than one frame forward in time of where we think we should be; emit
- a black frame.
- */
-
- /* XXX: I think this should be a copy of the last frame... */
- boost::shared_ptr<Image> black (
- new Image (
- static_cast<AVPixelFormat> (_frame->format),
- libdcp::Size (video_codec_context()->width, video_codec_context()->height),
- true
- )
- );
-
- black->make_black ();
- video (shared_ptr<ImageProxy> (new RawImageProxy (image)), false, _video_position);
- delta -= one_frame;
- }
-
- if (delta > -one_frame) {
- /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */
- video (shared_ptr<ImageProxy> (new RawImageProxy (image)), false, _video_position);
- }
-
+ double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset.seconds ();
+ video (shared_ptr<ImageProxy> (new RawImageProxy (image)), rint (pts * _ffmpeg_content->video_frame_rate ()));
} else {
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
- film->log()->log ("Dropping frame without PTS");
+ _log->log ("Dropping frame without PTS");
}
}
}
}
-bool
-FFmpegDecoder::done () const
-{
- bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length());
- bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length());
- return vd && ad;
-}
-
void
FFmpegDecoder::decode_subtitle_packet ()
{
indicate that the previous subtitle should stop.
*/
if (sub.num_rects <= 0) {
- subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0);
+ image_subtitle (ContentTime (), ContentTime (), shared_ptr<Image> (), dcpomatic::Rect<double> ());
return;
} else if (sub.num_rects > 1) {
throw DecodeError (_("multi-part subtitles not yet supported"));
}
- /* Subtitle PTS in seconds (within the source, not taking into account any of the
+ /* Subtitle PTS (within the source, not taking into account any of the
source that we may have chopped off for the DCP)
*/
- double const packet_time = (static_cast<double> (sub.pts ) / AV_TIME_BASE) + _pts_offset;
-
+ ContentTime packet_time = ContentTime::from_seconds (static_cast<double> (sub.pts) / AV_TIME_BASE) + _pts_offset;
+
/* hence start time for this sub */
- Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
- Time const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
+ ContentTime const from = packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3);
+ ContentTime const to = packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3);
AVSubtitleRect const * rect = sub.rects[0];
if (rect->type != SUBTITLE_BITMAP) {
- throw DecodeError (_("non-bitmap subtitles not yet supported"));
+ /* XXX */
+ // throw DecodeError (_("non-bitmap subtitles not yet supported"));
+ return;
}
/* Note RGBA is expressed little-endian, so the first byte in the word is R, second
G, third B, fourth A.
*/
- shared_ptr<Image> image (new Image (PIX_FMT_RGBA, libdcp::Size (rect->w, rect->h), true));
+ shared_ptr<Image> image (new Image (PIX_FMT_RGBA, dcp::Size (rect->w, rect->h), true));
/* Start of the first line in the subtitle */
uint8_t* sub_p = rect->pict.data[0];
out_p += image->stride()[0] / sizeof (uint32_t);
}
- libdcp::Size const vs = _ffmpeg_content->video_size ();
+ dcp::Size const vs = _ffmpeg_content->video_size ();
- subtitle (
+ image_subtitle (
+ from,
+ to,
image,
dcpomatic::Rect<double> (
static_cast<double> (rect->x) / vs.width,
static_cast<double> (rect->y) / vs.height,
static_cast<double> (rect->w) / vs.width,
static_cast<double> (rect->h) / vs.height
- ),
- from,
- to
+ )
);
-
avsubtitle_free (&sub);
}
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "subtitle_decoder.h"
#include "ffmpeg.h"
-class Film;
+class Log;
class FilterGraph;
class ffmpeg_pts_offset_test;
class FFmpegDecoder : public VideoDecoder, public AudioDecoder, public SubtitleDecoder, public FFmpeg
{
public:
- FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio);
+ FFmpegDecoder (boost::shared_ptr<const FFmpegContent>, boost::shared_ptr<Log>);
~FFmpegDecoder ();
- void pass ();
- void seek (VideoContent::Frame, bool);
- bool done () const;
-
private:
friend class ::ffmpeg_pts_offset_test;
- static double compute_pts_offset (double, double, float);
-
+ void seek (ContentTime time, bool);
+ bool pass ();
void flush ();
void setup_subtitle ();
void maybe_add_subtitle ();
boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
+ bool seek_overrun_finished (ContentTime, boost::optional<ContentTime>, boost::optional<ContentTime>) const;
+ bool seek_final_finished (int, int) const;
+ int minimal_run (boost::function<bool (boost::optional<ContentTime>, boost::optional<ContentTime>, int)>);
+ void seek_and_flush (ContentTime);
+
+ boost::shared_ptr<Log> _log;
AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle
AVCodec* _subtitle_codec; ///< may be 0 if there is no subtitle
std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
boost::mutex _filter_graphs_mutex;
- bool _decode_video;
- bool _decode_audio;
-
- double _pts_offset;
- bool _just_sought;
+ ContentTime _pts_offset;
};
}
}
-optional<double>
+optional<ContentTime>
FFmpegExaminer::frame_time (AVStream* s) const
{
- optional<double> t;
+ optional<ContentTime> t;
int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
if (bet != AV_NOPTS_VALUE) {
- t = bet * av_q2d (s->time_base);
+ t = ContentTime::from_seconds (bet * av_q2d (s->time_base));
}
return t;
float
FFmpegExaminer::video_frame_rate () const
{
- AVStream* s = _format_context->streams[_video_stream];
-
- if (s->avg_frame_rate.num && s->avg_frame_rate.den) {
- return av_q2d (s->avg_frame_rate);
- }
-
- return av_q2d (s->r_frame_rate);
+ /* This use of r_frame_rate is debateable; there's a few different
+ * frame rates in the format context, but this one seems to be the most
+ * reliable.
+ */
+ return av_q2d (av_stream_get_r_frame_rate (_format_context->streams[_video_stream]));
}
-libdcp::Size
+dcp::Size
FFmpegExaminer::video_size () const
{
- return libdcp::Size (video_codec_context()->width, video_codec_context()->height);
+ return dcp::Size (video_codec_context()->width, video_codec_context()->height);
}
-/** @return Length (in video frames) according to our content's header */
-VideoContent::Frame
+/** @return Length according to our content's header */
+ContentTime
FFmpegExaminer::video_length () const
{
- VideoContent::Frame const length = (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
- return max (1, length);
+ ContentTime const length = ContentTime::from_seconds (double (_format_context->duration) / AV_TIME_BASE);
+ return ContentTime (max (int64_t (1), length.get ()));
}
string
FFmpegExaminer (boost::shared_ptr<const FFmpegContent>);
float video_frame_rate () const;
- libdcp::Size video_size () const;
- VideoContent::Frame video_length () const;
+ dcp::Size video_size () const;
+ ContentTime video_length () const;
std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const {
return _subtitle_streams;
return _audio_streams;
}
- boost::optional<double> first_video () const {
+ boost::optional<ContentTime> first_video () const {
return _first_video;
}
std::string stream_name (AVStream* s) const;
std::string audio_stream_name (AVStream* s) const;
std::string subtitle_stream_name (AVStream* s) const;
- boost::optional<double> frame_time (AVStream* s) const;
+ boost::optional<ContentTime> frame_time (AVStream* s) const;
std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
- boost::optional<double> _first_video;
+ boost::optional<ContentTime> _first_video;
};
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/lib/file_group.cc
+ * @brief FileGroup class.
+ */
+
#include <cstdio>
#include <sndfile.h>
#include "file_group.h"
using std::vector;
using std::cout;
+/** Construct a FileGroup with no files */
FileGroup::FileGroup ()
: _current_path (0)
, _current_file (0)
}
+/** Construct a FileGroup with a single file */
FileGroup::FileGroup (boost::filesystem::path p)
: _current_path (0)
, _current_file (0)
{
_paths.push_back (p);
+ ensure_open_path (0);
seek (0, SEEK_SET);
}
+/** Construct a FileGroup with multiple files */
FileGroup::FileGroup (vector<boost::filesystem::path> const & p)
: _paths (p)
, _current_path (0)
seek (0, SEEK_SET);
}
+/** Destroy a FileGroup, closing any open file */
FileGroup::~FileGroup ()
{
if (_current_file) {
return read;
}
+/** @return Combined length of all the files */
int64_t
FileGroup::length () const
{
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/lib/file_group.h
+ * @brief FileGroup class.
+ */
+
#ifndef DCPOMATIC_FILE_GROUP_H
#define DCPOMATIC_FILE_GROUP_H
#include <vector>
#include <boost/filesystem.hpp>
+/** @class FileGroup
+ * @brief A class to make a list of files behave like they were concatenated.
+ */
class FileGroup
{
public:
#include <unistd.h>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
-#include <boost/date_time.hpp>
+#include <boost/lexical_cast.hpp>
#include <libxml++/libxml++.h>
#include <libcxml/cxml.h>
-#include <libdcp/signer_chain.h>
-#include <libdcp/cpl.h>
-#include <libdcp/signer.h>
-#include <libdcp/util.h>
-#include <libdcp/kdm.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/signer_chain.h>
+#include <dcp/cpl.h>
+#include <dcp/signer.h>
+#include <dcp/util.h>
+#include <dcp/local_time.h>
+#include <dcp/raw_convert.h>
#include "film.h"
#include "job.h"
#include "util.h"
using boost::ends_with;
using boost::starts_with;
using boost::optional;
-using libdcp::Size;
-using libdcp::Signer;
-using libdcp::raw_convert;
+using dcp::Size;
+using dcp::Signer;
+using dcp::raw_convert;
/* 5 -> 6
* AudioMapping XML changed.
_sequence_video = f.bool_child ("SequenceVideo");
_three_d = f.bool_child ("ThreeD");
_interop = f.bool_child ("Interop");
- _key = libdcp::Key (f.string_child ("Key"));
+ _key = dcp::Key (f.string_child ("Key"));
list<string> notes;
/* This method is the only one that can return notes (so far) */
return file (p);
}
-/** @return List of subdirectories (not full paths) containing DCPs that can be successfully libdcp::DCP::read() */
+/** @return List of subdirectories (not full paths) containing DCPs that can be successfully dcp::DCP::read() */
list<boost::filesystem::path>
Film::dcps () const
{
) {
try {
- libdcp::DCP dcp (*i);
+ dcp::DCP dcp (*i);
dcp.read ();
out.push_back (i->path().leaf ());
} catch (...) {
_playlist->move_later (c);
}
-Time
+DCPTime
Film::length () const
{
return _playlist->length ();
return _playlist->has_subtitles ();
}
-OutputVideoFrame
+int
Film::best_video_frame_rate () const
{
return _playlist->best_dcp_frame_rate ();
}
+FrameRateChange
+Film::active_frame_rate_change (DCPTime t) const
+{
+ return _playlist->active_frame_rate_change (t, video_frame_rate ());
+}
+
void
Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
{
signal_changed (CONTENT);
}
-OutputAudioFrame
-Film::time_to_audio_frames (Time t) const
-{
- return divide_with_round (t * audio_frame_rate (), TIME_HZ);
-}
-
-OutputVideoFrame
-Film::time_to_video_frames (Time t) const
-{
- return divide_with_round (t * video_frame_rate (), TIME_HZ);
-}
-
-Time
-Film::audio_frames_to_time (OutputAudioFrame f) const
-{
- return divide_with_round (f * TIME_HZ, audio_frame_rate ());
-}
-
-Time
-Film::video_frames_to_time (OutputVideoFrame f) const
-{
- return divide_with_round (f * TIME_HZ, video_frame_rate ());
-}
-
-OutputAudioFrame
+int
Film::audio_frame_rate () const
{
/* XXX */
}
/** @return Size of the largest possible image in whatever resolution we are using */
-libdcp::Size
+dcp::Size
Film::full_frame () const
{
switch (_resolution) {
case RESOLUTION_2K:
- return libdcp::Size (2048, 1080);
+ return dcp::Size (2048, 1080);
case RESOLUTION_4K:
- return libdcp::Size (4096, 2160);
+ return dcp::Size (4096, 2160);
}
assert (false);
- return libdcp::Size ();
+ return dcp::Size ();
}
/** @return Size of the frame */
-libdcp::Size
+dcp::Size
Film::frame_size () const
{
return fit_ratio_within (container()->ratio(), full_frame ());
}
-/** @param from KDM from time in local time.
- * @param to KDM to time in local time.
- */
-libdcp::KDM
+dcp::EncryptedKDM
Film::make_kdm (
- shared_ptr<libdcp::Certificate> target,
+ shared_ptr<dcp::Certificate> target,
boost::filesystem::path dcp_dir,
- boost::posix_time::ptime from,
- boost::posix_time::ptime until
+ dcp::LocalTime from,
+ dcp::LocalTime until
) const
{
shared_ptr<const Signer> signer = make_signer ();
- libdcp::DCP dcp (dir (dcp_dir.string ()));
+ dcp::DCP dcp (dir (dcp_dir.string ()));
try {
dcp.read ();
throw KDMError (_("Could not read DCP to make KDM for"));
}
- time_t now = time (0);
- struct tm* tm = localtime (&now);
- string const issue_date = libdcp::tm_to_string (tm);
-
dcp.cpls().front()->set_mxf_keys (key ());
- return libdcp::KDM (dcp.cpls().front(), signer, target, from, until, "DCP-o-matic", issue_date);
+ return dcp::DecryptedKDM (
+ dcp.cpls().front(), from, until, "DCP-o-matic", dcp.cpls().front()->content_title_text(), dcp::LocalTime().as_string()
+ ).encrypt (signer, target);
}
-list<libdcp::KDM>
+list<dcp::EncryptedKDM>
Film::make_kdms (
list<shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime until
+ dcp::LocalTime from,
+ dcp::LocalTime until
) const
{
- list<libdcp::KDM> kdms;
+ list<dcp::EncryptedKDM> kdms;
for (list<shared_ptr<Screen> >::iterator i = screens.begin(); i != screens.end(); ++i) {
kdms.push_back (make_kdm ((*i)->certificate, dcp, from, until));
uint64_t
Film::required_disk_space () const
{
- return uint64_t (j2k_bandwidth() / 8) * length() / TIME_HZ;
+ return uint64_t (j2k_bandwidth() / 8) * length().seconds();
}
/** This method checks the disk that the Film is on and tries to decide whether or not
#include <boost/signals2.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/filesystem.hpp>
-#include <libdcp/key.h>
-#include <libdcp/kdm.h>
+#include <dcp/key.h>
+#include <dcp/decrypted_kdm.h>
+#include <dcp/encrypted_kdm.h>
#include "util.h"
#include "types.h"
#include "dci_metadata.h"
return _dirty;
}
- libdcp::Size full_frame () const;
- libdcp::Size frame_size () const;
+ dcp::Size full_frame () const;
+ dcp::Size frame_size () const;
std::list<boost::filesystem::path> dcps () const;
boost::shared_ptr<Player> make_player () const;
boost::shared_ptr<Playlist> playlist () const;
- OutputAudioFrame audio_frame_rate () const;
-
- OutputAudioFrame time_to_audio_frames (Time) const;
- OutputVideoFrame time_to_video_frames (Time) const;
- Time video_frames_to_time (OutputVideoFrame) const;
- Time audio_frames_to_time (OutputAudioFrame) const;
+ int audio_frame_rate () const;
uint64_t required_disk_space () const;
bool should_be_enough_disk_space (double &, double &) const;
/* Proxies for some Playlist methods */
ContentList content () const;
- Time length () const;
+ DCPTime length () const;
bool has_subtitles () const;
- OutputVideoFrame best_video_frame_rate () const;
+ int best_video_frame_rate () const;
+ FrameRateChange active_frame_rate_change (DCPTime) const;
- libdcp::KDM
+ dcp::EncryptedKDM
make_kdm (
- boost::shared_ptr<libdcp::Certificate> target,
+ boost::shared_ptr<dcp::Certificate> target,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime until
+ dcp::LocalTime from,
+ dcp::LocalTime until
) const;
- std::list<libdcp::KDM> make_kdms (
+ std::list<dcp::EncryptedKDM> make_kdms (
std::list<boost::shared_ptr<Screen> >,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime until
+ dcp::LocalTime from,
+ dcp::LocalTime until
) const;
- libdcp::Key key () const {
+ dcp::Key key () const {
return _key;
}
bool _three_d;
bool _sequence_video;
bool _interop;
- libdcp::Key _key;
+ dcp::Key _key;
int _state_version;
using std::cout;
using boost::shared_ptr;
using boost::weak_ptr;
-using libdcp::Size;
+using dcp::Size;
/** Construct a FilterGraph for the settings in a piece of content.
* @param content Content.
* @param s Size of the images to process.
* @param p Pixel format of the images to process.
*/
-FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p)
+FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, dcp::Size s, AVPixelFormat p)
: _buffer_src_context (0)
, _buffer_sink_context (0)
, _size (s)
throw DecodeError (N_("could not configure filter graph."));
}
- /* XXX: leaking `inputs' / `outputs' ? */
+ avfilter_inout_free (&inputs);
+ avfilter_inout_free (&outputs);
}
FilterGraph::~FilterGraph ()
* @return true if this chain can process images with `s' and `p', otherwise false.
*/
bool
-FilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const
+FilterGraph::can_process (dcp::Size s, AVPixelFormat p) const
{
return (_size == s && _pixel_format == p);
}
class FilterGraph : public boost::noncopyable
{
public:
- FilterGraph (boost::shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p);
+ FilterGraph (boost::shared_ptr<const FFmpegContent> content, dcp::Size s, AVPixelFormat p);
~FilterGraph ();
- bool can_process (libdcp::Size s, AVPixelFormat p) const;
+ bool can_process (dcp::Size s, AVPixelFormat p) const;
std::list<std::pair<boost::shared_ptr<Image>, int64_t> > process (AVFrame * frame);
private:
AVFilterContext* _buffer_src_context;
AVFilterContext* _buffer_sink_context;
- libdcp::Size _size; ///< size of the images that this chain can process
+ dcp::Size _size; ///< size of the images that this chain can process
AVPixelFormat _pixel_format; ///< pixel format of the images that this chain can process
AVFrame* _frame;
};
--- /dev/null
+/*
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <cmath>
+#include "frame_rate_change.h"
+#include "compose.hpp"
+
+#include "i18n.h"
+
+static bool
+about_equal (float a, float b)
+{
+ /* A film of F seconds at f FPS will be Ff frames;
+ Consider some delta FPS d, so if we run the same
+ film at (f + d) FPS it will last F(f + d) seconds.
+
+ Hence the difference in length over the length of the film will
+ be F(f + d) - Ff frames
+ = Ff + Fd - Ff frames
+ = Fd frames
+ = Fd/f seconds
+
+ So if we accept a difference of 1 frame, ie 1/f seconds, we can
+ say that
+
+ 1/f = Fd/f
+ ie 1 = Fd
+ ie d = 1/F
+
+ So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable
+ FPS error is 1/F ~= 0.0001 ~= 10-e4
+ */
+
+ return (fabs (a - b) < 1e-4);
+}
+
+
+FrameRateChange::FrameRateChange (float source, int dcp)
+ : skip (false)
+ , repeat (1)
+ , change_speed (false)
+{
+ if (fabs (source / 2.0 - dcp) < fabs (source - dcp)) {
+ /* The difference between source and DCP frame rate will be lower
+ (i.e. better) if we skip.
+ */
+ skip = true;
+ } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) {
+ /* The difference between source and DCP frame rate would be better
+ if we repeated each frame once; it may be better still if we
+ repeated more than once. Work out the required repeat.
+ */
+ repeat = round (dcp / source);
+ }
+
+ speed_up = dcp / (source * factor());
+ change_speed = !about_equal (speed_up, 1.0);
+
+ if (!skip && repeat == 1 && !change_speed) {
+ description = _("Content and DCP have the same rate.\n");
+ } else {
+ if (skip) {
+ description = _("DCP will use every other frame of the content.\n");
+ } else if (repeat == 2) {
+ description = _("Each content frame will be doubled in the DCP.\n");
+ } else if (repeat > 2) {
+ description = String::compose (_("Each content frame will be repeated %1 more times in the DCP.\n"), repeat - 1);
+ }
+
+ if (change_speed) {
+ float const pc = dcp * 100 / (source * factor());
+ description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc);
+ }
+ }
+}
--- /dev/null
+/*
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <string>
+
+struct FrameRateChange
+{
+ FrameRateChange (float, int);
+
+ /** @return factor by which to multiply a source frame rate
+ to get the effective rate after any skip or repeat has happened.
+ */
+ float factor () const {
+ if (skip) {
+ return 0.5;
+ }
+
+ return repeat;
+ }
+
+ /** true to skip every other frame */
+ bool skip;
+ /** number of times to use each frame (e.g. 1 is normal, 2 means repeat each frame once, and so on) */
+ int repeat;
+ /** true if this DCP will run its video faster or slower than the source
+ * without taking into account `repeat' nor `skip'.
+ * (e.g. change_speed will be true if
+ * source is 29.97fps, DCP is 30fps
+ * source is 14.50fps, DCP is 30fps
+ * but not if
+ * source is 15.00fps, DCP is 30fps
+ * source is 12.50fps, DCP is 25fps)
+ */
+ bool change_speed;
+
+ /** Amount by which the video is being sped-up in the DCP; e.g. for a
+ * 24fps source in a 25fps DCP this would be 25/24.
+ */
+ float speed_up;
+
+ std::string description;
+};
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "image.h"
#include "exceptions.h"
#include "scaler.h"
+#include "timer.h"
+#include "rect.h"
#include "i18n.h"
using std::min;
using std::cout;
using std::cerr;
+using std::list;
using std::stringstream;
using boost::shared_ptr;
-using libdcp::Size;
+using dcp::Size;
int
Image::line_factor (int n) const
/** Crop this image, scale it to `inter_size' and then place it in a black frame of `out_size' */
shared_ptr<Image>
-Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
+Image::crop_scale_window (Crop crop, dcp::Size inter_size, dcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
{
assert (scaler);
/* Empirical testing suggests that sws_scale() will crash if
out->make_black ();
/* Size of the image after any crop */
- libdcp::Size const cropped_size = crop.apply (size ());
+ dcp::Size const cropped_size = crop.apply (size ());
/* Scale context for a scale from cropped_size to inter_size */
struct SwsContext* scale_context = sws_getContext (
- cropped_size.width, cropped_size.height, pixel_format(),
- inter_size.width, inter_size.height, out_format,
- scaler->ffmpeg_id (), 0, 0, 0
+ cropped_size.width, cropped_size.height, pixel_format(),
+ inter_size.width, inter_size.height, out_format,
+ scaler->ffmpeg_id (), 0, 0, 0
);
if (!scale_context) {
}
shared_ptr<Image>
-Image::scale (libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
+Image::scale (dcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
{
assert (scaler);
/* Empirical testing suggests that sws_scale() will crash if
shared_ptr<Image>
Image::crop (Crop crop, bool aligned) const
{
- libdcp::Size cropped_size = crop.apply (size ());
+ dcp::Size cropped_size = crop.apply (size ());
shared_ptr<Image> out (new Image (pixel_format(), cropped_size, aligned));
for (int c = 0; c < components(); ++c) {
}
}
+void
+Image::make_transparent ()
+{
+ if (_pixel_format != PIX_FMT_RGBA) {
+ throw PixelFormatError ("make_transparent()", _pixel_format);
+ }
+
+ memset (data()[0], 0, lines(0) * stride()[0]);
+}
+
void
Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
{
- /* Only implemented for RGBA onto RGB24 so far */
- assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA);
+ int this_bpp = 0;
+ int other_bpp = 0;
+
+ if (_pixel_format == PIX_FMT_BGRA && other->pixel_format() == PIX_FMT_RGBA) {
+ this_bpp = 4;
+ other_bpp = 4;
+ } else if (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA) {
+ this_bpp = 3;
+ other_bpp = 4;
+ } else {
+ assert (false);
+ }
int start_tx = position.x;
int start_ox = 0;
}
for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) {
- uint8_t* tp = data()[0] + ty * stride()[0] + position.x * 3;
+ uint8_t* tp = data()[0] + ty * stride()[0] + position.x * this_bpp;
uint8_t* op = other->data()[0] + oy * other->stride()[0];
for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) {
float const alpha = float (op[3]) / 255;
tp[0] = (tp[0] * (1 - alpha)) + op[0] * alpha;
tp[1] = (tp[1] * (1 - alpha)) + op[1] * alpha;
tp[2] = (tp[2] * (1 - alpha)) + op[2] * alpha;
- tp += 3;
- op += 4;
+ tp += this_bpp;
+ op += other_bpp;
}
}
}
* @param p Pixel format.
* @param s Size in pixels.
*/
-Image::Image (AVPixelFormat p, libdcp::Size s, bool aligned)
- : libdcp::Image (s)
+Image::Image (AVPixelFormat p, dcp::Size s, bool aligned)
+ : dcp::Image (s)
, _pixel_format (p)
, _aligned (aligned)
{
}
Image::Image (Image const & other)
- : libdcp::Image (other)
+ : dcp::Image (other)
, _pixel_format (other._pixel_format)
, _aligned (other._aligned)
{
}
Image::Image (AVFrame* frame)
- : libdcp::Image (libdcp::Size (frame->width, frame->height))
+ : dcp::Image (dcp::Size (frame->width, frame->height))
, _pixel_format (static_cast<AVPixelFormat> (frame->format))
, _aligned (true)
{
}
Image::Image (shared_ptr<const Image> other, bool aligned)
- : libdcp::Image (other)
+ : dcp::Image (other)
, _pixel_format (other->_pixel_format)
, _aligned (aligned)
{
void
Image::swap (Image & other)
{
- libdcp::Image::swap (other);
+ dcp::Image::swap (other);
std::swap (_pixel_format, other._pixel_format);
return _stride;
}
-libdcp::Size
+dcp::Size
Image::size () const
{
return _size;
return _aligned;
}
+PositionImage
+merge (list<PositionImage> images)
+{
+ if (images.empty ()) {
+ return PositionImage ();
+ }
+
+ dcpomatic::Rect<int> all (images.front().position, images.front().image->size().width, images.front().image->size().height);
+ for (list<PositionImage>::const_iterator i = images.begin(); i != images.end(); ++i) {
+ all.extend (dcpomatic::Rect<int> (i->position, i->image->size().width, i->image->size().height));
+ }
+
+ shared_ptr<Image> merged (new Image (images.front().image->pixel_format (), dcp::Size (all.width, all.height), true));
+ merged->make_transparent ();
+ for (list<PositionImage>::const_iterator i = images.begin(); i != images.end(); ++i) {
+ merged->alpha_blend (i->image, i->position);
+ }
+
+ return PositionImage (merged, all.position ());
+}
+
string
Image::digest () const
{
return s.str ();
}
-
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
}
-#include <libdcp/image.h>
+#include <dcp/image.h>
#include "util.h"
#include "position.h"
+#include "position_image.h"
class Scaler;
-class Image : public libdcp::Image
+class Image : public dcp::Image
{
public:
- Image (AVPixelFormat, libdcp::Size, bool);
+ Image (AVPixelFormat, dcp::Size, bool);
Image (AVFrame *);
Image (Image const &);
Image (boost::shared_ptr<const Image>, bool);
uint8_t ** data () const;
int * line_size () const;
int * stride () const;
- libdcp::Size size () const;
+ dcp::Size size () const;
bool aligned () const;
int components () const;
int line_factor (int) const;
int lines (int) const;
- boost::shared_ptr<Image> scale (libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
+ boost::shared_ptr<Image> scale (dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
boost::shared_ptr<Image> crop (Crop c, bool aligned) const;
- boost::shared_ptr<Image> crop_scale_window (Crop c, libdcp::Size, libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
+ boost::shared_ptr<Image> crop_scale_window (Crop c, dcp::Size, dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
void make_black ();
+ void make_transparent ();
void alpha_blend (boost::shared_ptr<const Image> image, Position<int> pos);
void copy (boost::shared_ptr<const Image> image, Position<int> pos);
bool _aligned;
};
+extern PositionImage merge (std::list<PositionImage> images);
+
#endif
}
-ImageContent::ImageContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version)
+ImageContent::ImageContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version)
: Content (f, node)
, VideoContent (f, node, version)
{
}
void
-ImageContent::set_video_length (VideoContent::Frame len)
+ImageContent::set_video_length (ContentTime len)
{
{
boost::mutex::scoped_lock lm (_mutex);
signal_changed (ContentProperty::LENGTH);
}
-Time
+DCPTime
ImageContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
-
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
- return video_length_after_3d_combine() * frc.factor() * TIME_HZ / video_frame_rate();
+ return DCPTime (video_length_after_3d_combine(), FrameRateChange (video_frame_rate(), film->video_frame_rate()));
}
string
{
stringstream s;
s << VideoContent::identifier ();
- s << "_" << video_length();
+ s << "_" << video_length().get();
return s.str ();
}
{
public:
ImageContent (boost::shared_ptr<const Film>, boost::filesystem::path);
- ImageContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
+ ImageContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int);
boost::shared_ptr<ImageContent> shared_from_this () {
return boost::dynamic_pointer_cast<ImageContent> (Content::shared_from_this ());
std::string summary () const;
std::string technical_summary () const;
void as_xml (xmlpp::Node *) const;
- Time full_length () const;
+ DCPTime full_length () const;
std::string identifier () const;
- void set_video_length (VideoContent::Frame);
+ void set_video_length (ContentTime);
bool still () const;
void set_video_frame_rate (float);
};
using std::cout;
using boost::shared_ptr;
-using libdcp::Size;
+using dcp::Size;
-ImageDecoder::ImageDecoder (shared_ptr<const Film> f, shared_ptr<const ImageContent> c)
- : Decoder (f)
- , VideoDecoder (f, c)
+ImageDecoder::ImageDecoder (shared_ptr<const ImageContent> c)
+ : VideoDecoder (c)
, _image_content (c)
{
}
-void
+bool
ImageDecoder::pass ()
{
- if (_video_position >= _image_content->video_length ()) {
- return;
+ if (_video_position >= _image_content->video_length().frames (_image_content->video_frame_rate ())) {
+ return true;
}
- if (_image && _image_content->still ()) {
- video (_image, true, _video_position);
- return;
+ if (!_image_content->still() || !_image) {
+ /* Either we need an image or we are using moving images, so load one */
+ _image.reset (new MagickImageProxy (_image_content->path (_image_content->still() ? 0 : _video_position)));
}
-
- _image.reset (new MagickImageProxy (_image_content->path (_image_content->still() ? 0 : _video_position)));
- video (_image, false, _video_position);
+
+ video (_image, _video_position);
+ ++_video_position;
+ return false;
}
void
-ImageDecoder::seek (VideoContent::Frame frame, bool)
-{
- _video_position = frame;
-}
-
-bool
-ImageDecoder::done () const
+ImageDecoder::seek (ContentTime time, bool accurate)
{
- return _video_position >= _image_content->video_length ();
+ VideoDecoder::seek (time, accurate);
+ _video_position = time.frames (_image_content->video_frame_rate ());
}
class ImageDecoder : public VideoDecoder
{
public:
- ImageDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageContent>);
+ ImageDecoder (boost::shared_ptr<const ImageContent> c);
boost::shared_ptr<const ImageContent> content () {
return _image_content;
}
- /* Decoder */
-
- void pass ();
- void seek (VideoContent::Frame, bool);
- bool done () const;
+ void seek (ContentTime, bool);
private:
+ bool pass ();
+
boost::shared_ptr<const ImageContent> _image_content;
boost::shared_ptr<ImageProxy> _image;
+ VideoFrame _video_position;
};
ImageExaminer::ImageExaminer (shared_ptr<const Film> film, shared_ptr<const ImageContent> content, shared_ptr<Job>)
: _film (film)
, _image_content (content)
- , _video_length (0)
{
using namespace MagickCore;
Magick::Image* image = new Magick::Image (content->path(0).string());
- _video_size = libdcp::Size (image->columns(), image->rows());
+ _video_size = dcp::Size (image->columns(), image->rows());
delete image;
if (content->still ()) {
- _video_length = Config::instance()->default_still_length() * video_frame_rate();
+ _video_length = ContentTime::from_seconds (Config::instance()->default_still_length());
} else {
- _video_length = _image_content->number_of_paths ();
+ _video_length = ContentTime::from_frames (_image_content->number_of_paths (), video_frame_rate ());
}
}
-libdcp::Size
+dcp::Size
ImageExaminer::video_size () const
{
return _video_size.get ();
ImageExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageContent>, boost::shared_ptr<Job>);
float video_frame_rate () const;
- libdcp::Size video_size () const;
- VideoContent::Frame video_length () const {
+ dcp::Size video_size () const;
+ ContentTime video_length () const {
return _video_length;
}
private:
boost::weak_ptr<const Film> _film;
boost::shared_ptr<const ImageContent> _image_content;
- boost::optional<libdcp::Size> _video_size;
- VideoContent::Frame _video_length;
+ boost::optional<dcp::Size> _video_size;
+ ContentTime _video_length;
};
*/
#include <Magick++.h>
-#include <libdcp/util.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/util.h>
+#include <dcp/raw_convert.h>
#include "image_proxy.h"
#include "image.h"
#include "exceptions.h"
RawImageProxy::RawImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket)
{
- libdcp::Size size (
+ dcp::Size size (
xml->number_child<int> ("Width"), xml->number_child<int> ("Height")
);
RawImageProxy::add_metadata (xmlpp::Node* node) const
{
node->add_child("Type")->add_child_text (N_("Raw"));
- node->add_child("Width")->add_child_text (libdcp::raw_convert<string> (_image->size().width));
- node->add_child("Height")->add_child_text (libdcp::raw_convert<string> (_image->size().height));
+ node->add_child("Width")->add_child_text (dcp::raw_convert<string> (_image->size().width));
+ node->add_child("Height")->add_child_text (dcp::raw_convert<string> (_image->size().height));
}
void
throw DecodeError (_("Could not decode image file"));
}
- libdcp::Size size (magick_image->columns(), magick_image->rows());
+ dcp::Size size (magick_image->columns(), magick_image->rows());
_image.reset (new Image (PIX_FMT_RGB24, size, true));
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include <boost/thread.hpp>
#include <boost/filesystem.hpp>
-#include <libdcp/exceptions.h>
+#include <dcp/exceptions.h>
#include "job.h"
#include "util.h"
#include "cross.h"
#include "ui_signaller.h"
#include "exceptions.h"
+#include "film.h"
+#include "log.h"
#include "i18n.h"
run ();
- } catch (libdcp::FileError& e) {
-
+ } catch (dcp::FileError& e) {
+
string m = String::compose (_("An error occurred whilst handling the file %1."), boost::filesystem::path (e.filename()).leaf());
try {
}
}
-/** @return Time (in seconds) that this sub-job has been running */
+/** @return DCPTime (in seconds) that this sub-job has been running */
int
Job::elapsed_time () const
{
void
Job::set_error (string s, string d)
{
+ _film->log()->log (String::compose ("Error in job: %1 (%2)", s, d));
boost::mutex::scoped_lock lm (_state_mutex);
_error_summary = s;
_error_details = d;
#include <boost/shared_ptr.hpp>
#include <quickmail.h>
#include <zip.h>
-#include <libdcp/kdm.h>
+#include <dcp/encrypted_kdm.h>
#include "kdm.h"
#include "cinema.h"
#include "exceptions.h"
struct ScreenKDM
{
- ScreenKDM (shared_ptr<Screen> s, libdcp::KDM k)
+ ScreenKDM (shared_ptr<Screen> s, dcp::EncryptedKDM k)
: screen (s)
, kdm (k)
{}
shared_ptr<Screen> screen;
- libdcp::KDM kdm;
+ dcp::EncryptedKDM kdm;
};
static string
shared_ptr<const Film> film,
list<shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to
+ dcp::LocalTime from,
+ dcp::LocalTime to
)
{
- list<libdcp::KDM> kdms = film->make_kdms (screens, dcp, from, to);
+ list<dcp::EncryptedKDM> kdms = film->make_kdms (screens, dcp, from, to);
list<ScreenKDM> screen_kdms;
list<shared_ptr<Screen> >::iterator i = screens.begin ();
- list<libdcp::KDM>::iterator j = kdms.begin ();
+ list<dcp::EncryptedKDM>::iterator j = kdms.begin ();
while (i != screens.end() && j != kdms.end ()) {
screen_kdms.push_back (ScreenKDM (*i, *j));
++i;
shared_ptr<const Film> film,
list<shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to
+ dcp::LocalTime from,
+ dcp::LocalTime to
)
{
list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, dcp, from, to);
shared_ptr<const Film> film,
list<shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to,
+ dcp::LocalTime from,
+ dcp::LocalTime to,
boost::filesystem::path directory
)
{
shared_ptr<const Film> film,
list<shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to,
+ dcp::LocalTime from,
+ dcp::LocalTime to,
boost::filesystem::path directory
)
{
shared_ptr<const Film> film,
list<shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to
+ dcp::LocalTime from,
+ dcp::LocalTime to
)
{
list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, dcp, from, to);
boost::shared_ptr<const Film> film,
std::list<boost::shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to,
+ dcp::LocalTime from,
+ dcp::LocalTime to,
boost::filesystem::path directory
);
boost::shared_ptr<const Film> film,
std::list<boost::shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to,
+ dcp::LocalTime from,
+ dcp::LocalTime to,
boost::filesystem::path directory
);
boost::shared_ptr<const Film> film,
std::list<boost::shared_ptr<Screen> > screens,
boost::filesystem::path dcp,
- boost::posix_time::ptime from,
- boost::posix_time::ptime to
+ dcp::LocalTime from,
+ dcp::LocalTime to
);
+++ /dev/null
-/*
- Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
-
- This program 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.
-
- This program 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 this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "piece.h"
-#include "player.h"
-
-using boost::shared_ptr;
-
-Piece::Piece (shared_ptr<Content> c)
- : content (c)
- , video_position (c->position ())
- , audio_position (c->position ())
- , repeat_to_do (0)
- , repeat_done (0)
-{
-
-}
-
-Piece::Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
- : content (c)
- , decoder (d)
- , video_position (c->position ())
- , audio_position (c->position ())
- , repeat_to_do (0)
- , repeat_done (0)
-{
-
-}
-
-/** Set this piece to repeat a video frame a given number of times */
-void
-Piece::set_repeat (IncomingVideo video, int num)
-{
- repeat_video = video;
- repeat_to_do = num;
- repeat_done = 0;
-}
-
-void
-Piece::reset_repeat ()
-{
- repeat_video.image.reset ();
- repeat_to_do = 0;
- repeat_done = 0;
-}
-
-bool
-Piece::repeating () const
-{
- return repeat_done != repeat_to_do;
-}
-
-void
-Piece::repeat (Player* player)
-{
- player->process_video (
- repeat_video.weak_piece,
- repeat_video.image,
- repeat_video.eyes,
- repeat_video.part,
- repeat_done > 0,
- repeat_video.frame,
- (repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ())
- );
-
- ++repeat_done;
-}
-
class Content;
class Decoder;
-class Piece;
-class ImageProxy;
-class Player;
-
-struct IncomingVideo
-{
-public:
- boost::weak_ptr<Piece> weak_piece;
- boost::shared_ptr<const ImageProxy> image;
- Eyes eyes;
- Part part;
- bool same;
- VideoContent::Frame frame;
- Time extra;
-};
class Piece
{
public:
- Piece (boost::shared_ptr<Content> c);
- Piece (boost::shared_ptr<Content> c, boost::shared_ptr<Decoder> d);
- void set_repeat (IncomingVideo video, int num);
- void reset_repeat ();
- bool repeating () const;
- void repeat (Player* player);
-
+ Piece (boost::shared_ptr<Content> c, boost::shared_ptr<Decoder> d, FrameRateChange f)
+ : content (c)
+ , decoder (d)
+ , frc (f)
+ {}
+
boost::shared_ptr<Content> content;
boost::shared_ptr<Decoder> decoder;
- /** Time of the last video we emitted relative to the start of the DCP */
- Time video_position;
- /** Time of the last audio we emitted relative to the start of the DCP */
- Time audio_position;
-
- IncomingVideo repeat_video;
- int repeat_to_do;
- int repeat_done;
+ FrameRateChange frc;
};
#endif
*/
#include <stdint.h>
+#include <algorithm>
#include "player.h"
#include "film.h"
#include "ffmpeg_decoder.h"
+#include "audio_buffers.h"
#include "ffmpeg_content.h"
#include "image_decoder.h"
#include "image_content.h"
#include "sndfile_decoder.h"
#include "sndfile_content.h"
#include "subtitle_content.h"
+#include "subrip_decoder.h"
+#include "subrip_content.h"
#include "playlist.h"
#include "job.h"
#include "image.h"
#include "image_proxy.h"
#include "ratio.h"
-#include "resampler.h"
#include "log.h"
#include "scaler.h"
+#include "render_subtitles.h"
+#include "config.h"
+#include "content_video.h"
#include "player_video_frame.h"
using std::list;
using std::cout;
using std::min;
using std::max;
+using std::min;
using std::vector;
using std::pair;
using std::map;
+using std::make_pair;
using boost::shared_ptr;
using boost::weak_ptr;
using boost::dynamic_pointer_cast;
+using boost::optional;
Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
: _film (f)
, _playlist (p)
- , _video (true)
- , _audio (true)
, _have_valid_pieces (false)
- , _video_position (0)
- , _audio_position (0)
- , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
- , _last_emit_was_black (false)
+ , _approximate_size (false)
+ , _burn_subtitles (false)
{
_playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
_playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
}
void
-Player::disable_video ()
-{
- _video = false;
-}
-
-void
-Player::disable_audio ()
+Player::setup_pieces ()
{
- _audio = false;
-}
+ list<shared_ptr<Piece> > old_pieces = _pieces;
+ _pieces.clear ();
-bool
-Player::pass ()
-{
- if (!_have_valid_pieces) {
- setup_pieces ();
- }
+ ContentList content = _playlist->content ();
- Time earliest_t = TIME_MAX;
- shared_ptr<Piece> earliest;
- enum {
- VIDEO,
- AUDIO
- } type = VIDEO;
+ for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
- for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- if ((*i)->decoder->done ()) {
+ if (!(*i)->paths_valid ()) {
continue;
}
-
- shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
- shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
-
- if (_video && vd) {
- if ((*i)->video_position < earliest_t) {
- earliest_t = (*i)->video_position;
- earliest = *i;
- type = VIDEO;
+
+ shared_ptr<Decoder> decoder;
+ optional<FrameRateChange> frc;
+
+ /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */
+ DCPTime best_overlap_t;
+ shared_ptr<VideoContent> best_overlap;
+ for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
+ shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
+ if (!vc) {
+ continue;
}
- }
-
- if (_audio && ad && ad->has_audio ()) {
- if ((*i)->audio_position < earliest_t) {
- earliest_t = (*i)->audio_position;
- earliest = *i;
- type = AUDIO;
+
+ DCPTime const overlap = max (vc->position(), (*i)->position()) - min (vc->end(), (*i)->end());
+ if (overlap > best_overlap_t) {
+ best_overlap = vc;
+ best_overlap_t = overlap;
}
}
- }
- if (!earliest) {
- flush ();
- return true;
- }
-
- switch (type) {
- case VIDEO:
- if (earliest_t > _video_position) {
- emit_black ();
+ optional<FrameRateChange> best_overlap_frc;
+ if (best_overlap) {
+ best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
} else {
- if (earliest->repeating ()) {
- earliest->repeat (this);
- } else {
- earliest->decoder->pass ();
- }
+ /* No video overlap; e.g. if the DCP is just audio */
+ best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
}
- break;
- case AUDIO:
- if (earliest_t > _audio_position) {
- emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
- } else {
- earliest->decoder->pass ();
-
- if (earliest->decoder->done()) {
- shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
- assert (ac);
- shared_ptr<Resampler> re = resampler (ac, false);
- if (re) {
- shared_ptr<const AudioBuffers> b = re->flush ();
- if (b->frames ()) {
- process_audio (earliest, b, ac->audio_length ());
- }
- }
- }
+ /* FFmpeg */
+ shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
+ if (fc) {
+ decoder.reset (new FFmpegDecoder (fc, _film->log()));
+ frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
}
- break;
- }
- if (_audio) {
- boost::optional<Time> audio_done_up_to;
- for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- if ((*i)->decoder->done ()) {
- continue;
+ /* ImageContent */
+ shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
+ if (ic) {
+ /* See if we can re-use an old ImageDecoder */
+ for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
+ shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
+ if (imd && imd->content() == ic) {
+ decoder = imd;
+ }
}
- shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
- if (ad && ad->has_audio ()) {
- audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position);
+ if (!decoder) {
+ decoder.reset (new ImageDecoder (ic));
}
+
+ frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
}
- if (audio_done_up_to) {
- TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to.get ());
- Audio (tb.audio, tb.time);
- _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+ /* SndfileContent */
+ shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
+ if (sc) {
+ decoder.reset (new SndfileDecoder (sc));
+ frc = best_overlap_frc;
}
+
+ /* SubRipContent */
+ shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i);
+ if (rc) {
+ decoder.reset (new SubRipDecoder (rc));
+ frc = best_overlap_frc;
+ }
+
+ _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
}
-
- return false;
+
+ _have_valid_pieces = true;
}
-/** @param extra Amount of extra time to add to the content frame's time (for repeat) */
void
-Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const ImageProxy> image, Eyes eyes, Part part, bool same, VideoContent::Frame frame, Time extra)
+Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
{
- /* Keep a note of what came in so that we can repeat it if required */
- _last_incoming_video.weak_piece = weak_piece;
- _last_incoming_video.image = image;
- _last_incoming_video.eyes = eyes;
- _last_incoming_video.part = part;
- _last_incoming_video.same = same;
- _last_incoming_video.frame = frame;
- _last_incoming_video.extra = extra;
-
- shared_ptr<Piece> piece = weak_piece.lock ();
- if (!piece) {
- return;
- }
-
- shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
- assert (content);
-
- FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate());
- if (frc.skip && (frame % 2) == 1) {
- return;
- }
-
- Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
- if (content->trimmed (relative_time)) {
+ shared_ptr<Content> c = w.lock ();
+ if (!c) {
return;
}
- Time const time = content->position() + relative_time + extra - content->trim_start ();
- libdcp::Size const image_size = content->scale().size (content, _video_container_size, _film->frame_size ());
-
- shared_ptr<PlayerVideoFrame> pi (
- new PlayerVideoFrame (
- image,
- content->crop(),
- image_size,
- _video_container_size,
- _film->scaler(),
- eyes,
- part,
- content->colour_conversion()
- )
- );
-
- if (_film->with_subtitles ()) {
- for (list<Subtitle>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
- if (i->covers (time)) {
- /* This may be true for more than one of _subtitles, but the last (latest-starting)
- one is the one we want to use, so that's ok.
- */
- Position<int> const container_offset (
- (_video_container_size.width - image_size.width) / 2,
- (_video_container_size.height - image_size.width) / 2
- );
-
- pi->set_subtitle (i->out_image(), i->out_position() + container_offset);
- }
- }
- }
-
- /* Clear out old subtitles */
- for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ) {
- list<Subtitle>::iterator j = i;
- ++j;
+ if (
+ property == ContentProperty::POSITION ||
+ property == ContentProperty::LENGTH ||
+ property == ContentProperty::TRIM_START ||
+ property == ContentProperty::TRIM_END ||
+ property == ContentProperty::PATH ||
+ property == VideoContentProperty::VIDEO_FRAME_TYPE
+ ) {
- if (i->ends_before (time)) {
- _subtitles.erase (i);
- }
-
- i = j;
- }
-
-#ifdef DCPOMATIC_DEBUG
- _last_video = piece->content;
-#endif
-
- Video (pi, same, time);
-
- _last_emit_was_black = false;
- _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate());
+ _have_valid_pieces = false;
+ Changed (frequent);
- if (frc.repeat > 1 && !piece->repeating ()) {
- piece->set_repeat (_last_incoming_video, frc.repeat - 1);
+ } else if (
+ property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
+ property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
+ property == SubtitleContentProperty::SUBTITLE_SCALE ||
+ property == VideoContentProperty::VIDEO_CROP ||
+ property == VideoContentProperty::VIDEO_SCALE ||
+ property == VideoContentProperty::VIDEO_FRAME_RATE
+ ) {
+
+ Changed (frequent);
}
}
void
-Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
+Player::playlist_changed ()
{
- shared_ptr<Piece> piece = weak_piece.lock ();
- if (!piece) {
- return;
- }
+ _have_valid_pieces = false;
+ Changed (false);
+}
- shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
- assert (content);
+void
+Player::set_video_container_size (dcp::Size s)
+{
+ _video_container_size = s;
- /* Gain */
- if (content->audio_gain() != 0) {
- shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
- gain->apply_gain (content->audio_gain ());
- audio = gain;
- }
+ _black_image.reset (new Image (PIX_FMT_RGB24, _video_container_size, true));
+ _black_image->make_black ();
+}
- /* Resample */
- if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
- shared_ptr<Resampler> r = resampler (content, true);
- pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
- audio = ro.first;
- frame = ro.second;
- }
-
- Time const relative_time = _film->audio_frames_to_time (frame);
+void
+Player::film_changed (Film::Property p)
+{
+ /* Here we should notice Film properties that affect our output, and
+ alert listeners that our output now would be different to how it was
+ last time we were run.
+ */
- if (content->trimmed (relative_time)) {
- return;
+ if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) {
+ Changed (false);
}
+}
- Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start ();
+list<PositionImage>
+Player::process_content_image_subtitles (shared_ptr<SubtitleContent> content, list<shared_ptr<ContentImageSubtitle> > subs) const
+{
+ list<PositionImage> all;
- /* Remap channels */
- shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
- dcp_mapped->make_silent ();
-
- AudioMapping map = content->audio_mapping ();
- for (int i = 0; i < map.content_channels(); ++i) {
- for (int j = 0; j < _film->audio_channels(); ++j) {
- if (map.get (i, static_cast<libdcp::Channel> (j)) > 0) {
- dcp_mapped->accumulate_channel (
- audio.get(),
- i,
- static_cast<libdcp::Channel> (j),
- map.get (i, static_cast<libdcp::Channel> (j))
- );
- }
+ for (list<shared_ptr<ContentImageSubtitle> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
+ if (!(*i)->image) {
+ continue;
}
+
+ dcpomatic::Rect<double> in_rect = (*i)->rectangle;
+ dcp::Size scaled_size;
+
+ in_rect.x += content->subtitle_x_offset ();
+ in_rect.y += content->subtitle_y_offset ();
+
+ /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
+ scaled_size.width = in_rect.width * _video_container_size.width * content->subtitle_scale ();
+ scaled_size.height = in_rect.height * _video_container_size.height * content->subtitle_scale ();
+
+ /* Then we need a corrective translation, consisting of two parts:
+ *
+ * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
+ * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
+ *
+ * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
+ * (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
+ * (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
+ *
+ * Combining these two translations gives these expressions.
+ */
+
+ all.push_back (
+ PositionImage (
+ (*i)->image->scale (
+ scaled_size,
+ Scaler::from_id ("bicubic"),
+ (*i)->image->pixel_format (),
+ true
+ ),
+ Position<int> (
+ rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - content->subtitle_scale ()) / 2))),
+ rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - content->subtitle_scale ()) / 2)))
+ )
+ )
+ );
}
- audio = dcp_mapped;
+ return all;
+}
- /* We must cut off anything that comes before the start of all time */
- if (time < 0) {
- int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
- if (frames >= audio->frames ()) {
- return;
+list<PositionImage>
+Player::process_content_text_subtitles (list<shared_ptr<ContentTextSubtitle> > sub) const
+{
+ list<PositionImage> all;
+ for (list<shared_ptr<ContentTextSubtitle> >::const_iterator i = sub.begin(); i != sub.end(); ++i) {
+ if (!(*i)->subs.empty ()) {
+ all.push_back (render_subtitles ((*i)->subs, _video_container_size));
}
-
- shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
- trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
-
- audio = trimmed;
- time = 0;
}
- _audio_merger.push (audio, time);
- piece->audio_position += _film->audio_frames_to_time (audio->frames ());
+ return all;
}
void
-Player::flush ()
+Player::set_approximate_size ()
{
- TimedAudioBuffers<Time> tb = _audio_merger.flush ();
- if (_audio && tb.audio) {
- Audio (tb.audio, tb.time);
- _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
- }
+ _approximate_size = true;
+}
- while (_video && _video_position < _audio_position) {
- emit_black ();
- }
+shared_ptr<PlayerVideoFrame>
+Player::black_player_video_frame () const
+{
+ return shared_ptr<PlayerVideoFrame> (
+ new PlayerVideoFrame (
+ shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
+ Crop (),
+ _video_container_size,
+ _video_container_size,
+ Scaler::from_id ("bicubic"),
+ EYES_BOTH,
+ PART_WHOLE,
+ Config::instance()->colour_conversions().front().conversion
+ )
+ );
+}
- while (_audio && _audio_position < _video_position) {
- emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+shared_ptr<PlayerVideoFrame>
+Player::content_to_player_video_frame (
+ shared_ptr<VideoContent> content,
+ ContentVideo content_video,
+ list<shared_ptr<Piece> > subs,
+ DCPTime time,
+ dcp::Size image_size) const
+{
+ shared_ptr<PlayerVideoFrame> pvf (
+ new PlayerVideoFrame (
+ content_video.image,
+ content->crop (),
+ image_size,
+ _video_container_size,
+ _film->scaler(),
+ content_video.eyes,
+ content_video.part,
+ content->colour_conversion ()
+ )
+ );
+
+
+ /* Add subtitles */
+
+ list<PositionImage> sub_images;
+
+ for (list<shared_ptr<Piece> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
+ shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*i)->decoder);
+ shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*i)->content);
+ ContentTime const from = dcp_to_content_subtitle (*i, time);
+ ContentTime const to = from + ContentTime::from_frames (1, content->video_frame_rate ());
+
+ list<shared_ptr<ContentImageSubtitle> > image_subtitles = subtitle_decoder->get_image_subtitles (from, to);
+ if (!image_subtitles.empty ()) {
+ list<PositionImage> im = process_content_image_subtitles (
+ subtitle_content,
+ image_subtitles
+ );
+
+ copy (im.begin(), im.end(), back_inserter (sub_images));
+ }
+
+ if (_burn_subtitles) {
+ list<shared_ptr<ContentTextSubtitle> > text_subtitles = subtitle_decoder->get_text_subtitles (from, to);
+ if (!text_subtitles.empty ()) {
+ list<PositionImage> im = process_content_text_subtitles (text_subtitles);
+ copy (im.begin(), im.end(), back_inserter (sub_images));
+ }
+ }
}
+ if (!sub_images.empty ()) {
+ pvf->set_subtitle (merge (sub_images));
+ }
+
+ return pvf;
}
-/** Seek so that the next pass() will yield (approximately) the requested frame.
- * Pass accurate = true to try harder to get close to the request.
- * @return true on error
- */
-void
-Player::seek (Time t, bool accurate)
+/** @return All PlayerVideoFrames at the given time (there may be two frames for 3D) */
+list<shared_ptr<PlayerVideoFrame> >
+Player::get_video (DCPTime time, bool accurate)
{
if (!_have_valid_pieces) {
setup_pieces ();
}
+
+ list<shared_ptr<Piece> > ov = overlaps<VideoContent> (
+ time,
+ time + DCPTime::from_frames (1, _film->video_frame_rate ())
+ );
- if (_pieces.empty ()) {
- return;
+ list<shared_ptr<PlayerVideoFrame> > pvf;
+
+ if (ov.empty ()) {
+ /* No video content at this time */
+ pvf.push_back (black_player_video_frame ());
+ return pvf;
}
- for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
- if (!vc) {
- continue;
- }
-
- /* s is the offset of t from the start position of this content */
- Time s = t - vc->position ();
- s = max (static_cast<Time> (0), s);
- s = min (vc->length_after_trim(), s);
+ /* Create a PlayerVideoFrame from the content's video at this time */
- /* Hence set the piece positions to the `global' time */
- (*i)->video_position = (*i)->audio_position = vc->position() + s;
-
- /* And seek the decoder */
- dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
- vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
- );
+ shared_ptr<Piece> piece = ov.back ();
+ shared_ptr<VideoDecoder> decoder = dynamic_pointer_cast<VideoDecoder> (piece->decoder);
+ assert (decoder);
+ shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
+ assert (content);
- (*i)->reset_repeat ();
+ list<ContentVideo> content_video = decoder->get_video (dcp_to_content_video (piece, time), accurate);
+ if (content_video.empty ()) {
+ pvf.push_back (black_player_video_frame ());
+ return pvf;
}
- _video_position = _audio_position = t;
+ dcp::Size image_size = content->scale().size (content, _video_container_size, _film->frame_size ());
+ if (_approximate_size) {
+ image_size.width &= ~3;
+ image_size.height &= ~3;
+ }
- /* XXX: don't seek audio because we don't need to... */
+ for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
+ list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> (
+ time,
+ time + DCPTime::from_frames (1, _film->video_frame_rate ())
+ );
+
+ pvf.push_back (content_to_player_video_frame (content, *i, subs, time, image_size));
+ }
+
+ return pvf;
}
-void
-Player::setup_pieces ()
+shared_ptr<AudioBuffers>
+Player::get_audio (DCPTime time, DCPTime length, bool accurate)
{
- list<shared_ptr<Piece> > old_pieces = _pieces;
+ if (!_have_valid_pieces) {
+ setup_pieces ();
+ }
- _pieces.clear ();
+ AudioFrame const length_frames = length.frames (_film->audio_frame_rate ());
- ContentList content = _playlist->content ();
- sort (content.begin(), content.end(), ContentSorter ());
+ shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
+ audio->make_silent ();
+
+ list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time, time + length);
+ if (ov.empty ()) {
+ return audio;
+ }
- for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
+ for (list<shared_ptr<Piece> >::iterator i = ov.begin(); i != ov.end(); ++i) {
- if (!(*i)->paths_valid ()) {
+ shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> ((*i)->content);
+ assert (content);
+ shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
+ assert (decoder);
+
+ if (content->audio_frame_rate() == 0) {
+ /* This AudioContent has no audio (e.g. if it is an FFmpegContent with no
+ * audio stream).
+ */
continue;
}
- shared_ptr<Piece> piece (new Piece (*i));
-
- /* XXX: into content? */
-
- shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
- if (fc) {
- shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
-
- fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, _5, 0));
- fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
- fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
-
- fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
- piece->decoder = fd;
+ /* The time that we should request from the content */
+ DCPTime request = time - DCPTime::from_seconds (content->audio_delay() / 1000.0);
+ DCPTime offset;
+ if (request < DCPTime ()) {
+ /* We went off the start of the content, so we will need to offset
+ the stuff we get back.
+ */
+ offset = -request;
+ request = DCPTime ();
}
-
- shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
- if (ic) {
- bool reusing = false;
-
- /* See if we can re-use an old ImageDecoder */
- for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
- shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
- if (imd && imd->content() == ic) {
- piece = *j;
- reusing = true;
- }
- }
- if (!reusing) {
- shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
- id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, _5, 0));
- piece->decoder = id;
- }
- }
+ AudioFrame const content_frame = dcp_to_content_audio (*i, request);
- shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
- if (sc) {
- shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
- sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
+ /* Audio from this piece's decoder (which might be more or less than what we asked for) */
+ shared_ptr<ContentAudio> all = decoder->get_audio (content_frame, length_frames, accurate);
- piece->decoder = sd;
+ /* Gain */
+ if (content->audio_gain() != 0) {
+ shared_ptr<AudioBuffers> gain (new AudioBuffers (all->audio));
+ gain->apply_gain (content->audio_gain ());
+ all->audio = gain;
}
- _pieces.push_back (piece);
- }
-
- _have_valid_pieces = true;
-}
-
-void
-Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
-{
- shared_ptr<Content> c = w.lock ();
- if (!c) {
- return;
- }
-
- if (
- property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
- property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END ||
- property == VideoContentProperty::VIDEO_FRAME_TYPE
- ) {
-
- _have_valid_pieces = false;
- Changed (frequent);
-
- } else if (
- property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
- property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
- property == SubtitleContentProperty::SUBTITLE_SCALE
- ) {
-
- for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
- i->update (_film, _video_container_size);
+ /* Remap channels */
+ shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all->audio->frames()));
+ dcp_mapped->make_silent ();
+ AudioMapping map = content->audio_mapping ();
+ for (int i = 0; i < map.content_channels(); ++i) {
+ for (int j = 0; j < _film->audio_channels(); ++j) {
+ if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
+ dcp_mapped->accumulate_channel (
+ all->audio.get(),
+ i,
+ j,
+ map.get (i, static_cast<dcp::Channel> (j))
+ );
+ }
+ }
}
- Changed (frequent);
+ all->audio = dcp_mapped;
- } else if (
- property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_SCALE ||
- property == VideoContentProperty::VIDEO_FRAME_RATE
- ) {
-
- Changed (frequent);
-
- } else if (property == ContentProperty::PATH) {
-
- _have_valid_pieces = false;
- Changed (frequent);
+ audio->accumulate_frames (
+ all->audio.get(),
+ content_frame - all->frame,
+ offset.frames (_film->audio_frame_rate()),
+ min (AudioFrame (all->audio->frames()), length_frames) - offset.frames (_film->audio_frame_rate ())
+ );
}
-}
-
-void
-Player::playlist_changed ()
-{
- _have_valid_pieces = false;
- Changed (false);
-}
-void
-Player::set_video_container_size (libdcp::Size s)
-{
- _video_container_size = s;
-
- shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true));
- im->make_black ();
-
- _black_frame.reset (
- new PlayerVideoFrame (
- shared_ptr<ImageProxy> (new RawImageProxy (im)),
- Crop(),
- _video_container_size,
- _video_container_size,
- Scaler::from_id ("bicubic"),
- EYES_BOTH,
- PART_WHOLE,
- ColourConversion ()
- )
- );
+ return audio;
}
-shared_ptr<Resampler>
-Player::resampler (shared_ptr<AudioContent> c, bool create)
+VideoFrame
+Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
{
- map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
- if (i != _resamplers.end ()) {
- return i->second;
- }
+ /* s is the offset of t from the start position of this content */
+ DCPTime s = t - piece->content->position ();
+ s = DCPTime (max (int64_t (0), s.get ()));
+ s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
- if (!create) {
- return shared_ptr<Resampler> ();
- }
-
- _film->log()->log (
- String::compose (
- "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()
- )
- );
-
- shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
- _resamplers[c] = r;
- return r;
+ /* Convert this to the content frame */
+ return DCPTime (s + piece->content->trim_start()).frames (_film->video_frame_rate()) * piece->frc.factor ();
}
-void
-Player::emit_black ()
+AudioFrame
+Player::dcp_to_content_audio (shared_ptr<const Piece> piece, DCPTime t) const
{
-#ifdef DCPOMATIC_DEBUG
- _last_video.reset ();
-#endif
+ /* s is the offset of t from the start position of this content */
+ DCPTime s = t - piece->content->position ();
+ s = DCPTime (max (int64_t (0), s.get ()));
+ s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
- Video (_black_frame, _last_emit_was_black, _video_position);
- _video_position += _film->video_frames_to_time (1);
- _last_emit_was_black = true;
+ /* Convert this to the content frame */
+ return DCPTime (s + piece->content->trim_start()).frames (_film->audio_frame_rate());
}
-void
-Player::emit_silence (OutputAudioFrame most)
+ContentTime
+Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
{
- if (most == 0) {
- return;
- }
-
- OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
- shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
- silence->make_silent ();
- Audio (silence, _audio_position);
- _audio_position += _film->audio_frames_to_time (N);
-}
-
-void
-Player::film_changed (Film::Property p)
-{
- /* Here we should notice Film properties that affect our output, and
- alert listeners that our output now would be different to how it was
- last time we were run.
- */
+ /* s is the offset of t from the start position of this content */
+ DCPTime s = t - piece->content->position ();
+ s = DCPTime (max (int64_t (0), s.get ()));
+ s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
- if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) {
- Changed (false);
- }
+ return ContentTime (s, piece->frc);
}
void
-Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+PlayerStatistics::dump (shared_ptr<Log> log) const
{
- if (!image) {
- /* A null image means that we should stop any current subtitles at `from' */
- for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
- i->set_stop (from);
- }
- } else {
- _subtitles.push_back (Subtitle (_film, _video_container_size, weak_piece, image, rect, from, to));
- }
+ log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat));
+ log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence.seconds()));
}
-/** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
- * @return false if this could not be done.
- */
-bool
-Player::repeat_last_video ()
+PlayerStatistics const &
+Player::statistics () const
{
- if (!_last_incoming_video.image || !_have_valid_pieces) {
- return false;
- }
-
- process_video (
- _last_incoming_video.weak_piece,
- _last_incoming_video.image,
- _last_incoming_video.eyes,
- _last_incoming_video.part,
- _last_incoming_video.same,
- _last_incoming_video.frame,
- _last_incoming_video.extra
- );
-
- return true;
+ return _statistics;
}
#include "content.h"
#include "film.h"
#include "rect.h"
-#include "audio_merger.h"
#include "audio_content.h"
+#include "dcpomatic_time.h"
+#include "content_subtitle.h"
+#include "position_image.h"
#include "piece.h"
-#include "subtitle.h"
+#include "content_video.h"
class Job;
class Film;
class AudioContent;
class Piece;
class Image;
+class Decoder;
class Resampler;
class PlayerVideoFrame;
class ImageProxy;
+class PlayerStatistics
+{
+public:
+ struct Video {
+ Video ()
+ : black (0)
+ , repeat (0)
+ , good (0)
+ , skip (0)
+ {}
+
+ int black;
+ int repeat;
+ int good;
+ int skip;
+ } video;
+
+ struct Audio {
+ Audio ()
+ : silence (0)
+ , good (0)
+ , skip (0)
+ {}
+
+ DCPTime silence;
+ int64_t good;
+ int64_t skip;
+ } audio;
+
+ void dump (boost::shared_ptr<Log>) const;
+};
+
/** @class Player
- * @brief A class which can `play' a Playlist; emitting its audio and video.
+ * @brief A class which can `play' a Playlist.
*/
class Player : public boost::enable_shared_from_this<Player>, public boost::noncopyable
{
public:
Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>);
- void disable_video ();
- void disable_audio ();
-
- bool pass ();
- void seek (Time, bool);
+ std::list<boost::shared_ptr<PlayerVideoFrame> > get_video (DCPTime time, bool accurate);
+ boost::shared_ptr<AudioBuffers> get_audio (DCPTime time, DCPTime length, bool accurate);
- Time video_position () const {
- return _video_position;
+ void set_video_container_size (dcp::Size);
+ void set_approximate_size ();
+ void set_burn_subtitles (bool burn) {
+ _burn_subtitles = burn;
}
- void set_video_container_size (libdcp::Size);
-
- bool repeat_last_video ();
-
- /** Emitted when a video frame is ready.
- * First parameter is the video image.
- * Second parameter is true if the frame is the same as the last one that was emitted.
- * Third parameter is the time.
- */
- boost::signals2::signal<void (boost::shared_ptr<PlayerVideoFrame>, bool, Time)> Video;
+ PlayerStatistics const & statistics () const;
- /** Emitted when some audio data is ready */
- boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
-
/** Emitted when something has changed such that if we went back and emitted
* the last frame again it would look different. This is not emitted after
* a seek.
private:
friend class PlayerWrapper;
friend class Piece;
+ friend class player_overlaps_test;
- void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const ImageProxy>, Eyes, Part, bool, VideoContent::Frame, Time);
- void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
- void process_subtitle (boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
void setup_pieces ();
void playlist_changed ();
void content_changed (boost::weak_ptr<Content>, int, bool);
- void do_seek (Time, bool);
void flush ();
- void emit_black ();
- void emit_silence (OutputAudioFrame);
- boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>, bool);
void film_changed (Film::Property);
- void update_subtitle ();
-
+ std::list<PositionImage> process_content_image_subtitles (
+ boost::shared_ptr<SubtitleContent>, std::list<boost::shared_ptr<ContentImageSubtitle> >
+ ) const;
+ std::list<PositionImage> process_content_text_subtitles (std::list<boost::shared_ptr<ContentTextSubtitle> >) const;
+ void update_subtitle_from_text ();
+ VideoFrame dcp_to_content_video (boost::shared_ptr<const Piece> piece, DCPTime t) const;
+ AudioFrame dcp_to_content_audio (boost::shared_ptr<const Piece> piece, DCPTime t) const;
+ ContentTime dcp_to_content_subtitle (boost::shared_ptr<const Piece> piece, DCPTime t) const;
+ boost::shared_ptr<PlayerVideoFrame> black_player_video_frame () const;
+ boost::shared_ptr<PlayerVideoFrame> content_to_player_video_frame (
+ boost::shared_ptr<VideoContent> content,
+ ContentVideo content_video,
+ std::list<boost::shared_ptr<Piece> > subs,
+ DCPTime time,
+ dcp::Size image_size
+ ) const;
+
+ /** @return Pieces of content type C that overlap a specified time range in the DCP */
+ template<class C>
+ std::list<boost::shared_ptr<Piece> >
+ overlaps (DCPTime from, DCPTime to)
+ {
+ if (!_have_valid_pieces) {
+ setup_pieces ();
+ }
+
+ std::list<boost::shared_ptr<Piece> > overlaps;
+ for (typename std::list<boost::shared_ptr<Piece> >::const_iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
+ if (!boost::dynamic_pointer_cast<C> ((*i)->content)) {
+ continue;
+ }
+
+ if ((*i)->content->position() <= to && (*i)->content->end() >= from) {
+ overlaps.push_back (*i);
+ }
+ }
+
+ return overlaps;
+ }
+
boost::shared_ptr<const Film> _film;
boost::shared_ptr<const Playlist> _playlist;
-
- bool _video;
- bool _audio;
/** Our pieces are ready to go; if this is false the pieces must be (re-)created before they are used */
bool _have_valid_pieces;
std::list<boost::shared_ptr<Piece> > _pieces;
- /** The time after the last video that we emitted */
- Time _video_position;
- /** The time after the last audio that we emitted */
- Time _audio_position;
-
- AudioMerger<Time, AudioContent::Frame> _audio_merger;
-
- libdcp::Size _video_container_size;
- boost::shared_ptr<PlayerVideoFrame> _black_frame;
- std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers;
-
- std::list<Subtitle> _subtitles;
-
-#ifdef DCPOMATIC_DEBUG
- boost::shared_ptr<Content> _last_video;
-#endif
+ dcp::Size _video_container_size;
+ boost::shared_ptr<Image> _black_image;
- bool _last_emit_was_black;
+ bool _approximate_size;
+ bool _burn_subtitles;
- IncomingVideo _last_incoming_video;
+ PlayerStatistics _statistics;
boost::signals2::scoped_connection _playlist_changed_connection;
boost::signals2::scoped_connection _playlist_content_changed_connection;
*/
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "player_video_frame.h"
#include "image.h"
#include "image_proxy.h"
using std::string;
using std::cout;
using boost::shared_ptr;
-using libdcp::raw_convert;
+using dcp::raw_convert;
PlayerVideoFrame::PlayerVideoFrame (
shared_ptr<const ImageProxy> in,
Crop crop,
- libdcp::Size inter_size,
- libdcp::Size out_size,
+ dcp::Size inter_size,
+ dcp::Size out_size,
Scaler const * scaler,
Eyes eyes,
Part part,
{
_crop = Crop (node);
- _inter_size = libdcp::Size (node->number_child<int> ("InterWidth"), node->number_child<int> ("InterHeight"));
- _out_size = libdcp::Size (node->number_child<int> ("OutWidth"), node->number_child<int> ("OutHeight"));
+ _inter_size = dcp::Size (node->number_child<int> ("InterWidth"), node->number_child<int> ("InterHeight"));
+ _out_size = dcp::Size (node->number_child<int> ("OutWidth"), node->number_child<int> ("OutHeight"));
_scaler = Scaler::from_id (node->string_child ("Scaler"));
_eyes = (Eyes) node->number_child<int> ("Eyes");
_part = (Part) node->number_child<int> ("Part");
if (node->optional_number_child<int> ("SubtitleX")) {
- _subtitle_position = Position<int> (node->number_child<int> ("SubtitleX"), node->number_child<int> ("SubtitleY"));
+ _subtitle.position = Position<int> (node->number_child<int> ("SubtitleX"), node->number_child<int> ("SubtitleY"));
- shared_ptr<Image> image (
- new Image (PIX_FMT_RGBA, libdcp::Size (node->number_child<int> ("SubtitleWidth"), node->number_child<int> ("SubtitleHeight")), true)
+ _subtitle.image.reset (
+ new Image (PIX_FMT_RGBA, dcp::Size (node->number_child<int> ("SubtitleWidth"), node->number_child<int> ("SubtitleHeight")), true)
);
- image->read_from_socket (socket);
- _subtitle_image = image;
+ _subtitle.image->read_from_socket (socket);
}
}
void
-PlayerVideoFrame::set_subtitle (shared_ptr<const Image> image, Position<int> pos)
+PlayerVideoFrame::set_subtitle (PositionImage image)
{
- _subtitle_image = image;
- _subtitle_position = pos;
+ _subtitle = image;
}
shared_ptr<Image>
Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
- if (_subtitle_image) {
- out->alpha_blend (_subtitle_image, _subtitle_position);
+ if (_subtitle.image) {
+ out->alpha_blend (_subtitle.image, _subtitle.position);
}
return out;
node->add_child("Eyes")->add_child_text (raw_convert<string> (_eyes));
node->add_child("Part")->add_child_text (raw_convert<string> (_part));
_colour_conversion.as_xml (node);
- if (_subtitle_image) {
- node->add_child ("SubtitleWidth")->add_child_text (raw_convert<string> (_subtitle_image->size().width));
- node->add_child ("SubtitleHeight")->add_child_text (raw_convert<string> (_subtitle_image->size().height));
- node->add_child ("SubtitleX")->add_child_text (raw_convert<string> (_subtitle_position.x));
- node->add_child ("SubtitleY")->add_child_text (raw_convert<string> (_subtitle_position.y));
+ if (_subtitle.image) {
+ node->add_child ("SubtitleWidth")->add_child_text (raw_convert<string> (_subtitle.image->size().width));
+ node->add_child ("SubtitleHeight")->add_child_text (raw_convert<string> (_subtitle.image->size().height));
+ node->add_child ("SubtitleX")->add_child_text (raw_convert<string> (_subtitle.position.x));
+ node->add_child ("SubtitleY")->add_child_text (raw_convert<string> (_subtitle.position.y));
}
}
PlayerVideoFrame::send_binary (shared_ptr<Socket> socket) const
{
_in->send_binary (socket);
- if (_subtitle_image) {
- _subtitle_image->write_to_socket (socket);
+ if (_subtitle.image) {
+ _subtitle.image->write_to_socket (socket);
}
}
#include "types.h"
#include "position.h"
#include "colour_conversion.h"
+#include "position_image.h"
class Image;
class ImageProxy;
class PlayerVideoFrame
{
public:
- PlayerVideoFrame (boost::shared_ptr<const ImageProxy>, Crop, libdcp::Size, libdcp::Size, Scaler const *, Eyes, Part, ColourConversion);
+ PlayerVideoFrame (boost::shared_ptr<const ImageProxy>, Crop, dcp::Size, dcp::Size, Scaler const *, Eyes, Part, ColourConversion);
PlayerVideoFrame (boost::shared_ptr<cxml::Node>, boost::shared_ptr<Socket>);
- void set_subtitle (boost::shared_ptr<const Image>, Position<int>);
+ void set_subtitle (PositionImage);
boost::shared_ptr<Image> image () const;
private:
boost::shared_ptr<const ImageProxy> _in;
Crop _crop;
- libdcp::Size _inter_size;
- libdcp::Size _out_size;
+ dcp::Size _inter_size;
+ dcp::Size _out_size;
Scaler const * _scaler;
Eyes _eyes;
Part _part;
ColourConversion _colour_conversion;
- boost::shared_ptr<const Image> _subtitle_image;
- Position<int> _subtitle_position;
+ PositionImage _subtitle;
};
_sequencing_video = true;
ContentList cl = _content;
- Time next_left = 0;
- Time next_right = 0;
+ DCPTime next_left;
+ DCPTime next_right;
for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i);
if (!vc) {
continue;
}
-
+
if (vc->video_frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) {
vc->set_position (next_right);
- next_right = vc->end() + 1;
+ next_right = vc->end() + DCPTime::delta ();
} else {
vc->set_position (next_left);
- next_left = vc->end() + 1;
+ next_left = vc->end() + DCPTime::delta ();
}
}
/** @param node <Playlist> node */
void
-Playlist::set_from_xml (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node, int version, list<string>& notes)
+Playlist::set_from_xml (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version, list<string>& notes)
{
list<cxml::NodePtr> c = node->node_children ("Content");
for (list<cxml::NodePtr>::iterator i = c.begin(); i != c.end(); ++i) {
return best->dcp;
}
-Time
+DCPTime
Playlist::length () const
{
- Time len = 0;
+ DCPTime len;
for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
- len = max (len, (*i)->end() + 1);
+ len = max (len, (*i)->end() + DCPTime::delta ());
}
return len;
}
}
-Time
+DCPTime
Playlist::video_end () const
{
- Time end = 0;
+ DCPTime end;
for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
if (dynamic_pointer_cast<const VideoContent> (*i)) {
end = max (end, (*i)->end ());
return end;
}
+FrameRateChange
+Playlist::active_frame_rate_change (DCPTime t, int dcp_video_frame_rate) const
+{
+ for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+ shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i);
+ if (!vc) {
+ break;
+ }
+
+ if (vc->position() >= t && t < vc->end()) {
+ return FrameRateChange (vc->video_frame_rate(), dcp_video_frame_rate);
+ }
+ }
+
+ return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate);
+}
+
void
Playlist::set_sequence_video (bool s)
{
void
Playlist::repeat (ContentList c, int n)
{
- pair<Time, Time> range (TIME_MAX, 0);
+ pair<DCPTime, DCPTime> range (DCPTime::max (), DCPTime ());
for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
range.first = min (range.first, (*i)->position ());
range.second = max (range.second, (*i)->position ());
range.second = max (range.second, (*i)->end ());
}
- Time pos = range.second;
+ DCPTime pos = range.second;
for (int i = 0; i < n; ++i) {
for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
shared_ptr<Content> copy = (*i)->clone ();
return;
}
- Time const p = (*previous)->position ();
+ DCPTime const p = (*previous)->position ();
(*previous)->set_position (p + c->length_after_trim ());
c->set_position (p);
sort (_content.begin(), _content.end(), ContentSorter ());
return;
}
- Time const p = (*next)->position ();
+ DCPTime const p = (*next)->position ();
(*next)->set_position (c->position ());
c->set_position (p + c->length_after_trim ());
sort (_content.begin(), _content.end(), ContentSorter ());
#include <boost/enable_shared_from_this.hpp>
#include "ffmpeg_content.h"
#include "audio_mapping.h"
+#include "util.h"
class Content;
class FFmpegContent;
class Film;
class Region;
-/** @class Playlist
- * @brief A set of content files (video and audio), with knowledge of how they should be arranged into
- * a DCP.
- *
- * This class holds Content objects, and it knows how they should be arranged.
- */
-
struct ContentSorter
{
bool operator() (boost::shared_ptr<Content> a, boost::shared_ptr<Content> b);
};
+/** @class Playlist
+ * @brief A set of Content objects with knowledge of how they should be arranged into
+ * a DCP.
+ */
class Playlist : public boost::noncopyable
{
public:
~Playlist ();
void as_xml (xmlpp::Node *);
- void set_from_xml (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int, std::list<std::string> &);
+ void set_from_xml (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int, std::list<std::string> &);
void add (boost::shared_ptr<Content>);
void remove (boost::shared_ptr<Content>);
std::string video_identifier () const;
- Time length () const;
+ DCPTime length () const;
int best_dcp_frame_rate () const;
- Time video_end () const;
+ DCPTime video_end () const;
+ FrameRateChange active_frame_rate_change (DCPTime, int dcp_frame_rate) const;
void set_sequence_video (bool);
void maybe_sequence_video ();
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#define DCPOMATIC_POSITION_H
/** @struct Position
- * @brief A position.
+ * @brief A position (x and y coordinates)
*/
template <class T>
class Position
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_POSITION_IMAGE_H
+#define DCPOMATIC_POSITION_IMAGE_H
+
+#include "position.h"
+
+class Image;
+
+class PositionImage
+{
+public:
+ PositionImage () {}
+
+ PositionImage (boost::shared_ptr<Image> i, Position<int> p)
+ : image (i)
+ , position (p)
+ {}
+
+ boost::shared_ptr<Image> image;
+ Position<int> position;
+};
+
+#endif
*/
-#include <libdcp/types.h>
+#include <dcp/types.h>
#include "ratio.h"
#include "util.h"
#include <vector>
#include <boost/utility.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
class Ratio : public boost::noncopyable
{
, height (0)
{}
+ Rect (Position<T> p, T w_, T h_)
+ : x (p.x)
+ , y (p.y)
+ , width (w_)
+ , height (h_)
+ {}
+
Rect (T x_, T y_, T w_, T h_)
: x (x_)
, y (y_)
T width;
T height;
- Position<T> position () const {
+ Position<T> position () const
+ {
return Position<T> (x, y);
}
- Rect<T> intersection (Rect<T> const & other) const {
+ Rect<T> intersection (Rect<T> const & other) const
+ {
T const tx = max (x, other.x);
T const ty = max (y, other.y);
);
}
- bool contains (Position<T> p) const {
+ void extend (Rect<T> const & other)
+ {
+ x = std::min (x, other.x);
+ y = std::min (y, other.y);
+ width = std::max (x + width, other.x + other.width) - x;
+ height = std::max (y + height, other.y + other.height) - y;
+ }
+
+ bool contains (Position<T> p) const
+ {
return (p.x >= x && p.x <= (x + width) && p.y >= y && p.y <= (y + height));
}
};
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <cairomm/cairomm.h>
+#include <pangomm.h>
+#include "render_subtitles.h"
+#include "types.h"
+#include "image.h"
+
+using std::list;
+using std::cout;
+using std::string;
+using std::min;
+using std::max;
+using std::pair;
+using boost::shared_ptr;
+using boost::optional;
+
+static int
+calculate_position (dcp::VAlign v_align, double v_position, int target_height, int offset)
+{
+ switch (v_align) {
+ case dcp::TOP:
+ return (v_position / 100) * target_height - offset;
+ case dcp::CENTER:
+ return (0.5 + v_position / 100) * target_height - offset;
+ case dcp::BOTTOM:
+ return (1.0 - v_position / 100) * target_height - offset;
+ }
+
+ return 0;
+}
+
+PositionImage
+render_subtitles (list<dcp::SubtitleString> subtitles, dcp::Size target)
+{
+ if (subtitles.empty ()) {
+ return PositionImage ();
+ }
+
+ /* Estimate height that the subtitle image needs to be */
+ optional<int> top;
+ optional<int> bottom;
+ for (list<dcp::SubtitleString>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
+ int const b = calculate_position (i->v_align(), i->v_position(), target.height, 0);
+ int const t = b - i->size() * target.height / (11 * 72);
+
+ top = min (top.get_value_or (t), t);
+ bottom = max (bottom.get_value_or (b), b);
+ }
+
+ top = top.get() - 32;
+ bottom = bottom.get() + 32;
+
+ shared_ptr<Image> image (new Image (PIX_FMT_RGBA, dcp::Size (target.width, bottom.get() - top.get ()), false));
+ image->make_black ();
+
+ Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create (
+ image->data()[0],
+ Cairo::FORMAT_ARGB32,
+ image->size().width,
+ image->size().height,
+ Cairo::ImageSurface::format_stride_for_width (Cairo::FORMAT_ARGB32, image->size().width)
+ );
+
+ Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (surface);
+ Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
+
+ layout->set_width (image->size().width * PANGO_SCALE);
+ layout->set_alignment (Pango::ALIGN_CENTER);
+
+ context->set_line_width (1);
+
+ for (list<dcp::SubtitleString>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
+ string f = i->font ();
+ if (f.empty ()) {
+ f = "Arial";
+ }
+ Pango::FontDescription font (f);
+ font.set_absolute_size (i->size_in_pixels (target.height) * PANGO_SCALE);
+ if (i->italic ()) {
+ font.set_style (Pango::STYLE_ITALIC);
+ }
+ layout->set_font_description (font);
+ layout->set_text (i->text ());
+
+ /* Compute fade factor */
+ /* XXX */
+ float fade_factor = 1;
+#if 0
+ dcp::Time now (time * 1000 / (4 * TIME_HZ));
+ dcp::Time end_fade_up = i->in() + i->fade_up_time ();
+ dcp::Time start_fade_down = i->out() - i->fade_down_time ();
+ if (now < end_fade_up) {
+ fade_factor = (now - i->in()) / i->fade_up_time();
+ } else if (now > start_fade_down) {
+ fade_factor = 1.0 - ((now - start_fade_down) / i->fade_down_time ());
+ }
+#endif
+
+ layout->update_from_cairo_context (context);
+
+ /* Work out position */
+
+ int const x = 0;
+ int const y = calculate_position (i->v_align (), i->v_position (), target.height, (layout->get_baseline() / PANGO_SCALE) + top.get ());
+
+ if (i->effect() == dcp::SHADOW) {
+ /* Drop-shadow effect */
+ dcp::Color const ec = i->effect_color ();
+ context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
+ context->move_to (x + 4, y + 4);
+ layout->add_to_cairo_context (context);
+ context->fill ();
+ }
+
+ /* The actual subtitle */
+ context->move_to (x, y);
+ dcp::Color const c = i->color ();
+ context->set_source_rgba (float(c.r) / 255, float(c.g) / 255, float(c.b) / 255, fade_factor);
+ layout->add_to_cairo_context (context);
+ context->fill ();
+
+ if (i->effect() == dcp::BORDER) {
+ /* Border effect */
+ context->move_to (x, y);
+ dcp::Color ec = i->effect_color ();
+ context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
+ layout->add_to_cairo_context (context);
+ context->stroke ();
+ }
+ }
+
+ return PositionImage (image, Position<int> (0, top.get ()));
+}
+
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <dcp/subtitle_string.h>
+#include <dcp/util.h>
+#include "position_image.h"
+
+PositionImage render_subtitles (std::list<dcp::SubtitleString>, dcp::Size);
swr_free (&_swr_context);
}
-pair<shared_ptr<const AudioBuffers>, AudioContent::Frame>
-Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame)
+shared_ptr<const AudioBuffers>
+Resampler::run (shared_ptr<const AudioBuffers> in)
{
- AudioContent::Frame const resamp_time = swr_next_pts (_swr_context, frame * _out_rate) / _in_rate;
-
/* Compute the resampled frames count and add 32 for luck */
int const max_resampled_frames = ceil ((double) in->frames() * _out_rate / _in_rate) + 32;
shared_ptr<AudioBuffers> resampled (new AudioBuffers (_channels, max_resampled_frames));
}
resampled->set_frames (resampled_frames);
- return make_pair (resampled, resamp_time);
+ return resampled;
}
shared_ptr<const AudioBuffers>
Resampler (int, int, int);
~Resampler ();
- std::pair<boost::shared_ptr<const AudioBuffers>, AudioContent::Frame> run (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+ boost::shared_ptr<const AudioBuffers> run (boost::shared_ptr<const AudioBuffers>);
boost::shared_ptr<const AudioBuffers> flush ();
private:
#include <boost/algorithm/string.hpp>
#include <boost/scoped_array.hpp>
#include <libcxml/cxml.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "server.h"
#include "util.h"
#include "scaler.h"
using boost::bind;
using boost::scoped_array;
using boost::optional;
-using libdcp::Size;
-using libdcp::raw_convert;
+using dcp::Size;
+using dcp::raw_convert;
Server::Server (shared_ptr<Log> log, bool verbose)
: _log (log)
*/
#include <libcxml/cxml.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "server_finder.h"
#include "exceptions.h"
#include "util.h"
using std::cout;
using boost::shared_ptr;
using boost::scoped_array;
-using libdcp::raw_convert;
+using dcp::raw_convert;
ServerFinder* ServerFinder::_instance = 0;
*/
#include <libcxml/cxml.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "sndfile_content.h"
#include "sndfile_decoder.h"
#include "film.h"
using std::stringstream;
using std::cout;
using boost::shared_ptr;
-using libdcp::raw_convert;
-
-int const SndfileContentProperty::VIDEO_FRAME_RATE = 600;
+using dcp::raw_convert;
SndfileContent::SndfileContent (shared_ptr<const Film> f, boost::filesystem::path p)
: Content (f, p)
}
-SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version)
+SndfileContent::SndfileContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version)
: Content (f, node)
, AudioContent (f, node)
, _audio_mapping (node->node_child ("AudioMapping"), version)
{
_audio_channels = node->number_child<int> ("AudioChannels");
- _audio_length = node->number_child<AudioContent::Frame> ("AudioLength");
+ _audio_length = ContentTime (node->number_child<int64_t> ("AudioLength"));
_audio_frame_rate = node->number_child<int> ("AudioFrameRate");
}
s << String::compose (
_("%1 channels, %2kHz, %3 samples"),
audio_channels(),
- content_audio_frame_rate() / 1000.0,
- audio_length()
+ audio_frame_rate() / 1000.0,
+ audio_length().frames (audio_frame_rate ())
);
return s.str ();
job->set_progress_unknown ();
Content::examine (job);
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- SndfileDecoder dec (film, shared_from_this());
+ SndfileDecoder dec (shared_from_this());
{
boost::mutex::scoped_lock lm (_mutex);
AudioContent::as_xml (node);
node->add_child("AudioChannels")->add_child_text (raw_convert<string> (audio_channels ()));
- node->add_child("AudioLength")->add_child_text (raw_convert<string> (audio_length ()));
- node->add_child("AudioFrameRate")->add_child_text (raw_convert<string> (content_audio_frame_rate ()));
+ node->add_child("AudioLength")->add_child_text (raw_convert<string> (audio_length().get ()));
+ node->add_child("AudioFrameRate")->add_child_text (raw_convert<string> (audio_frame_rate ()));
_audio_mapping.as_xml (node->add_child("AudioMapping"));
}
-Time
+DCPTime
SndfileContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
-
- float const rate = _video_frame_rate.get_value_or (film->video_frame_rate ());
- OutputAudioFrame const len = divide_with_round (
- audio_length() * output_audio_frame_rate() * rate,
- content_audio_frame_rate() * film->video_frame_rate()
- );
-
- return film->audio_frames_to_time (len);
-}
-
-int
-SndfileContent::output_audio_frame_rate () const
-{
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- return film->audio_frame_rate ();
+ return DCPTime (audio_length(), film->active_frame_rate_change (position ()));
}
void
_audio_mapping = m;
}
- signal_changed (AudioContentProperty::AUDIO_MAPPING);
+ AudioContent::set_audio_mapping (m);
}
-float
-SndfileContent::video_frame_rate () const
-{
- {
- boost::mutex::scoped_lock lm (_mutex);
- if (_video_frame_rate) {
- return _video_frame_rate.get ();
- }
- }
-
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
- return film->video_frame_rate ();
-}
class Node;
}
-class SndfileContentProperty
-{
-public:
- static int const VIDEO_FRAME_RATE;
-};
-
class SndfileContent : public AudioContent
{
public:
SndfileContent (boost::shared_ptr<const Film>, boost::filesystem::path);
- SndfileContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
+ SndfileContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int);
boost::shared_ptr<SndfileContent> shared_from_this () {
return boost::dynamic_pointer_cast<SndfileContent> (Content::shared_from_this ());
std::string technical_summary () const;
std::string information () const;
void as_xml (xmlpp::Node *) const;
- Time full_length () const;
+ DCPTime full_length () const;
/* AudioContent */
int audio_channels () const {
return _audio_channels;
}
- AudioContent::Frame audio_length () const {
+ ContentTime audio_length () const {
boost::mutex::scoped_lock lm (_mutex);
return _audio_length;
}
- int content_audio_frame_rate () const {
+ int audio_frame_rate () const {
boost::mutex::scoped_lock lm (_mutex);
return _audio_frame_rate;
}
- int output_audio_frame_rate () const;
-
AudioMapping audio_mapping () const {
boost::mutex::scoped_lock lm (_mutex);
return _audio_mapping;
}
- void set_video_frame_rate (float r) {
- {
- boost::mutex::scoped_lock lm (_mutex);
- _video_frame_rate = r;
- }
-
- signal_changed (SndfileContentProperty::VIDEO_FRAME_RATE);
- }
-
- float video_frame_rate () const;
-
void set_audio_mapping (AudioMapping);
static bool valid_file (boost::filesystem::path);
private:
int _audio_channels;
- AudioContent::Frame _audio_length;
+ ContentTime _audio_length;
int _audio_frame_rate;
AudioMapping _audio_mapping;
- /** Video frame rate that this audio has been prepared for,
- if specified.
- */
- boost::optional<float> _video_frame_rate;
};
#endif
#include <sndfile.h>
#include "sndfile_content.h"
#include "sndfile_decoder.h"
-#include "film.h"
#include "exceptions.h"
#include "audio_buffers.h"
using std::cout;
using boost::shared_ptr;
-SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const SndfileContent> c)
- : Decoder (f)
- , AudioDecoder (f, c)
+SndfileDecoder::SndfileDecoder (shared_ptr<const SndfileContent> c)
+ : AudioDecoder (c)
, _sndfile_content (c)
, _deinterleave_buffer (0)
{
delete[] _deinterleave_buffer;
}
-void
+bool
SndfileDecoder::pass ()
{
+ if (_remaining == 0) {
+ return true;
+ }
+
/* Do things in half second blocks as I think there may be limits
to what FFmpeg (and in particular the resampler) can cope with.
*/
- sf_count_t const block = _sndfile_content->content_audio_frame_rate() / 2;
+ sf_count_t const block = _sndfile_content->audio_frame_rate() / 2;
sf_count_t const this_time = min (block, _remaining);
int const channels = _sndfile_content->audio_channels ();
}
data->set_frames (this_time);
- audio (data, _done);
+ audio (data, ContentTime::from_frames (_done, audio_frame_rate ()));
_done += this_time;
_remaining -= this_time;
+
+ return _remaining == 0;
}
int
return _info.channels;
}
-AudioContent::Frame
+ContentTime
SndfileDecoder::audio_length () const
{
- return _info.frames;
+ return ContentTime::from_frames (_info.frames, audio_frame_rate ());
}
int
return _info.samplerate;
}
-bool
-SndfileDecoder::done () const
+void
+SndfileDecoder::seek (ContentTime t, bool accurate)
{
- return _audio_position >= _sndfile_content->audio_length ();
+ AudioDecoder::seek (t, accurate);
+
+ _done = t.frames (audio_frame_rate ());
+ _remaining = _info.frames - _done;
}
class SndfileDecoder : public AudioDecoder
{
public:
- SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>);
+ SndfileDecoder (boost::shared_ptr<const SndfileContent> c);
~SndfileDecoder ();
- void pass ();
- bool done () const;
+ void seek (ContentTime, bool);
int audio_channels () const;
- AudioContent::Frame audio_length () const;
+ ContentTime audio_length () const;
int audio_frame_rate () const;
private:
+ bool pass ();
+
boost::shared_ptr<const SndfileContent> _sndfile_content;
SNDFILE* _sndfile;
SF_INFO _info;
- AudioContent::Frame _done;
- AudioContent::Frame _remaining;
+ int64_t _done;
+ int64_t _remaining;
float* _deinterleave_buffer;
};
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+#include "subrip.h"
+#include "subrip_content.h"
+#include "subrip_subtitle.h"
+#include "cross.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::list;
+using std::vector;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::algorithm::trim;
+
+SubRip::SubRip (shared_ptr<const SubRipContent> content)
+{
+ FILE* f = fopen_boost (content->path (0), "r");
+ if (!f) {
+ throw OpenFileError (content->path (0));
+ }
+
+ enum {
+ COUNTER,
+ METADATA,
+ CONTENT
+ } state = COUNTER;
+
+ char buffer[256];
+ int next_count = 1;
+
+ boost::optional<SubRipSubtitle> current;
+ list<string> lines;
+
+ while (!feof (f)) {
+ fgets (buffer, sizeof (buffer), f);
+ if (feof (f)) {
+ break;
+ }
+
+ string line (buffer);
+ trim_right_if (line, boost::is_any_of ("\n\r"));
+
+ switch (state) {
+ case COUNTER:
+ {
+ int x = 0;
+ try {
+ x = lexical_cast<int> (line);
+ } catch (...) {
+
+ }
+
+ if (x == next_count) {
+ state = METADATA;
+ ++next_count;
+ current = SubRipSubtitle ();
+ } else {
+ throw SubRipError (line, _("a subtitle count"), content->path (0));
+ }
+ }
+ break;
+ case METADATA:
+ {
+ vector<string> p;
+ boost::algorithm::split (p, line, boost::algorithm::is_any_of (" "));
+ if (p.size() != 3 && p.size() != 7) {
+ throw SubRipError (line, _("a time/position line"), content->path (0));
+ }
+
+ current->from = convert_time (p[0]);
+ current->to = convert_time (p[2]);
+
+ if (p.size() > 3) {
+ current->x1 = convert_coordinate (p[3]);
+ current->x2 = convert_coordinate (p[4]);
+ current->y1 = convert_coordinate (p[5]);
+ current->y2 = convert_coordinate (p[6]);
+ }
+ state = CONTENT;
+ break;
+ }
+ case CONTENT:
+ if (line.empty ()) {
+ state = COUNTER;
+ current->pieces = convert_content (lines);
+ _subtitles.push_back (current.get ());
+ current.reset ();
+ lines.clear ();
+ } else {
+ lines.push_back (line);
+ }
+ break;
+ }
+ }
+
+ if (state == CONTENT) {
+ current->pieces = convert_content (lines);
+ _subtitles.push_back (current.get ());
+ }
+
+ fclose (f);
+}
+
+ContentTime
+SubRip::convert_time (string t)
+{
+ ContentTime r;
+
+ vector<string> a;
+ boost::algorithm::split (a, t, boost::is_any_of (":"));
+ assert (a.size() == 3);
+ r += ContentTime::from_seconds (lexical_cast<int> (a[0]) * 60 * 60);
+ r += ContentTime::from_seconds (lexical_cast<int> (a[1]) * 60);
+
+ vector<string> b;
+ boost::algorithm::split (b, a[2], boost::is_any_of (","));
+ r += ContentTime::from_seconds (lexical_cast<int> (b[0]));
+ r += ContentTime::from_seconds (lexical_cast<double> (b[1]) / 1000);
+
+ return r;
+}
+
+int
+SubRip::convert_coordinate (string t)
+{
+ vector<string> a;
+ boost::algorithm::split (a, t, boost::is_any_of (":"));
+ assert (a.size() == 2);
+ return lexical_cast<int> (a[1]);
+}
+
+void
+SubRip::maybe_content (list<SubRipSubtitlePiece>& pieces, SubRipSubtitlePiece& p)
+{
+ if (!p.text.empty ()) {
+ pieces.push_back (p);
+ p.text.clear ();
+ }
+}
+
+list<SubRipSubtitlePiece>
+SubRip::convert_content (list<string> t)
+{
+ list<SubRipSubtitlePiece> pieces;
+
+ SubRipSubtitlePiece p;
+
+ enum {
+ TEXT,
+ TAG
+ } state = TEXT;
+
+ string tag;
+
+ /* XXX: missing <font> support */
+ /* XXX: nesting of tags e.g. <b>foo<i>bar<b>baz</b>fred</i>jim</b> might
+ not work, I think.
+ */
+
+ for (list<string>::const_iterator i = t.begin(); i != t.end(); ++i) {
+ for (size_t j = 0; j < i->size(); ++j) {
+ switch (state) {
+ case TEXT:
+ if ((*i)[j] == '<' || (*i)[j] == '{') {
+ state = TAG;
+ } else {
+ p.text += (*i)[j];
+ }
+ break;
+ case TAG:
+ if ((*i)[j] == '>' || (*i)[j] == '}') {
+ if (tag == "b") {
+ maybe_content (pieces, p);
+ p.bold = true;
+ } else if (tag == "/b") {
+ maybe_content (pieces, p);
+ p.bold = false;
+ } else if (tag == "i") {
+ maybe_content (pieces, p);
+ p.italic = true;
+ } else if (tag == "/i") {
+ maybe_content (pieces, p);
+ p.italic = false;
+ } else if (tag == "u") {
+ maybe_content (pieces, p);
+ p.underline = true;
+ } else if (tag == "/u") {
+ maybe_content (pieces, p);
+ p.underline = false;
+ }
+ tag.clear ();
+ state = TEXT;
+ } else {
+ tag += (*i)[j];
+ }
+ break;
+ }
+ }
+ }
+
+ maybe_content (pieces, p);
+
+ return pieces;
+}
+
+ContentTime
+SubRip::length () const
+{
+ if (_subtitles.empty ()) {
+ return ContentTime ();
+ }
+
+ return _subtitles.back().to;
+}
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_H
+#define DCPOMATIC_SUBRIP_H
+
+#include "subrip_subtitle.h"
+
+class SubRipContent;
+class subrip_time_test;
+class subrip_coordinate_test;
+class subrip_content_test;
+class subrip_parse_test;
+
+class SubRip
+{
+public:
+ SubRip (boost::shared_ptr<const SubRipContent>);
+
+ ContentTime length () const;
+
+protected:
+ std::vector<SubRipSubtitle> _subtitles;
+
+private:
+ friend class subrip_time_test;
+ friend class subrip_coordinate_test;
+ friend class subrip_content_test;
+ friend class subrip_parse_test;
+
+ static ContentTime convert_time (std::string);
+ static int convert_coordinate (std::string);
+ static std::list<SubRipSubtitlePiece> convert_content (std::list<std::string>);
+ static void maybe_content (std::list<SubRipSubtitlePiece> &, SubRipSubtitlePiece &);
+};
+
+#endif
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "subrip_content.h"
+#include "util.h"
+#include "subrip.h"
+#include "film.h"
+#include <dcp/raw_convert.h>
+
+#include "i18n.h"
+
+using std::stringstream;
+using std::string;
+using std::cout;
+using dcp::raw_convert;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, boost::filesystem::path path)
+ : Content (film, path)
+ , SubtitleContent (film, path)
+{
+
+}
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version)
+ : Content (film, node)
+ , SubtitleContent (film, node, version)
+ , _length (node->number_child<int64_t> ("Length"))
+{
+
+}
+
+void
+SubRipContent::examine (boost::shared_ptr<Job> job)
+{
+ Content::examine (job);
+ SubRip s (shared_from_this ());
+
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ DCPTime len (s.length (), film->active_frame_rate_change (position ()));
+
+ boost::mutex::scoped_lock lm (_mutex);
+ _length = len;
+}
+
+string
+SubRipContent::summary () const
+{
+ return path_summary() + " " + _("[subtitles]");
+}
+
+string
+SubRipContent::technical_summary () const
+{
+ return Content::technical_summary() + " - " + _("SubRip subtitles");
+}
+
+string
+SubRipContent::information () const
+{
+
+}
+
+void
+SubRipContent::as_xml (xmlpp::Node* node) const
+{
+ node->add_child("Type")->add_child_text ("SubRip");
+ Content::as_xml (node);
+ SubtitleContent::as_xml (node);
+ node->add_child("Length")->add_child_text (raw_convert<string> (_length.get ()));
+}
+
+DCPTime
+SubRipContent::full_length () const
+{
+ /* XXX: this assumes that the timing of the SubRip file is appropriate
+ for the DCP's frame rate.
+ */
+ return _length;
+}
+
+string
+SubRipContent::identifier () const
+{
+ stringstream s;
+ s << Content::identifier()
+ << "_" << raw_convert<string> (subtitle_scale())
+ << "_" << raw_convert<string> (subtitle_x_offset())
+ << "_" << raw_convert<string> (subtitle_y_offset());
+
+ return s.str ();
+}
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "subtitle_content.h"
+
+class SubRipContent : public SubtitleContent
+{
+public:
+ SubRipContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+ SubRipContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int);
+
+ boost::shared_ptr<SubRipContent> shared_from_this () {
+ return boost::dynamic_pointer_cast<SubRipContent> (Content::shared_from_this ());
+ }
+
+ void examine (boost::shared_ptr<Job>);
+ std::string summary () const;
+ std::string technical_summary () const;
+ std::string information () const;
+ void as_xml (xmlpp::Node *) const;
+ DCPTime full_length () const;
+ std::string identifier () const;
+
+private:
+ DCPTime _length;
+};
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <dcp/subtitle_string.h>
+#include "subrip_decoder.h"
+
+using std::list;
+using boost::shared_ptr;
+
+SubRipDecoder::SubRipDecoder (shared_ptr<const SubRipContent> content)
+ : SubRip (content)
+ , _next (0)
+{
+
+}
+
+void
+SubRipDecoder::seek (ContentTime time, bool accurate)
+{
+ SubtitleDecoder::seek (time, accurate);
+
+ _next = 0;
+ list<SubRipSubtitlePiece>::const_iterator i = _subtitles[_next].pieces.begin();
+ while (i != _subtitles[_next].pieces.end() && _subtitles[_next].from < time) {
+ ++i;
+ }
+
+}
+
+bool
+SubRipDecoder::pass ()
+{
+ if (_next >= _subtitles.size ()) {
+ return true;
+ }
+
+ list<dcp::SubtitleString> out;
+ for (list<SubRipSubtitlePiece>::const_iterator i = _subtitles[_next].pieces.begin(); i != _subtitles[_next].pieces.end(); ++i) {
+ out.push_back (
+ dcp::SubtitleString (
+ "Arial",
+ i->italic,
+ dcp::Color (255, 255, 255),
+ 72,
+ dcp::Time (rint (_subtitles[_next].from.seconds() * 250)),
+ dcp::Time (rint (_subtitles[_next].to.seconds() * 250)),
+ 0.9,
+ dcp::BOTTOM,
+ i->text,
+ dcp::NONE,
+ dcp::Color (255, 255, 255),
+ 0,
+ 0
+ )
+ );
+ }
+
+ text_subtitle (out);
+ _next++;
+ return false;
+}
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_DECODER_H
+#define DCPOMATIC_SUBRIP_DECODER_H
+
+#include "subtitle_decoder.h"
+#include "subrip.h"
+
+class SubRipContent;
+
+class SubRipDecoder : public SubtitleDecoder, public SubRip
+{
+public:
+ SubRipDecoder (boost::shared_ptr<const SubRipContent>);
+
+protected:
+ void seek (ContentTime time, bool accurate);
+ bool pass ();
+
+private:
+ size_t _next;
+};
+
+#endif
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_SUBTITLE_H
+#define DCPOMATIC_SUBRIP_SUBTITLE_H
+
+#include <boost/optional.hpp>
+#include <dcp/types.h>
+#include "types.h"
+#include "dcpomatic_time.h"
+
+struct SubRipSubtitlePiece
+{
+ SubRipSubtitlePiece ()
+ : bold (false)
+ , italic (false)
+ , underline (false)
+ {}
+
+ std::string text;
+ bool bold;
+ bool italic;
+ bool underline;
+ dcp::Color color;
+};
+
+struct SubRipSubtitle
+{
+ SubRipSubtitle ()
+ : from (0)
+ , to (0)
+ {}
+
+ ContentTime from;
+ ContentTime to;
+ boost::optional<int> x1;
+ boost::optional<int> x2;
+ boost::optional<int> y1;
+ boost::optional<int> y2;
+ std::list<SubRipSubtitlePiece> pieces;
+};
+
+#endif
+++ /dev/null
-/*
- Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
-
- This program 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.
-
- This program 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 this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "subtitle.h"
-#include "subtitle_content.h"
-#include "piece.h"
-#include "image.h"
-#include "scaler.h"
-#include "film.h"
-
-using boost::shared_ptr;
-using boost::dynamic_pointer_cast;
-using boost::weak_ptr;
-
-Subtitle::Subtitle (shared_ptr<const Film> film, libdcp::Size video_container_size, weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
- : _piece (weak_piece)
- , _in_image (image)
- , _in_rect (rect)
- , _in_from (from)
- , _in_to (to)
-{
- update (film, video_container_size);
-}
-
-void
-Subtitle::update (shared_ptr<const Film> film, libdcp::Size video_container_size)
-{
- shared_ptr<Piece> piece = _piece.lock ();
- if (!piece) {
- return;
- }
-
- if (!_in_image) {
- _out_image.reset ();
- return;
- }
-
- shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
- assert (sc);
-
- dcpomatic::Rect<double> in_rect = _in_rect;
- libdcp::Size scaled_size;
-
- in_rect.x += sc->subtitle_x_offset ();
- in_rect.y += sc->subtitle_y_offset ();
-
- /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
- scaled_size.width = in_rect.width * video_container_size.width * sc->subtitle_scale ();
- scaled_size.height = in_rect.height * video_container_size.height * sc->subtitle_scale ();
-
- /* Then we need a corrective translation, consisting of two parts:
- *
- * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
- * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
- *
- * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
- * (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
- * (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
- *
- * Combining these two translations gives these expressions.
- */
-
- _out_position.x = rint (video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
- _out_position.y = rint (video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
-
- _out_image = _in_image->scale (
- scaled_size,
- Scaler::from_id ("bicubic"),
- _in_image->pixel_format (),
- true
- );
-
- /* XXX: hack */
- Time from = _in_from;
- Time to = _in_to;
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (piece->content);
- if (vc) {
- from = rint (from * vc->video_frame_rate() / film->video_frame_rate());
- to = rint (to * vc->video_frame_rate() / film->video_frame_rate());
- }
-
- _out_from = from + piece->content->position ();
- _out_to = to + piece->content->position ();
-
- check_out_to ();
-}
-
-bool
-Subtitle::covers (Time t) const
-{
- return _out_from <= t && t <= _out_to;
-}
-
-void
-Subtitle::check_out_to ()
-{
- if (_stop && _out_to > _stop.get ()) {
- _out_to = _stop.get ();
- }
-}
+++ /dev/null
-/*
- Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
-
- This program 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.
-
- This program 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 this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <boost/shared_ptr.hpp>
-#include <boost/weak_ptr.hpp>
-#include <boost/optional.hpp>
-#include <libdcp/util.h>
-#include "rect.h"
-#include "types.h"
-
-class Film;
-class Piece;
-class Image;
-
-class Subtitle
-{
-public:
-
- Subtitle (boost::shared_ptr<const Film>, libdcp::Size, boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
-
- void update (boost::shared_ptr<const Film>, libdcp::Size);
- void set_stop (Time t) {
- _stop = t;
- check_out_to ();
- }
-
- bool covers (Time t) const;
- bool ends_before (Time t) const {
- return _out_to < t;
- }
-
- boost::shared_ptr<Image> out_image () const {
- return _out_image;
- }
-
- Position<int> out_position () const {
- return _out_position;
- }
-
-private:
- void check_out_to ();
-
- boost::weak_ptr<Piece> _piece;
- boost::shared_ptr<Image> _in_image;
- dcpomatic::Rect<double> _in_rect;
- Time _in_from;
- Time _in_to;
-
- boost::shared_ptr<Image> _out_image;
- Position<int> _out_position;
- Time _out_from;
- Time _out_to;
-
- /** Time at which this subtitle should stop (overriding _out_to) */
- boost::optional<Time> _stop;
-};
*/
#include <libcxml/cxml.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "subtitle_content.h"
#include "util.h"
#include "exceptions.h"
using std::string;
using std::vector;
+using std::cout;
using boost::shared_ptr;
using boost::dynamic_pointer_cast;
-using libdcp::raw_convert;
+using dcp::raw_convert;
int const SubtitleContentProperty::SUBTITLE_X_OFFSET = 500;
int const SubtitleContentProperty::SUBTITLE_Y_OFFSET = 501;
}
-SubtitleContent::SubtitleContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version)
+SubtitleContent::SubtitleContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version)
: Content (f, node)
, _subtitle_x_offset (0)
, _subtitle_y_offset (0)
{
public:
SubtitleContent (boost::shared_ptr<const Film>, boost::filesystem::path);
- SubtitleContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int version);
+ SubtitleContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int version);
SubtitleContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
void as_xml (xmlpp::Node *) const;
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include <boost/shared_ptr.hpp>
#include "subtitle_decoder.h"
+using std::list;
+using std::cout;
using boost::shared_ptr;
+using boost::optional;
-SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
- : Decoder (f)
+SubtitleDecoder::SubtitleDecoder ()
{
}
-
-/** Called by subclasses when a subtitle is ready.
+/** Called by subclasses when an image subtitle is ready.
* Image may be 0 to say that there is no current subtitle.
*/
void
-SubtitleDecoder::subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+SubtitleDecoder::image_subtitle (ContentTime from, ContentTime to, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
+{
+ _decoded_image_subtitles.push_back (shared_ptr<ContentImageSubtitle> (new ContentImageSubtitle (from, to, image, rect)));
+}
+
+void
+SubtitleDecoder::text_subtitle (list<dcp::SubtitleString> s)
+{
+ _decoded_text_subtitles.push_back (shared_ptr<ContentTextSubtitle> (new ContentTextSubtitle (s)));
+}
+
+template <class T>
+list<shared_ptr<T> >
+SubtitleDecoder::get (list<shared_ptr<T> > const & subs, ContentTime from, ContentTime to)
+{
+ if (subs.empty() || from < subs.front()->from() || to > (subs.back()->to() + ContentTime::from_seconds (10))) {
+ /* Either we have no decoded data, or what we do have is a long way from what we want: seek */
+ seek (from, true);
+ }
+
+ /* Now enough pass() calls will either:
+ * (a) give us what we want, or
+ * (b) hit the end of the decoder.
+ *
+ * XXX: with subs being sparse, this may need more care...
+ */
+ while (!pass() && (subs.empty() || (subs.front()->from() > from || to < subs.back()->to()))) {}
+
+ /* Now look for what we wanted in the data we have collected */
+ /* XXX: inefficient */
+
+ list<shared_ptr<T> > out;
+ for (typename list<shared_ptr<T> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
+ if ((*i)->from() <= to && (*i)->to() >= from) {
+ out.push_back (*i);
+ }
+ }
+
+ return out;
+}
+
+list<shared_ptr<ContentTextSubtitle> >
+SubtitleDecoder::get_text_subtitles (ContentTime from, ContentTime to)
+{
+ return get<ContentTextSubtitle> (_decoded_text_subtitles, from, to);
+}
+
+list<shared_ptr<ContentImageSubtitle> >
+SubtitleDecoder::get_image_subtitles (ContentTime from, ContentTime to)
+{
+ return get<ContentImageSubtitle> (_decoded_image_subtitles, from, to);
+}
+
+void
+SubtitleDecoder::seek (ContentTime, bool)
{
- Subtitle (image, rect, from, to);
+ _decoded_text_subtitles.clear ();
+ _decoded_image_subtitles.clear ();
}
*/
-#include <boost/signals2.hpp>
+#ifndef DCPOMATIC_SUBTITLE_DECODER_H
+#define DCPOMATIC_SUBTITLE_DECODER_H
+
+#include <dcp/subtitle_string.h>
#include "decoder.h"
#include "rect.h"
#include "types.h"
+#include "content_subtitle.h"
class Film;
-class TimedSubtitle;
+class DCPTimedSubtitle;
class Image;
class SubtitleDecoder : public virtual Decoder
{
public:
- SubtitleDecoder (boost::shared_ptr<const Film>);
+ SubtitleDecoder ();
- boost::signals2::signal<void (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time)> Subtitle;
+ std::list<boost::shared_ptr<ContentImageSubtitle> > get_image_subtitles (ContentTime from, ContentTime to);
+ std::list<boost::shared_ptr<ContentTextSubtitle> > get_text_subtitles (ContentTime from, ContentTime to);
protected:
- void subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
+ void seek (ContentTime, bool);
+
+ void image_subtitle (ContentTime from, ContentTime to, boost::shared_ptr<Image>, dcpomatic::Rect<double>);
+ void text_subtitle (std::list<dcp::SubtitleString>);
+
+ std::list<boost::shared_ptr<ContentImageSubtitle> > _decoded_image_subtitles;
+ std::list<boost::shared_ptr<ContentTextSubtitle> > _decoded_text_subtitles;
+
+private:
+ template <class T>
+ std::list<boost::shared_ptr<T> > get (std::list<boost::shared_ptr<T> > const & subs, ContentTime from, ContentTime to);
};
+
+#endif
using std::stringstream;
using std::fixed;
using std::setprecision;
+using std::cout;
using boost::shared_ptr;
/** @param s Film to use.
_transcoder.reset ();
} catch (...) {
- set_progress (1);
- set_state (FINISHED_ERROR);
- _film->log()->log (N_("Transcode job failed or cancelled"));
_transcoder.reset ();
throw;
}
return s.str ();
}
+/** @return Approximate remaining time in seconds */
int
TranscodeJob::remaining_time () const
{
}
/* Compute approximate proposed length here, as it's only here that we need it */
- OutputVideoFrame const left = _film->time_to_video_frames (_film->length ()) - t->video_frames_out();
- return left / fps;
+ return (_film->length().frames (_film->video_frame_rate ()) - t->video_frames_out()) / fps;
}
#include "job.h"
using std::string;
+using std::cout;
+using std::list;
using boost::shared_ptr;
using boost::weak_ptr;
using boost::dynamic_pointer_cast;
-static void
-video_proxy (weak_ptr<Encoder> encoder, shared_ptr<PlayerVideoFrame> pvf, bool same)
-{
- shared_ptr<Encoder> e = encoder.lock ();
- if (e) {
- e->process_video (pvf, same);
- }
-}
-
-static void
-audio_proxy (weak_ptr<Encoder> encoder, shared_ptr<const AudioBuffers> audio)
-{
- shared_ptr<Encoder> e = encoder.lock ();
- if (e) {
- e->process_audio (audio);
- }
-}
-
/** Construct a transcoder using a Decoder that we create and a supplied Encoder.
* @param f Film that we are transcoding.
* @param e Encoder to use.
*/
Transcoder::Transcoder (shared_ptr<const Film> f, shared_ptr<Job> j)
- : _player (f->make_player ())
+ : _film (f)
+ , _player (f->make_player ())
, _encoder (new Encoder (f, j))
, _finishing (false)
{
- _player->Video.connect (bind (video_proxy, _encoder, _1, _2));
- _player->Audio.connect (bind (audio_proxy, _encoder, _1));
+
}
void
Transcoder::go ()
{
_encoder->process_begin ();
- while (!_player->pass ()) {}
+
+ DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate ());
+ for (DCPTime t; t < _film->length(); t += frame) {
+ list<shared_ptr<PlayerVideoFrame> > v = _player->get_video (t, true);
+ for (list<shared_ptr<PlayerVideoFrame> >::const_iterator i = v.begin(); i != v.end(); ++i) {
+ _encoder->process_video (*i);
+ }
+ _encoder->process_audio (_player->get_audio (t, frame, true));
+ }
_finishing = true;
_encoder->process_end ();
+
+ _player->statistics().dump (_film->log ());
}
float
}
private:
+ boost::shared_ptr<const Film> _film;
boost::shared_ptr<Player> _player;
boost::shared_ptr<Encoder> _encoder;
bool _finishing;
#include <vector>
#include <stdint.h>
#include <boost/shared_ptr.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
+#include "dcpomatic_time.h"
+#include "position.h"
class Content;
class VideoContent;
*/
#define SERVER_LINK_VERSION 2
-typedef int64_t Time;
-#define TIME_MAX INT64_MAX
-#define TIME_HZ ((Time) 96000)
-typedef int64_t OutputAudioFrame;
-typedef int OutputVideoFrame;
typedef std::vector<boost::shared_ptr<Content> > ContentList;
typedef std::vector<boost::shared_ptr<VideoContent> > VideoContentList;
typedef std::vector<boost::shared_ptr<AudioContent> > AudioContentList;
typedef std::vector<boost::shared_ptr<SubtitleContent> > SubtitleContentList;
typedef std::vector<boost::shared_ptr<FFmpegContent> > FFmpegContentList;
-template<class T>
+typedef int64_t VideoFrame;
+typedef int64_t AudioFrame;
+
+/* XXX -> DCPAudio */
struct TimedAudioBuffers
{
TimedAudioBuffers ()
: time (0)
{}
- TimedAudioBuffers (boost::shared_ptr<AudioBuffers> a, T t)
+ TimedAudioBuffers (boost::shared_ptr<AudioBuffers> a, DCPTime t)
: audio (a)
, time (t)
{}
boost::shared_ptr<AudioBuffers> audio;
- T time;
+ DCPTime time;
};
enum VideoFrameType
/** Number of pixels to remove from the bottom */
int bottom;
- libdcp::Size apply (libdcp::Size s, int minimum = 4) const {
+ dcp::Size apply (dcp::Size s, int minimum = 4) const {
s.width -= left + right;
s.height -= top + bottom;
#include <boost/algorithm/string.hpp>
#include <curl/curl.h>
#include <libcxml/cxml.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "update.h"
#include "version.h"
#include "ui_signaller.h"
using std::min;
using std::string;
using std::stringstream;
-using libdcp::raw_convert;
+using dcp::raw_convert;
+/** Singleton instance */
UpdateChecker* UpdateChecker::_instance = 0;
static size_t
return reinterpret_cast<UpdateChecker*>(user)->write_callback (data, size, nmemb);
}
+/** Construct an UpdateChecker. This sets things up and starts a thread to
+ * do the work.
+ */
UpdateChecker::UpdateChecker ()
: _buffer (new char[BUFFER_SIZE])
, _offset (0)
delete[] _buffer;
}
+/** Start running the update check */
void
UpdateChecker::run ()
{
UpdateChecker::thread ()
{
while (1) {
+ /* Block until there is something to do */
boost::mutex::scoped_lock lock (_process_mutex);
while (_to_do == 0) {
_condition.wait (lock);
try {
_offset = 0;
+
+ /* Perform the request */
int r = curl_easy_perform (_curl);
if (r != CURLE_OK) {
set_state (FAILED);
return;
}
+
+ /* Parse the reply */
_buffer[_offset] = '\0';
stringstream s;
*/
+/** @file src/lib/update.h
+ * @brief UpdateChecker class.
+ */
+
#include <boost/signals2.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread.hpp>
#include <curl/curl.h>
+/** Class to check for the existance of an update for DCP-o-matic on a remote server */
class UpdateChecker
{
public:
void run ();
enum State {
- YES,
- FAILED,
- NO,
- NOT_RUN
+ YES, ///< there is an update
+ FAILED, ///< the check failed, so we don't know
+ NO, ///< there is no update
+ NOT_RUN ///< the check has not been run (yet)
};
+ /** @return state of the checker */
State state () {
boost::mutex::scoped_lock lm (_data_mutex);
return _state;
}
+ /** @return the version string of the latest stable version (if _state == YES or NO) */
std::string stable () {
boost::mutex::scoped_lock lm (_data_mutex);
return _stable;
}
+ /** @return the version string of the latest test version (if _state == YES or NO) */
std::string test () {
boost::mutex::scoped_lock lm (_data_mutex);
return _test;
}
- /** @return true if the list signal emission was the first */
+ /** @return true if the last signal emission was the first */
bool last_emit_was_first () const {
boost::mutex::scoped_lock lm (_data_mutex);
return _emits == 1;
#include <glib.h>
#include <openjpeg.h>
#include <openssl/md5.h>
+#include <pangomm/init.h>
#include <magick/MagickCore.h>
#include <magick/version.h>
-#include <libdcp/version.h>
-#include <libdcp/util.h>
-#include <libdcp/signer_chain.h>
-#include <libdcp/signer.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/version.h>
+#include <dcp/util.h>
+#include <dcp/signer_chain.h>
+#include <dcp/signer.h>
+#include <dcp/raw_convert.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include "job.h"
#include "cross.h"
#include "video_content.h"
+#include "rect.h"
#ifdef DCPOMATIC_WINDOWS
#include "stack.hpp"
#endif
using boost::shared_ptr;
using boost::thread;
using boost::optional;
-using libdcp::Size;
-using libdcp::raw_convert;
+using dcp::Size;
+using dcp::raw_convert;
static boost::thread::id ui_thread;
static boost::filesystem::path backtrace_file;
return s.str ();
}
-/** Return a user-readable string summarising the versions of our dependencies */
-string
-dependency_version_summary ()
-{
- stringstream s;
- s << N_("libopenjpeg ") << opj_version () << N_(", ")
- << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
- << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
- << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
- << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
- << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
- << MagickVersion << N_(", ")
- << N_("libssh ") << ssh_version (0) << N_(", ")
- << N_("libdcp ") << libdcp::version << N_(" git ") << libdcp::git_commit;
-
- return s.str ();
-}
-
double
seconds (struct timeval t)
{
set_terminate (terminate);
- libdcp::init ();
+ Pango::init ();
+ dcp::init ();
Ratio::setup_ratios ();
VideoContentScale::setup_scales ();
return s.str ();
}
-static bool
-about_equal (float a, float b)
-{
- /* A film of F seconds at f FPS will be Ff frames;
- Consider some delta FPS d, so if we run the same
- film at (f + d) FPS it will last F(f + d) seconds.
-
- Hence the difference in length over the length of the film will
- be F(f + d) - Ff frames
- = Ff + Fd - Ff frames
- = Fd frames
- = Fd/f seconds
-
- So if we accept a difference of 1 frame, ie 1/f seconds, we can
- say that
-
- 1/f = Fd/f
- ie 1 = Fd
- ie d = 1/F
-
- So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable
- FPS error is 1/F ~= 0.0001 ~= 10-e4
- */
-
- return (fabs (a - b) < 1e-4);
-}
-
/** @param An arbitrary audio frame rate.
* @return The appropriate DCP-approved frame rate (48kHz or 96kHz).
*/
assert (boost::this_thread::get_id() == ui_thread);
}
-/** @param v Content video frame.
- * @param audio_sample_rate Source audio sample rate.
- * @param frames_per_second Number of video frames per second.
- * @return Equivalent number of audio frames for `v'.
- */
-int64_t
-video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
-{
- return ((int64_t) v * audio_sample_rate / frames_per_second);
-}
-
string
audio_channel_name (int c)
{
return channels[c];
}
-FrameRateConversion::FrameRateConversion (float source, int dcp)
- : skip (false)
- , repeat (1)
- , change_speed (false)
-{
- if (fabs (source / 2.0 - dcp) < fabs (source - dcp)) {
- /* The difference between source and DCP frame rate will be lower
- (i.e. better) if we skip.
- */
- skip = true;
- } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) {
- /* The difference between source and DCP frame rate would be better
- if we repeated each frame once; it may be better still if we
- repeated more than once. Work out the required repeat.
- */
- repeat = round (dcp / source);
- }
-
- change_speed = !about_equal (source * factor(), dcp);
-
- if (!skip && repeat == 1 && !change_speed) {
- description = _("Content and DCP have the same rate.\n");
- } else {
- if (skip) {
- description = _("DCP will use every other frame of the content.\n");
- } else if (repeat == 2) {
- description = _("Each content frame will be doubled in the DCP.\n");
- } else if (repeat > 2) {
- description = String::compose (_("Each content frame will be repeated %1 more times in the DCP.\n"), repeat - 1);
- }
-
- if (change_speed) {
- float const pc = dcp * 100 / (source * factor());
- description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc);
- }
- }
-}
-
bool
valid_image_file (boost::filesystem::path f)
{
return t;
}
-shared_ptr<const libdcp::Signer>
+shared_ptr<const dcp::Signer>
make_signer ()
{
boost::filesystem::path const sd = Config::instance()->signer_chain_directory ();
if (!boost::filesystem::exists (p)) {
boost::filesystem::remove_all (sd);
boost::filesystem::create_directories (sd);
- libdcp::make_signer_chain (sd, openssl_path ());
+ dcp::make_signer_chain (sd, openssl_path ());
break;
}
++i;
}
- libdcp::CertificateChain chain;
+ dcp::CertificateChain chain;
{
boost::filesystem::path p (sd);
p /= "ca.self-signed.pem";
- chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
+ chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
}
{
boost::filesystem::path p (sd);
p /= "intermediate.signed.pem";
- chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
+ chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
}
{
boost::filesystem::path p (sd);
p /= "leaf.signed.pem";
- chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
+ chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
}
boost::filesystem::path signer_key (sd);
signer_key /= "leaf.key";
- return shared_ptr<const libdcp::Signer> (new libdcp::Signer (chain, signer_key));
+ return shared_ptr<const dcp::Signer> (new dcp::Signer (chain, signer_key));
}
map<string, string>
return r;
}
-libdcp::Size
-fit_ratio_within (float ratio, libdcp::Size full_frame)
+dcp::Size
+fit_ratio_within (float ratio, dcp::Size full_frame)
{
if (ratio < full_frame.ratio ()) {
- return libdcp::Size (rint (full_frame.height * ratio), full_frame.height);
+ return dcp::Size (rint (full_frame.height * ratio), full_frame.height);
}
- return libdcp::Size (full_frame.width, rint (full_frame.width / ratio));
+ return dcp::Size (full_frame.width, rint (full_frame.width / ratio));
}
void *
}
}
+/** Return a user-readable string summarising the versions of our dependencies */
+string
+dependency_version_summary ()
+{
+ stringstream s;
+ s << N_("libopenjpeg ") << opj_version () << N_(", ")
+ << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
+ << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
+ << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
+ << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
+ << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
+ << MagickVersion << N_(", ")
+ << N_("libssh ") << ssh_version (0) << N_(", ")
+ << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit;
+
+ return s.str ();
+}
+
+/** Construct a ScopedTemporary. A temporary filename is decided but the file is not opened
+ * until ::open() is called.
+ */
ScopedTemporary::ScopedTemporary ()
: _open (0)
{
_file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
}
+/** Close and delete the temporary file */
ScopedTemporary::~ScopedTemporary ()
{
close ();
boost::filesystem::remove (_file, ec);
}
+/** @return temporary filename */
char const *
ScopedTemporary::c_str () const
{
return _file.string().c_str ();
}
+/** Open the temporary file.
+ * @return File's FILE pointer.
+ */
FILE*
ScopedTemporary::open (char const * params)
{
return _open;
}
+/** Close the file */
void
ScopedTemporary::close ()
{
#include <boost/asio.hpp>
#include <boost/optional.hpp>
#include <boost/filesystem.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
+#include <dcp/signer.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
extern boost::filesystem::path mo_path ();
#endif
extern std::string tidy_for_filename (std::string);
-extern boost::shared_ptr<const libdcp::Signer> make_signer ();
-extern libdcp::Size fit_ratio_within (float ratio, libdcp::Size);
+extern boost::shared_ptr<const dcp::Signer> make_signer ();
+extern dcp::Size fit_ratio_within (float ratio, dcp::Size);
extern std::string entities_to_text (std::string e);
extern std::map<std::string, std::string> split_get_request (std::string url);
-
-struct FrameRateConversion
-{
- FrameRateConversion (float, int);
-
- /** @return factor by which to multiply a source frame rate
- to get the effective rate after any skip or repeat has happened.
- */
- float factor () const {
- if (skip) {
- return 0.5;
- }
-
- return repeat;
- }
-
- /** true to skip every other frame */
- bool skip;
- /** number of times to use each frame (e.g. 1 is normal, 2 means repeat each frame once, and so on) */
- int repeat;
- /** true if this DCP will run its video faster or slower than the source
- * without taking into account `repeat' nor `skip'.
- * (e.g. change_speed will be true if
- * source is 29.97fps, DCP is 30fps
- * source is 14.50fps, DCP is 30fps
- * but not if
- * source is 15.00fps, DCP is 30fps
- * source is 12.50fps, DCP is 25fps)
- */
- bool change_speed;
-
- std::string description;
-};
-
extern int dcp_audio_frame_rate (int);
extern int stride_round_up (int, int const *, int);
extern std::multimap<std::string, std::string> read_key_value (std::istream& s);
extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
+/** @class ScopedTemporary
+ * @brief A temporary file which is deleted when the ScopedTemporary object goes out of scope.
+ */
class ScopedTemporary
{
public:
ScopedTemporary ();
~ScopedTemporary ();
+ /** @return temporary filename */
boost::filesystem::path file () const {
return _file;
}
-
+
char const * c_str () const;
FILE* open (char const *);
void close ();
#include <iomanip>
#include <libcxml/cxml.h>
-#include <libdcp/colour_matrix.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/colour_matrix.h>
+#include <dcp/raw_convert.h>
#include "video_content.h"
#include "video_examiner.h"
#include "compose.hpp"
using boost::shared_ptr;
using boost::optional;
using boost::dynamic_pointer_cast;
-using libdcp::raw_convert;
+using dcp::raw_convert;
vector<VideoContentScale> VideoContentScale::_scales;
setup_default_colour_conversion ();
}
-VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len)
+VideoContent::VideoContent (shared_ptr<const Film> f, DCPTime s, ContentTime len)
: Content (f, s)
, _video_length (len)
, _video_frame_rate (0)
setup_default_colour_conversion ();
}
-VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version)
+VideoContent::VideoContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version)
: Content (f, node)
{
- _video_length = node->number_child<VideoContent::Frame> ("VideoLength");
+ _video_length = ContentTime (node->number_child<int64_t> ("VideoLength"));
_video_size.width = node->number_child<int> ("VideoWidth");
_video_size.height = node->number_child<int> ("VideoHeight");
_video_frame_rate = node->number_child<float> ("VideoFrameRate");
VideoContent::as_xml (xmlpp::Node* node) const
{
boost::mutex::scoped_lock lm (_mutex);
- node->add_child("VideoLength")->add_child_text (raw_convert<string> (_video_length));
+ node->add_child("VideoLength")->add_child_text (raw_convert<string> (_video_length.get ()));
node->add_child("VideoWidth")->add_child_text (raw_convert<string> (_video_size.width));
node->add_child("VideoHeight")->add_child_text (raw_convert<string> (_video_size.height));
node->add_child("VideoFrameRate")->add_child_text (raw_convert<string> (_video_frame_rate));
void
VideoContent::setup_default_colour_conversion ()
{
- _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
+ _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
}
void
VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d)
{
/* These examiner calls could call other content methods which take a lock on the mutex */
- libdcp::Size const vs = d->video_size ();
+ dcp::Size const vs = d->video_size ();
float const vfr = d->video_frame_rate ();
{
{
return String::compose (
"video: length %1, size %2x%3, rate %4",
- video_length_after_3d_combine(), video_size().width, video_size().height, video_frame_rate()
+ video_length_after_3d_combine().seconds(),
+ video_size().width,
+ video_size().height,
+ video_frame_rate()
);
}
-libdcp::Size
+dcp::Size
VideoContent::video_size_after_3d_split () const
{
- libdcp::Size const s = video_size ();
+ dcp::Size const s = video_size ();
switch (video_frame_type ()) {
case VIDEO_FRAME_TYPE_2D:
case VIDEO_FRAME_TYPE_3D_ALTERNATE:
case VIDEO_FRAME_TYPE_3D_RIGHT:
return s;
case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
- return libdcp::Size (s.width / 2, s.height);
+ return dcp::Size (s.width / 2, s.height);
case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
- return libdcp::Size (s.width, s.height / 2);
+ return dcp::Size (s.width, s.height / 2);
}
assert (false);
}
/** @return Video size after 3D split and crop */
-libdcp::Size
+dcp::Size
VideoContent::video_size_after_crop () const
{
return crop().apply (video_size_after_3d_split ());
}
/** @param t A time offset from the start of this piece of content.
- * @return Corresponding frame index.
+ * @return Corresponding time with respect to the content.
*/
-VideoContent::Frame
-VideoContent::time_to_content_video_frames (Time t) const
+ContentTime
+VideoContent::dcp_time_to_content_time (DCPTime t) const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
-
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
-
- /* Here we are converting from time (in the DCP) to a frame number in the content.
- Hence we need to use the DCP's frame rate and the double/skip correction, not
- the source's rate.
- */
- return t * film->video_frame_rate() / (frc.factor() * TIME_HZ);
+ return ContentTime (t, FrameRateChange (video_frame_rate(), film->video_frame_rate()));
}
VideoContentScale::VideoContentScale (Ratio const * r)
}
-VideoContentScale::VideoContentScale (shared_ptr<cxml::Node> node)
+VideoContentScale::VideoContentScale (cxml::NodePtr node)
: _ratio (0)
, _scale (true)
{
/** @param display_container Size of the container that we are displaying this content in.
* @param film_container The size of the film's image.
*/
-libdcp::Size
-VideoContentScale::size (shared_ptr<const VideoContent> c, libdcp::Size display_container, libdcp::Size film_container) const
+dcp::Size
+VideoContentScale::size (shared_ptr<const VideoContent> c, dcp::Size display_container, dcp::Size film_container) const
{
if (_ratio) {
return fit_ratio_within (_ratio->ratio (), display_container);
}
- libdcp::Size const ac = c->video_size_after_crop ();
+ dcp::Size const ac = c->video_size_after_crop ();
/* Force scale if the film_container is smaller than the content's image */
if (_scale || film_container.width < ac.width || film_container.height < ac.height) {
/* Scale the image so that it will be in the right place in film_container, even if display_container is a
different size.
*/
- return libdcp::Size (
+ return dcp::Size (
c->video_size().width * float(display_container.width) / film_container.width,
c->video_size().height * float(display_container.height) / film_container.height
);
VideoContentScale ();
VideoContentScale (Ratio const *);
VideoContentScale (bool);
- VideoContentScale (boost::shared_ptr<cxml::Node>);
+ VideoContentScale (cxml::NodePtr);
- libdcp::Size size (boost::shared_ptr<const VideoContent>, libdcp::Size, libdcp::Size) const;
+ dcp::Size size (boost::shared_ptr<const VideoContent>, dcp::Size, dcp::Size) const;
std::string id () const;
std::string name () const;
void as_xml (xmlpp::Node *) const;
typedef int Frame;
VideoContent (boost::shared_ptr<const Film>);
- VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame);
+ VideoContent (boost::shared_ptr<const Film>, DCPTime, ContentTime);
VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path);
- VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
+ VideoContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int);
VideoContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
void as_xml (xmlpp::Node *) const;
virtual std::string information () const;
virtual std::string identifier () const;
- VideoContent::Frame video_length () const {
+ ContentTime video_length () const {
boost::mutex::scoped_lock lm (_mutex);
return _video_length;
}
- VideoContent::Frame video_length_after_3d_combine () const {
+ ContentTime video_length_after_3d_combine () const {
boost::mutex::scoped_lock lm (_mutex);
if (_video_frame_type == VIDEO_FRAME_TYPE_3D_ALTERNATE) {
- return _video_length / 2;
+ return ContentTime (_video_length.get() / 2);
}
return _video_length;
}
- libdcp::Size video_size () const {
+ dcp::Size video_size () const {
boost::mutex::scoped_lock lm (_mutex);
return _video_size;
}
return _colour_conversion;
}
- libdcp::Size video_size_after_3d_split () const;
- libdcp::Size video_size_after_crop () const;
+ dcp::Size video_size_after_3d_split () const;
+ dcp::Size video_size_after_crop () const;
- VideoContent::Frame time_to_content_video_frames (Time) const;
+ ContentTime dcp_time_to_content_time (DCPTime) const;
protected:
void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
- VideoContent::Frame _video_length;
+ ContentTime _video_length;
float _video_frame_rate;
private:
void setup_default_colour_conversion ();
- libdcp::Size _video_size;
+ dcp::Size _video_size;
VideoFrameType _video_frame_type;
Crop _crop;
VideoContentScale _scale;
#include "video_decoder.h"
#include "image.h"
+#include "content_video.h"
#include "i18n.h"
using std::cout;
+using std::list;
using boost::shared_ptr;
+using boost::optional;
-VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c)
- : Decoder (f)
+VideoDecoder::VideoDecoder (shared_ptr<const VideoContent> c)
+#ifdef DCPOMATIC_DEBUG
+ : test_gaps (0)
, _video_content (c)
- , _video_position (0)
+#else
+ : _video_content (c)
+#endif
{
}
+list<ContentVideo>
+VideoDecoder::decoded_video (VideoFrame frame)
+{
+ list<ContentVideo> output;
+
+ for (list<ContentVideo>::const_iterator i = _decoded_video.begin(); i != _decoded_video.end(); ++i) {
+ if (i->frame == frame) {
+ output.push_back (*i);
+ }
+ }
+
+ return output;
+}
+
+/** Get all frames which exist in the content at a given frame index.
+ * @param frame Frame index.
+ * @param accurate true to try hard to return frames at the precise time that was requested, otherwise frames nearby may be returned.
+ * @return Frames; there may be none (if there is no video there), 1 for 2D or 2 for 3D.
+ */
+list<ContentVideo>
+VideoDecoder::get_video (VideoFrame frame, bool accurate)
+{
+ if (_decoded_video.empty() || (frame < _decoded_video.front().frame || frame > (_decoded_video.back().frame + 1))) {
+ /* Either we have no decoded data, or what we do have is a long way from what we want: seek */
+ seek (ContentTime::from_frames (frame, _video_content->video_frame_rate()), accurate);
+ }
+
+ list<ContentVideo> dec;
+
+ /* Now enough pass() calls should either:
+ * (a) give us what we want, or
+ * (b) hit the end of the decoder.
+ */
+ if (accurate) {
+ /* We are being accurate, so we want the right frame.
+ * This could all be one statement but it's split up for clarity.
+ */
+ while (true) {
+ if (!decoded_video(frame).empty ()) {
+ /* We got what we want */
+ break;
+ }
+
+ if (pass ()) {
+ /* The decoder has nothing more for us */
+ break;
+ }
+
+ if (!_decoded_video.empty() && _decoded_video.front().frame > frame) {
+ /* We're never going to get the frame we want. Perhaps the caller is asking
+ * for a video frame before the content's video starts (if its audio
+ * begins before its video, for example).
+ */
+ break;
+ }
+ }
+
+ dec = decoded_video (frame);
+ } else {
+ /* Any frame will do: use the first one that comes out of pass() */
+ while (_decoded_video.empty() && !pass ()) {}
+ if (!_decoded_video.empty ()) {
+ dec.push_back (_decoded_video.front ());
+ }
+ }
+
+ /* Clean up decoded_video */
+ while (!_decoded_video.empty() && _decoded_video.front().frame < (frame - 1)) {
+ _decoded_video.pop_front ();
+ }
+
+ return dec;
+}
+
+
+/** Called by subclasses when they have a video frame ready */
void
-VideoDecoder::video (shared_ptr<const ImageProxy> image, bool same, VideoContent::Frame frame)
+VideoDecoder::video (shared_ptr<const ImageProxy> image, VideoFrame frame)
{
+ /* We should not receive the same thing twice */
+ assert (_decoded_video.empty() || frame != _decoded_video.back().frame);
+
+ /* Fill in gaps */
+ /* XXX: 3D */
+
+ while (!_decoded_video.empty () && (_decoded_video.back().frame + 1) < frame) {
+#ifdef DCPOMATIC_DEBUG
+ test_gaps++;
+#endif
+ _decoded_video.push_back (
+ ContentVideo (
+ _decoded_video.back().image,
+ _decoded_video.back().eyes,
+ _decoded_video.back().part,
+ _decoded_video.back().frame + 1
+ )
+ );
+ }
+
switch (_video_content->video_frame_type ()) {
case VIDEO_FRAME_TYPE_2D:
- Video (image, EYES_BOTH, PART_WHOLE, same, frame);
+ _decoded_video.push_back (ContentVideo (image, EYES_BOTH, PART_WHOLE, frame));
break;
case VIDEO_FRAME_TYPE_3D_ALTERNATE:
- Video (image, (frame % 2) ? EYES_RIGHT : EYES_LEFT, PART_WHOLE, same, frame / 2);
+ _decoded_video.push_back (ContentVideo (image, (frame % 2) ? EYES_RIGHT : EYES_LEFT, PART_WHOLE, frame));
break;
case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
- Video (image, EYES_LEFT, PART_LEFT_HALF, same, frame);
- Video (image, EYES_RIGHT, PART_RIGHT_HALF, same, frame);
+ _decoded_video.push_back (ContentVideo (image, EYES_LEFT, PART_LEFT_HALF, frame));
+ _decoded_video.push_back (ContentVideo (image, EYES_RIGHT, PART_RIGHT_HALF, frame));
break;
case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
- Video (image, EYES_LEFT, PART_TOP_HALF, same, frame);
- Video (image, EYES_RIGHT, PART_BOTTOM_HALF, same, frame);
+ _decoded_video.push_back (ContentVideo (image, EYES_LEFT, PART_TOP_HALF, frame));
+ _decoded_video.push_back (ContentVideo (image, EYES_RIGHT, PART_BOTTOM_HALF, frame));
break;
case VIDEO_FRAME_TYPE_3D_LEFT:
- Video (image, EYES_LEFT, PART_WHOLE, same, frame);
+ _decoded_video.push_back (ContentVideo (image, EYES_LEFT, PART_WHOLE, frame));
break;
case VIDEO_FRAME_TYPE_3D_RIGHT:
- Video (image, EYES_RIGHT, PART_WHOLE, same, frame);
+ _decoded_video.push_back (ContentVideo (image, EYES_RIGHT, PART_WHOLE, frame));
break;
+ default:
+ assert (false);
}
-
- _video_position = frame + 1;
+}
+
+void
+VideoDecoder::seek (ContentTime, bool)
+{
+ _decoded_video.clear ();
}
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/lib/video_decoder.h
+ * @brief VideoDecoder class.
+ */
+
#ifndef DCPOMATIC_VIDEO_DECODER_H
#define DCPOMATIC_VIDEO_DECODER_H
#include "decoder.h"
#include "video_content.h"
#include "util.h"
+#include "content_video.h"
class VideoContent;
class ImageProxy;
+/** @class VideoDecoder
+ * @brief Parent for classes which decode video.
+ */
class VideoDecoder : public virtual Decoder
{
public:
- VideoDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const VideoContent>);
-
- /** Seek so that the next pass() will yield (approximately) the requested frame.
- * Pass accurate = true to try harder to get close to the request.
- */
- virtual void seek (VideoContent::Frame frame, bool accurate) = 0;
-
- /** Emitted when a video frame is ready.
- * First parameter is the video image.
- * Second parameter is the eye(s) which should see this image.
- * Third parameter is the part of this image that should be used.
- * Fourth parameter is true if the image is the same as the last one that was emitted for this Eyes value.
- * Fourth parameter is the frame within our source.
- */
- boost::signals2::signal<void (boost::shared_ptr<const ImageProxy>, Eyes, Part, bool, VideoContent::Frame)> Video;
-
+ VideoDecoder (boost::shared_ptr<const VideoContent> c);
+
+ std::list<ContentVideo> get_video (VideoFrame frame, bool accurate);
+
+ boost::shared_ptr<const VideoContent> video_content () const {
+ return _video_content;
+ }
+
+#ifdef DCPOMATIC_DEBUG
+ int test_gaps;
+#endif
+
protected:
- void video (boost::shared_ptr<const ImageProxy>, bool, VideoContent::Frame);
+ void seek (ContentTime time, bool accurate);
+ void video (boost::shared_ptr<const ImageProxy>, VideoFrame frame);
+ std::list<ContentVideo> decoded_video (VideoFrame frame);
+
boost::shared_ptr<const VideoContent> _video_content;
- /** This is in frames without taking 3D into account (e.g. if we are doing 3D alternate,
- * this would equal 2 on the left-eye second frame (not 1)).
- */
- VideoContent::Frame _video_position;
+ std::list<ContentVideo> _decoded_video;
};
#endif
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
-#include <libdcp/types.h>
+/** @file src/lib/video_examiner.h
+ * @brief VideoExaminer class.
+ */
+
+#include <dcp/types.h>
#include "types.h"
#include "video_content.h"
+/** @class VideoExaminer
+ * @brief Parent for classes which examine video sources and obtain information about them.
+ */
class VideoExaminer
{
public:
virtual ~VideoExaminer () {}
virtual float video_frame_rate () const = 0;
- virtual libdcp::Size video_size () const = 0;
- virtual VideoContent::Frame video_length () const = 0;
+ virtual dcp::Size video_size () const = 0;
+ virtual ContentTime video_length () const = 0;
};
#include <fstream>
#include <cerrno>
-#include <libdcp/mono_picture_asset.h>
-#include <libdcp/stereo_picture_asset.h>
-#include <libdcp/sound_asset.h>
-#include <libdcp/reel.h>
-#include <libdcp/dcp.h>
-#include <libdcp/cpl.h>
+#include <dcp/mono_picture_mxf.h>
+#include <dcp/stereo_picture_mxf.h>
+#include <dcp/sound_mxf.h>
+#include <dcp/sound_mxf_writer.h>
+#include <dcp/reel.h>
+#include <dcp/reel_mono_picture_asset.h>
+#include <dcp/reel_stereo_picture_asset.h>
+#include <dcp/reel_sound_asset.h>
+#include <dcp/dcp.h>
+#include <dcp/cpl.h>
#include "writer.h"
#include "compose.hpp"
#include "film.h"
#include "config.h"
#include "job.h"
#include "cross.h"
+#include "audio_buffers.h"
#include "i18n.h"
using std::stringstream;
using boost::shared_ptr;
using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
int const Writer::_maximum_frames_in_memory = Config::instance()->num_local_encoding_threads() + 4;
, _last_written_eyes (EYES_RIGHT)
, _full_written (0)
, _fake_written (0)
- , _repeat_written (0)
, _pushed_to_disk (0)
{
/* Remove any old DCP */
*/
if (_film->three_d ()) {
- _picture_asset.reset (new libdcp::StereoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
+ _picture_mxf.reset (new dcp::StereoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
} else {
- _picture_asset.reset (new libdcp::MonoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
+ _picture_mxf.reset (new dcp::MonoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
}
- _picture_asset->set_edit_rate (_film->video_frame_rate ());
- _picture_asset->set_size (_film->frame_size ());
- _picture_asset->set_interop (_film->interop ());
+ _picture_mxf->set_size (_film->frame_size ());
if (_film->encrypted ()) {
- _picture_asset->set_key (_film->key ());
+ _picture_mxf->set_key (_film->key ());
}
- _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
+ _picture_mxf_writer = _picture_mxf->start_write (
+ _film->internal_video_mxf_dir() / _film->internal_video_mxf_filename(),
+ _film->interop() ? dcp::INTEROP : dcp::SMPTE,
+ _first_nonexistant_frame > 0
+ );
if (_film->audio_channels ()) {
- _sound_asset.reset (new libdcp::SoundAsset (_film->directory (), _film->audio_mxf_filename ()));
- _sound_asset->set_edit_rate (_film->video_frame_rate ());
- _sound_asset->set_channels (_film->audio_channels ());
- _sound_asset->set_sampling_rate (_film->audio_frame_rate ());
- _sound_asset->set_interop (_film->interop ());
+ _sound_mxf.reset (new dcp::SoundMXF (dcp::Fraction (_film->video_frame_rate(), 1), _film->audio_frame_rate (), _film->audio_channels ()));
if (_film->encrypted ()) {
- _sound_asset->set_key (_film->key ());
+ _sound_mxf->set_key (_film->key ());
}
-
- /* Write the sound asset into the film directory so that we leave the creation
+
+ /* Write the sound MXF into the film directory so that we leave the creation
of the DCP directory until the last minute.
*/
- _sound_asset_writer = _sound_asset->start_write ();
+ _sound_mxf_writer = _sound_mxf->start_write (_film->directory() / _film->audio_mxf_filename(), _film->interop() ? dcp::INTEROP : dcp::SMPTE);
}
_thread = new boost::thread (boost::bind (&Writer::thread, this));
}
FILE* ifi = fopen_boost (_film->info_path (frame, eyes), "r");
- libdcp::FrameInfo info (ifi);
+ dcp::FrameInfo info (ifi);
fclose (ifi);
QueueItem qi;
void
Writer::write (shared_ptr<const AudioBuffers> audio)
{
- if (_sound_asset) {
- _sound_asset_writer->write (audio->data(), audio->frames());
+ if (_sound_mxf_writer) {
+ _sound_mxf_writer->write (audio->data(), audio->frames());
}
}
qi.encoded.reset (new EncodedData (_film->j2c_path (qi.frame, qi.eyes, false)));
}
- libdcp::FrameInfo fin = _picture_asset_writer->write (qi.encoded->data(), qi.encoded->size());
+ dcp::FrameInfo fin = _picture_mxf_writer->write (qi.encoded->data(), qi.encoded->size());
qi.encoded->write_info (_film, qi.frame, qi.eyes, fin);
_last_written[qi.eyes] = qi.encoded;
++_full_written;
}
case QueueItem::FAKE:
_film->log()->log (String::compose (N_("Writer FAKE-writes %1 to MXF"), qi.frame));
- _picture_asset_writer->fake_write (qi.size);
+ _picture_mxf_writer->fake_write (qi.size);
_last_written[qi.eyes].reset ();
++_fake_written;
break;
- case QueueItem::REPEAT:
- {
- _film->log()->log (String::compose (N_("Writer REPEAT-writes %1 to MXF"), qi.frame));
- libdcp::FrameInfo fin = _picture_asset_writer->write (
- _last_written[qi.eyes]->data(),
- _last_written[qi.eyes]->size()
- );
-
- _last_written[qi.eyes]->write_info (_film, qi.frame, qi.eyes, fin);
- ++_repeat_written;
- break;
- }
}
lock.lock ();
_last_written_frame = qi.frame;
_last_written_eyes = qi.eyes;
- if (_film->length()) {
- shared_ptr<Job> job = _job.lock ();
- assert (job);
- int total = _film->time_to_video_frames (_film->length ());
- if (_film->three_d ()) {
- /* _full_written and so on are incremented for each eye, so we need to double the total
- frames to get the correct progress.
- */
- total *= 2;
- }
- job->set_progress (float (_full_written + _fake_written + _repeat_written) / total);
+ shared_ptr<Job> job = _job.lock ();
+ assert (job);
+ int64_t total = _film->length().frames (_film->video_frame_rate ());
+ if (_film->three_d ()) {
+ /* _full_written and so on are incremented for each eye, so we need to double the total
+ frames to get the correct progress.
+ */
+ total *= 2;
+ }
+ if (total) {
+ job->set_progress (float (_full_written + _fake_written) / total);
}
}
terminate_thread (true);
- _picture_asset_writer->finalize ();
- if (_sound_asset_writer) {
- _sound_asset_writer->finalize ();
+ _picture_mxf_writer->finalize ();
+ if (_sound_mxf_writer) {
+ _sound_mxf_writer->finalize ();
}
- int const frames = _last_written_frame + 1;
-
- _picture_asset->set_duration (frames);
-
/* Hard-link the video MXF into the DCP */
boost::filesystem::path video_from;
video_from /= _film->internal_video_mxf_dir();
_film->log()->log ("Hard-link failed; fell back to copying");
}
- /* And update the asset */
-
- _picture_asset->set_directory (_film->dir (_film->dcp_name ()));
- _picture_asset->set_file_name (_film->video_mxf_filename ());
+ _picture_mxf->set_file (video_to);
/* Move the audio MXF into the DCP */
- if (_sound_asset) {
+ if (_sound_mxf) {
boost::filesystem::path audio_to;
audio_to /= _film->dir (_film->dcp_name ());
audio_to /= _film->audio_mxf_filename ();
String::compose (_("could not move audio MXF into the DCP (%1)"), ec.value ()), _film->file (_film->audio_mxf_filename ())
);
}
-
- _sound_asset->set_directory (_film->dir (_film->dcp_name ()));
- _sound_asset->set_duration (frames);
+
+ _sound_mxf->set_file (audio_to);
}
-
- libdcp::DCP dcp (_film->dir (_film->dcp_name()));
- shared_ptr<libdcp::CPL> cpl (
- new libdcp::CPL (
- _film->dir (_film->dcp_name()),
+ dcp::DCP dcp (_film->dir (_film->dcp_name()));
+
+ shared_ptr<dcp::CPL> cpl (
+ new dcp::CPL (
_film->dcp_name(),
- _film->dcp_content_type()->libdcp_kind (),
- frames,
- _film->video_frame_rate ()
+ _film->dcp_content_type()->libdcp_kind ()
)
);
- dcp.add_cpl (cpl);
+ dcp.add (cpl);
+
+ shared_ptr<dcp::Reel> reel (new dcp::Reel ());
- cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (
- _picture_asset,
- _sound_asset,
- shared_ptr<libdcp::SubtitleAsset> ()
- )
- ));
+ shared_ptr<dcp::MonoPictureMXF> mono = dynamic_pointer_cast<dcp::MonoPictureMXF> (_picture_mxf);
+ if (mono) {
+ reel->add (shared_ptr<dcp::ReelPictureAsset> (new dcp::ReelMonoPictureAsset (mono, 0)));
+ dcp.add (mono);
+ }
+
+ shared_ptr<dcp::StereoPictureMXF> stereo = dynamic_pointer_cast<dcp::StereoPictureMXF> (_picture_mxf);
+ if (stereo) {
+ reel->add (shared_ptr<dcp::ReelPictureAsset> (new dcp::ReelStereoPictureAsset (stereo, 0)));
+ dcp.add (stereo);
+ }
+
+ if (_sound_mxf) {
+ reel->add (shared_ptr<dcp::ReelSoundAsset> (new dcp::ReelSoundAsset (_sound_mxf, 0)));
+ dcp.add (_sound_mxf);
+ }
+
+ cpl->add (reel);
shared_ptr<Job> job = _job.lock ();
assert (job);
job->sub (_("Computing image digest"));
- _picture_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false));
+ _picture_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
- if (_sound_asset) {
+ if (_sound_mxf) {
job->sub (_("Computing audio digest"));
- _sound_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false));
+ _sound_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
}
- libdcp::XMLMetadata meta = Config::instance()->dcp_metadata ();
+ dcp::XMLMetadata meta = Config::instance()->dcp_metadata ();
meta.set_issue_date_now ();
- dcp.write_xml (_film->interop (), meta, _film->is_signed() ? make_signer () : shared_ptr<const libdcp::Signer> ());
+
+ dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, _film->is_signed() ? make_signer () : shared_ptr<const dcp::Signer> ());
_film->log()->log (
- String::compose (N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk)
+ String::compose (N_("Wrote %1 FULL, %2 FAKE, %3 pushed to disk"), _full_written, _fake_written, _pushed_to_disk)
);
}
-/** Tell the writer that frame `f' should be a repeat of the frame before it */
-void
-Writer::repeat (int f, Eyes e)
-{
- boost::mutex::scoped_lock lock (_mutex);
-
- while (_queued_full_in_memory > _maximum_frames_in_memory) {
- _full_condition.wait (lock);
- }
-
- QueueItem qi;
- qi.type = QueueItem::REPEAT;
- qi.frame = f;
- if (_film->three_d() && e == EYES_BOTH) {
- qi.eyes = EYES_LEFT;
- _queue.push_back (qi);
- qi.eyes = EYES_RIGHT;
- _queue.push_back (qi);
- } else {
- qi.eyes = e;
- _queue.push_back (qi);
- }
-
- _empty_condition.notify_all ();
-}
-
bool
Writer::check_existing_picture_mxf_frame (FILE* mxf, int f, Eyes eyes)
{
return false;
}
- libdcp::FrameInfo info (ifi);
+ dcp::FrameInfo info (ifi);
fclose (ifi);
if (info.size == 0) {
_film->log()->log (String::compose ("Existing frame %1 has no info file", f));
*/
+/** @file src/lib/writer.h
+ * @brief Writer class.
+ */
+
#include <list>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
class AudioBuffers;
class Job;
-namespace libdcp {
- class MonoPictureAsset;
- class MonoPictureAssetWriter;
- class StereoPictureAsset;
- class StereoPictureAssetWriter;
- class PictureAsset;
- class PictureAssetWriter;
- class SoundAsset;
- class SoundAssetWriter;
+namespace dcp {
+ class MonoPictureMXF;
+ class MonoPictureMXFWriter;
+ class StereoPictureMXF;
+ class StereoPictureMXFWriter;
+ class PictureMXF;
+ class PictureMXFWriter;
+ class SoundMXF;
+ class SoundMXFWriter;
}
struct QueueItem
state but we use the data that is already on disk.
*/
FAKE,
- /** this is a repeat of the last frame to be written */
- REPEAT
} type;
/** encoded data for FULL */
bool operator< (QueueItem const & a, QueueItem const & b);
bool operator== (QueueItem const & a, QueueItem const & b);
+/** @class Writer
+ * @brief Class to manage writing JPEG2000 and audio data to MXFs on disk.
+ *
+ * This class creates sound and picture MXFs, then takes EncodedData
+ * or AudioBuffers objects (containing image or sound data respectively)
+ * and writes them to the MXFs.
+ *
+ * ::write() for EncodedData can be called out of order, and the Writer
+ * will sort it out. write() for AudioBuffers must be called in order.
+ */
+
class Writer : public ExceptionStore, public boost::noncopyable
{
public:
int _full_written;
/** number of FAKE written frames */
int _fake_written;
- /** number of REPEAT written frames */
- int _repeat_written;
/** number of frames pushed to disk and then recovered
due to the limit of frames to be held in memory.
*/
int _pushed_to_disk;
- boost::shared_ptr<libdcp::PictureAsset> _picture_asset;
- boost::shared_ptr<libdcp::PictureAssetWriter> _picture_asset_writer;
- boost::shared_ptr<libdcp::SoundAsset> _sound_asset;
- boost::shared_ptr<libdcp::SoundAssetWriter> _sound_asset_writer;
+ boost::shared_ptr<dcp::PictureMXF> _picture_mxf;
+ boost::shared_ptr<dcp::PictureMXFWriter> _picture_mxf_writer;
+ boost::shared_ptr<dcp::SoundMXF> _sound_mxf;
+ boost::shared_ptr<dcp::SoundMXFWriter> _sound_mxf_writer;
};
config.cc
content.cc
content_factory.cc
+ content_subtitle.cc
cross.cc
dci_metadata.cc
dcp_content_type.cc
dcp_video_frame.cc
- decoder.cc
+ dcpomatic_time.cc
dolby_cp750.cc
encoder.cc
examine_content_job.cc
ffmpeg_examiner.cc
film.cc
filter.cc
+ frame_rate_change.cc
internet.cc
image.cc
image_content.cc
kdm.cc
json_server.cc
log.cc
- piece.cc
player.cc
player_video_frame.cc
playlist.cc
ratio.cc
+ render_subtitles.cc
resampler.cc
scp_dcp_job.cc
scaler.cc
sndfile_content.cc
sndfile_decoder.cc
sound_processor.cc
- subtitle.cc
+ subrip.cc
+ subrip_content.cc
+ subrip_decoder.cc
subtitle_content.cc
subtitle_decoder.cc
timer.cc
AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE
BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2
SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++
- CURL ZIP QUICKMAIL
+ CURL ZIP QUICKMAIL PANGOMM CAIROMM
"""
if bld.env.TARGET_OSX:
{ wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
};
+/** @class App
+ * @brief The magic App class for wxWidgets.
+ */
class App : public wxApp
{
bool OnInit ()
#include <iostream>
#include <iomanip>
#include <getopt.h>
-#include <libdcp/version.h>
+#include <dcp/version.h>
#include "lib/film.h"
#include "lib/filter.h"
#include "lib/transcode_job.h"
for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (*i);
if (ic) {
- ic->set_video_length (still_length * 24);
+ ic->set_video_length (ContentTime::from_seconds (still_length));
}
}
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
#include <getopt.h>
-#include <libdcp/certificates.h>
+#include <dcp/certificates.h>
#include "lib/film.h"
#include "lib/cinema.h"
#include "lib/kdm.h"
cerr << "Syntax: " << program_name << " [OPTION] [<FILM>]\n"
" -h, --help show this help\n"
" -o, --output output file or directory\n"
- " -f, --valid-from valid from time (e.g. \"2013-09-28 01:41:51\") or \"now\"\n"
- " -t, --valid-to valid to time (e.g. \"2014-09-28 01:41:51\")\n"
+ " -f, --valid-from valid from time (in local time zone) (e.g. \"2013-09-28 01:41:51\") or \"now\"\n"
+ " -t, --valid-to valid to time (in local time zone) (e.g. \"2014-09-28 01:41:51\")\n"
" -d, --valid-duration valid duration (e.g. \"1 day\", \"4 hours\", \"2 weeks\")\n"
" -z, --zip ZIP each cinema's KDMs into its own file\n"
" -v, --verbose be verbose\n"
error ("you must specify --output");
}
- shared_ptr<libdcp::Certificate> certificate (new libdcp::Certificate (boost::filesystem::path (certificate_file)));
- libdcp::KDM kdm = film->make_kdm (certificate, dcp, valid_from.get(), valid_to.get());
+ shared_ptr<dcp::Certificate> certificate (new dcp::Certificate (boost::filesystem::path (certificate_file)));
+ dcp::EncryptedKDM kdm = film->make_kdm (certificate, dcp, valid_from.get(), valid_to.get());
kdm.as_xml (output);
if (verbose) {
cout << "Generated KDM " << output << " for certificate.\n";
try {
if (zip) {
- write_kdm_zip_files (film, (*i)->screens(), dcp, valid_from.get(), valid_to.get(), output);
+ write_kdm_zip_files (film, (*i)->screens(), dcp, dcp::LocalTime (valid_from.get()), dcp::LocalTime (valid_to.get()), output);
if (verbose) {
cout << "Wrote ZIP files to " << output << "\n";
}
} else {
- write_kdm_files (film, (*i)->screens(), dcp, valid_from.get(), valid_to.get(), output);
+ write_kdm_files (film, (*i)->screens(), dcp, dcp::LocalTime (valid_from.get()), dcp::LocalTime (valid_to.get()), output);
if (verbose) {
cout << "Wrote KDM files to " << output << "\n";
}
static shared_ptr<Film> film;
static ServerDescription* server;
static shared_ptr<FileLog> log_ (new FileLog ("servomatictest.log"));
-static int frame = 0;
+static int frame_count = 0;
void
process_video (shared_ptr<PlayerVideoFrame> pvf)
{
- shared_ptr<DCPVideoFrame> local (new DCPVideoFrame (pvf, frame, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_));
- shared_ptr<DCPVideoFrame> remote (new DCPVideoFrame (pvf, frame, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_));
+ shared_ptr<DCPVideoFrame> local (new DCPVideoFrame (pvf, frame_count, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_));
+ shared_ptr<DCPVideoFrame> remote (new DCPVideoFrame (pvf, frame_count, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_));
- cout << "Frame " << frame << ": ";
+ cout << "Frame " << frame_count << ": ";
cout.flush ();
- ++frame;
+ ++frame_count;
shared_ptr<EncodedData> local_encoded = local->encode_locally ();
shared_ptr<EncodedData> remote_encoded;
film->read_metadata ();
shared_ptr<Player> player = film->make_player ();
- player->disable_audio ();
- player->Video.connect (boost::bind (process_video, _1));
- bool done = false;
- while (!done) {
- done = player->pass ();
+ DCPTime const frame = DCPTime::from_frames (1, film->video_frame_rate ());
+ for (DCPTime t; t < film->length(); t += frame) {
+ process_video (player->get_video(t, true).front ());
}
} catch (std::exception& e) {
cerr << "Error: " << e.what() << "\n";
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/wx/about_dialog.cc
+ * @brief The "about DCP-o-matic" dialogue box.
+ */
+
#include <wx/notebook.h>
#include <wx/hyperlink.h>
#include "lib/version.h"
SetSizerAndFit (overall_sizer);
}
+/** Add a section of credits.
+ * @param name Name of section.
+ * @param credits List of names.
+ */
void
AboutDialog::add_section (wxString name, wxArrayString credits)
{
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/wx/about_dialog.h
+ * @brief The "about DCP-o-matic" dialogue box.
+ */
+
#include <wx/wx.h>
class wxNotebook;
+/** @class AboutDialog
+ * @brief The "about DCP-o-matic" dialogue box.
+ */
class AboutDialog : public wxDialog
{
public:
private:
void add_section (wxString, wxArrayString);
- wxNotebook* _notebook;
+ wxNotebook* _notebook; ///< notebook used to keep each list of names for the credits
};
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/wx/audio_mapping_view.cc
+ * @brief AudioMappingView class and helpers.
+ */
+
#include <wx/wx.h>
#include <wx/renderer.h>
#include <wx/grid.h>
-#include <libdcp/types.h>
+#include <dcp/types.h>
#include "lib/audio_mapping.h"
#include "lib/util.h"
#include "audio_mapping_view.h"
#include "wx_util.h"
#include "audio_gain_dialog.h"
+#include <boost/lexical_cast.hpp>
using std::cout;
using std::list;
}
};
+/** @class ValueRenderer
+ * @brief wxGridCellRenderer for a gain value.
+ */
class ValueRenderer : public wxGridCellRenderer
{
public:
return;
}
- libdcp::Channel d = static_cast<libdcp::Channel> (ev.GetCol() - 1);
+ dcp::Channel d = static_cast<dcp::Channel> (ev.GetCol() - 1);
if (_map.get (ev.GetRow(), d) > 0) {
_map.set (ev.GetRow(), d, 0);
void
AudioMappingView::off ()
{
- _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), 0);
+ _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 0);
map_changed ();
}
void
AudioMappingView::full ()
{
- _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), 1);
+ _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 1);
map_changed ();
}
void
AudioMappingView::minus3dB ()
{
- _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), 1 / sqrt (2));
+ _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 1 / sqrt (2));
map_changed ();
}
void
AudioMappingView::edit ()
{
- libdcp::Channel d = static_cast<libdcp::Channel> (_menu_column - 1);
+ dcp::Channel d = static_cast<dcp::Channel> (_menu_column - 1);
AudioGainDialog* dialog = new AudioGainDialog (this, _menu_row, _menu_column - 1, _map.get (_menu_row, d));
if (dialog->ShowModal () == wxID_OK) {
_grid->SetCellValue (i, 0, wxString::Format (wxT("%d"), i + 1));
for (int j = 1; j < _grid->GetNumberCols(); ++j) {
- _grid->SetCellValue (i, j, std_to_wx (lexical_cast<string> (_map.get (i, static_cast<libdcp::Channel> (j - 1)))));
+ _grid->SetCellValue (i, j, std_to_wx (lexical_cast<string> (_map.get (i, static_cast<dcp::Channel> (j - 1)))));
}
}
if (row != _last_tooltip_row || column != _last_tooltip_column) {
wxString s;
- float const gain = _map.get (row, static_cast<libdcp::Channel> (column - 1));
+ float const gain = _map.get (row, static_cast<dcp::Channel> (column - 1));
if (gain == 0) {
s = wxString::Format (_("No audio will be passed from content channel %d to DCP channel %d."), row + 1, column);
} else if (gain == 1) {
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/wx/audio_mapping_view.h
+ * @brief AudioMappingView class
+ *
+ * This class displays the mapping of one set of audio channels to another,
+ * with gain values on each node of the map.
+ */
+
#include <boost/signals2.hpp>
#include <wx/wx.h>
#include <wx/grid.h>
} else {
s << fcs->audio_channels() << wxT (" ") << _("channels");
}
- s << wxT (", ") << fcs->content_audio_frame_rate() << _("Hz");
+ s << wxT (", ") << fcs->audio_frame_rate() << _("Hz");
_description->SetLabel (s);
}
}
#include <wx/preferences.h>
#include <wx/filepicker.h>
#include <wx/spinctrl.h>
-#include <libdcp/colour_matrix.h>
+#include <dcp/colour_matrix.h>
#include "lib/config.h"
#include "lib/ratio.h"
#include "lib/scaler.h"
void issuer_changed ()
{
- libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
+ dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
m.issuer = wx_to_std (_issuer->GetValue ());
Config::instance()->set_dcp_metadata (m);
}
void creator_changed ()
{
- libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
+ dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
m.creator = wx_to_std (_creator->GetValue ());
Config::instance()->set_dcp_metadata (m);
}
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/wx/content_widget.h
+ * @brief ContentWidget class.
+ */
+
#ifndef DCPOMATIC_MULTIPLE_WIDGET_H
#define DCPOMATIC_MULTIPLE_WIDGET_H
#include <boost/function.hpp>
#include "wx_util.h"
-/** A widget which represents some Content state and which can be used
+/** @class ContentWidget
+ * @brief A widget which represents some Content state and which can be used
* when multiple pieces of content are selected.
*
* @param S Type containing the content being represented (e.g. VideoContent)
_video_panel->Enable (video_selection.size() > 0 && _generally_sensitive);
_audio_panel->Enable (audio_selection.size() > 0 && _generally_sensitive);
- _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<FFmpegContent> (selection.front()) && _generally_sensitive);
+ _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<SubtitleContent> (selection.front()) && _generally_sensitive);
_timing_panel->Enable (selection.size() == 1 && _generally_sensitive);
}
#include "lib/player_video_frame.h"
#include "lib/video_content.h"
#include "lib/video_decoder.h"
+#include "lib/timer.h"
#include "film_viewer.h"
#include "wx_util.h"
using boost::shared_ptr;
using boost::dynamic_pointer_cast;
using boost::weak_ptr;
-using libdcp::Size;
+using dcp::Size;
FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
: wxPanel (p)
, _frame_number (new wxStaticText (this, wxID_ANY, wxT("")))
, _timecode (new wxStaticText (this, wxID_ANY, wxT("")))
, _play_button (new wxToggleButton (this, wxID_ANY, _("Play")))
- , _got_frame (false)
{
#ifndef __WXOSX__
_panel->SetDoubleBuffered (true);
_frame.reset ();
_slider->SetValue (0);
- set_position_text (0);
+ set_position_text ();
if (!_film) {
return;
return;
}
- _player->disable_audio ();
- _player->Video.connect (boost::bind (&FilmViewer::process_video, this, _1, _3));
+ _player->set_approximate_size ();
_player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1));
calculate_sizes ();
- fetch_next_frame ();
+ get (_position, true);
}
void
-FilmViewer::fetch_current_frame_again ()
+FilmViewer::get (DCPTime p, bool accurate)
{
if (!_player) {
return;
}
- /* We could do this with a seek and a fetch_next_frame, but this is
- a shortcut to make it quicker.
- */
-
- _got_frame = false;
- if (!_player->repeat_last_video ()) {
- fetch_next_frame ();
+ list<shared_ptr<PlayerVideoFrame> > pvf = _player->get_video (p, accurate);
+ if (!pvf.empty ()) {
+ _frame = pvf.front()->image ();
+ _frame = _frame->scale (_frame->size(), Scaler::from_id ("fastbilinear"), PIX_FMT_RGB24, false);
+ } else {
+ _frame.reset ();
}
+
+ _position = p;
+ set_position_text ();
_panel->Refresh ();
_panel->Update ();
}
void
FilmViewer::timer ()
{
- if (!_player) {
- return;
- }
-
- fetch_next_frame ();
+ get (_position + DCPTime::from_frames (1, _film->video_frame_rate ()), true);
- Time const len = _film->length ();
+ DCPTime const len = _film->length ();
- if (len) {
- int const new_slider_position = 4096 * _player->video_position() / len;
+ if (len.get ()) {
+ int const new_slider_position = 4096 * _position.get() / len.get();
if (new_slider_position != _slider->GetValue()) {
_slider->SetValue (new_slider_position);
}
void
FilmViewer::slider_moved ()
{
- if (_film && _player) {
- Time t = _slider->GetValue() * _film->length() / 4096;
- /* Ensure that we hit the end of the film at the end of the slider */
- if (t >= _film->length ()) {
- t = _film->length() - _film->video_frames_to_time (1);
- }
- _player->seek (t, false);
- fetch_next_frame ();
+ if (!_film) {
+ return;
+ }
+
+ DCPTime t (_slider->GetValue() * _film->length().get() / 4096);
+ /* Ensure that we hit the end of the film at the end of the slider */
+ if (t >= _film->length ()) {
+ t = _film->length() - DCPTime::from_frames (1, _film->video_frame_rate ());
}
+ get (t, false);
}
void
_panel_size.width = ev.GetSize().GetWidth();
_panel_size.height = ev.GetSize().GetHeight();
calculate_sizes ();
- fetch_current_frame_again ();
+ get (_position, true);
}
void
_out_size.width = max (64, _out_size.width);
_out_size.height = max (64, _out_size.height);
+ /* The player will round its image down to the nearest 4 pixels
+ to speed up its scale, so do similar here to avoid black borders
+ around things. This is a bit of a hack.
+ */
+ _out_size.width &= ~3;
+ _out_size.height &= ~3;
+
_player->set_video_container_size (_out_size);
}
}
void
-FilmViewer::process_video (shared_ptr<PlayerVideoFrame> pvf, Time t)
-{
- if (pvf->eyes() == EYES_RIGHT) {
- return;
- }
-
- _frame = pvf->image ();
- _got_frame = true;
-
- set_position_text (t);
-}
-
-void
-FilmViewer::set_position_text (Time t)
+FilmViewer::set_position_text ()
{
if (!_film) {
_frame_number->SetLabel ("0");
double const fps = _film->video_frame_rate ();
/* Count frame number from 1 ... not sure if this is the best idea */
- _frame_number->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps / TIME_HZ)) + 1));
+ _frame_number->SetLabel (wxString::Format (wxT("%d"), int (rint (_position.seconds() * fps)) + 1));
- double w = static_cast<double>(t) / TIME_HZ;
+ double w = _position.seconds ();
int const h = (w / 3600);
w -= h * 3600;
int const m = (w / 60);
_timecode->SetLabel (wxString::Format (wxT("%02d:%02d:%02d.%02d"), h, m, s, f));
}
-/** Ask the player to emit its next frame, then update our display */
-void
-FilmViewer::fetch_next_frame ()
-{
- /* Clear our frame in case we don't get a new one */
- _frame.reset ();
-
- if (!_player) {
- return;
- }
-
- _got_frame = false;
-
- try {
- while (!_got_frame && !_player->pass ()) {}
- } catch (DecodeError& e) {
- _play_button->SetValue (false);
- check_play_state ();
- error_dialog (this, wxString::Format (_("Could not decode video for view (%s)"), std_to_wx(e.what()).data()));
- } catch (OpenFileError& e) {
- /* There was a problem opening a content file; we'll let this slide as it
- probably means a missing content file, which we're already taking care of.
- */
- }
-
- _panel->Refresh ();
- _panel->Update ();
-}
-
void
FilmViewer::active_jobs_changed (bool a)
{
void
FilmViewer::back_clicked ()
{
- if (!_player) {
- return;
+ DCPTime p = _position - DCPTime::from_frames (1, _film->video_frame_rate ());
+ if (p < DCPTime ()) {
+ p = DCPTime ();
}
- /* Player::video_position is the time after the last frame that we received.
- We want to see the one before it, so we need to go back 2.
- */
-
- Time p = _player->video_position() - _film->video_frames_to_time (2);
- if (p < 0) {
- p = 0;
- }
-
- _player->seek (p, true);
- fetch_next_frame ();
+ get (p, true);
}
void
FilmViewer::forward_clicked ()
{
- if (!_player) {
- return;
- }
-
- fetch_next_frame ();
+ get (_position + DCPTime::from_frames (1, _film->video_frame_rate ()), true);
}
void
}
calculate_sizes ();
- fetch_current_frame_again ();
+ get (_position, true);
}
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/** @class FilmViewer
* @brief A wx widget to view a preview of a Film.
- *
- * The film takes the following path through the viewer:
- *
- * 1. fetch_next_frame() asks our _player to decode some data. If it does, process_video()
- * will be called.
- *
- * 2. process_video() takes the image from the player (_frame).
- *
- * 3. fetch_next_frame() calls _panel->Refresh() and _panel->Update() which results in
- * paint_panel() being called; this creates frame_bitmap from _frame and blits it to the display.
- *
- * fetch_current_frame_again() asks the player to re-emit its current frame on the next pass(), and then
- * starts from step #1.
*/
class FilmViewer : public wxPanel
{
void slider_moved ();
void play_clicked ();
void timer ();
- void process_video (boost::shared_ptr<PlayerVideoFrame>, Time);
void calculate_sizes ();
void check_play_state ();
- void fetch_current_frame_again ();
- void fetch_next_frame ();
void active_jobs_changed (bool);
void back_clicked ();
void forward_clicked ();
void player_changed (bool);
- void set_position_text (Time);
+ void set_position_text ();
+ void get (DCPTime, bool);
boost::shared_ptr<Film> _film;
boost::shared_ptr<Player> _player;
wxTimer _timer;
boost::shared_ptr<const Image> _frame;
- bool _got_frame;
+ DCPTime _position;
/** Size of our output (including padding if we have any) */
- libdcp::Size _out_size;
+ dcp::Size _out_size;
/** Size of the panel that we have available */
- libdcp::Size _panel_size;
+ dcp::Size _panel_size;
};
add (_("Frames already encoded"), true);
_encoded = add (new ThreadedStaticText (this, _("counting..."), boost::bind (&PropertiesDialog::frames_already_encoded, this)));
_encoded->Finished.connect (boost::bind (&PropertiesDialog::layout, this));
-
- _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->time_to_video_frames (_film->length()))));
+ _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->length().frames (_film->video_frame_rate ()))));
double const disk = double (_film->required_disk_space()) / 1073741824.0f;
stringstream s;
s << fixed << setprecision (1) << disk << wx_to_std (_("Gb"));
} catch (boost::thread_interrupted &) {
return "";
}
-
- if (_film->length()) {
+
+ uint64_t const frames = _film->length().frames (_film->video_frame_rate ());
+ if (frames) {
/* XXX: encoded_frames() should check which frames have been encoded */
- u << " (" << (_film->encoded_frames() * 100 / _film->time_to_video_frames (_film->length())) << "%)";
+ u << " (" << (_film->encoded_frames() * 100 / frames) << "%)";
}
return u.str ();
}
#include <wx/filepicker.h>
#include <wx/validate.h>
-#include <libdcp/exceptions.h>
+#include <dcp/exceptions.h>
#include "lib/compose.hpp"
#include "lib/util.h"
#include "screen_dialog.h"
using std::cout;
using boost::shared_ptr;
-ScreenDialog::ScreenDialog (wxWindow* parent, string title, string name, shared_ptr<libdcp::Certificate> certificate)
+ScreenDialog::ScreenDialog (wxWindow* parent, string title, string name, shared_ptr<dcp::Certificate> certificate)
: TableDialog (parent, std_to_wx (title), 2, true)
, _certificate (certificate)
{
return wx_to_std (_name->GetValue());
}
-shared_ptr<libdcp::Certificate>
+shared_ptr<dcp::Certificate>
ScreenDialog::certificate () const
{
return _certificate;
ScreenDialog::load_certificate (boost::filesystem::path file)
{
try {
- _certificate.reset (new libdcp::Certificate (file));
+ _certificate.reset (new dcp::Certificate (file));
_certificate_text->SetValue (_certificate->certificate ());
- } catch (libdcp::MiscError& e) {
+ } catch (dcp::MiscError& e) {
error_dialog (this, String::compose ("Could not read certificate file (%1)", e.what()));
}
}
ScreenDialog::setup_sensitivity ()
{
wxButton* ok = dynamic_cast<wxButton*> (FindWindowById (wxID_OK, this));
- ok->Enable (_certificate);
+ ok->Enable (_certificate.get ());
_download_certificate->Enable (
_manufacturer->GetStringSelection() == _("Doremi") ||
#include <wx/wx.h>
#include <boost/shared_ptr.hpp>
-#include <libdcp/certificates.h>
+#include <dcp/certificates.h>
#include "table_dialog.h"
class Progress;
class ScreenDialog : public TableDialog
{
public:
- ScreenDialog (wxWindow *, std::string, std::string name = "", boost::shared_ptr<libdcp::Certificate> c = boost::shared_ptr<libdcp::Certificate> ());
+ ScreenDialog (wxWindow *, std::string, std::string name = "", boost::shared_ptr<dcp::Certificate> c = boost::shared_ptr<dcp::Certificate> ());
std::string name () const;
- boost::shared_ptr<libdcp::Certificate> certificate () const;
+ boost::shared_ptr<dcp::Certificate> certificate () const;
private:
void select_certificate ();
wxButton* _download_certificate;
wxTextCtrl* _certificate_text;
- boost::shared_ptr<libdcp::Certificate> _certificate;
+ boost::shared_ptr<dcp::Certificate> _certificate;
};
/*
- Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include <boost/lexical_cast.hpp>
#include <wx/spinctrl.h>
#include "lib/ffmpeg_content.h"
+#include "lib/subrip_content.h"
#include "subtitle_panel.h"
#include "film_editor.h"
#include "wx_util.h"
+#include "subtitle_view.h"
using std::vector;
using std::string;
SubtitlePanel::SubtitlePanel (FilmEditor* e)
: FilmEditorPanel (e, _("Subtitles"))
+ , _view (0)
{
wxFlexGridSizer* grid = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
_sizer->Add (grid, 0, wxALL, 8);
add_label_to_sizer (grid, this, _("Subtitle Stream"), true);
_stream = new wxChoice (this, wxID_ANY);
grid->Add (_stream, 1, wxEXPAND);
+
+ _view_button = new wxButton (this, wxID_ANY, _("View..."));
+ grid->Add (_view_button);
_x_offset->SetRange (-100, 100);
_y_offset->SetRange (-100, 100);
_y_offset->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::y_offset_changed, this));
_scale->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::scale_changed, this));
_stream->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&SubtitlePanel::stream_changed, this));
+ _view_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&SubtitlePanel::view_clicked, this));
}
void
_y_offset->Enable (j);
_scale->Enable (j);
_stream->Enable (j);
+
+ SubtitleContentList c = _editor->selected_subtitle_content ();
+ _view_button->Enable (c.size() == 1);
}
void
film_content_changed (SubtitleContentProperty::SUBTITLE_Y_OFFSET);
film_content_changed (SubtitleContentProperty::SUBTITLE_SCALE);
}
+
+void
+SubtitlePanel::view_clicked ()
+{
+ if (_view) {
+ _view->Destroy ();
+ _view = 0;
+ }
+
+ SubtitleContentList c = _editor->selected_subtitle_content ();
+ assert (c.size() == 1);
+ shared_ptr<SubRipContent> sr = dynamic_pointer_cast<SubRipContent> (c.front ());
+ if (sr) {
+ _view = new SubtitleView (this, sr);
+ }
+
+ _view->Show ();
+}
/*
- Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
class wxCheckBox;
class wxSpinCtrl;
+class SubtitleView;
class SubtitlePanel : public FilmEditorPanel
{
void y_offset_changed ();
void scale_changed ();
void stream_changed ();
+ void view_clicked ();
void setup_sensitivity ();
wxSpinCtrl* _y_offset;
wxSpinCtrl* _scale;
wxChoice* _stream;
+ wxButton* _view_button;
+ SubtitleView* _view;
};
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "lib/subrip_decoder.h"
+#include "lib/content_subtitle.h"
+#include "subtitle_view.h"
+
+using std::list;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+
+SubtitleView::SubtitleView (wxWindow* parent, shared_ptr<SubRipContent> content)
+ : wxDialog (parent, wxID_ANY, _("Subtitles"))
+{
+ _list = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL);
+
+ {
+ wxListItem ip;
+ ip.SetId (0);
+ ip.SetText (_("Start"));
+ ip.SetWidth (100);
+ _list->InsertColumn (0, ip);
+ }
+
+ {
+ wxListItem ip;
+ ip.SetId (1);
+ ip.SetText (_("End"));
+ ip.SetWidth (100);
+ _list->InsertColumn (1, ip);
+ }
+
+ {
+ wxListItem ip;
+ ip.SetId (2);
+ ip.SetText (_("Subtitle"));
+ ip.SetWidth (640);
+ _list->InsertColumn (2, ip);
+ }
+
+ wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
+ sizer->Add (_list, 1, wxEXPAND);
+
+ wxSizer* buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL);
+ if (buttons) {
+ sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+ }
+
+ shared_ptr<SubRipDecoder> decoder (new SubRipDecoder (content));
+ list<shared_ptr<ContentTextSubtitle> > subs = decoder->get_text_subtitles (ContentTime(), ContentTime::max ());
+ int n = 0;
+ for (list<shared_ptr<ContentTextSubtitle> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
+ for (list<dcp::SubtitleString>::const_iterator j = (*i)->subs.begin(); j != (*i)->subs.end(); ++j) {
+ wxListItem list_item;
+ list_item.SetId (n);
+ _list->InsertItem (list_item);
+ _list->SetItem (n, 2, j->text ());
+ ++n;
+ }
+ }
+
+ SetSizerAndFit (sizer);
+}
+
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/shared_ptr.hpp>
+#include <wx/wx.h>
+#include <wx/listctrl.h>
+
+class SubRipContent;
+
+class SubtitleView : public wxDialog
+{
+public:
+ SubtitleView (wxWindow *, boost::shared_ptr<SubRipContent>);
+
+private:
+ wxListCtrl* _list;
+};
}
void
-Timecode::set (Time t, int fps)
+Timecode::set (DCPTime t, int fps)
{
/* Do this calculation with frames so that we can round
to a frame boundary at the start rather than the end.
*/
- int64_t f = divide_with_round (t * fps, TIME_HZ);
+ int64_t f = rint (t.seconds() * fps);
int const h = f / (3600 * fps);
f -= h * 3600 * fps;
_fixed->SetLabel (wxString::Format ("%02d:%02d:%02d.%02" wxLongLongFmtSpec "d", h, m, s, f));
}
-Time
+DCPTime
Timecode::get (int fps) const
{
- Time t = 0;
+ DCPTime t;
string const h = wx_to_std (_hours->GetValue ());
- t += lexical_cast<int> (h.empty() ? "0" : h) * 3600 * TIME_HZ;
+ t += DCPTime::from_seconds (lexical_cast<int> (h.empty() ? "0" : h) * 3600);
string const m = wx_to_std (_minutes->GetValue());
- t += lexical_cast<int> (m.empty() ? "0" : m) * 60 * TIME_HZ;
+ t += DCPTime::from_seconds (lexical_cast<int> (m.empty() ? "0" : m) * 60);
string const s = wx_to_std (_seconds->GetValue());
- t += lexical_cast<int> (s.empty() ? "0" : s) * TIME_HZ;
+ t += DCPTime::from_seconds (lexical_cast<int> (s.empty() ? "0" : s));
string const f = wx_to_std (_frames->GetValue());
- t += lexical_cast<int> (f.empty() ? "0" : f) * TIME_HZ / fps;
+ t += DCPTime::from_seconds (lexical_cast<double> (f.empty() ? "0" : f) / fps);
return t;
}
public:
Timecode (wxWindow *);
- void set (Time, int);
- Time get (int) const;
+ void set (DCPTime, int);
+ DCPTime get (int) const;
void set_editable (bool);
using boost::bind;
using boost::optional;
-/** Parent class for components of the timeline (e.g. a piece of content or an axis) */
+/** @class View
+ * @brief Parent class for components of the timeline (e.g. a piece of content or an axis).
+ */
class View : public boost::noncopyable
{
public:
protected:
virtual void do_paint (wxGraphicsContext *) = 0;
- int time_x (Time t) const
+ int time_x (DCPTime t) const
{
- return _timeline.tracks_position().x + t * _timeline.pixels_per_time_unit();
+ return _timeline.tracks_position().x + t.seconds() * _timeline.pixels_per_second ();
}
Timeline& _timeline;
};
-/** Parent class for views of pieces of content */
+/** @class ContentView
+ * @brief Parent class for views of pieces of content.
+ */
class ContentView : public View
{
public:
return dcpomatic::Rect<int> (
time_x (content->position ()) - 8,
y_pos (_track.get()) - 8,
- content->length_after_trim () * _timeline.pixels_per_time_unit() + 16,
+ content->length_after_trim().seconds() * _timeline.pixels_per_second() + 16,
_timeline.track_height() + 16
);
}
return;
}
- Time const position = cont->position ();
- Time const len = cont->length_after_trim ();
+ DCPTime const position = cont->position ();
+ DCPTime const len = cont->length_after_trim ();
wxColour selected (colour().Red() / 2, colour().Green() / 2, colour().Blue() / 2);
wxDouble name_leading;
gc->GetTextExtent (name, &name_width, &name_height, &name_descent, &name_leading);
- gc->Clip (wxRegion (time_x (position), y_pos (_track.get()), len * _timeline.pixels_per_time_unit(), _timeline.track_height()));
+ gc->Clip (wxRegion (time_x (position), y_pos (_track.get()), len.seconds() * _timeline.pixels_per_second(), _timeline.track_height()));
gc->DrawText (name, time_x (position) + 12, y_pos (_track.get() + 1) - name_height - 4);
gc->ResetClip ();
}
}
if (!frequent) {
- _timeline.setup_pixels_per_time_unit ();
+ _timeline.setup_pixels_per_second ();
_timeline.Refresh ();
}
}
}
};
+class SubtitleContentView : public ContentView
+{
+public:
+ SubtitleContentView (Timeline& tl, shared_ptr<Content> c)
+ : ContentView (tl, c)
+ {}
+
+private:
+ wxString type () const
+ {
+ return _("subtitles");
+ }
+
+ wxColour colour () const
+ {
+ return wxColour (163, 255, 154, 255);
+ }
+};
+
class TimeAxisView : public View
{
public:
{
gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxPENSTYLE_SOLID));
- int mark_interval = rint (128 / (TIME_HZ * _timeline.pixels_per_time_unit ()));
+ double mark_interval = rint (128 / _timeline.pixels_per_second ());
if (mark_interval > 5) {
- mark_interval -= mark_interval % 5;
+ mark_interval -= int (rint (mark_interval)) % 5;
}
if (mark_interval > 10) {
- mark_interval -= mark_interval % 10;
+ mark_interval -= int (rint (mark_interval)) % 10;
}
if (mark_interval > 60) {
- mark_interval -= mark_interval % 60;
+ mark_interval -= int (rint (mark_interval)) % 60;
}
if (mark_interval > 3600) {
- mark_interval -= mark_interval % 3600;
+ mark_interval -= int (rint (mark_interval)) % 3600;
}
if (mark_interval < 1) {
path.AddLineToPoint (_timeline.width(), _y);
gc->StrokePath (path);
- Time t = 0;
- while ((t * _timeline.pixels_per_time_unit()) < _timeline.width()) {
+ /* Time in seconds */
+ DCPTime t;
+ while ((t.seconds() * _timeline.pixels_per_second()) < _timeline.width()) {
wxGraphicsPath path = gc->CreatePath ();
path.MoveToPoint (time_x (t), _y - 4);
path.AddLineToPoint (time_x (t), _y + 4);
gc->StrokePath (path);
- int tc = t / TIME_HZ;
+ double tc = t.seconds ();
int const h = tc / 3600;
tc -= h * 3600;
int const m = tc / 60;
wxDouble str_leading;
gc->GetTextExtent (str, &str_width, &str_height, &str_descent, &str_leading);
- int const tx = _timeline.x_offset() + t * _timeline.pixels_per_time_unit();
+ int const tx = _timeline.x_offset() + t.seconds() * _timeline.pixels_per_second();
if ((tx + str_width) < _timeline.width()) {
gc->DrawText (str, time_x (t), _y + 16);
}
- t += mark_interval * TIME_HZ;
+ t += DCPTime::from_seconds (mark_interval);
}
}
, _film (film)
, _time_axis_view (new TimeAxisView (*this, 32))
, _tracks (0)
- , _pixels_per_time_unit (0)
+ , _pixels_per_second (0)
, _left_down (false)
, _down_view_position (0)
, _first_move (false)
if (dynamic_pointer_cast<AudioContent> (*i)) {
_views.push_back (shared_ptr<View> (new AudioContentView (*this, *i)));
}
+ if (dynamic_pointer_cast<SubtitleContent> (*i)) {
+ _views.push_back (shared_ptr<View> (new SubtitleContentView (*this, *i)));
+ }
}
assign_tracks ();
- setup_pixels_per_time_unit ();
+ setup_pixels_per_second ();
Refresh ();
}
if (!cv) {
continue;
}
-
+
shared_ptr<Content> content = cv->content();
int t = 0;
- while (1) {
+ while (true) {
ViewList::iterator j = _views.begin();
while (j != _views.end()) {
shared_ptr<ContentView> test = dynamic_pointer_cast<ContentView> (*j);
}
void
-Timeline::setup_pixels_per_time_unit ()
+Timeline::setup_pixels_per_second ()
{
shared_ptr<const Film> film = _film.lock ();
- if (!film || film->length() == 0) {
+ if (!film || film->length() == DCPTime ()) {
return;
}
- _pixels_per_time_unit = static_cast<double>(width() - x_offset() * 2) / film->length ();
+ _pixels_per_second = static_cast<double>(width() - x_offset() * 2) / film->length().seconds ();
}
shared_ptr<View>
return;
}
- Time new_position = _down_view_position + (p.x - _down_point.x) / _pixels_per_time_unit;
+ DCPTime new_position = _down_view_position + DCPTime::from_seconds ((p.x - _down_point.x) / _pixels_per_second);
if (_snap) {
bool first = true;
- Time nearest_distance = TIME_MAX;
- Time nearest_new_position = TIME_MAX;
+ DCPTime nearest_distance = DCPTime::max ();
+ DCPTime nearest_new_position = DCPTime::max ();
/* Find the nearest content edge; this is inefficient */
for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
{
/* Snap starts to ends */
- Time const d = abs (cv->content()->end() - new_position);
+ DCPTime const d = DCPTime (cv->content()->end() - new_position).abs ();
if (first || d < nearest_distance) {
nearest_distance = d;
nearest_new_position = cv->content()->end();
{
/* Snap ends to starts */
- Time const d = abs (cv->content()->position() - (new_position + _down_view->content()->length_after_trim()));
+ DCPTime const d = DCPTime (
+ cv->content()->position() - (new_position + _down_view->content()->length_after_trim())
+ ).abs ();
+
if (d < nearest_distance) {
nearest_distance = d;
nearest_new_position = cv->content()->position() - _down_view->content()->length_after_trim ();
if (!first) {
/* Snap if it's close; `close' means within a proportion of the time on the timeline */
- if (nearest_distance < (width() / pixels_per_time_unit()) / 32) {
+ if (nearest_distance < DCPTime::from_seconds ((width() / pixels_per_second()) / 32)) {
new_position = nearest_new_position;
}
}
}
- if (new_position < 0) {
- new_position = 0;
+ if (new_position < DCPTime ()) {
+ new_position = DCPTime ();
}
_down_view->content()->set_position (new_position);
void
Timeline::resized ()
{
- setup_pixels_per_time_unit ();
+ setup_pixels_per_second ();
}
void
return 48;
}
- double pixels_per_time_unit () const {
- return _pixels_per_time_unit;
+ double pixels_per_second () const {
+ return _pixels_per_second;
}
Position<int> tracks_position () const {
int tracks () const;
- void setup_pixels_per_time_unit ();
+ void setup_pixels_per_second ();
void set_snap (bool s) {
_snap = s;
ViewList _views;
boost::shared_ptr<TimeAxisView> _time_axis_view;
int _tracks;
- double _pixels_per_time_unit;
+ double _pixels_per_second;
bool _left_down;
wxPoint _down_point;
boost::shared_ptr<ContentView> _down_view;
- Time _down_view_position;
+ DCPTime _down_view_position;
bool _first_move;
ContentMenu _menu;
bool _snap;
if (content) {
_position->set (content->position (), film_video_frame_rate);
} else {
- _position->set (0, 24);
+ _position->set (DCPTime () , 24);
}
} else if (
property == ContentProperty::LENGTH ||
property == VideoContentProperty::VIDEO_FRAME_RATE ||
- property == VideoContentProperty::VIDEO_FRAME_TYPE ||
- property == SndfileContentProperty::VIDEO_FRAME_RATE
+ property == VideoContentProperty::VIDEO_FRAME_TYPE
) {
if (content) {
_full_length->set (content->full_length (), film_video_frame_rate);
_play_length->set (content->length_after_trim (), film_video_frame_rate);
} else {
- _full_length->set (0, 24);
- _play_length->set (0, 24);
+ _full_length->set (DCPTime (), 24);
+ _play_length->set (DCPTime (), 24);
}
} else if (property == ContentProperty::TRIM_START) {
if (content) {
_trim_start->set (content->trim_start (), film_video_frame_rate);
_play_length->set (content->length_after_trim (), film_video_frame_rate);
} else {
- _trim_start->set (0, 24);
- _play_length->set (0, 24);
+ _trim_start->set (DCPTime (), 24);
+ _play_length->set (DCPTime (), 24);
}
} else if (property == ContentProperty::TRIM_END) {
if (content) {
_trim_end->set (content->trim_end (), film_video_frame_rate);
_play_length->set (content->length_after_trim (), film_video_frame_rate);
} else {
- _trim_end->set (0, 24);
- _play_length->set (0, 24);
+ _trim_end->set (DCPTime (), 24);
+ _play_length->set (DCPTime (), 24);
}
}
if (property == VideoContentProperty::VIDEO_FRAME_RATE) {
if (content) {
shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (content);
- shared_ptr<SndfileContent> sc = dynamic_pointer_cast<SndfileContent> (content);
if (vc) {
_video_frame_rate->SetValue (std_to_wx (lexical_cast<string> (vc->video_frame_rate ())));
- } else if (sc) {
- _video_frame_rate->SetValue (std_to_wx (lexical_cast<string> (sc->video_frame_rate ())));
} else {
_video_frame_rate->SetValue ("24");
}
if (c.size() == 1) {
shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (c.front ());
if (ic && ic->still ()) {
- ic->set_video_length (rint (_full_length->get (_editor->film()->video_frame_rate()) * ic->video_frame_rate() / TIME_HZ));
+ /* XXX: No effective FRC here... is this right? */
+ ic->set_video_length (ContentTime (_full_length->get (_editor->film()->video_frame_rate()), FrameRateChange (1, 1)));
}
}
}
if (ic) {
ic->set_video_frame_rate (lexical_cast<float> (wx_to_std (_video_frame_rate->GetValue ())));
}
- shared_ptr<SndfileContent> sc = dynamic_pointer_cast<SndfileContent> (c.front ());
- if (sc) {
- sc->set_video_frame_rate (lexical_cast<float> (wx_to_std (_video_frame_rate->GetValue ())));
- }
_set_video_frame_rate->Enable (false);
}
}
}
Crop const crop = vcs->crop ();
- if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != libdcp::Size (0, 0)) {
- libdcp::Size cropped = vcs->video_size_after_crop ();
+ if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != dcp::Size (0, 0)) {
+ dcp::Size cropped = vcs->video_size_after_crop ();
d << wxString::Format (
_("Cropped to %dx%d (%.2f:1)\n"),
cropped.width, cropped.height,
++lines;
}
- libdcp::Size const container_size = _editor->film()->frame_size ();
- libdcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size);
+ dcp::Size const container_size = _editor->film()->frame_size ();
+ dcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size);
if (scaled != vcs->video_size_after_crop ()) {
d << wxString::Format (
d << wxString::Format (_("Content frame rate %.4f\n"), vcs->video_frame_rate ());
++lines;
- FrameRateConversion frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
+ FrameRateChange frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
d << std_to_wx (frc.description) << "\n";
++lines;
/*
- Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/lib/video_panel.h
+ * @brief VideoPanel class.
+ */
+
#include "lib/film.h"
#include "film_editor_panel.h"
#include "content_widget.h"
class wxSpinCtrl;
class wxButton;
+/** @class VideoPanel
+ * @brief The video tab of the film editor.
+ */
class VideoPanel : public FilmEditorPanel
{
public:
server_dialog.cc
servers_list_dialog.cc
subtitle_panel.cc
+ subtitle_view.cc
table_dialog.cc
timecode.cc
timeline.cc
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
class wxEvtHandler;
+/** @class wxUISignaller
+ * @brief UISignaller for the wxWidgets event loop
+ */
+
class wxUISignaller : public UISignaller
{
public:
* @param initial Initial text for the wxStaticText while the computation is being run.
* @param fn Function which works out what the wxStaticText content should be and returns it.
*/
-ThreadedStaticText::ThreadedStaticText (wxWindow* parent, wxString initial, function<string ()> fn)
+ThreadedStaticText::ThreadedStaticText (wxWindow* parent, wxString initial, boost::function<string ()> fn)
: wxStaticText (parent, wxID_ANY, initial)
{
Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ThreadedStaticText::thread_finished, this, _1), _update_event_id);
/** Run our thread and post the result to the GUI thread via AddPendingEvent */
void
-ThreadedStaticText::run (function<string ()> fn)
+ThreadedStaticText::run (boost::function<string ()> fn)
try
{
wxCommandEvent ev (wxEVT_COMMAND_TEXT_UPDATED, _update_event_id);
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/4k_test.cc
+ * @brief Run a 4K encode from a simple input.
+ *
+ * The output is checked against test/data/4k_test.
+ */
+
#include <boost/test/unit_test.hpp>
#include "lib/film.h"
#include "lib/ffmpeg_content.h"
--- /dev/null
+DCP-o-matic unit tests
+----------------------
+
+They can be grouped roughly into the following:
+
+* Self-contained tests of single classes / method sets
+
+AudioAnalysis: audio_analysis_test
+AudioBuffers: audio_buffers_test
+AudioDecoder: audio_decoder_test
+AudioMapping: audio_mapping_test
+ColourConversion: colour_conversion_test
+FileGroup: file_group_test
+Image: image_test, pixel_formats_test, make_black_test
+Player: player_test
+Job/JobManager: job_test
+SubRip: subrip_test
+Ratio: ratio_test
+Resampler: resampler_test
+util.cc: util_test
+
+* "Complete" builds of DCPs with various characteristics, aiming
+to test broad areas of code
+
+4k_test
+threed_test
+
+* Tests of fairly specific areas
+
+audio_delay_test
+black_fill_test
+client_server_test
+film_metadata_test
+frame_rate_test
+recover_test
+repeat_frame_test
+scaling_test
+silence_padding_test
+skip_frame_test
+
+ - FFmpeg decoding
+
+ ffmpeg_audio_test
+ ffmpeg_dcp_test
+ ffmpeg_decoder_seek_test
+ ffmpeg_decoder_sequential_test
+ ffmpeg_examiner_test
+ ffmpeg_pts_offset_test
+ seek_zero_test.cc
+ stream_test
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/audio_analysis_test.cc
+ * @brief Check serialisation of audio analyses.
+ */
+
#include <boost/test/unit_test.hpp>
#include "lib/audio_analysis.h"
return (float (rand ()) / RAND_MAX) * 2 - 1;
}
-/* Check serialisation of audio analyses */
BOOST_AUTO_TEST_CASE (audio_analysis_test)
{
int const channels = 3;
AudioAnalysis b ("build/test/audio_analysis_test");
for (int i = 0; i < channels; ++i) {
- BOOST_CHECK (b.points(i) == points);
+ BOOST_CHECK_EQUAL (b.points(i), points);
for (int j = 0; j < points; ++j) {
AudioPoint p = b.get_point (i, j);
BOOST_CHECK_CLOSE (p[AudioPoint::PEAK], random_float (), 1);
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file test/audio_buffers_test.cc
+ * @brief Test AudioBuffers in various ways.
+ */
+
+#include <cmath>
+#include <boost/test/unit_test.hpp>
+#include "lib/audio_buffers.h"
+
+using std::pow;
+
+static float tolerance = 1e-3;
+
+static float
+random_float ()
+{
+ return float (rand ()) / RAND_MAX;
+}
+
+static void
+random_fill (AudioBuffers& buffers)
+{
+ for (int i = 0; i < buffers.frames(); ++i) {
+ for (int j = 0; j < buffers.channels(); ++j) {
+ buffers.data(j)[i] = random_float ();
+ }
+ }
+}
+
+static void
+random_check (AudioBuffers& buffers, int from, int frames)
+{
+ for (int i = from; i < (from + frames); ++i) {
+ for (int j = 0; j < buffers.channels(); ++j) {
+ BOOST_CHECK_CLOSE (buffers.data(j)[i], random_float (), tolerance);
+ }
+ }
+}
+
+/** Basic setup */
+BOOST_AUTO_TEST_CASE (audio_buffers_setup_test)
+{
+ AudioBuffers buffers (4, 9155);
+
+ BOOST_CHECK (buffers.data ());
+ for (int i = 0; i < 4; ++i) {
+ BOOST_CHECK (buffers.data (i));
+ }
+
+ BOOST_CHECK_EQUAL (buffers.channels(), 4);
+ BOOST_CHECK_EQUAL (buffers.frames(), 9155);
+}
+
+/** Extending some buffers */
+BOOST_AUTO_TEST_CASE (audio_buffers_extend_test)
+{
+ AudioBuffers buffers (3, 150);
+ srand (1);
+ random_fill (buffers);
+
+ /* Extend */
+ buffers.ensure_size (299);
+
+ srand (1);
+ random_check (buffers, 0, 150);
+
+ /* New space should be silent */
+ for (int i = 150; i < 299; ++i) {
+ for (int c = 0; c < 3; ++c) {
+ BOOST_CHECK_EQUAL (buffers.data(c)[i], 0);
+ }
+ }
+}
+
+/** make_silent() */
+BOOST_AUTO_TEST_CASE (audio_buffers_make_silent_test)
+{
+ AudioBuffers buffers (9, 9933);
+ srand (2);
+ random_fill (buffers);
+
+ buffers.make_silent ();
+
+ for (int i = 0; i < 9933; ++i) {
+ for (int c = 0; c < 9; ++c) {
+ BOOST_CHECK_EQUAL (buffers.data(c)[i], 0);
+ }
+ }
+}
+
+/** make_silent (int c) */
+BOOST_AUTO_TEST_CASE (audio_buffers_make_silent_channel_test)
+{
+ AudioBuffers buffers (9, 9933);
+ srand (3);
+ random_fill (buffers);
+
+ buffers.make_silent (4);
+
+ srand (3);
+ for (int i = 0; i < 9933; ++i) {
+ for (int c = 0; c < 9; ++c) {
+ if (c == 4) {
+ random_float ();
+ BOOST_CHECK_EQUAL (buffers.data(c)[i], 0);
+ } else {
+ BOOST_CHECK_CLOSE (buffers.data(c)[i], random_float (), tolerance);
+ }
+ }
+ }
+}
+
+/** make_silent (int from, int frames) */
+BOOST_AUTO_TEST_CASE (audio_buffers_make_silent_part_test)
+{
+ AudioBuffers buffers (9, 9933);
+ srand (4);
+ random_fill (buffers);
+
+ buffers.make_silent (145, 833);
+
+ srand (4);
+ for (int i = 0; i < 145; ++i) {
+ for (int c = 0; c < 9; ++c) {
+ BOOST_CHECK_EQUAL (buffers.data(c)[i], random_float ());
+ }
+ }
+
+ for (int i = 145; i < (145 + 833); ++i) {
+ for (int c = 0; c < 9; ++c) {
+ random_float ();
+ BOOST_CHECK_EQUAL (buffers.data(c)[i], 0);
+ }
+ }
+
+ for (int i = (145 + 833); i < 9933; ++i) {
+ for (int c = 0; c < 9; ++c) {
+ BOOST_CHECK_EQUAL (buffers.data(c)[i], random_float ());
+ }
+ }
+}
+
+/* apply_gain */
+BOOST_AUTO_TEST_CASE (audio_buffers_apply_gain)
+{
+ AudioBuffers buffers (2, 417315);
+ srand (9);
+ random_fill (buffers);
+
+ buffers.apply_gain (5.4);
+
+ srand (9);
+ for (int i = 0; i < 417315; ++i) {
+ for (int c = 0; c < 2; ++c) {
+ BOOST_CHECK_CLOSE (buffers.data(c)[i], random_float() * pow (10, 5.4 / 20), tolerance);
+ }
+ }
+}
+
+/* copy_from */
+BOOST_AUTO_TEST_CASE (audio_buffers_copy_from)
+{
+ AudioBuffers a (5, 63711);
+ AudioBuffers b (5, 12345);
+
+ srand (42);
+ random_fill (a);
+
+ srand (99);
+ random_fill (b);
+
+ a.copy_from (&b, 517, 233, 194);
+
+ /* Re-seed a's generator and check the numbers that came from it */
+
+ /* First part; not copied-over */
+ srand (42);
+ random_check (a, 0, 194);
+
+ /* Second part; copied-over (just burn generator a's numbers) */
+ for (int i = 0; i < (517 * 5); ++i) {
+ random_float ();
+ }
+
+ /* Third part; not copied-over */
+ random_check (a, 194 + 517, a.frames() - 194 - 517);
+
+ /* Re-seed b's generator and check the numbers that came from it */
+ srand (99);
+
+ /* First part; burn */
+ for (int i = 0; i < 194 * 5; ++i) {
+ random_float ();
+ }
+
+ /* Second part; copied */
+ random_check (b, 194, 517);
+}
+
+/* move */
+BOOST_AUTO_TEST_CASE (audio_buffers_move)
+{
+ AudioBuffers buffers (7, 65536);
+
+ srand (84);
+ random_fill (buffers);
+
+ int const from = 888;
+ int const to = 666;
+ int const frames = 444;
+
+ buffers.move (from, to, frames);
+
+ /* Re-seed and check the un-moved parts */
+ srand (84);
+
+ random_check (buffers, 0, to);
+
+ /* Burn a few */
+ for (int i = 0; i < (from - to + frames) * 7; ++i) {
+ random_float ();
+ }
+
+ random_check (buffers, from + frames, 65536 - frames - from);
+
+ /* Re-seed and check the moved part */
+ srand (84);
+
+ /* Burn a few */
+ for (int i = 0; i < from * 7; ++i) {
+ random_float ();
+ }
+
+ random_check (buffers, to, frames);
+}
+
+/** accumulate_channel */
+BOOST_AUTO_TEST_CASE (audio_buffers_accumulate_channel)
+{
+ AudioBuffers a (3, 256);
+ srand (38);
+ random_fill (a);
+
+ AudioBuffers b (3, 256);
+ random_fill (b);
+
+ a.accumulate_channel (&b, 2, 1, 1.2);
+
+ srand (38);
+ for (int i = 0; i < 256; ++i) {
+ for (int c = 0; c < 3; ++c) {
+ float const A = random_float ();
+ if (c == 1) {
+ BOOST_CHECK_CLOSE (a.data(c)[i], A + b.data(2)[i] * 1.2, tolerance);
+ } else {
+ BOOST_CHECK_CLOSE (a.data(c)[i], A, tolerance);
+ }
+ }
+ }
+}
+
+/** accumulate_frames */
+BOOST_AUTO_TEST_CASE (audio_buffers_accumulate_frames)
+{
+ AudioBuffers a (3, 256);
+ srand (38);
+ random_fill (a);
+
+ AudioBuffers b (3, 256);
+ random_fill (b);
+
+ a.accumulate_frames (&b, 91, 44, 129);
+
+ srand (38);
+ for (int i = 0; i < 256; ++i) {
+ for (int c = 0; c < 3; ++c) {
+ float const A = random_float ();
+ if (i < 44 || i >= (44 + 129)) {
+ BOOST_CHECK_CLOSE (a.data(c)[i], A, tolerance);
+ } else {
+ BOOST_CHECK_CLOSE (a.data(c)[i], A + b.data(c)[i + 91 - 44], tolerance);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file test/audio_decoder_test.cc
+ * @brief Tests of the AudioDecoder class.
+ */
+
+#include <cassert>
+#include <boost/test/unit_test.hpp>
+#include "test.h"
+#include "lib/audio_decoder.h"
+#include "lib/audio_content.h"
+
+using std::string;
+using std::cout;
+using std::min;
+using boost::shared_ptr;
+
+class TestAudioDecoder : public AudioDecoder
+{
+public:
+ TestAudioDecoder (shared_ptr<AudioContent> content)
+ : AudioDecoder (content)
+ , _position (0)
+ {}
+
+ bool pass ()
+ {
+ AudioFrame const N = min (
+ AudioFrame (2000),
+ _audio_content->audio_length().frames (_audio_content->resampled_audio_frame_rate ()) - _position
+ );
+
+ shared_ptr<AudioBuffers> buffers (new AudioBuffers (_audio_content->audio_channels(), N));
+ for (int i = 0; i < _audio_content->audio_channels(); ++i) {
+ for (int j = 0; j < N; ++j) {
+ buffers->data(i)[j] = j + _position;
+ }
+ }
+
+ audio (buffers, ContentTime::from_frames (_position, _audio_content->resampled_audio_frame_rate ()));
+ _position += N;
+
+ return N < 2000;
+ }
+
+ void seek (ContentTime t, bool accurate)
+ {
+ AudioDecoder::seek (t, accurate);
+ _position = t.frames (_audio_content->resampled_audio_frame_rate ());
+ }
+
+private:
+ AudioFrame _position;
+};
+
+class TestAudioContent : public AudioContent
+{
+public:
+ TestAudioContent (shared_ptr<Film> film)
+ : Content (film)
+ , AudioContent (film, DCPTime ())
+ {}
+
+ string summary () const {
+ return "";
+ }
+
+ string information () const {
+ return "";
+ }
+
+ DCPTime full_length () const {
+ return DCPTime (audio_length().get ());
+ }
+
+ int audio_channels () const {
+ return 2;
+ }
+
+ ContentTime audio_length () const {
+ return ContentTime::from_seconds (61.2942);
+ }
+
+ int audio_frame_rate () const {
+ return 48000;
+ }
+
+ AudioMapping audio_mapping () const {
+ return AudioMapping (audio_channels ());
+ }
+
+ void set_audio_mapping (AudioMapping) {}
+};
+
+shared_ptr<TestAudioContent> content;
+shared_ptr<TestAudioDecoder> decoder;
+
+static shared_ptr<ContentAudio>
+get (AudioFrame from, AudioFrame length)
+{
+ decoder->seek (ContentTime::from_frames (from, content->resampled_audio_frame_rate ()), true);
+ shared_ptr<ContentAudio> ca = decoder->get_audio (from, length, true);
+ BOOST_CHECK_EQUAL (ca->frame, from);
+ return ca;
+}
+
+static void
+check (AudioFrame from, AudioFrame length)
+{
+ shared_ptr<ContentAudio> ca = get (from, length);
+ for (int i = 0; i < content->audio_channels(); ++i) {
+ for (int j = 0; j < length; ++j) {
+ BOOST_CHECK_EQUAL (ca->audio->data(i)[j], j + from);
+ assert (ca->audio->data(i)[j] == j + from);
+ }
+ }
+}
+
+/** Check the logic in AudioDecoder::get_audio */
+BOOST_AUTO_TEST_CASE (audio_decoder_get_audio_test)
+{
+ shared_ptr<Film> film = new_test_film ("audio_decoder_test");
+
+ content.reset (new TestAudioContent (film));
+ decoder.reset (new TestAudioDecoder (content));
+
+ /* Simple reads */
+ check (0, 48000);
+ check (44, 9123);
+ check (9991, 22);
+
+ /* Read off the end */
+
+ AudioFrame const from = content->resampled_audio_frame_rate() * 61;
+ AudioFrame const length = content->resampled_audio_frame_rate() * 4;
+ shared_ptr<ContentAudio> ca = get (from, length);
+
+ for (int i = 0; i < content->audio_channels(); ++i) {
+ for (int j = 0; j < ca->audio->frames(); ++j) {
+ BOOST_CHECK_EQUAL (ca->audio->data(i)[j], j + from);
+ assert (ca->audio->data(i)[j] == j + from);
+ }
+ }
+}
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/audio_delay_test.cc
+ * @brief Test encode using some SndfileContents which have audio delays.
+ *
+ * The output is checked algorithmically using knowledge of the input.
+ */
+
#include <boost/test/unit_test.hpp>
-#include <libdcp/sound_frame.h>
-#include <libdcp/cpl.h>
-#include <libdcp/reel.h>
-#include <libdcp/sound_asset.h>
+#include <dcp/sound_frame.h>
+#include <dcp/cpl.h>
+#include <dcp/reel.h>
+#include <dcp/sound_mxf.h>
+#include <dcp/reel_sound_asset.h>
#include "lib/sndfile_content.h"
#include "lib/dcp_content_type.h"
#include "lib/ratio.h"
boost::filesystem::path path = "build/test";
path /= film_name;
path /= film->dcp_name ();
- libdcp::DCP check (path.string ());
+ dcp::DCP check (path.string ());
check.read ();
- shared_ptr<const libdcp::SoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
+ shared_ptr<const dcp::ReelSoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
BOOST_CHECK (sound_asset);
/* Sample index in the DCP */
/* Delay in frames */
int const delay_in_frames = delay_in_ms * 48000 / 1000;
- while (n < sound_asset->intrinsic_duration()) {
- shared_ptr<const libdcp::SoundFrame> sound_frame = sound_asset->get_frame (frame++);
+ while (n < sound_asset->mxf()->intrinsic_duration()) {
+ shared_ptr<const dcp::SoundFrame> sound_frame = sound_asset->mxf()->get_frame (frame++);
uint8_t const * d = sound_frame->data ();
- for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->channels())) {
+ for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->mxf()->channels())) {
/* Mono input so it will appear on centre */
int const sample = d[i + 7] | (d[i + 8] << 8);
}
}
-
/* Test audio delay when specified in a piece of audio content */
BOOST_AUTO_TEST_CASE (audio_delay_test)
{
*/
+/** @file test/audio_mapping_test.cc
+ * @brief Basic tests of the AudioMapping class, which itself doesn't really do much.
+ */
+
#include <boost/test/unit_test.hpp>
#include "lib/audio_mapping.h"
#include "lib/util.h"
-/* Basic tests of the AudioMapping class, which itself
- doesn't really do much.
-*/
BOOST_AUTO_TEST_CASE (audio_mapping_test)
{
AudioMapping none;
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < MAX_DCP_AUDIO_CHANNELS; ++j) {
- BOOST_CHECK_EQUAL (four.get (i, static_cast<libdcp::Channel> (j)), i == j ? 1 : 0);
+ BOOST_CHECK_EQUAL (four.get (i, static_cast<dcp::Channel> (j)), i == j ? 1 : 0);
}
}
- four.set (0, libdcp::RIGHT, 1);
- BOOST_CHECK_EQUAL (four.get (0, libdcp::RIGHT), 1);
+ four.set (0, dcp::RIGHT, 1);
+ BOOST_CHECK_EQUAL (four.get (0, dcp::RIGHT), 1);
}
+++ /dev/null
-/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
-
- This program 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.
-
- This program 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 this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <boost/test/unit_test.hpp>
-#include <boost/bind.hpp>
-#include <boost/function.hpp>
-#include <boost/signals2.hpp>
-#include "lib/audio_merger.h"
-#include "lib/audio_buffers.h"
-
-using boost::shared_ptr;
-using boost::bind;
-
-static shared_ptr<const AudioBuffers> last_audio;
-
-static int
-pass_through (int x)
-{
- return x;
-}
-
-BOOST_AUTO_TEST_CASE (audio_merger_test1)
-{
- AudioMerger<int, int> merger (1, bind (&pass_through, _1), boost::bind (&pass_through, _1));
-
- /* Push 64 samples, 0 -> 63 at time 0 */
- shared_ptr<AudioBuffers> buffers (new AudioBuffers (1, 64));
- for (int i = 0; i < 64; ++i) {
- buffers->data()[0][i] = i;
- }
- merger.push (buffers, 0);
-
- /* Push 64 samples, 0 -> 63 at time 22 */
- merger.push (buffers, 22);
-
- TimedAudioBuffers<int> tb = merger.pull (22);
- BOOST_CHECK (tb.audio != shared_ptr<const AudioBuffers> ());
- BOOST_CHECK_EQUAL (tb.audio->frames(), 22);
- BOOST_CHECK_EQUAL (tb.time, 0);
-
- /* And they should be a staircase */
- for (int i = 0; i < 22; ++i) {
- BOOST_CHECK_EQUAL (tb.audio->data()[0][i], i);
- }
-
- tb = merger.flush ();
-
- /* That flush should give us 64 samples at 22 */
- BOOST_CHECK_EQUAL (tb.audio->frames(), 64);
- BOOST_CHECK_EQUAL (tb.time, 22);
-
- /* Check the sample values */
- for (int i = 0; i < 64; ++i) {
- int correct = i;
- if (i < (64 - 22)) {
- correct += i + 22;
- }
- BOOST_CHECK_EQUAL (tb.audio->data()[0][i], correct);
- }
-}
-
-BOOST_AUTO_TEST_CASE (audio_merger_test2)
-{
- AudioMerger<int, int> merger (1, bind (&pass_through, _1), boost::bind (&pass_through, _1));
-
- /* Push 64 samples, 0 -> 63 at time 9 */
- shared_ptr<AudioBuffers> buffers (new AudioBuffers (1, 64));
- for (int i = 0; i < 64; ++i) {
- buffers->data()[0][i] = i;
- }
- merger.push (buffers, 9);
-
- TimedAudioBuffers<int> tb = merger.pull (9);
- BOOST_CHECK_EQUAL (tb.audio->frames(), 9);
- BOOST_CHECK_EQUAL (tb.time, 0);
-
- for (int i = 0; i < 9; ++i) {
- BOOST_CHECK_EQUAL (tb.audio->data()[0][i], 0);
- }
-
- tb = merger.flush ();
-
- /* That flush should give us 64 samples at 9 */
- BOOST_CHECK_EQUAL (tb.audio->frames(), 64);
- BOOST_CHECK_EQUAL (tb.time, 9);
-
- /* Check the sample values */
- for (int i = 0; i < 64; ++i) {
- BOOST_CHECK_EQUAL (tb.audio->data()[0][i], i);
- }
-}
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "test.h"
/** @file test/black_fill_test.cc
- * @brief Test insertion of black frames between video content.
+ * @brief Test insertion of black frames between separate bits of video content.
*/
using boost::shared_ptr;
film->examine_and_add_content (contentB);
wait_for_jobs ();
- contentA->set_video_length (3);
- contentA->set_position (film->video_frames_to_time (2));
- contentB->set_video_length (1);
- contentB->set_position (film->video_frames_to_time (7));
+ contentA->set_video_length (ContentTime::from_frames (3, 24));
+ contentA->set_position (DCPTime::from_frames (2, film->video_frame_rate ()));
+ contentB->set_video_length (ContentTime::from_frames (1, 24));
+ contentB->set_position (DCPTime::from_frames (7, film->video_frame_rate ()));
film->make_dcp ();
*/
+/** @file test/client_server_test.cc
+ * @brief Test the server class.
+ *
+ * Create a test image and then encode it using the standard mechanism
+ * and also using a Server object running on localhost. Compare the resulting
+ * encoded data to check that they are the same.
+ */
+
#include <boost/test/unit_test.hpp>
#include <boost/thread.hpp>
#include "lib/server.h"
BOOST_CHECK (remotely_encoded);
BOOST_CHECK_EQUAL (locally_encoded->size(), remotely_encoded->size());
- BOOST_CHECK (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()) == 0);
+ BOOST_CHECK_EQUAL (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()), 0);
}
BOOST_AUTO_TEST_CASE (client_server_test)
{
- shared_ptr<Image> image (new Image (PIX_FMT_RGB24, libdcp::Size (1998, 1080), true));
+ shared_ptr<Image> image (new Image (PIX_FMT_RGB24, dcp::Size (1998, 1080), true));
uint8_t* p = image->data()[0];
for (int y = 0; y < 1080; ++y) {
p += image->stride()[0];
}
- shared_ptr<Image> sub_image (new Image (PIX_FMT_RGBA, libdcp::Size (100, 200), true));
+ shared_ptr<Image> sub_image (new Image (PIX_FMT_RGBA, dcp::Size (100, 200), true));
p = sub_image->data()[0];
for (int y = 0; y < 200; ++y) {
uint8_t* q = p;
new PlayerVideoFrame (
shared_ptr<ImageProxy> (new RawImageProxy (image)),
Crop (),
- libdcp::Size (1998, 1080),
- libdcp::Size (1998, 1080),
+ dcp::Size (1998, 1080),
+ dcp::Size (1998, 1080),
Scaler::from_id ("bicubic"),
EYES_BOTH,
PART_WHOLE,
)
);
- pvf->set_subtitle (sub_image, Position<int> (50, 60));
+ pvf->set_subtitle (PositionImage (sub_image, Position<int> (50, 60)));
shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test.log"));
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/colour_conversion_test.cc
+ * @brief Basic test of identifier() for ColourConversion (i.e. a hash of the numbers)
+ */
+
#include <boost/test/unit_test.hpp>
-#include <libdcp/colour_matrix.h>
+#include <dcp/colour_matrix.h>
#include "lib/colour_conversion.h"
using std::cout;
-/* Basic test of identifier() for ColourConversion (i.e. a hash of the numbers) */
BOOST_AUTO_TEST_CASE (colour_conversion_test)
{
- ColourConversion A (2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6);
- ColourConversion B (2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6);
+ ColourConversion A (2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6);
+ ColourConversion B (2.4, false, dcp::colour_matrix::srgb_to_xyz, 2.6);
BOOST_CHECK_EQUAL (A.identifier(), "246ff9b7dc32c0488948a32a713924b3");
BOOST_CHECK_EQUAL (B.identifier(), "a8d1da30f96a121d8db06a03409758b3");
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/ffmpeg_audio_test.cc
+ * @brief A simple test of reading audio from an FFmpeg file.
+ */
+
#include <boost/test/unit_test.hpp>
-#include <libdcp/cpl.h>
-#include <libdcp/dcp.h>
-#include <libdcp/sound_asset.h>
-#include <libdcp/sound_frame.h>
-#include <libdcp/reel.h>
+#include <dcp/cpl.h>
+#include <dcp/dcp.h>
+#include <dcp/sound_mxf.h>
+#include <dcp/sound_frame.h>
+#include <dcp/reel_sound_asset.h>
+#include <dcp/reel.h>
#include "lib/sndfile_content.h"
#include "lib/film.h"
#include "lib/dcp_content_type.h"
boost::filesystem::path path = "build/test";
path /= "ffmpeg_audio_test";
path /= film->dcp_name ();
- libdcp::DCP check (path.string ());
+ dcp::DCP check (path.string ());
check.read ();
- shared_ptr<const libdcp::SoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
+ shared_ptr<const dcp::ReelSoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
BOOST_CHECK (sound_asset);
- BOOST_CHECK (sound_asset->channels () == 6);
+ BOOST_CHECK_EQUAL (sound_asset->mxf()->channels (), 6);
/* Sample index in the DCP */
int n = 0;
/* DCP sound asset frame */
int frame = 0;
- while (n < sound_asset->intrinsic_duration()) {
- shared_ptr<const libdcp::SoundFrame> sound_frame = sound_asset->get_frame (frame++);
+ while (n < sound_asset->mxf()->intrinsic_duration()) {
+ shared_ptr<const dcp::SoundFrame> sound_frame = sound_asset->mxf()->get_frame (frame++);
uint8_t const * d = sound_frame->data ();
- for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->channels())) {
+ for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->mxf()->channels())) {
- if (sound_asset->channels() > 0) {
+ if (sound_asset->mxf()->channels() > 0) {
/* L should be silent */
int const sample = d[i + 0] | (d[i + 1] << 8);
BOOST_CHECK_EQUAL (sample, 0);
}
- if (sound_asset->channels() > 1) {
+ if (sound_asset->mxf()->channels() > 1) {
/* R should be silent */
int const sample = d[i + 2] | (d[i + 3] << 8);
BOOST_CHECK_EQUAL (sample, 0);
}
- if (sound_asset->channels() > 2) {
+ if (sound_asset->mxf()->channels() > 2) {
/* Mono input so it will appear on centre */
int const sample = d[i + 7] | (d[i + 8] << 8);
BOOST_CHECK_EQUAL (sample, n);
}
- if (sound_asset->channels() > 3) {
+ if (sound_asset->mxf()->channels() > 3) {
/* Lfe should be silent */
int const sample = d[i + 9] | (d[i + 10] << 8);
BOOST_CHECK_EQUAL (sample, 0);
}
- if (sound_asset->channels() > 4) {
+ if (sound_asset->mxf()->channels() > 4) {
/* Ls should be silent */
int const sample = d[i + 11] | (d[i + 12] << 8);
BOOST_CHECK_EQUAL (sample, 0);
}
- if (sound_asset->channels() > 5) {
+ if (sound_asset->mxf()->channels() > 5) {
/* Rs should be silent */
int const sample = d[i + 13] | (d[i + 14] << 8);
BOOST_CHECK_EQUAL (sample, 0);
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/ffmpeg_dcp_test.cc
+ * @brief Test creation of a very simple DCP from some FFmpegContent (data/test.mp4).
+ *
+ * Also a quick test of Film::have_dcp ().
+ */
+
#include <boost/test/unit_test.hpp>
#include <boost/filesystem.hpp>
#include "lib/film.h"
using boost::shared_ptr;
-/** @file test/ffmpeg_dcp_test.cc
- * @brief Test scaling and black-padding of images from a still-image source.
- */
-
BOOST_AUTO_TEST_CASE (ffmpeg_dcp_test)
{
shared_ptr<Film> film = new_test_film ("ffmpeg_dcp_test");
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file test/ffmpeg_decoder_seek_test.cc
+ * @brief Check that get_video() returns the frame indexes that we ask for
+ * for FFmpegDecoder.
+ *
+ * This doesn't check that the contents of those frames are right, which
+ * it probably should.
+ */
+
+#include <vector>
+#include <boost/test/unit_test.hpp>
+#include <boost/filesystem.hpp>
+#include "lib/ffmpeg_content.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/log.h"
+#include "lib/film.h"
+#include "test.h"
+
+using std::cerr;
+using std::vector;
+using std::list;
+using boost::shared_ptr;
+using boost::optional;
+
+static void
+check (FFmpegDecoder& decoder, int frame)
+{
+ list<ContentVideo> v;
+ v = decoder.get_video (frame, true);
+ BOOST_CHECK (v.size() == 1);
+ BOOST_CHECK_EQUAL (v.front().frame, frame);
+}
+
+static void
+test (boost::filesystem::path file, vector<int> frames)
+{
+ boost::filesystem::path path = private_data / file;
+ if (!boost::filesystem::exists (path)) {
+ cerr << "Skipping test: " << path.string() << " not found.\n";
+ return;
+ }
+
+ shared_ptr<Film> film = new_test_film ("ffmpeg_decoder_seek_test_" + file.string());
+ shared_ptr<FFmpegContent> content (new FFmpegContent (film, path));
+ film->examine_and_add_content (content);
+ wait_for_jobs ();
+ shared_ptr<Log> log (new NullLog);
+ FFmpegDecoder decoder (content, log);
+
+ for (vector<int>::const_iterator i = frames.begin(); i != frames.end(); ++i) {
+ check (decoder, *i);
+ }
+}
+
+BOOST_AUTO_TEST_CASE (ffmpeg_decoder_seek_test)
+{
+ vector<int> frames;
+
+ frames.clear ();
+ frames.push_back (0);
+ frames.push_back (42);
+ frames.push_back (999);
+ frames.push_back (0);
+
+ test ("boon_telly.mkv", frames);
+ test ("Sintel_Trailer1.480p.DivX_Plus_HD.mkv", frames);
+
+ frames.clear ();
+ frames.push_back (15);
+ frames.push_back (42);
+ frames.push_back (999);
+ frames.push_back (15);
+
+ test ("prophet_clip.mkv", frames);
+}
+
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file test/ffmpeg_decoder_sequential_test.cc
+ * @brief Check that the FFmpeg decoder produces sequential frames without gaps or dropped frames;
+ * (dropped frames being checked by assert() in VideoDecoder). Also that the decoder picks up frame rates correctly.
+ */
+
+#include <boost/test/unit_test.hpp>
+#include <boost/filesystem.hpp>
+#include "lib/ffmpeg_content.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/log.h"
+#include "lib/film.h"
+#include "test.h"
+
+using std::cout;
+using std::cerr;
+using std::list;
+using boost::shared_ptr;
+using boost::optional;
+
+/** @param black Frame index of first frame in the video */
+static void
+test (boost::filesystem::path file, float fps, int first)
+{
+ boost::filesystem::path path = private_data / file;
+ if (!boost::filesystem::exists (path)) {
+ cerr << "Skipping test: " << path.string() << " not found.\n";
+ return;
+ }
+
+ shared_ptr<Film> film = new_test_film ("ffmpeg_decoder_seek_test_" + file.string());
+ shared_ptr<FFmpegContent> content (new FFmpegContent (film, path));
+ film->examine_and_add_content (content);
+ wait_for_jobs ();
+ shared_ptr<Log> log (new NullLog);
+ FFmpegDecoder decoder (content, log);
+
+ BOOST_CHECK_CLOSE (decoder.video_content()->video_frame_rate(), fps, 0.01);
+
+ VideoFrame const N = decoder.video_content()->video_length().frames (decoder.video_content()->video_frame_rate ());
+ decoder.test_gaps = 0;
+ for (VideoFrame i = 0; i < N; ++i) {
+ list<ContentVideo> v;
+ v = decoder.get_video (i, true);
+ if (i < first) {
+ BOOST_CHECK (v.empty ());
+ } else {
+ BOOST_CHECK (v.size() == 1);
+ BOOST_CHECK_EQUAL (v.front().frame, i);
+ }
+ }
+ BOOST_CHECK_EQUAL (decoder.test_gaps, 0);
+}
+
+BOOST_AUTO_TEST_CASE (ffmpeg_decoder_sequential_test)
+{
+ test ("boon_telly.mkv", 29.97, 0);
+ test ("Sintel_Trailer1.480p.DivX_Plus_HD.mkv", 24, 0);
+ test ("prophet_clip.mkv", 23.976, 12);
+}
+
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/ffmpeg_examiner_test.cc
+ * @brief Check that the FFmpegExaminer can extract the first video and audio time
+ * correctly from data/count300bd24.m2ts.
+ */
+
#include <boost/test/unit_test.hpp>
#include "lib/ffmpeg_examiner.h"
#include "lib/ffmpeg_content.h"
shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/count300bd24.m2ts"));
shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (content));
- BOOST_CHECK_EQUAL (examiner->first_video().get(), 600);
+ BOOST_CHECK_EQUAL (examiner->first_video().get(), ContentTime::from_seconds (600));
BOOST_CHECK_EQUAL (examiner->audio_streams().size(), 1);
- BOOST_CHECK_EQUAL (examiner->audio_streams()[0]->first_audio.get(), 600);
+ BOOST_CHECK_EQUAL (examiner->audio_streams()[0]->first_audio.get(), ContentTime::from_seconds (600));
}
+++ /dev/null
-/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
-
- This program 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.
-
- This program 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 this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <boost/test/unit_test.hpp>
-#include "lib/film.h"
-#include "lib/ffmpeg_decoder.h"
-#include "lib/ffmpeg_content.h"
-#include "test.h"
-
-using boost::shared_ptr;
-
-BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
-{
- shared_ptr<Film> film = new_test_film ("ffmpeg_pts_offset_test");
- shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/test.mp4"));
- content->_audio_stream.reset (new FFmpegAudioStream);
- content->_video_frame_rate = 24;
-
- {
- /* Sound == video so no offset required */
- content->_first_video = 0;
- content->_audio_stream->first_audio = 0;
- FFmpegDecoder decoder (film, content, true, true);
- BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
- BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
- }
-
- {
- /* Common offset should be removed */
- content->_first_video = 600;
- content->_audio_stream->first_audio = 600;
- FFmpegDecoder decoder (film, content, true, true);
- BOOST_CHECK_EQUAL (decoder._pts_offset, -600);
- BOOST_CHECK_EQUAL (decoder._pts_offset, -600);
- }
-
- {
- /* Video is on a frame boundary */
- content->_first_video = 1.0 / 24.0;
- content->_audio_stream->first_audio = 0;
- FFmpegDecoder decoder (film, content, true, true);
- BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
- BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
- }
-
- {
- /* Video is off a frame boundary */
- double const frame = 1.0 / 24.0;
- content->_first_video = frame + 0.0215;
- content->_audio_stream->first_audio = 0;
- FFmpegDecoder decoder (film, content, true, true);
- BOOST_CHECK_CLOSE (decoder._pts_offset, (frame - 0.0215), 0.00001);
- BOOST_CHECK_CLOSE (decoder._pts_offset, (frame - 0.0215), 0.00001);
- }
-
- {
- /* Video is off a frame boundary and both have a common offset */
- double const frame = 1.0 / 24.0;
- content->_first_video = frame + 0.0215 + 4.1;
- content->_audio_stream->first_audio = 4.1;
- FFmpegDecoder decoder (film, content, true, true);
- BOOST_CHECK_EQUAL (decoder._pts_offset, (frame - 0.0215) - 4.1);
- BOOST_CHECK_EQUAL (decoder._pts_offset, (frame - 0.0215) - 4.1);
- }
-}
--- /dev/null
+/*
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file test/ffmpeg_pts_offset_test.cc
+ * @brief Check the computation of _pts_offset in FFmpegDecoder.
+ */
+
+#include <boost/test/unit_test.hpp>
+#include "lib/film.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/ffmpeg_content.h"
+#include "test.h"
+
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
+{
+ shared_ptr<Film> film = new_test_film ("ffmpeg_pts_offset_test");
+ shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/test.mp4"));
+ content->_audio_stream.reset (new FFmpegAudioStream);
+ content->_video_frame_rate = 24;
+
+ {
+ /* Sound == video so no offset required */
+ content->_first_video = ContentTime ();
+ content->_audio_stream->first_audio = ContentTime ();
+ FFmpegDecoder decoder (content, film->log());
+ BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime ());
+ }
+
+ {
+ /* Common offset should be removed */
+ content->_first_video = ContentTime::from_seconds (600);
+ content->_audio_stream->first_audio = ContentTime::from_seconds (600);
+ FFmpegDecoder decoder (content, film->log());
+ BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime::from_seconds (-600));
+ }
+
+ {
+ /* Video is on a frame boundary */
+ content->_first_video = ContentTime::from_frames (1, 24);
+ content->_audio_stream->first_audio = ContentTime ();
+ FFmpegDecoder decoder (content, film->log());
+ BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime ());
+ }
+
+ {
+ /* Video is off a frame boundary */
+ double const frame = 1.0 / 24.0;
+ content->_first_video = ContentTime::from_seconds (frame + 0.0215);
+ content->_audio_stream->first_audio = ContentTime ();
+ FFmpegDecoder decoder (content, film->log());
+ BOOST_CHECK_CLOSE (decoder._pts_offset.seconds(), (frame - 0.0215), 0.00001);
+ }
+
+ {
+ /* Video is off a frame boundary and both have a common offset */
+ double const frame = 1.0 / 24.0;
+ content->_first_video = ContentTime::from_seconds (frame + 0.0215 + 4.1);
+ content->_audio_stream->first_audio = ContentTime::from_seconds (4.1);
+ FFmpegDecoder decoder (content, film->log());
+ BOOST_CHECK_CLOSE (decoder._pts_offset.seconds(), (frame - 0.0215) - 4.1, 0.1);
+ }
+}
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/file_group_test.cc
+ * @brief Check that FileGroup works.
+ */
+
#include <stdint.h>
#include <cstdio>
#include <boost/test/unit_test.hpp>
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/film_metadata_test.cc
+ * @brief Test some basic reading/writing of film metadata.
+ */
+
#include <sstream>
#include <boost/test/unit_test.hpp>
#include <boost/filesystem.hpp>
BOOST_AUTO_TEST_CASE (film_metadata_test)
{
- string const test_film = "build/test/film_metadata_test";
-
- if (boost::filesystem::exists (test_film)) {
- boost::filesystem::remove_all (test_film);
- }
+ shared_ptr<Film> f = new_test_film ("film_metadata_test");
+ boost::filesystem::path dir = test_film_dir ("film_metadata_test");
- shared_ptr<Film> f (new Film (test_film));
f->_dci_date = boost::gregorian::from_undelimited_string ("20130211");
BOOST_CHECK (f->container() == 0);
BOOST_CHECK (f->dcp_content_type() == 0);
list<string> ignore;
ignore.push_back ("Key");
- check_xml ("test/data/metadata.xml.ref", test_film + "/metadata.xml", ignore);
+ check_xml ("test/data/metadata.xml.ref", dir.string() + "/metadata.xml", ignore);
- shared_ptr<Film> g (new Film (test_film));
+ shared_ptr<Film> g (new Film (dir));
g->read_metadata ();
BOOST_CHECK_EQUAL (g->name(), "fred");
BOOST_CHECK_EQUAL (g->container(), Ratio::from_id ("185"));
g->write_metadata ();
- check_xml ("test/data/metadata.xml.ref", test_film + "/metadata.xml", ignore);
+ check_xml ("test/data/metadata.xml.ref", dir.string() + "/metadata.xml", ignore);
}
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/frame_rate_test.cc
+ * @brief Tests for FrameRateChange and the computation of the best
+ * frame rate for the DCP.
+ */
+
#include <boost/test/unit_test.hpp>
#include "lib/film.h"
#include "lib/config.h"
using boost::shared_ptr;
-/* Test Playlist::best_dcp_frame_rate and FrameRateConversion
+/* Test Playlist::best_dcp_frame_rate and FrameRateChange
with a single piece of content.
*/
BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single)
content->_video_frame_rate = 60;
int best = film->playlist()->best_dcp_frame_rate ();
- FrameRateConversion frc = FrameRateConversion (60, best);
+ FrameRateChange frc = FrameRateChange (60, best);
BOOST_CHECK_EQUAL (best, 30);
BOOST_CHECK_EQUAL (frc.skip, true);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 50;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (50, best);
+ frc = FrameRateChange (50, best);
BOOST_CHECK_EQUAL (best, 25);
BOOST_CHECK_EQUAL (frc.skip, true);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 48;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (48, best);
+ frc = FrameRateChange (48, best);
BOOST_CHECK_EQUAL (best, 24);
BOOST_CHECK_EQUAL (frc.skip, true);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 30;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (30, best);
+ frc = FrameRateChange (30, best);
BOOST_CHECK_EQUAL (best, 30);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 29.97;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (29.97, best);
+ frc = FrameRateChange (29.97, best);
BOOST_CHECK_EQUAL (best, 30);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, true);
+ BOOST_CHECK_CLOSE (frc.speed_up, 30 / 29.97, 0.1);
content->_video_frame_rate = 25;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (25, best);
+ frc = FrameRateChange (25, best);
BOOST_CHECK_EQUAL (best, 25);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 24;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (24, best);
+ frc = FrameRateChange (24, best);
BOOST_CHECK_EQUAL (best, 24);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 14.5;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (14.5, best);
+ frc = FrameRateChange (14.5, best);
BOOST_CHECK_EQUAL (best, 30);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 2);
BOOST_CHECK_EQUAL (frc.change_speed, true);
+ BOOST_CHECK_CLOSE (frc.speed_up, 15 / 14.5, 0.1);
content->_video_frame_rate = 12.6;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (12.6, best);
+ frc = FrameRateChange (12.6, best);
BOOST_CHECK_EQUAL (best, 25);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 2);
BOOST_CHECK_EQUAL (frc.change_speed, true);
+ BOOST_CHECK_CLOSE (frc.speed_up, 25 / 25.2, 0.1);
content->_video_frame_rate = 12.4;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (12.4, best);
+ frc = FrameRateChange (12.4, best);
BOOST_CHECK_EQUAL (best, 25);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 2);
BOOST_CHECK_EQUAL (frc.change_speed, true);
+ BOOST_CHECK_CLOSE (frc.speed_up, 25 / 24.8, 0.1);
content->_video_frame_rate = 12;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (12, best);
+ frc = FrameRateChange (12, best);
BOOST_CHECK_EQUAL (best, 24);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 2);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
/* Now add some more rates and see if it will use them
in preference to skip/repeat.
content->_video_frame_rate = 60;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (60, best);
+ frc = FrameRateChange (60, best);
BOOST_CHECK_EQUAL (best, 60);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 50;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (50, best);
+ frc = FrameRateChange (50, best);
BOOST_CHECK_EQUAL (best, 50);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 48;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (48, best);
+ frc = FrameRateChange (48, best);
BOOST_CHECK_EQUAL (best, 48);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
/* Check some out-there conversions (not the best) */
- frc = FrameRateConversion (14.99, 24);
+ frc = FrameRateChange (14.99, 24);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 2);
BOOST_CHECK_EQUAL (frc.change_speed, true);
+ BOOST_CHECK_CLOSE (frc.speed_up, 24 / (2 * 14.99), 0.1);
/* Check some conversions with limited DCP targets */
content->_video_frame_rate = 25;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (25, best);
+ frc = FrameRateChange (25, best);
BOOST_CHECK_EQUAL (best, 24);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, true);
+ BOOST_CHECK_CLOSE (frc.speed_up, 24.0 / 25, 0.1);
}
-/* Test Playlist::best_dcp_frame_rate and FrameRateConversion
+/* Test Playlist::best_dcp_frame_rate and FrameRateChange
with two pieces of content.
*/
BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_double)
shared_ptr<Film> film = new_test_film ("audio_sampling_rate_test");
/* Get any piece of content, it doesn't matter what */
shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/test.mp4"));
- film->add_content (content);
+ film->examine_and_add_content (content);
wait_for_jobs ();
std::list<int> afr;
content->_video_frame_rate = 24;
film->set_video_frame_rate (24);
content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
- BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 48000);
+ BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 48000);
content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
- BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 48000);
+ BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 48000);
content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 80000, 0)));
- BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 96000);
+ BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 96000);
content->_video_frame_rate = 23.976;
film->set_video_frame_rate (24);
content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
- BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 47952);
+ BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 47952);
content->_video_frame_rate = 29.97;
film->set_video_frame_rate (30);
BOOST_CHECK_EQUAL (film->video_frame_rate (), 30);
content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
- BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 47952);
+ BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 47952);
content->_video_frame_rate = 25;
film->set_video_frame_rate (24);
content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
- BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 50000);
+ BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 50000);
content->_video_frame_rate = 25;
film->set_video_frame_rate (24);
content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
- BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), 50000);
+ BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 50000);
/* Check some out-there conversions (not the best) */
content->_video_frame_rate = 14.99;
film->set_video_frame_rate (25);
content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0)));
- /* The FrameRateConversion within output_audio_frame_rate should choose to double-up
+ /* The FrameRateChange within resampled_audio_frame_rate should choose to double-up
the 14.99 fps video to 30 and then run it slow at 25.
*/
- BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), rint (48000 * 2 * 14.99 / 25));
+ BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), rint (48000 * 2 * 14.99 / 25));
}
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/image_test.cc
+ * @brief Tests of the Image class.
+ *
+ * @see test/make_black_test.cc, test/pixel_formats_test.cc
+ */
+
#include <boost/test/unit_test.hpp>
#include <Magick++.h>
#include "lib/image.h"
BOOST_AUTO_TEST_CASE (aligned_image_test)
{
- Image* s = new Image (PIX_FMT_RGB24, libdcp::Size (50, 50), true);
+ Image* s = new Image (PIX_FMT_RGB24, dcp::Size (50, 50), true);
BOOST_CHECK_EQUAL (s->components(), 1);
/* 160 is 150 aligned to the nearest 32 bytes */
BOOST_CHECK_EQUAL (s->stride()[0], 160);
BOOST_CHECK (t->data() != s->data());
BOOST_CHECK (t->data()[0] != s->data()[0]);
BOOST_CHECK (t->line_size() != s->line_size());
- BOOST_CHECK (t->line_size()[0] == s->line_size()[0]);
+ BOOST_CHECK_EQUAL (t->line_size()[0], s->line_size()[0]);
BOOST_CHECK (t->stride() != s->stride());
- BOOST_CHECK (t->stride()[0] == s->stride()[0]);
+ BOOST_CHECK_EQUAL (t->stride()[0], s->stride()[0]);
/* assignment operator */
- Image* u = new Image (PIX_FMT_YUV422P, libdcp::Size (150, 150), false);
+ Image* u = new Image (PIX_FMT_YUV422P, dcp::Size (150, 150), false);
*u = *s;
BOOST_CHECK_EQUAL (u->components(), 1);
BOOST_CHECK_EQUAL (u->stride()[0], 160);
BOOST_CHECK (u->data() != s->data());
BOOST_CHECK (u->data()[0] != s->data()[0]);
BOOST_CHECK (u->line_size() != s->line_size());
- BOOST_CHECK (u->line_size()[0] == s->line_size()[0]);
+ BOOST_CHECK_EQUAL (u->line_size()[0], s->line_size()[0]);
BOOST_CHECK (u->stride() != s->stride());
- BOOST_CHECK (u->stride()[0] == s->stride()[0]);
+ BOOST_CHECK_EQUAL (u->stride()[0], s->stride()[0]);
delete s;
delete t;
BOOST_AUTO_TEST_CASE (compact_image_test)
{
- Image* s = new Image (PIX_FMT_RGB24, libdcp::Size (50, 50), false);
+ Image* s = new Image (PIX_FMT_RGB24, dcp::Size (50, 50), false);
BOOST_CHECK_EQUAL (s->components(), 1);
BOOST_CHECK_EQUAL (s->stride()[0], 50 * 3);
BOOST_CHECK_EQUAL (s->line_size()[0], 50 * 3);
BOOST_CHECK (t->data() != s->data());
BOOST_CHECK (t->data()[0] != s->data()[0]);
BOOST_CHECK (t->line_size() != s->line_size());
- BOOST_CHECK (t->line_size()[0] == s->line_size()[0]);
+ BOOST_CHECK_EQUAL (t->line_size()[0], s->line_size()[0]);
BOOST_CHECK (t->stride() != s->stride());
- BOOST_CHECK (t->stride()[0] == s->stride()[0]);
+ BOOST_CHECK_EQUAL (t->stride()[0], s->stride()[0]);
/* assignment operator */
- Image* u = new Image (PIX_FMT_YUV422P, libdcp::Size (150, 150), true);
+ Image* u = new Image (PIX_FMT_YUV422P, dcp::Size (150, 150), true);
*u = *s;
BOOST_CHECK_EQUAL (u->components(), 1);
BOOST_CHECK_EQUAL (u->stride()[0], 50 * 3);
BOOST_CHECK (u->data() != s->data());
BOOST_CHECK (u->data()[0] != s->data()[0]);
BOOST_CHECK (u->line_size() != s->line_size());
- BOOST_CHECK (u->line_size()[0] == s->line_size()[0]);
+ BOOST_CHECK_EQUAL (u->line_size()[0], s->line_size()[0]);
BOOST_CHECK (u->stride() != s->stride());
- BOOST_CHECK (u->stride()[0] == s->stride()[0]);
+ BOOST_CHECK_EQUAL (u->stride()[0], s->stride()[0]);
delete s;
delete t;
BOOST_AUTO_TEST_CASE (crop_image_test)
{
/* This was to check out a bug with valgrind, and is probably not very useful */
- shared_ptr<Image> image (new Image (PIX_FMT_YUV420P, libdcp::Size (16, 16), true));
+ shared_ptr<Image> image (new Image (PIX_FMT_YUV420P, dcp::Size (16, 16), true));
image->make_black ();
Crop crop;
crop.top = 3;
BOOST_AUTO_TEST_CASE (crop_image_test2)
{
/* Here's a 1998 x 1080 image which is black */
- shared_ptr<Image> image (new Image (PIX_FMT_YUV420P, libdcp::Size (1998, 1080), true));
+ shared_ptr<Image> image (new Image (PIX_FMT_YUV420P, dcp::Size (1998, 1080), true));
image->make_black ();
/* Crop it by 1 pixel */
read_file (string file)
{
Magick::Image magick_image (file.c_str ());
- libdcp::Size size (magick_image.columns(), magick_image.rows());
+ dcp::Size size (magick_image.columns(), magick_image.rows());
boost::shared_ptr<Image> image (new Image (PIX_FMT_RGB24, size, true));
static
void
-crop_scale_window_single (AVPixelFormat in_format, libdcp::Size in_size, Crop crop, libdcp::Size inter_size, libdcp::Size out_size)
+crop_scale_window_single (AVPixelFormat in_format, dcp::Size in_size, Crop crop, dcp::Size inter_size, dcp::Size out_size)
{
/* Set up our test image */
shared_ptr<Image> test (new Image (in_format, in_size, true));
/** Test Image::crop_scale_window against separate calls to crop/scale/copy */
BOOST_AUTO_TEST_CASE (crop_scale_window_test)
{
- crop_scale_window_single (AV_PIX_FMT_YUV422P, libdcp::Size (640, 480), Crop (), libdcp::Size (640, 480), libdcp::Size (640, 480));
- crop_scale_window_single (AV_PIX_FMT_YUV422P, libdcp::Size (640, 480), Crop (2, 4, 6, 8), libdcp::Size (640, 480), libdcp::Size (640, 480));
- crop_scale_window_single (AV_PIX_FMT_YUV422P, libdcp::Size (640, 480), Crop (2, 4, 6, 8), libdcp::Size (1920, 1080), libdcp::Size (1998, 1080));
- crop_scale_window_single (AV_PIX_FMT_YUV422P, libdcp::Size (640, 480), Crop (1, 4, 6, 8), libdcp::Size (1920, 1080), libdcp::Size (1998, 1080));
- crop_scale_window_single (AV_PIX_FMT_YUV420P, libdcp::Size (640, 480), Crop (16, 16, 0, 0), libdcp::Size (1920, 1080), libdcp::Size (1998, 1080));
- crop_scale_window_single (AV_PIX_FMT_YUV420P, libdcp::Size (640, 480), Crop (16, 3, 3, 0), libdcp::Size (1920, 1080), libdcp::Size (1998, 1080));
- crop_scale_window_single (AV_PIX_FMT_RGB24, libdcp::Size (1000, 800), Crop (0, 0, 0, 0), libdcp::Size (1920, 1080), libdcp::Size (1998, 1080));
- crop_scale_window_single (AV_PIX_FMT_RGB24, libdcp::Size (1000, 800), Crop (55, 0, 1, 9), libdcp::Size (1920, 1080), libdcp::Size (1998, 1080));
+ crop_scale_window_single (AV_PIX_FMT_YUV422P, dcp::Size (640, 480), Crop (), dcp::Size (640, 480), dcp::Size (640, 480));
+ crop_scale_window_single (AV_PIX_FMT_YUV422P, dcp::Size (640, 480), Crop (2, 4, 6, 8), dcp::Size (640, 480), dcp::Size (640, 480));
+ crop_scale_window_single (AV_PIX_FMT_YUV422P, dcp::Size (640, 480), Crop (2, 4, 6, 8), dcp::Size (1920, 1080), dcp::Size (1998, 1080));
+ crop_scale_window_single (AV_PIX_FMT_YUV422P, dcp::Size (640, 480), Crop (1, 4, 6, 8), dcp::Size (1920, 1080), dcp::Size (1998, 1080));
+ crop_scale_window_single (AV_PIX_FMT_YUV420P, dcp::Size (640, 480), Crop (16, 16, 0, 0), dcp::Size (1920, 1080), dcp::Size (1998, 1080));
+ crop_scale_window_single (AV_PIX_FMT_YUV420P, dcp::Size (640, 480), Crop (16, 3, 3, 0), dcp::Size (1920, 1080), dcp::Size (1998, 1080));
+ crop_scale_window_single (AV_PIX_FMT_RGB24, dcp::Size (1000, 800), Crop (0, 0, 0, 0), dcp::Size (1920, 1080), dcp::Size (1998, 1080));
+ crop_scale_window_single (AV_PIX_FMT_RGB24, dcp::Size (1000, 800), Crop (55, 0, 1, 9), dcp::Size (1920, 1080), dcp::Size (1998, 1080));
}
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/job_test.cc
+ * @brief Basic tests of Job and JobManager.
+ */
+
#include <boost/test/unit_test.hpp>
#include "lib/job.h"
#include "lib/job_manager.h"
*/
+/** @file test/make_black_test.cc
+ * @brief Check that Image::make_black works, and doesn't use values which crash
+ * sws_scale().
+ *
+ * @see test/image_test.cc
+ */
+
#include <boost/test/unit_test.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
extern "C" {
#include <libavutil/pixfmt.h>
}
using std::list;
-/* Check that Image::make_black works, and doesn't use values which crash
- sws_scale().
-*/
BOOST_AUTO_TEST_CASE (make_black_test)
{
- libdcp::Size in_size (512, 512);
- libdcp::Size out_size (1024, 1024);
+ dcp::Size in_size (512, 512);
+ dcp::Size out_size (1024, 1024);
list<AVPixelFormat> pix_fmts;
pix_fmts.push_back (AV_PIX_FMT_RGB24);
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file src/pixel_formats_test.cc
+ * @brief Make sure that Image::lines() and Image::bytes_per_pixel() return the right
+ * things for various pixel formats.
+ *
+ * @see test/image_test.cc
+ */
+
#include <boost/test/unit_test.hpp>
#include <list>
extern "C" {
using std::list;
using std::cout;
+/** @struct Case
+ * @brief A test case for pixel_formats_test.
+ */
struct Case
{
Case (AVPixelFormat f, int c, int l0, int l1, int l2, float b0, float b1, float b2)
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file test/player_test.cc
+ * @brief Various tests of Player.
+ */
+
+#include <iostream>
+#include <boost/test/unit_test.hpp>
+#include "lib/film.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ratio.h"
+#include "lib/audio_buffers.h"
+#include "lib/player.h"
+#include "test.h"
+
+using std::cout;
+using std::list;
+using boost::shared_ptr;
+
+/** Player::overlaps */
+BOOST_AUTO_TEST_CASE (player_overlaps_test)
+{
+ shared_ptr<Film> film = new_test_film ("player_overlaps_test");
+ film->set_container (Ratio::from_id ("185"));
+ shared_ptr<FFmpegContent> A (new FFmpegContent (film, "test/data/test.mp4"));
+ shared_ptr<FFmpegContent> B (new FFmpegContent (film, "test/data/test.mp4"));
+ shared_ptr<FFmpegContent> C (new FFmpegContent (film, "test/data/test.mp4"));
+
+ film->examine_and_add_content (A);
+ film->examine_and_add_content (B);
+ film->examine_and_add_content (C);
+ wait_for_jobs ();
+
+ BOOST_CHECK_EQUAL (A->full_length(), DCPTime::from_seconds (3));
+
+ A->set_position (DCPTime::from_seconds (0));
+ B->set_position (DCPTime::from_seconds (10));
+ C->set_position (DCPTime::from_seconds (20));
+
+ shared_ptr<Player> player = film->make_player ();
+
+ list<shared_ptr<Piece> > o = player->overlaps<FFmpegContent> (DCPTime::from_seconds (0), DCPTime::from_seconds (5));
+ BOOST_CHECK_EQUAL (o.size(), 1);
+ BOOST_CHECK_EQUAL (o.front()->content, A);
+
+ o = player->overlaps<FFmpegContent> (DCPTime::from_seconds (5), DCPTime::from_seconds (8));
+ BOOST_CHECK_EQUAL (o.size(), 0);
+
+ o = player->overlaps<FFmpegContent> (DCPTime::from_seconds (8), DCPTime::from_seconds (12));
+ BOOST_CHECK_EQUAL (o.size(), 1);
+ BOOST_CHECK_EQUAL (o.front()->content, B);
+
+ o = player->overlaps<FFmpegContent> (DCPTime::from_seconds (2), DCPTime::from_seconds (12));
+ BOOST_CHECK_EQUAL (o.size(), 2);
+ BOOST_CHECK_EQUAL (o.front()->content, A);
+ BOOST_CHECK_EQUAL (o.back()->content, B);
+
+ o = player->overlaps<FFmpegContent> (DCPTime::from_seconds (8), DCPTime::from_seconds (11));
+ BOOST_CHECK_EQUAL (o.size(), 1);
+ BOOST_CHECK_EQUAL (o.front()->content, B);
+}
+
+/** Check that the Player correctly generates silence when used with a silent FFmpegContent */
+BOOST_AUTO_TEST_CASE (player_silence_padding_test)
+{
+ shared_ptr<Film> film = new_test_film ("player_silence_padding_test");
+ film->set_name ("player_silence_padding_test");
+ shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/test.mp4"));
+ film->set_container (Ratio::from_id ("185"));
+ film->set_audio_channels (6);
+
+ film->examine_and_add_content (c);
+ wait_for_jobs ();
+
+ shared_ptr<Player> player = film->make_player ();
+ shared_ptr<AudioBuffers> test = player->get_audio (DCPTime (0), DCPTime::from_seconds (1), true);
+ BOOST_CHECK_EQUAL (test->frames(), 48000);
+ BOOST_CHECK_EQUAL (test->channels(), film->audio_channels ());
+
+ for (int i = 0; i < test->frames(); ++i) {
+ for (int c = 0; c < test->channels(); ++c) {
+ BOOST_CHECK_EQUAL (test->data()[c][i], 0);
+ }
+ }
+}
+
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/ratio_test.cc
+ * @brief Test Ratio and fit_ratio_within().
+ */
+
#include <iostream>
#include <boost/test/unit_test.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
#include "lib/ratio.h"
#include "lib/util.h"
using std::ostream;
-namespace libdcp {
+namespace dcp {
ostream&
-operator<< (ostream& s, libdcp::Size const & t)
+operator<< (ostream& s, dcp::Size const & t)
{
s << t.width << "x" << t.height;
return s;
Ratio const * r = Ratio::from_id ("119");
BOOST_CHECK (r);
- BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1290, 1080));
+ BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1290, 1080));
r = Ratio::from_id ("133");
BOOST_CHECK (r);
- BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1440, 1080));
+ BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1440, 1080));
r = Ratio::from_id ("137");
BOOST_CHECK (r);
- BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1480, 1080));
+ BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1480, 1080));
r = Ratio::from_id ("138");
BOOST_CHECK (r);
- BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1485, 1080));
+ BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1485, 1080));
r = Ratio::from_id ("166");
BOOST_CHECK (r);
- BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1800, 1080));
+ BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1800, 1080));
r = Ratio::from_id ("178");
BOOST_CHECK (r);
- BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1920, 1080));
+ BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1920, 1080));
r = Ratio::from_id ("185");
BOOST_CHECK (r);
- BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1998, 1080));
+ BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1998, 1080));
r = Ratio::from_id ("239");
BOOST_CHECK (r);
- BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (2048, 858));
+ BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (2048, 858));
r = Ratio::from_id ("full-frame");
BOOST_CHECK (r);
- BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (2048, 1080));
+ BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (2048, 1080));
}
*/
+/** @file test/recover_test.cc
+ * @brief Test recovery of a DCP transcode after a crash.
+ */
+
#include <boost/test/unit_test.hpp>
-#include <libdcp/stereo_picture_asset.h>
+#include <dcp/stereo_picture_mxf.h>
#include "lib/film.h"
#include "lib/dcp_content_type.h"
#include "lib/image_content.h"
using boost::shared_ptr;
static void
-note (libdcp::NoteType, string n)
+note (dcp::NoteType t, string n)
{
- cout << n << "\n";
+ if (t == dcp::ERROR) {
+ cout << n << "\n";
+ }
}
-/** Test recovery of a DCP transcode after a crash */
BOOST_AUTO_TEST_CASE (recover_test)
{
shared_ptr<Film> film = new_test_film ("recover_test");
film->make_dcp ();
wait_for_jobs ();
+ boost::filesystem::path const video = "build/test/recover_test/video/185_2K_3651eded785682b85f4baca4b1d3b7a9_24_bicubic_200000000_P_S_3D.mxf";
+
boost::filesystem::copy_file (
- "build/test/recover_test/video/185_2K_58a090f8d70a2b410c534120d35e5256_24_bicubic_200000000_P_S_3D.mxf",
+ video,
"build/test/recover_test/original.mxf"
);
- boost::filesystem::resize_file ("build/test/recover_test/video/185_2K_58a090f8d70a2b410c534120d35e5256_24_bicubic_200000000_P_S_3D.mxf", 2 * 1024 * 1024);
+ boost::filesystem::resize_file (video, 2 * 1024 * 1024);
film->make_dcp ();
wait_for_jobs ();
- shared_ptr<libdcp::StereoPictureAsset> A (new libdcp::StereoPictureAsset ("build/test/recover_test", "original.mxf"));
- shared_ptr<libdcp::StereoPictureAsset> B (new libdcp::StereoPictureAsset ("build/test/recover_test/video", "185_2K_58a090f8d70a2b410c534120d35e5256_24_bicubic_200000000_P_S_3D.mxf"));
+ shared_ptr<dcp::StereoPictureMXF> A (new dcp::StereoPictureMXF ("build/test/recover_test/original.mxf"));
+ shared_ptr<dcp::StereoPictureMXF> B (new dcp::StereoPictureMXF (video));
- libdcp::EqualityOptions eq;
+ dcp::EqualityOptions eq;
eq.mxf_names_can_differ = true;
BOOST_CHECK (A->equals (B, eq, boost::bind (¬e, _1, _2)));
}
--- /dev/null
+/*
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file test/repeat_frame_test.cc
+ * @brief Test the repeat of frames by the player when putting a 24fps
+ * source into a 48fps DCP.
+ *
+ * @see test/skip_frame_test.cc
+ */
+
+#include <boost/test/unit_test.hpp>
+#include "test.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/dcp_content_type.h"
+
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (repeat_frame_test)
+{
+ shared_ptr<Film> film = new_test_film ("repeat_frame_test");
+ film->set_name ("repeat_frame_test");
+ film->set_container (Ratio::from_id ("185"));
+ film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+ shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/red_24.mp4"));
+ c->set_scale (VideoContentScale (Ratio::from_id ("185")));
+ film->examine_and_add_content (c);
+
+ wait_for_jobs ();
+
+ film->set_video_frame_rate (48);
+ film->make_dcp ();
+ wait_for_jobs ();
+
+ check_dcp ("test/data/repeat_frame_test", film->dir (film->dcp_name ()));
+}
+
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/resampler_test.cc
+ * @brief Check that the timings that come back from the resampler correspond
+ * to the number of samples it generates.
+ */
+
#include <boost/test/unit_test.hpp>
#include "lib/audio_buffers.h"
#include "lib/resampler.h"
/* 3 hours */
int64_t const N = int64_t (from) * 60 * 60 * 3;
-
+
+ /* XXX: no longer checks anything */
for (int64_t i = 0; i < N; i += 1000) {
shared_ptr<AudioBuffers> a (new AudioBuffers (1, 1000));
a->make_silent ();
- pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> r = resamp.run (a, i);
- BOOST_CHECK_EQUAL (r.second, total_out);
- total_out += r.first->frames ();
+ shared_ptr<const AudioBuffers> r = resamp.run (a);
+ total_out += r->frames ();
}
}
-/** Check that the timings that come back from the resampler correspond
- to the number of samples it generates.
-*/
BOOST_AUTO_TEST_CASE (resampler_test)
{
resampler_test_one (44100, 48000);
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/scaling_test.cc
+ * @brief Test scaling and black-padding of images from a still-image source.
+ */
+
#include <boost/test/unit_test.hpp>
#include "lib/image_content.h"
#include "lib/ratio.h"
#include "lib/dcp_content_type.h"
#include "test.h"
-/** @file test/scaling_test.cc
- * @brief Test scaling and black-padding of images from a still-image source.
- */
-
using std::string;
using boost::shared_ptr;
wait_for_jobs ();
- imc->set_video_length (1);
+ imc->set_video_length (ContentTime::from_frames (1, 24));
scaling_test_for (film, imc, "133", "185");
scaling_test_for (film, imc, "185", "185");
--- /dev/null
+/*
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file test/seek_zero_test.cc
+ * @brief Test seek to zero with a raw FFmpegDecoder (without the player
+ * confusing things as it might in ffmpeg_seek_test).
+ */
+
+#include <boost/test/unit_test.hpp>
+#include "lib/film.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/ratio.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/content_video.h"
+#include "test.h"
+
+using std::cout;
+using std::list;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+using boost::optional;
+
+BOOST_AUTO_TEST_CASE (seek_zero_test)
+{
+ shared_ptr<Film> film = new_test_film ("seek_zero_test");
+ film->set_name ("seek_zero_test");
+ film->set_container (Ratio::from_id ("185"));
+ film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+ shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/count300bd48.m2ts"));
+ content->set_scale (VideoContentScale (Ratio::from_id ("185")));
+ film->examine_and_add_content (content);
+ wait_for_jobs ();
+
+ /* Work out the first video frame index that we will be given, taking into account
+ * the difference between first video and first audio.
+ */
+ ContentTime video_delay = content->first_video().get() - content->audio_stream()->first_audio.get();
+ if (video_delay < ContentTime ()) {
+ video_delay = ContentTime ();
+ }
+
+ VideoFrame const first_frame = video_delay.round_up (content->video_frame_rate ()).frames (content->video_frame_rate ());
+
+ FFmpegDecoder decoder (content, film->log());
+ list<ContentVideo> a = decoder.get_video (first_frame, true);
+ BOOST_CHECK (a.size() == 1);
+ BOOST_CHECK_EQUAL (a.front().frame, first_frame);
+}
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/silence_padding_test.cc
+ * @brief Test the padding (with silence) of a mono source to a 6-channel DCP.
+ */
+
#include <boost/test/unit_test.hpp>
-#include <libdcp/cpl.h>
-#include <libdcp/dcp.h>
-#include <libdcp/sound_asset.h>
-#include <libdcp/sound_frame.h>
-#include <libdcp/reel.h>
+#include <dcp/cpl.h>
+#include <dcp/dcp.h>
+#include <dcp/sound_mxf.h>
+#include <dcp/sound_frame.h>
+#include <dcp/reel.h>
+#include <dcp/reel_sound_asset.h>
#include "lib/sndfile_content.h"
#include "lib/film.h"
#include "lib/dcp_content_type.h"
using boost::lexical_cast;
using boost::shared_ptr;
-static void test_silence_padding (int channels)
+static void
+test_silence_padding (int channels)
{
string const film_name = "silence_padding_test_" + lexical_cast<string> (channels);
shared_ptr<Film> film = new_test_film (film_name);
boost::filesystem::path path = "build/test";
path /= film_name;
path /= film->dcp_name ();
- libdcp::DCP check (path.string ());
+ dcp::DCP check (path.string ());
check.read ();
- shared_ptr<const libdcp::SoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
+ shared_ptr<const dcp::ReelSoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
BOOST_CHECK (sound_asset);
- BOOST_CHECK (sound_asset->channels () == channels);
+ BOOST_CHECK_EQUAL (sound_asset->mxf()->channels (), channels);
/* Sample index in the DCP */
int n = 0;
/* DCP sound asset frame */
int frame = 0;
- while (n < sound_asset->intrinsic_duration()) {
- shared_ptr<const libdcp::SoundFrame> sound_frame = sound_asset->get_frame (frame++);
+ while (n < sound_asset->mxf()->intrinsic_duration()) {
+ shared_ptr<const dcp::SoundFrame> sound_frame = sound_asset->mxf()->get_frame (frame++);
uint8_t const * d = sound_frame->data ();
- for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->channels())) {
+ for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->mxf()->channels())) {
- if (sound_asset->channels() > 0) {
+ if (sound_asset->mxf()->channels() > 0) {
/* L should be silent */
int const sample = d[i + 0] | (d[i + 1] << 8);
BOOST_CHECK_EQUAL (sample, 0);
}
- if (sound_asset->channels() > 1) {
+ if (sound_asset->mxf()->channels() > 1) {
/* R should be silent */
int const sample = d[i + 2] | (d[i + 3] << 8);
BOOST_CHECK_EQUAL (sample, 0);
}
- if (sound_asset->channels() > 2) {
+ if (sound_asset->mxf()->channels() > 2) {
/* Mono input so it will appear on centre */
int const sample = d[i + 7] | (d[i + 8] << 8);
BOOST_CHECK_EQUAL (sample, n);
}
- if (sound_asset->channels() > 3) {
+ if (sound_asset->mxf()->channels() > 3) {
/* Lfe should be silent */
int const sample = d[i + 9] | (d[i + 10] << 8);
BOOST_CHECK_EQUAL (sample, 0);
}
- if (sound_asset->channels() > 4) {
+ if (sound_asset->mxf()->channels() > 4) {
/* Ls should be silent */
int const sample = d[i + 11] | (d[i + 12] << 8);
BOOST_CHECK_EQUAL (sample, 0);
}
- if (sound_asset->channels() > 5) {
+ if (sound_asset->mxf()->channels() > 5) {
/* Rs should be silent */
int const sample = d[i + 13] | (d[i + 14] << 8);
BOOST_CHECK_EQUAL (sample, 0);
--- /dev/null
+/*
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file test/skip_frame_test.cc
+ * @brief Test the skip of frames by the player when putting a 48fps
+ * source into a 24fps DCP.
+ *
+ * @see test/repeat_frame_test.cc
+ */
+
+#include <boost/test/unit_test.hpp>
+#include "test.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/dcp_content_type.h"
+
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (skip_frame_test)
+{
+ shared_ptr<Film> film = new_test_film ("skip_frame_test");
+ film->set_name ("skip_frame_test");
+ film->set_container (Ratio::from_id ("185"));
+ film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+ shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/count300bd48.m2ts"));
+ c->set_scale (VideoContentScale (Ratio::from_id ("185")));
+ film->examine_and_add_content (c);
+
+ wait_for_jobs ();
+ film->write_metadata ();
+
+ film->set_video_frame_rate (24);
+ film->make_dcp ();
+ wait_for_jobs ();
+
+ check_dcp ("test/data/skip_frame_test", film->dir (film->dcp_name ()));
+}
+
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @test test/stream_test.cc
+ * @brief Some simple tests of FFmpegAudioStream.
+ */
+
#include <boost/test/unit_test.hpp>
#include <libxml++/libxml++.h>
#include <libcxml/cxml.h>
map->add_child("DCP")->add_child_text ("2");
}
- FFmpegAudioStream a (shared_ptr<cxml::Node> (new cxml::Node (root)), 5);
+ FFmpegAudioStream a (cxml::NodePtr (new cxml::Node (root)), 5);
BOOST_CHECK_EQUAL (a.identifier(), "4");
BOOST_CHECK_EQUAL (a.frame_rate, 44100);
BOOST_CHECK_EQUAL (a.name, "hello there world");
BOOST_CHECK_EQUAL (a.mapping.content_channels(), 2);
- BOOST_CHECK_EQUAL (a.mapping.get (0, libdcp::LEFT), 1);
- BOOST_CHECK_EQUAL (a.mapping.get (0, libdcp::RIGHT), 0);
- BOOST_CHECK_EQUAL (a.mapping.get (0, libdcp::CENTRE), 1);
- BOOST_CHECK_EQUAL (a.mapping.get (1, libdcp::LEFT), 0);
- BOOST_CHECK_EQUAL (a.mapping.get (1, libdcp::RIGHT), 1);
- BOOST_CHECK_EQUAL (a.mapping.get (1, libdcp::CENTRE), 1);
+ BOOST_CHECK_EQUAL (a.mapping.get (0, dcp::LEFT), 1);
+ BOOST_CHECK_EQUAL (a.mapping.get (0, dcp::RIGHT), 0);
+ BOOST_CHECK_EQUAL (a.mapping.get (0, dcp::CENTRE), 1);
+ BOOST_CHECK_EQUAL (a.mapping.get (1, dcp::LEFT), 0);
+ BOOST_CHECK_EQUAL (a.mapping.get (1, dcp::RIGHT), 1);
+ BOOST_CHECK_EQUAL (a.mapping.get (1, dcp::CENTRE), 1);
}
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file test/subrip_test.cc
+ * @brief Various tests of the subrip code.
+ */
+
+#include <boost/test/unit_test.hpp>
+#include <dcp/subtitle_content.h>
+#include "lib/subrip.h"
+#include "lib/subrip_content.h"
+#include "lib/subrip_decoder.h"
+#include "lib/render_subtitles.h"
+#include "test.h"
+
+using std::list;
+using std::vector;
+using std::string;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+
+/** Test SubRip::convert_time */
+BOOST_AUTO_TEST_CASE (subrip_time_test)
+{
+ BOOST_CHECK_EQUAL (SubRip::convert_time ("00:03:10,500"), ContentTime::from_seconds ((3 * 60) + 10 + 0.5));
+ BOOST_CHECK_EQUAL (SubRip::convert_time ("04:19:51,782"), ContentTime::from_seconds ((4 * 3600) + (19 * 60) + 51 + 0.782));
+}
+
+/** Test SubRip::convert_coordinate */
+BOOST_AUTO_TEST_CASE (subrip_coordinate_test)
+{
+ BOOST_CHECK_EQUAL (SubRip::convert_coordinate ("foo:42"), 42);
+ BOOST_CHECK_EQUAL (SubRip::convert_coordinate ("X1:999"), 999);
+}
+
+/** Test SubRip::convert_content */
+BOOST_AUTO_TEST_CASE (subrip_content_test)
+{
+ list<string> c;
+ list<SubRipSubtitlePiece> p;
+
+ c.push_back ("Hello world");
+ p = SubRip::convert_content (c);
+ BOOST_CHECK_EQUAL (p.size(), 1);
+ BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+ c.clear ();
+
+ c.push_back ("<b>Hello world</b>");
+ p = SubRip::convert_content (c);
+ BOOST_CHECK_EQUAL (p.size(), 1);
+ BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+ BOOST_CHECK_EQUAL (p.front().bold, true);
+ c.clear ();
+
+ c.push_back ("<i>Hello world</i>");
+ p = SubRip::convert_content (c);
+ BOOST_CHECK_EQUAL (p.size(), 1);
+ BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+ BOOST_CHECK_EQUAL (p.front().italic, true);
+ c.clear ();
+
+ c.push_back ("<u>Hello world</u>");
+ p = SubRip::convert_content (c);
+ BOOST_CHECK_EQUAL (p.size(), 1);
+ BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+ BOOST_CHECK_EQUAL (p.front().underline, true);
+ c.clear ();
+
+ c.push_back ("{b}Hello world{/b}");
+ p = SubRip::convert_content (c);
+ BOOST_CHECK_EQUAL (p.size(), 1);
+ BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+ BOOST_CHECK_EQUAL (p.front().bold, true);
+ c.clear ();
+
+ c.push_back ("{i}Hello world{/i}");
+ p = SubRip::convert_content (c);
+ BOOST_CHECK_EQUAL (p.size(), 1);
+ BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+ BOOST_CHECK_EQUAL (p.front().italic, true);
+ c.clear ();
+
+ c.push_back ("{u}Hello world{/u}");
+ p = SubRip::convert_content (c);
+ BOOST_CHECK_EQUAL (p.size(), 1);
+ BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+ BOOST_CHECK_EQUAL (p.front().underline, true);
+ c.clear ();
+
+ c.push_back ("<b>This is <i>nesting</i> of subtitles</b>");
+ p = SubRip::convert_content (c);
+ BOOST_CHECK_EQUAL (p.size(), 3);
+ list<SubRipSubtitlePiece>::iterator i = p.begin ();
+ BOOST_CHECK_EQUAL (i->text, "This is ");
+ BOOST_CHECK_EQUAL (i->bold, true);
+ BOOST_CHECK_EQUAL (i->italic, false);
+ ++i;
+ BOOST_CHECK_EQUAL (i->text, "nesting");
+ BOOST_CHECK_EQUAL (i->bold, true);
+ BOOST_CHECK_EQUAL (i->italic, true);
+ ++i;
+ BOOST_CHECK_EQUAL (i->text, " of subtitles");
+ BOOST_CHECK_EQUAL (i->bold, true);
+ BOOST_CHECK_EQUAL (i->italic, false);
+ ++i;
+ c.clear ();
+}
+
+/** Test parsing of full SubRip file content */
+BOOST_AUTO_TEST_CASE (subrip_parse_test)
+{
+ shared_ptr<Film> film = new_test_film ("subrip_parse_test");
+ shared_ptr<SubRipContent> content (new SubRipContent (film, "test/data/subrip.srt"));
+ content->examine (shared_ptr<Job> ());
+ BOOST_CHECK_EQUAL (content->full_length(), DCPTime::from_seconds ((3 * 60) + 56.471));
+
+ SubRip s (content);
+
+ vector<SubRipSubtitle>::const_iterator i = s._subtitles.begin();
+
+ BOOST_CHECK (i != s._subtitles.end ());
+ BOOST_CHECK_EQUAL (i->from, ContentTime::from_seconds ((1 * 60) + 49.200));
+ BOOST_CHECK_EQUAL (i->to, ContentTime::from_seconds ((1 * 60) + 52.351));
+ BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+ BOOST_CHECK_EQUAL (i->pieces.front().text, "This is a subtitle, and it goes over two lines.");
+
+ ++i;
+ BOOST_CHECK (i != s._subtitles.end ());
+ BOOST_CHECK_EQUAL (i->from, ContentTime::from_seconds ((1 * 60) + 52.440));
+ BOOST_CHECK_EQUAL (i->to, ContentTime::from_seconds ((1 * 60) + 54.351));
+ BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+ BOOST_CHECK_EQUAL (i->pieces.front().text, "We have emboldened this");
+ BOOST_CHECK_EQUAL (i->pieces.front().bold, true);
+
+ ++i;
+ BOOST_CHECK (i != s._subtitles.end ());
+ BOOST_CHECK_EQUAL (i->from, ContentTime::from_seconds ((1 * 60) + 54.440));
+ BOOST_CHECK_EQUAL (i->to, ContentTime::from_seconds ((1 * 60) + 56.590));
+ BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+ BOOST_CHECK_EQUAL (i->pieces.front().text, "And italicised this.");
+ BOOST_CHECK_EQUAL (i->pieces.front().italic, true);
+
+ ++i;
+ BOOST_CHECK (i != s._subtitles.end ());
+ BOOST_CHECK_EQUAL (i->from, ContentTime::from_seconds ((1 * 60) + 56.680));
+ BOOST_CHECK_EQUAL (i->to, ContentTime::from_seconds ((1 * 60) + 58.955));
+ BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+ BOOST_CHECK_EQUAL (i->pieces.front().text, "Shall I compare thee to a summers' day?");
+
+ ++i;
+ BOOST_CHECK (i != s._subtitles.end ());
+ BOOST_CHECK_EQUAL (i->from, ContentTime::from_seconds ((2 * 60) + 0.840));
+ BOOST_CHECK_EQUAL (i->to, ContentTime::from_seconds ((2 * 60) + 3.400));
+ BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+ BOOST_CHECK_EQUAL (i->pieces.front().text, "Is this a dagger I see before me?");
+
+ ++i;
+ BOOST_CHECK (i != s._subtitles.end ());
+ BOOST_CHECK_EQUAL (i->from, ContentTime::from_seconds ((3 * 60) + 54.560));
+ BOOST_CHECK_EQUAL (i->to, ContentTime::from_seconds ((3 * 60) + 56.471));
+ BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+ BOOST_CHECK_EQUAL (i->pieces.front().text, "Hello world.");
+
+ ++i;
+ BOOST_CHECK (i == s._subtitles.end ());
+}
+
+/** Test rendering of a SubRip subtitle */
+BOOST_AUTO_TEST_CASE (subrip_render_test)
+{
+ shared_ptr<Film> film = new_test_film ("subrip_render_test");
+ shared_ptr<SubRipContent> content (new SubRipContent (film, "test/data/subrip.srt"));
+ content->examine (shared_ptr<Job> ());
+ BOOST_CHECK_EQUAL (content->full_length(), DCPTime::from_seconds ((3 * 60) + 56.471));
+
+ shared_ptr<SubRipDecoder> decoder (new SubRipDecoder (content));
+ list<shared_ptr<ContentTextSubtitle> > cts = decoder->get_text_subtitles (ContentTime::from_seconds (109), ContentTime::from_seconds (110));
+ BOOST_CHECK_EQUAL (cts.size(), 1);
+
+ PositionImage image = render_subtitles (cts.front()->subs, dcp::Size (1998, 1080));
+ write_image (image.image, "build/test/subrip_render_test.png");
+ check_file ("build/test/subrip_render_test.png", "test/data/subrip_render_test.png");
+}
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/test.cc
+ * @brief Overall test stuff and useful methods for tests.
+ */
+
#include <vector>
#include <list>
+#include <Magick++.h>
#include <libxml++/libxml++.h>
-#include <libdcp/dcp.h>
+#include <dcp/dcp.h>
#include "lib/config.h"
#include "lib/util.h"
#include "lib/ui_signaller.h"
#include "lib/job.h"
#include "lib/cross.h"
#include "lib/server_finder.h"
+#include "lib/image.h"
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE dcpomatic_test
#include <boost/test/unit_test.hpp>
using std::list;
using boost::shared_ptr;
+boost::filesystem::path private_data = boost::filesystem::path ("test") / boost::filesystem::path ("private");
+
class TestUISignaller : public UISignaller
{
public:
struct TestConfig
{
- TestConfig()
+ TestConfig ()
{
- dcpomatic_setup();
+ dcpomatic_setup ();
Config::instance()->set_num_local_encoding_threads (1);
Config::instance()->set_server_port_base (61920);
Config::instance()->set_default_dci_metadata (DCIMetadata ());
Config::instance()->set_default_container (static_cast<Ratio*> (0));
Config::instance()->set_default_dcp_content_type (static_cast<DCPContentType*> (0));
+ Config::instance()->set_default_audio_delay (0);
ServerFinder::instance()->disable ();
ui_signaller = new TestUISignaller ();
}
+
+ ~TestConfig ()
+ {
+ JobManager::drop ();
+ }
};
BOOST_GLOBAL_FIXTURE (TestConfig);
check_file (boost::filesystem::path ref, boost::filesystem::path check)
{
uintmax_t N = boost::filesystem::file_size (ref);
- BOOST_CHECK_EQUAL (N, boost::filesystem::file_size(check));
- FILE* ref_file = fopen_boost (ref, "rb");
+ BOOST_CHECK_EQUAL (N, boost::filesystem::file_size (check));
+ FILE* ref_file = fopen (ref.c_str(), "rb");
BOOST_CHECK (ref_file);
FILE* check_file = fopen_boost (check, "rb");
BOOST_CHECK (check_file);
}
static void
-note (libdcp::NoteType t, string n)
+note (dcp::NoteType t, string n)
{
- if (t == libdcp::ERROR) {
+ if (t == dcp::ERROR) {
cerr << n << "\n";
}
}
void
-check_dcp (string ref, string check)
+check_dcp (boost::filesystem::path ref, boost::filesystem::path check)
{
- libdcp::DCP ref_dcp (ref);
+ dcp::DCP ref_dcp (ref);
ref_dcp.read ();
- libdcp::DCP check_dcp (check);
+ dcp::DCP check_dcp (check);
check_dcp.read ();
- libdcp::EqualityOptions options;
+ dcp::EqualityOptions options;
options.max_mean_pixel_error = 5;
options.max_std_dev_pixel_error = 5;
options.max_audio_sample_error = 255;
- options.cpl_names_can_differ = true;
+ options.cpl_annotation_texts_can_differ = true;
options.mxf_names_can_differ = true;
BOOST_CHECK (ref_dcp.equals (check_dcp, options, boost::bind (note, _1, _2)));
ui_signaller->ui_idle ();
}
if (jm->errors ()) {
+ int N = 0;
for (list<shared_ptr<Job> >::iterator i = jm->_jobs.begin(); i != jm->_jobs.end(); ++i) {
if ((*i)->finished_in_error ()) {
- cerr << (*i)->error_summary () << "\n"
- << (*i)->error_details () << "\n";
+ ++N;
+ }
+ }
+ cerr << N << " errors.\n";
+
+ for (list<shared_ptr<Job> >::iterator i = jm->_jobs.begin(); i != jm->_jobs.end(); ++i) {
+ if ((*i)->finished_in_error ()) {
+ cerr << (*i)->name() << ":\n"
+ << "\tsummary: " << (*i)->error_summary () << "\n"
+ << "\tdetails: " << (*i)->error_details () << "\n";
}
}
}
ui_signaller->ui_idle ();
}
+
+void
+write_image (shared_ptr<const Image> image, boost::filesystem::path file)
+{
+ using namespace MagickCore;
+
+ Magick::Image m (image->size().width, image->size().height, "ARGB", CharPixel, (void *) image->data()[0]);
+ m.write (file.string ());
+}
#include <boost/filesystem.hpp>
class Film;
+class Image;
+
+extern boost::filesystem::path private_data;
extern void wait_for_jobs ();
extern boost::shared_ptr<Film> new_test_film (std::string);
-extern void check_dcp (std::string, std::string);
+extern void check_dcp (boost::filesystem::path, boost::filesystem::path);
+extern void check_file (boost::filesystem::path ref, boost::filesystem::path check);
extern void check_xml (boost::filesystem::path, boost::filesystem::path, std::list<std::string>);
extern void check_file (boost::filesystem::path, boost::filesystem::path);
extern boost::filesystem::path test_film_dir (std::string);
+extern void write_image (boost::shared_ptr<const Image> image, boost::filesystem::path file);
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/threed_test.cc
+ * @brief Create a 3D DCP (without comparing the result to anything).
+ */
+
#include <boost/test/unit_test.hpp>
#include "test.h"
#include "lib/film.h"
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
+/** @file test/util_test.cc
+ * @brief Test various utility methods.
+ */
+
#include <boost/test/unit_test.hpp>
#include "lib/util.h"
#include "lib/exceptions.h"
BOOST_CHECK_THROW (md5_digest (p, shared_ptr<Job> ()), std::runtime_error);
}
+/* Straightforward test of DCPTime::round_up */
+BOOST_AUTO_TEST_CASE (dcptime_round_up_test)
+{
+ BOOST_CHECK_EQUAL (DCPTime (0).round_up (DCPTime::HZ / 2), DCPTime (0));
+ BOOST_CHECK_EQUAL (DCPTime (1).round_up (DCPTime::HZ / 2), DCPTime (2));
+ BOOST_CHECK_EQUAL (DCPTime (2).round_up (DCPTime::HZ / 2), DCPTime (2));
+ BOOST_CHECK_EQUAL (DCPTime (3).round_up (DCPTime::HZ / 2), DCPTime (4));
+
+ BOOST_CHECK_EQUAL (DCPTime (0).round_up (DCPTime::HZ / 42), DCPTime (0));
+ BOOST_CHECK_EQUAL (DCPTime (1).round_up (DCPTime::HZ / 42), DCPTime (42));
+ BOOST_CHECK_EQUAL (DCPTime (42).round_up (DCPTime::HZ / 42), DCPTime (42));
+ BOOST_CHECK_EQUAL (DCPTime (43).round_up (DCPTime::HZ / 42), DCPTime (84));
+
+ /* Check that rounding up to non-integer frame rates works */
+ BOOST_CHECK_EQUAL (DCPTime (45312).round_up (29.976), DCPTime (48045));
+}
+
+
BOOST_AUTO_TEST_CASE (divide_with_round_test)
{
BOOST_CHECK_EQUAL (divide_with_round (0, 4), 0);
""", msg = 'Checking for boost unit testing library', lib = 'boost_unit_test_framework%s' % boost_test_suffix, uselib_store = 'BOOST_TEST')
def build(bld):
- obj = bld(features = 'cxx cxxprogram')
+ obj = bld(features='cxx cxxprogram')
obj.name = 'unit-tests'
obj.uselib = 'BOOST_TEST BOOST_THREAD DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
obj.use = 'libdcpomatic'
obj.source = """
4k_test.cc
audio_analysis_test.cc
+ audio_buffers_test.cc
audio_delay_test.cc
+ audio_decoder_test.cc
audio_mapping_test.cc
- audio_merger_test.cc
black_fill_test.cc
client_server_test.cc
colour_conversion_test.cc
ffmpeg_audio_test.cc
ffmpeg_dcp_test.cc
+ ffmpeg_decoder_seek_test.cc
+ ffmpeg_decoder_sequential_test.cc
ffmpeg_examiner_test.cc
- ffmpeg_pts_offset.cc
+ ffmpeg_pts_offset_test.cc
file_group_test.cc
film_metadata_test.cc
frame_rate_test.cc
image_test.cc
job_test.cc
make_black_test.cc
+ player_test.cc
pixel_formats_test.cc
- play_test.cc
ratio_test.cc
+ repeat_frame_test.cc
recover_test.cc
resampler_test.cc
scaling_test.cc
+ seek_zero_test.cc
silence_padding_test.cc
+ skip_frame_test.cc
stream_test.cc
+ subrip_test.cc
test.cc
threed_test.cc
util_test.cc
obj.target = 'unit-tests'
obj.install_path = ''
+
+ obj = bld(features='cxx cxxprogram')
+ obj.name = 'long-unit-tests'
+ obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
+ obj.use = 'libdcpomatic'
+ obj.source = """
+ test.cc
+ """
+
+ obj.target = 'long-unit-tests'
+ obj.install_path = ''
import sys
APPNAME = 'dcpomatic'
-VERSION = '1.69.9devel'
+VERSION = '2.0.0devel'
def options(opt):
opt.load('compiler_cxx')
conf.check_cfg(package='libopenjpeg', args='--cflags --libs', max_version='1.5.2', mandatory=True)
def static_dcp(conf, static_boost, static_xmlpp, static_xmlsec, static_ssh):
- conf.check_cfg(package='libdcp', atleast_version='0.92', args='--cflags', uselib_store='DCP', mandatory=True)
+ conf.check_cfg(package='libdcp-1.0', atleast_version='0.92', args='--cflags', uselib_store='DCP', mandatory=True)
conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
- conf.env.STLIB_DCP = ['dcp', 'asdcp-libdcp', 'kumu-libdcp']
+ conf.env.STLIB_DCP = ['dcp-1.0', 'asdcp-libdcp', 'kumu-libdcp']
conf.env.LIB_DCP = ['glibmm-2.4', 'ssl', 'crypto', 'bz2', 'xslt']
if static_boost:
conf.env.LIB_DCP.append('ssh')
def dynamic_dcp(conf):
- conf.check_cfg(package='libdcp', atleast_version='0.92', args='--cflags --libs', uselib_store='DCP', mandatory=True)
+ conf.check_cfg(package='libdcp-1.0', atleast_version='0.92', args='--cflags --libs', uselib_store='DCP', mandatory=True)
conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
def dynamic_ssh(conf):
conf.check_cfg(package='glib-2.0', args='--cflags --libs', uselib_store='GLIB', mandatory=True)
conf.check_cfg(package= '', path=conf.options.magickpp_config, args='--cppflags --cxxflags --libs', uselib_store='MAGICK', mandatory=True)
conf.check_cfg(package='libzip', args='--cflags --libs', uselib_store='ZIP', mandatory=True)
+ conf.check_cfg(package='pangomm-1.4', args='--cflags --libs', uselib_store='PANGOMM', mandatory=True)
+ conf.check_cfg(package='cairomm-1.0', args='--cflags --libs', uselib_store='CAIROMM', mandatory=True)
conf.check_cc(fragment="""
#include <glib.h>
bld.recurse('src')
def tags(bld):
- os.system('etags src/lib/*.cc src/lib/*.h src/wx/*.cc src/wx/*.h src/tools/*.cc src/tools/*.h')
+ os.system('etags src/lib/*.cc src/lib/*.h src/wx/*.cc src/wx/*.h src/tools/*.cc src/tools/*.h test/*.cc')