diff options
208 files changed, 7630 insertions, 3985 deletions
@@ -1,3 +1,7 @@ +2014-03-07 Carl Hetherington <cth@carlh.net> + + * Add subtitle view. + 2014-06-18 Carl Hetherington <cth@carlh.net> * Version 1.69.29 released. @@ -1,110 +1,121 @@ -# 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 @@ -112,174 +123,204 @@ 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 @@ -287,67 +328,61 @@ 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 @@ -356,341 +391,394 @@ 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. @@ -699,14 +787,16 @@ EXCLUDE = # 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 = @@ -715,765 +805,1080 @@ 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 = @@ -1481,106 +1886,128 @@ 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 @@ -1588,222 +2015,293 @@ 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 @@ -142,7 +142,7 @@ def make_control(debian_version, bits, filename, debug): def dependencies(target): return (('ffmpeg-cdist', 'bba68a5'), - ('libdcp', 'v0.95.0')) + ('libdcp', '1.0')) def build(target, options): cmd = './waf configure --prefix=%s' % target.work_dir_cscript() diff --git a/doc/design/resampling.tex b/doc/design/resampling.tex index 44aeee9b1..cf9cfb1ed 100644 --- a/doc/design/resampling.tex +++ b/doc/design/resampling.tex @@ -1,4 +1,5 @@ \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$. @@ -18,6 +19,7 @@ $C_V$ is a DCI rate, $C_A$ is not. e.g.\ if $C_V = 24$, $C_A = 44.1\times{}10^3 \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$, @@ -31,5 +33,24 @@ resample audio to $25 * 48\times{}10^3 / 24 = 50\times{}10^3$. \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} diff --git a/doc/mainpage.txt b/doc/mainpage.txt index 649c9c609..a1cd9bac8 100644 --- a/doc/mainpage.txt +++ b/doc/mainpage.txt @@ -1,7 +1,7 @@ /** @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 @@ -26,12 +26,8 @@ * 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/ */ diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc index bfe0ed61f..af58e77ac 100644 --- a/src/lib/analyse_audio_job.cc +++ b/src/lib/analyse_audio_job.cc @@ -18,6 +18,7 @@ */ #include "audio_analysis.h" +#include "audio_buffers.h" #include "analyse_audio_job.h" #include "compose.hpp" #include "film.h" @@ -65,19 +66,18 @@ AnalyseAudioJob::run () 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 ()); @@ -87,7 +87,7 @@ AnalyseAudioJob::run () } 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) { diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h index 3e376634c..4d657951b 100644 --- a/src/lib/analyse_audio_job.h +++ b/src/lib/analyse_audio_job.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,13 +17,25 @@ */ +/** @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: @@ -34,10 +46,10 @@ 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; diff --git a/src/lib/audio_analysis.h b/src/lib/audio_analysis.h index 824472dda..b91a1cf51 100644 --- a/src/lib/audio_analysis.h +++ b/src/lib/audio_analysis.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @file src/lib/audio_analysis.h + * @brief AudioAnalysis and AudioPoint classes. + */ + #ifndef DCPOMATIC_AUDIO_ANALYSIS_H #define DCPOMATIC_AUDIO_ANALYSIS_H @@ -24,6 +28,9 @@ #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: @@ -48,6 +55,14 @@ private: 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: diff --git a/src/lib/audio_buffers.cc b/src/lib/audio_buffers.cc index a1c9b81ac..4ada94db8 100644 --- a/src/lib/audio_buffers.cc +++ b/src/lib/audio_buffers.cc @@ -73,6 +73,9 @@ AudioBuffers::~AudioBuffers () void AudioBuffers::allocate (int channels, int frames) { + assert (frames >= 0); + assert (channels >= 0); + _channels = channels; _frames = frames; _allocated_frames = frames; @@ -172,6 +175,11 @@ AudioBuffers::make_silent (int from, int 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); @@ -255,6 +263,8 @@ void 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) { diff --git a/src/lib/audio_buffers.h b/src/lib/audio_buffers.h index c9030dbfa..51488c39a 100644 --- a/src/lib/audio_buffers.h +++ b/src/lib/audio_buffers.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,8 +17,12 @@ */ -#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> @@ -61,7 +65,7 @@ public: 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: diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc index 29d159a29..9f0d26573 100644 --- a/src/lib/audio_content.cc +++ b/src/lib/audio_content.cc @@ -1,5 +1,5 @@ /* - 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 @@ -18,7 +18,7 @@ */ #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" @@ -34,7 +34,7 @@ using std::cout; 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; @@ -43,7 +43,7 @@ int const AudioContentProperty::AUDIO_GAIN = 203; 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 ()) @@ -59,7 +59,7 @@ AudioContent::AudioContent (shared_ptr<const Film> f, boost::filesystem::path p) } -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"); @@ -148,24 +148,38 @@ AudioContent::audio_analysis_path () const 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::output_audio_frame_rate () const +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 (content_audio_frame_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). - skip/repeat doesn't come into effect here. */ if (frc.change_speed) { @@ -174,4 +188,3 @@ AudioContent::output_audio_frame_rate () const return rint (t); } - diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h index 2c324a3a4..336a98629 100644 --- a/src/lib/audio_content.h +++ b/src/lib/audio_content.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @file src/lib/audio_content.h + * @brief AudioContent and AudioContentProperty classes. + */ + #ifndef DCPOMATIC_AUDIO_CONTENT_H #define DCPOMATIC_AUDIO_CONTENT_H @@ -38,28 +42,34 @@ public: 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; + /** @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 output_audio_frame_rate () const; - + int resampled_audio_frame_rate () const; + boost::signals2::connection analyse_audio (boost::function<void()>); void set_audio_gain (double); diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index c0ef02f65..4a543cea9 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -1,5 +1,5 @@ /* - 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 @@ -22,6 +22,8 @@ #include "exceptions.h" #include "log.h" #include "resampler.h" +#include "util.h" +#include "film.h" #include "i18n.h" @@ -29,30 +31,136 @@ using std::stringstream; 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 (); } diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index ab6c4b8a9..0b0d306f6 100644 --- a/src/lib/audio_decoder.h +++ b/src/lib/audio_decoder.h @@ -1,5 +1,5 @@ /* - 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 @@ -27,8 +27,10 @@ #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. @@ -36,18 +38,32 @@ class AudioBuffers; 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 diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc index e35c1ae94..b3757c5f1 100644 --- a/src/lib/audio_mapping.cc +++ b/src/lib/audio_mapping.cc @@ -1,5 +1,5 @@ /* - 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 @@ -19,7 +19,7 @@ #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" #include "md5_digester.h" @@ -32,7 +32,7 @@ using std::string; 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) @@ -41,11 +41,11 @@ AudioMapping::AudioMapping () } /** 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 @@ -70,16 +70,16 @@ AudioMapping::make_default () 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")); @@ -87,14 +87,14 @@ AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node, int state_version /* 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 ()) ); } @@ -102,13 +102,13 @@ AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node, int state_version } 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]; } @@ -123,7 +123,7 @@ AudioMapping::as_xml (xmlpp::Node* node) const 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)))); } } } diff --git a/src/lib/audio_mapping.h b/src/lib/audio_mapping.h index b0b75ac06..8be8eeb6f 100644 --- a/src/lib/audio_mapping.h +++ b/src/lib/audio_mapping.h @@ -1,5 +1,5 @@ /* - 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 @@ -21,8 +21,9 @@ #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; @@ -41,8 +42,8 @@ class AudioMapping { public: AudioMapping (); - AudioMapping (int); - AudioMapping (boost::shared_ptr<const cxml::Node>, int); + AudioMapping (int channels); + AudioMapping (cxml::ConstNodePtr, int); /* Default copy constructor is fine */ @@ -50,8 +51,8 @@ public: 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; diff --git a/src/lib/audio_merger.h b/src/lib/audio_merger.h deleted file mode 100644 index 226601e0e..000000000 --- a/src/lib/audio_merger.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - 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; -}; diff --git a/src/lib/cinema.cc b/src/lib/cinema.cc index fca6b6afd..c8cb7b66c 100644 --- a/src/lib/cinema.cc +++ b/src/lib/cinema.cc @@ -24,7 +24,7 @@ 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")) { @@ -35,7 +35,7 @@ Cinema::Cinema (shared_ptr<const cxml::Node> node) 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) { @@ -67,10 +67,10 @@ Cinema::remove_screen (shared_ptr<Screen> s) _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 diff --git a/src/lib/cinema.h b/src/lib/cinema.h index 40dc15ae0..74357f65d 100644 --- a/src/lib/cinema.h +++ b/src/lib/cinema.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,32 +17,45 @@ */ +/** @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: @@ -51,9 +64,9 @@ 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; diff --git a/src/lib/colour_conversion.cc b/src/lib/colour_conversion.cc index f8675900e..aacefaa05 100644 --- a/src/lib/colour_conversion.cc +++ b/src/lib/colour_conversion.cc @@ -18,8 +18,8 @@ */ #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" @@ -35,7 +35,7 @@ using std::cout; using std::vector; using boost::shared_ptr; using boost::optional; -using libdcp::raw_convert; +using dcp::raw_convert; ColourConversion::ColourConversion () : input_gamma (2.4) @@ -45,7 +45,7 @@ ColourConversion::ColourConversion () { 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]; } } } diff --git a/src/lib/config.cc b/src/lib/config.cc index 0c3dd023d..a0211386b 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -23,8 +23,8 @@ #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" @@ -50,7 +50,7 @@ using boost::shared_ptr; 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; @@ -82,9 +82,9 @@ Config::Config () _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 @@ -171,7 +171,7 @@ Config::read () /* 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"); diff --git a/src/lib/config.h b/src/lib/config.h index 671f53ef3..d82f52046 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -28,10 +28,9 @@ #include <boost/shared_ptr.hpp> #include <boost/signals2.hpp> #include <boost/filesystem.hpp> -#include <libdcp/metadata.h> +#include <dcp/metadata.h> #include "isdcf_metadata.h" #include "colour_conversion.h" -#include "server.h" class ServerDescription; class Scaler; @@ -141,7 +140,7 @@ public: return _default_dcp_content_type; } - libdcp::XMLMetadata dcp_metadata () const { + dcp::XMLMetadata dcp_metadata () const { return _dcp_metadata; } @@ -284,7 +283,7 @@ public: changed (); } - void set_dcp_metadata (libdcp::XMLMetadata m) { + void set_dcp_metadata (dcp::XMLMetadata m) { _dcp_metadata = m; changed (); } @@ -395,7 +394,7 @@ private: 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; diff --git a/src/lib/content.cc b/src/lib/content.cc index 7966219ff..bbbe9b6ce 100644 --- a/src/lib/content.cc +++ b/src/lib/content.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,10 +17,14 @@ */ +/** @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" @@ -38,7 +42,7 @@ using std::cout; using std::vector; using std::max; using boost::shared_ptr; -using libdcp::raw_convert; +using dcp::raw_convert; int const ContentProperty::PATH = 400; int const ContentProperty::POSITION = 401; @@ -56,7 +60,7 @@ Content::Content (shared_ptr<const Film> f) } -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) @@ -76,7 +80,7 @@ Content::Content (shared_ptr<const Film> f, boost::filesystem::path p) _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) { @@ -85,9 +89,9 @@ Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node) _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) @@ -98,11 +102,11 @@ 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.")); } @@ -121,9 +125,9 @@ Content::as_xml (xmlpp::Node* node) const 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 @@ -148,7 +152,7 @@ Content::signal_changed (int p) } void -Content::set_position (Time p) +Content::set_position (DCPTime p) { { boost::mutex::scoped_lock lm (_mutex); @@ -163,7 +167,7 @@ Content::set_position (Time p) } void -Content::set_trim_start (Time t) +Content::set_trim_start (DCPTime t) { { boost::mutex::scoped_lock lm (_mutex); @@ -174,7 +178,7 @@ Content::set_trim_start (Time t) } void -Content::set_trim_end (Time t) +Content::set_trim_end (DCPTime t) { { boost::mutex::scoped_lock lm (_mutex); @@ -206,22 +210,13 @@ Content::clone () const 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 max (int64_t (0), 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 max (DCPTime (), full_length() - trim_start() - trim_end()); } /** @return string which includes everything about how this content affects @@ -233,9 +228,9 @@ Content::identifier () const stringstream s; s << Content::digest() - << "_" << position() - << "_" << trim_start() - << "_" << trim_end(); + << "_" << position().get() + << "_" << trim_start().get() + << "_" << trim_end().get(); return s.str (); } diff --git a/src/lib/content.h b/src/lib/content.h index 596a0a905..6bbf33b35 100644 --- a/src/lib/content.h +++ b/src/lib/content.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @file src/lib/content.h + * @brief Content class. + */ + #ifndef DCPOMATIC_CONTENT_H #define DCPOMATIC_CONTENT_H @@ -26,7 +30,9 @@ #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; @@ -45,13 +51,16 @@ public: 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 () {} @@ -63,7 +72,7 @@ public: 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; @@ -95,41 +104,43 @@ public: 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::shared_ptr<const Film> film () const { + return _film.lock (); + } boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> Changed; @@ -139,8 +150,8 @@ 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 */ @@ -148,9 +159,9 @@ protected: private: std::string _digest; - Time _position; - Time _trim_start; - Time _trim_end; + DCPTime _position; + DCPTime _trim_start; + DCPTime _trim_end; bool _change_signals_frequent; }; diff --git a/src/lib/content_audio.h b/src/lib/content_audio.h new file mode 100644 index 000000000..535c0b491 --- /dev/null +++ b/src/lib/content_audio.h @@ -0,0 +1,44 @@ +/* + 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; +}; diff --git a/src/lib/content_factory.cc b/src/lib/content_factory.cc index 98b1dd859..789496d08 100644 --- a/src/lib/content_factory.cc +++ b/src/lib/content_factory.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,16 +17,28 @@ */ +/** @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) { @@ -40,20 +52,32 @@ content_factory (shared_ptr<const Film> film, cxml::NodePtr node, int version, l 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)); } diff --git a/src/lib/content_factory.h b/src/lib/content_factory.h index 2eeebbc9f..fae7648ea 100644 --- a/src/lib/content_factory.h +++ b/src/lib/content_factory.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @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> &); diff --git a/src/lib/content_subtitle.cc b/src/lib/content_subtitle.cc new file mode 100644 index 000000000..93e0677bb --- /dev/null +++ b/src/lib/content_subtitle.cc @@ -0,0 +1,31 @@ +/* + 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" + +ContentTimePeriod +ContentTextSubtitle::period () const +{ + /* XXX: assuming we have some subs and they are all at the same time */ + assert (!subs.empty ()); + return ContentTimePeriod ( + ContentTime::from_seconds (double (subs.front().in().to_ticks()) / 250), + ContentTime::from_seconds (double (subs.front().out().to_ticks()) / 250) + ); +} diff --git a/src/lib/content_subtitle.h b/src/lib/content_subtitle.h new file mode 100644 index 000000000..6a28c37bf --- /dev/null +++ b/src/lib/content_subtitle.h @@ -0,0 +1,68 @@ +/* + 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 ContentTimePeriod period () const = 0; +}; + +class ContentImageSubtitle : public ContentSubtitle +{ +public: + ContentImageSubtitle (ContentTimePeriod p, boost::shared_ptr<Image> im, dcpomatic::Rect<double> r) + : image (im) + , rectangle (r) + , _period (p) + {} + + ContentTimePeriod period () const { + return _period; + } + + boost::shared_ptr<Image> image; + dcpomatic::Rect<double> rectangle; + +private: + ContentTimePeriod _period; +}; + +class ContentTextSubtitle : public ContentSubtitle +{ +public: + ContentTextSubtitle (std::list<dcp::SubtitleString> s) + : subs (s) + {} + + ContentTimePeriod period () const; + + std::list<dcp::SubtitleString> subs; +}; + +#endif diff --git a/src/lib/content_video.h b/src/lib/content_video.h new file mode 100644 index 000000000..a7f73597c --- /dev/null +++ b/src/lib/content_video.h @@ -0,0 +1,48 @@ +/* + 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 diff --git a/src/lib/cross.cc b/src/lib/cross.cc index 70f643913..53ef5723a 100644 --- a/src/lib/cross.cc +++ b/src/lib/cross.cc @@ -1,5 +1,5 @@ /* - 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 diff --git a/src/lib/cross.h b/src/lib/cross.h index 1c7754503..c206fa55d 100644 --- a/src/lib/cross.h +++ b/src/lib/cross.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @file src/lib/cross.h + * @brief Cross-platform compatibility code. + */ + #ifndef DCPOMATIC_CROSS_H #define DCPOMATIC_CROSS_H @@ -42,7 +46,9 @@ extern boost::filesystem::path app_contents (); 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. */ diff --git a/src/lib/dcp_content_type.cc b/src/lib/dcp_content_type.cc index f24ed95ea..e5466e139 100644 --- a/src/lib/dcp_content_type.cc +++ b/src/lib/dcp_content_type.cc @@ -30,7 +30,7 @@ using namespace std; 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) , _isdcf_name (d) @@ -41,16 +41,16 @@ DCPContentType::DCPContentType (string p, libdcp::ContentKind k, string 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 * diff --git a/src/lib/dcp_content_type.h b/src/lib/dcp_content_type.h index 88f3c4a85..ebfe09518 100644 --- a/src/lib/dcp_content_type.h +++ b/src/lib/dcp_content_type.h @@ -26,7 +26,7 @@ #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.) @@ -34,14 +34,14 @@ 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; } @@ -58,7 +58,7 @@ public: private: std::string _pretty_name; - libdcp::ContentKind _libdcp_kind; + dcp::ContentKind _libdcp_kind; std::string _isdcf_name; /** All available DCP content types */ diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc index 09b909696..4054f05cd 100644 --- a/src/lib/dcp_video_frame.cc +++ b/src/lib/dcp_video_frame.cc @@ -42,13 +42,12 @@ #include <boost/array.hpp> #include <boost/asio.hpp> #include <boost/filesystem.hpp> -#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 <boost/lexical_cast.hpp> +#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" @@ -70,8 +69,9 @@ using std::string; 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) @@ -110,13 +110,10 @@ DCPVideoFrame::DCPVideoFrame (shared_ptr<const PlayerVideoFrame> frame, shared_p 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]; @@ -126,10 +123,10 @@ DCPVideoFrame::encode_locally () } } - 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 ); @@ -374,7 +371,7 @@ EncodedData::write (shared_ptr<const Film> film, int frame, Eyes eyes) const } 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"); diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h index e4006d986..7393efde6 100644 --- a/src/lib/dcp_video_frame.h +++ b/src/lib/dcp_video_frame.h @@ -18,9 +18,7 @@ */ -#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 @@ -50,7 +48,7 @@ public: 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 { @@ -104,7 +102,7 @@ class DCPVideoFrame : public boost::noncopyable { 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); diff --git a/src/lib/dcpomatic_time.cc b/src/lib/dcpomatic_time.cc new file mode 100644 index 000000000..ae4dea44f --- /dev/null +++ b/src/lib/dcpomatic_time.cc @@ -0,0 +1,57 @@ +/* + 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; +} + +bool +ContentTimePeriod::overlaps (ContentTimePeriod const & other) const +{ + return (from < other.to && to > other.from); +} diff --git a/src/lib/dcpomatic_time.h b/src/lib/dcpomatic_time.h new file mode 100644 index 000000000..142104073 --- /dev/null +++ b/src/lib/dcpomatic_time.h @@ -0,0 +1,257 @@ +/* + 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 ContentTimePeriod +{ +public: + ContentTimePeriod () {} + ContentTimePeriod (ContentTime f, ContentTime t) + : from (f) + , to (t) + {} + + ContentTime from; + ContentTime to; + + ContentTimePeriod operator+ (ContentTime const & o) const { + return ContentTimePeriod (from + o, to + o); + } + + bool overlaps (ContentTimePeriod const & o) const; +}; + +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 diff --git a/src/lib/decoder.h b/src/lib/decoder.h index d67592ed8..18f612e53 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -18,7 +18,7 @@ */ /** @file src/decoder.h - * @brief Parent class for decoders of content. + * @brief Decoder class. */ #ifndef DCPOMATIC_DECODER_H @@ -27,8 +27,10 @@ #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> #include <boost/utility.hpp> +#include "types.h" +#include "dcpomatic_time.h" -class Film; +class Decoded; /** @class Decoder. * @brief Parent class for decoders of content. @@ -36,21 +38,18 @@ class Film; 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 diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc index e83ac70f5..5dc9e47c7 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -65,9 +65,7 @@ Encoder::Encoder (shared_ptr<const Film> f, weak_ptr<Job> j) , _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 () @@ -183,7 +181,7 @@ Encoder::frame_done () } void -Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf, bool same) +Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf) { _waker.nudge (); @@ -211,24 +209,22 @@ Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf, bool same) 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 */ LOG_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) { diff --git a/src/lib/encoder.h b/src/lib/encoder.h index a8ee220aa..678cdf04e 100644 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@ -38,6 +38,7 @@ extern "C" { #include "util.h" #include "config.h" #include "cross.h" +#include "exceptions.h" class Image; class AudioBuffers; @@ -67,10 +68,9 @@ public: 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>); @@ -106,7 +106,6 @@ private: /** 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; diff --git a/src/lib/exceptions.cc b/src/lib/exceptions.cc index 8144f41b9..4d9ccc95c 100644 --- a/src/lib/exceptions.cc +++ b/src/lib/exceptions.cc @@ -1,5 +1,5 @@ /* - 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 @@ -56,8 +56,14 @@ MissingSettingError::MissingSettingError (string s) } -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) +{ + +} diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h index 3423a5754..38452ffc0 100644 --- a/src/lib/exceptions.h +++ b/src/lib/exceptions.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,13 +17,13 @@ */ -#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> @@ -205,7 +205,7 @@ public: {} }; -/** @class NetworkError. +/** @class NetworkError * @brief Indicates some problem with communication on the network. */ class NetworkError : public StringError @@ -216,6 +216,9 @@ public: {} }; +/** @class KDMError + * @brief A problem with a KDM. + */ class KDMError : public StringError { public: @@ -224,15 +227,30 @@ 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 { diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc index 7ecc811be..8505626df 100644 --- a/src/lib/ffmpeg.cc +++ b/src/lib/ffmpeg.cc @@ -1,5 +1,5 @@ /* - 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 @@ -22,9 +22,10 @@ extern "C" { #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 "ffmpeg_audio_stream.h" #include "exceptions.h" #include "util.h" @@ -34,7 +35,7 @@ using std::string; using std::cout; using std::stringstream; using boost::shared_ptr; -using libdcp::raw_convert; +using dcp::raw_convert; boost::mutex FFmpeg::_mutex; @@ -48,8 +49,7 @@ FFmpeg::FFmpeg (boost::shared_ptr<const FFmpegContent> c) , _video_stream (-1) { setup_general (); - setup_video (); - setup_audio (); + setup_decoders (); } FFmpeg::~FFmpeg () @@ -57,14 +57,10 @@ FFmpeg::~FFmpeg () boost::mutex::scoped_lock lm (_mutex); for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { - AVCodecContext* context = _format_context->streams[i]->codec; - if (context->codec_type == AVMEDIA_TYPE_VIDEO || context->codec_type == AVMEDIA_TYPE_AUDIO) { - avcodec_close (context); - } + avcodec_close (_format_context->streams[i]->codec); } av_frame_free (&_frame); - avformat_close_input (&_format_context); } @@ -144,46 +140,24 @@ FFmpeg::setup_general () } void -FFmpeg::setup_video () -{ - boost::mutex::scoped_lock lm (_mutex); - - assert (_video_stream >= 0); - AVCodecContext* context = _format_context->streams[_video_stream]->codec; - AVCodec* codec = avcodec_find_decoder (context->codec_id); - - if (codec == 0) { - throw DecodeError (_("could not find video decoder")); - } - - if (avcodec_open2 (context, codec, 0) < 0) { - throw DecodeError (N_("could not open video decoder")); - } -} - -void -FFmpeg::setup_audio () +FFmpeg::setup_decoders () { boost::mutex::scoped_lock lm (_mutex); for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { AVCodecContext* context = _format_context->streams[i]->codec; - if (context->codec_type != AVMEDIA_TYPE_AUDIO) { - continue; - } AVCodec* codec = avcodec_find_decoder (context->codec_id); - if (codec == 0) { - throw DecodeError (_("could not find audio decoder")); - } - - if (avcodec_open2 (context, codec, 0) < 0) { - throw DecodeError (N_("could not open audio decoder")); + if (codec) { + if (avcodec_open2 (context, codec, 0) < 0) { + throw DecodeError (N_("could not open decoder")); + } } + + /* We are silently ignoring any failures to find suitable decoders here */ } } - AVCodecContext * FFmpeg::video_codec_context () const { @@ -193,6 +167,10 @@ FFmpeg::video_codec_context () const AVCodecContext * FFmpeg::audio_codec_context () const { + if (!_ffmpeg_content->audio_stream ()) { + return 0; + } + return _ffmpeg_content->audio_stream()->stream(_format_context)->codec; } diff --git a/src/lib/ffmpeg.h b/src/lib/ffmpeg.h index 760918437..1d2c312e6 100644 --- a/src/lib/ffmpeg.h +++ b/src/lib/ffmpeg.h @@ -79,8 +79,7 @@ protected: private: void setup_general (); - void setup_video (); - void setup_audio (); + void setup_decoders (); }; #endif diff --git a/src/lib/ffmpeg_audio_stream.cc b/src/lib/ffmpeg_audio_stream.cc new file mode 100644 index 000000000..255952b8f --- /dev/null +++ b/src/lib/ffmpeg_audio_stream.cc @@ -0,0 +1,47 @@ +/* + 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 <libxml++/libxml++.h> +#include <libcxml/cxml.h> +#include <dcp/raw_convert.h> +#include "ffmpeg_audio_stream.h" + +using std::string; +using dcp::raw_convert; + +FFmpegAudioStream::FFmpegAudioStream (cxml::ConstNodePtr node, int version) + : FFmpegStream (node) + , mapping (node->node_child ("Mapping"), version) +{ + frame_rate = node->number_child<int> ("FrameRate"); + channels = node->number_child<int64_t> ("Channels"); + first_audio = node->optional_number_child<double> ("FirstAudio"); +} + +void +FFmpegAudioStream::as_xml (xmlpp::Node* root) const +{ + FFmpegStream::as_xml (root); + 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().get())); + } + mapping.as_xml (root->add_child("Mapping")); +} diff --git a/src/lib/ffmpeg_audio_stream.h b/src/lib/ffmpeg_audio_stream.h new file mode 100644 index 000000000..5e455d18a --- /dev/null +++ b/src/lib/ffmpeg_audio_stream.h @@ -0,0 +1,57 @@ +/* + 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 "ffmpeg_stream.h" +#include "audio_mapping.h" +#include "dcpomatic_time.h" + +class ffmpeg_pts_offset_test; + +class FFmpegAudioStream : public FFmpegStream +{ +public: + FFmpegAudioStream (std::string n, int i, int f, int c) + : FFmpegStream (n, i) + , frame_rate (f) + , channels (c) + , mapping (c) + { + mapping.make_default (); + } + + FFmpegAudioStream (cxml::ConstNodePtr, int); + + void as_xml (xmlpp::Node *) const; + + int frame_rate; + int channels; + AudioMapping mapping; + boost::optional<ContentTime> first_audio; + +private: + friend class ffmpeg_pts_offset_test; + + /* Constructor for tests */ + FFmpegAudioStream () + : FFmpegStream ("", 0) + , frame_rate (0) + , channels (0) + , mapping (1) + {} +}; diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index 4d886a6dd..d3e0fa7b2 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -21,9 +21,11 @@ extern "C" { #include <libavformat/avformat.h> } #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "ffmpeg_content.h" #include "ffmpeg_examiner.h" +#include "ffmpeg_subtitle_stream.h" +#include "ffmpeg_audio_stream.h" #include "compose.hpp" #include "job.h" #include "util.h" @@ -45,7 +47,7 @@ using std::cout; using std::pair; using boost::shared_ptr; using boost::dynamic_pointer_cast; -using libdcp::raw_convert; +using dcp::raw_convert; int const FFmpegContentProperty::SUBTITLE_STREAMS = 100; int const FFmpegContentProperty::SUBTITLE_STREAM = 101; @@ -62,7 +64,7 @@ FFmpegContent::FFmpegContent (shared_ptr<const Film> f, boost::filesystem::path } -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) @@ -156,7 +158,7 @@ FFmpegContent::as_xml (xmlpp::Node* node) const } 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())); } } @@ -167,14 +169,14 @@ FFmpegContent::examine (shared_ptr<Job> job) 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 (); - LOG_GENERAL ("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); + LOG_GENERAL ("Video length obtained from header as %1 frames", video_length.frames (video_frame_rate ())); { boost::mutex::scoped_lock lm (_mutex); @@ -194,8 +196,6 @@ FFmpegContent::examine (shared_ptr<Job> job) _first_video = examiner->first_video (); } - take_from_video_examiner (examiner); - signal_changed (ContentProperty::LENGTH); signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS); signal_changed (FFmpegContentProperty::SUBTITLE_STREAM); @@ -237,13 +237,13 @@ FFmpegContent::technical_summary () const 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 (); @@ -271,19 +271,14 @@ FFmpegContent::set_audio_stream (shared_ptr<FFmpegAudioStream> s) 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 @@ -299,7 +294,7 @@ FFmpegContent::audio_channels () const } int -FFmpegContent::content_audio_frame_rate () const +FFmpegContent::audio_frame_rate () const { boost::mutex::scoped_lock lm (_mutex); @@ -322,94 +317,12 @@ operator!= (FFmpegStream const & a, FFmpegStream const & b) return a._id != b._id; } -FFmpegStream::FFmpegStream (shared_ptr<const cxml::Node> node) - : name (node->string_child ("Name")) - , _id (node->number_child<int> ("Id")) -{ - -} - -void -FFmpegStream::as_xml (xmlpp::Node* root) const -{ - root->add_child("Name")->add_child_text (name); - root->add_child("Id")->add_child_text (raw_convert<string> (_id)); -} - -FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node, int version) - : FFmpegStream (node) - , mapping (node->node_child ("Mapping"), version) -{ - frame_rate = node->number_child<int> ("FrameRate"); - channels = node->number_child<int64_t> ("Channels"); - first_audio = node->optional_number_child<double> ("FirstAudio"); -} - -void -FFmpegAudioStream::as_xml (xmlpp::Node* root) const -{ - FFmpegStream::as_xml (root); - 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 ())); - } - mapping.as_xml (root->add_child("Mapping")); -} - -bool -FFmpegStream::uses_index (AVFormatContext const * fc, int index) const -{ - size_t i = 0; - while (i < fc->nb_streams) { - if (fc->streams[i]->id == _id) { - return int (i) == index; - } - ++i; - } - - return false; -} - -AVStream * -FFmpegStream::stream (AVFormatContext const * fc) const -{ - size_t i = 0; - while (i < fc->nb_streams) { - if (fc->streams[i]->id == _id) { - return fc->streams[i]; - } - ++i; - } - - assert (false); - return 0; -} - -/** Construct a SubtitleStream from a value returned from to_string(). - * @param t String returned from to_string(). - * @param v State file version. - */ -FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node) - : FFmpegStream (node) -{ - -} - -void -FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const -{ - FFmpegStream::as_xml (root); -} - -Time +DCPTime FFmpegContent::full_length () const { shared_ptr<const Film> film = _film.lock (); assert (film); - - FrameRateChange 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 @@ -439,7 +352,7 @@ void FFmpegContent::set_audio_mapping (AudioMapping m) { audio_stream()->mapping = m; - signal_changed (AudioContentProperty::AUDIO_MAPPING); + AudioContent::set_audio_mapping (m); } string @@ -482,3 +395,21 @@ FFmpegContent::audio_analysis_path () const p /= name; return p; } + +bool +FFmpegContent::has_subtitle_during (ContentTimePeriod period) const +{ + shared_ptr<FFmpegSubtitleStream> stream = subtitle_stream (); + if (!stream) { + return false; + } + + /* XXX: inefficient */ + for (vector<ContentTimePeriod>::const_iterator i = stream->periods.begin(); i != stream->periods.end(); ++i) { + if (i->from <= period.to && i->to >= period.from) { + return true; + } + } + + return false; +} diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h index c15e5c10e..367c30103 100644 --- a/src/lib/ffmpeg_content.h +++ b/src/lib/ffmpeg_content.h @@ -1,5 +1,5 @@ /* - 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 @@ -21,6 +21,7 @@ #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" @@ -30,89 +31,10 @@ struct AVFormatContext; struct AVStream; class Filter; +class FFmpegSubtitleStream; +class FFmpegAudioStream; class ffmpeg_pts_offset_test; -class FFmpegStream -{ -public: - FFmpegStream (std::string n, int i) - : name (n) - , _id (i) - {} - - FFmpegStream (boost::shared_ptr<const cxml::Node>); - - void as_xml (xmlpp::Node *) const; - - /** @param c An AVFormatContext. - * @param index A stream index within the AVFormatContext. - * @return true if this FFmpegStream uses the given stream index. - */ - bool uses_index (AVFormatContext const * c, int index) const; - AVStream* stream (AVFormatContext const * c) const; - - std::string technical_summary () const { - return "id " + boost::lexical_cast<std::string> (_id); - } - - std::string identifier () const { - return boost::lexical_cast<std::string> (_id); - } - - std::string name; - - friend bool operator== (FFmpegStream const & a, FFmpegStream const & b); - friend bool operator!= (FFmpegStream const & a, FFmpegStream const & b); - -private: - int _id; -}; - -class FFmpegAudioStream : public FFmpegStream -{ -public: - FFmpegAudioStream (std::string n, int i, int f, int c) - : FFmpegStream (n, i) - , frame_rate (f) - , channels (c) - , mapping (c) - { - mapping.make_default (); - } - - FFmpegAudioStream (boost::shared_ptr<const cxml::Node>, int); - - void as_xml (xmlpp::Node *) const; - - int frame_rate; - int channels; - AudioMapping mapping; - boost::optional<double> first_audio; - -private: - friend class ffmpeg_pts_offset_test; - - /* Constructor for tests */ - FFmpegAudioStream () - : FFmpegStream ("", 0) - , frame_rate (0) - , channels (0) - , mapping (1) - {} -}; - -class FFmpegSubtitleStream : public FFmpegStream -{ -public: - FFmpegSubtitleStream (std::string n, int i) - : FFmpegStream (n, i) - {} - - FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>); - - void as_xml (xmlpp::Node *) const; -}; - class FFmpegContentProperty : public VideoContentProperty { public: @@ -127,7 +49,7 @@ class FFmpegContent : public VideoContent, public AudioContent, public SubtitleC { 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 () { @@ -139,14 +61,14 @@ public: 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; + 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; @@ -181,11 +103,13 @@ public: 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; } + bool has_subtitle_during (ContentTimePeriod) const; + private: friend class ffmpeg_pts_offset_test; @@ -193,7 +117,7 @@ private: 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; }; diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index a51b521d0..2041a4d17 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -32,23 +32,25 @@ extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> } -#include "film.h" #include "filter.h" #include "exceptions.h" #include "image.h" #include "util.h" #include "log.h" #include "ffmpeg_decoder.h" +#include "ffmpeg_audio_stream.h" +#include "ffmpeg_subtitle_stream.h" #include "filter_graph.h" #include "audio_buffers.h" #include "ffmpeg_content.h" #include "image_proxy.h" +#include "film.h" #include "i18n.h" -#define LOG_GENERAL(...) film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); -#define LOG_ERROR(...) film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_ERROR); -#define LOG_WARNING(...) film->log()->log (__VA_ARGS__, Log::TYPE_WARNING); +#define LOG_GENERAL(...) _video_content->film()->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); +#define LOG_ERROR(...) _video_content->film()->log()->log (String::compose (__VA_ARGS__), Log::TYPE_ERROR); +#define LOG_WARNING(...) _video_content->film()->log()->log (__VA_ARGS__, Log::TYPE_WARNING); using std::cout; using std::string; @@ -60,23 +62,17 @@ using std::pair; 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) + , SubtitleDecoder (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 (); - /* Audio and video frame PTS values may not start with 0. We want to fiddle them so that: @@ -86,13 +82,11 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC 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 */ @@ -106,15 +100,9 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC /* 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; } } @@ -137,20 +125,15 @@ FFmpegDecoder::flush () /* 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); @@ -160,29 +143,25 @@ FFmpegDecoder::pass () /* 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); LOG_ERROR (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. @@ -314,83 +293,132 @@ FFmpegDecoder::bytes_per_audio_sample () const 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) { - if (accurate) { - initial -= 5; + av_frame_unref (_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) { + + 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. - */ - if (!accurate) { - _just_sought = true; - } - - _video_position = frame; +void +FFmpegDecoder::seek (ContentTime time, bool accurate) +{ + VideoDecoder::seek (time, accurate); + AudioDecoder::seek (time, accurate); - if (frame == 0 || !accurate) { - /* We're already there, or we're as close as we need to be */ - return; + /* 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. + */ + + 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); } - while (1) { - int r = av_read_frame (_format_context, &_packet); - if (r < 0) { - return; - } + /* Initial seek time in the video stream's timebase */ - 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() - ); + seek_and_flush (initial_seek); - if (_video_position >= (frame - 1)) { - av_free_packet (&_packet); - break; - } - } - - av_free_packet (&_packet); + if (!accurate) { + /* That'll do */ + return; } - /* _video_position should be the next thing to be emitted, which will the one after the thing - we just saw. - */ - _video_position++; + int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2)); + + seek_and_flush (initial_seek); + if (N > 0) { + minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _3)); + } } void @@ -406,42 +434,23 @@ FFmpegDecoder::decode_audio_packet () 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); LOG_ERROR ("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 */ - int64_t frames = pts * _ffmpeg_content->content_audio_frame_rate (); - while (frames > 0) { - int64_t const this_time = min (frames, (int64_t) _ffmpeg_content->content_audio_frame_rate() / 2); - - shared_ptr<AudioBuffers> silence ( - new AudioBuffers (_ffmpeg_content->audio_channels(), this_time) - ); - - silence->make_silent (); - audio (silence, _audio_position); - frames -= this_time; - } - } - } + 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; @@ -462,17 +471,13 @@ FFmpegDecoder::decode_video_packet () 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); - LOG_GENERAL (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format); } else { graph = *i; @@ -480,56 +485,16 @@ FFmpegDecoder::decode_video_packet () list<pair<shared_ptr<Image>, int64_t> > images = graph->process (_frame); - shared_ptr<const Film> film = _film.lock (); - assert (film); - for (list<pair<shared_ptr<Image>, int64_t> >::iterator i = images.begin(); i != images.end(); ++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 - ) - ); - - shared_ptr<const Film> film = _film.lock (); - assert (film); - - black->make_black (); - video (shared_ptr<ImageProxy> (new RawImageProxy (image, film->log())), 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, film->log())), 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, _video_content->film()->log())), + rint (pts * _ffmpeg_content->video_frame_rate ()) + ); } else { LOG_WARNING ("Dropping frame without PTS"); } @@ -537,40 +502,6 @@ FFmpegDecoder::decode_video_packet () return true; } - - -void -FFmpegDecoder::setup_subtitle () -{ - boost::mutex::scoped_lock lm (_mutex); - - if (!_ffmpeg_content->subtitle_stream()) { - return; - } - - _subtitle_codec_context = _ffmpeg_content->subtitle_stream()->stream(_format_context)->codec; - if (_subtitle_codec_context == 0) { - throw DecodeError (N_("could not find subtitle stream")); - } - - _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id); - - if (_subtitle_codec == 0) { - throw DecodeError (N_("could not find subtitle decoder")); - } - - if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) { - throw DecodeError (N_("could not open subtitle decoder")); - } -} - -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 () @@ -585,31 +516,29 @@ 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 (ContentTimePeriod (), 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; - - /* 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; + ContentTimePeriod period = subtitle_period (sub) + _pts_offset; 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]; @@ -631,20 +560,24 @@ FFmpegDecoder::decode_subtitle_packet () 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 ( + period, 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); } + +bool +FFmpegDecoder::has_subtitle_during (ContentTimePeriod p) const +{ + return _ffmpeg_content->has_subtitle_during (p); +} diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index d4b4fa1c0..335364d2e 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -1,5 +1,5 @@ /* - 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 @@ -37,7 +37,7 @@ extern "C" { #include "subtitle_decoder.h" #include "ffmpeg.h" -class Film; +class Log; class FilterGraph; class ffmpeg_pts_offset_test; @@ -47,22 +47,16 @@ 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 (); - AVSampleFormat audio_sample_format () const; int bytes_per_audio_sample () const; @@ -73,15 +67,19 @@ private: 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); + + bool has_subtitle_during (ContentTimePeriod) const; + + 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; }; diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc index d73063083..d9bcedfc5 100644 --- a/src/lib/ffmpeg_examiner.cc +++ b/src/lib/ffmpeg_examiner.cc @@ -1,5 +1,5 @@ /* - 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 @@ -23,6 +23,9 @@ extern "C" { } #include "ffmpeg_examiner.h" #include "ffmpeg_content.h" +#include "ffmpeg_audio_stream.h" +#include "ffmpeg_subtitle_stream.h" +#include "util.h" #include "i18n.h" @@ -61,55 +64,93 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c) } } - /* Run through until we find the first audio (for each stream) and video */ - + /* Run through until we find: + * - the first video. + * - the first audio for each stream. + * - the subtitle periods for each stream. + * + * We have to note subtitle periods as otherwise we have no way of knowing + * where we should look for subtitles (video and audio are always present, + * so they are ok). + */ while (1) { int r = av_read_frame (_format_context, &_packet); if (r < 0) { break; } - int frame_finished; - AVCodecContext* context = _format_context->streams[_packet.stream_index]->codec; - if (_packet.stream_index == _video_stream && !_first_video) { - if (avcodec_decode_video2 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { - _first_video = frame_time (_format_context->streams[_video_stream]); - } - } else { - for (size_t i = 0; i < _audio_streams.size(); ++i) { - if (_audio_streams[i]->uses_index (_format_context, _packet.stream_index) && !_audio_streams[i]->first_audio) { - if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { - _audio_streams[i]->first_audio = frame_time (_audio_streams[i]->stream (_format_context)); - } - } + if (_packet.stream_index == _video_stream) { + video_packet (context); + } + + for (size_t i = 0; i < _audio_streams.size(); ++i) { + if (_audio_streams[i]->uses_index (_format_context, _packet.stream_index)) { + audio_packet (context, _audio_streams[i]); } } - bool have_all_audio = true; - size_t i = 0; - while (i < _audio_streams.size() && have_all_audio) { - have_all_audio = _audio_streams[i]->first_audio; - ++i; + for (size_t i = 0; i < _subtitle_streams.size(); ++i) { + if (_subtitle_streams[i]->uses_index (_format_context, _packet.stream_index)) { + subtitle_packet (context, _subtitle_streams[i]); + } } av_free_packet (&_packet); - - if (_first_video && have_all_audio) { - break; + } +} + +void +FFmpegExaminer::video_packet (AVCodecContext* context) +{ + if (_first_video) { + return; + } + + int frame_finished; + if (avcodec_decode_video2 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { + _first_video = frame_time (_format_context->streams[_video_stream]); + } +} + +void +FFmpegExaminer::audio_packet (AVCodecContext* context, shared_ptr<FFmpegAudioStream> stream) +{ + if (stream->first_audio) { + return; + } + + int frame_finished; + if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { + stream->first_audio = frame_time (stream->stream (_format_context)); + } +} + +void +FFmpegExaminer::subtitle_packet (AVCodecContext* context, shared_ptr<FFmpegSubtitleStream> stream) +{ + int frame_finished; + AVSubtitle sub; + if (avcodec_decode_subtitle2 (context, &sub, &frame_finished, &_packet) >= 0 && frame_finished) { + ContentTimePeriod const period = subtitle_period (sub); + if (sub.num_rects == 0 && !stream->periods.empty () && stream->periods.back().to > period.from) { + /* Finish the last subtitle */ + stream->periods.back().to = period.from; + } else if (sub.num_rects == 1) { + stream->periods.push_back (period); } } } -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; @@ -118,27 +159,25 @@ FFmpegExaminer::frame_time (AVStream* s) const 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 - _format_context->start_time) / AV_TIME_BASE) * video_frame_rate(); - return max (1, length); + ContentTime const length = ContentTime::from_seconds (double (_format_context->duration - _format_context->start_time) / AV_TIME_BASE); + return ContentTime (max (int64_t (1), length.get ())); } string diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h index 369dac29c..8c31eb2c4 100644 --- a/src/lib/ffmpeg_examiner.h +++ b/src/lib/ffmpeg_examiner.h @@ -30,8 +30,8 @@ public: 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; @@ -41,17 +41,21 @@ public: return _audio_streams; } - boost::optional<double> first_video () const { + boost::optional<ContentTime> first_video () const { return _first_video; } private: + void video_packet (AVCodecContext *); + void audio_packet (AVCodecContext *, boost::shared_ptr<FFmpegAudioStream>); + void subtitle_packet (AVCodecContext *, boost::shared_ptr<FFmpegSubtitleStream>); + 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; }; diff --git a/src/lib/ffmpeg_stream.cc b/src/lib/ffmpeg_stream.cc new file mode 100644 index 000000000..3fac33327 --- /dev/null +++ b/src/lib/ffmpeg_stream.cc @@ -0,0 +1,71 @@ +/* + 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. + +*/ + +extern "C" { +#include <libavformat/avformat.h> +} +#include <libxml++/libxml++.h> +#include <dcp/raw_convert.h> +#include "ffmpeg_stream.h" + +using std::string; +using dcp::raw_convert; + +FFmpegStream::FFmpegStream (cxml::ConstNodePtr node) + : name (node->string_child ("Name")) + , _id (node->number_child<int> ("Id")) +{ + +} + +void +FFmpegStream::as_xml (xmlpp::Node* root) const +{ + root->add_child("Name")->add_child_text (name); + root->add_child("Id")->add_child_text (raw_convert<string> (_id)); +} + +bool +FFmpegStream::uses_index (AVFormatContext const * fc, int index) const +{ + size_t i = 0; + while (i < fc->nb_streams) { + if (fc->streams[i]->id == _id) { + return int (i) == index; + } + ++i; + } + + return false; +} + +AVStream * +FFmpegStream::stream (AVFormatContext const * fc) const +{ + size_t i = 0; + while (i < fc->nb_streams) { + if (fc->streams[i]->id == _id) { + return fc->streams[i]; + } + ++i; + } + + assert (false); + return 0; +} diff --git a/src/lib/ffmpeg_stream.h b/src/lib/ffmpeg_stream.h new file mode 100644 index 000000000..6bbcd0b01 --- /dev/null +++ b/src/lib/ffmpeg_stream.h @@ -0,0 +1,65 @@ +/* + 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_FFMPEG_STREAM_H +#define DCPOMATIC_FFMPEG_STREAM_H + +#include <boost/lexical_cast.hpp> +#include <libcxml/cxml.h> + +struct AVFormatContext; +struct AVStream; + +class FFmpegStream +{ +public: + FFmpegStream (std::string n, int i) + : name (n) + , _id (i) + {} + + FFmpegStream (cxml::ConstNodePtr); + + void as_xml (xmlpp::Node *) const; + + /** @param c An AVFormatContext. + * @param index A stream index within the AVFormatContext. + * @return true if this FFmpegStream uses the given stream index. + */ + bool uses_index (AVFormatContext const * c, int index) const; + AVStream* stream (AVFormatContext const * c) const; + + std::string technical_summary () const { + return "id " + boost::lexical_cast<std::string> (_id); + } + + std::string identifier () const { + return boost::lexical_cast<std::string> (_id); + } + + std::string name; + + friend bool operator== (FFmpegStream const & a, FFmpegStream const & b); + friend bool operator!= (FFmpegStream const & a, FFmpegStream const & b); + +private: + int _id; +}; + +#endif diff --git a/src/lib/ffmpeg_subtitle_stream.cc b/src/lib/ffmpeg_subtitle_stream.cc new file mode 100644 index 000000000..3d8fd4e83 --- /dev/null +++ b/src/lib/ffmpeg_subtitle_stream.cc @@ -0,0 +1,36 @@ +/* + 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 "ffmpeg_subtitle_stream.h" + +/** Construct a SubtitleStream from a value returned from to_string(). + * @param t String returned from to_string(). + * @param v State file version. + */ +FFmpegSubtitleStream::FFmpegSubtitleStream (cxml::ConstNodePtr node) + : FFmpegStream (node) +{ + +} + +void +FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const +{ + FFmpegStream::as_xml (root); +} diff --git a/src/lib/ffmpeg_subtitle_stream.h b/src/lib/ffmpeg_subtitle_stream.h new file mode 100644 index 000000000..b16b825e7 --- /dev/null +++ b/src/lib/ffmpeg_subtitle_stream.h @@ -0,0 +1,36 @@ +/* + 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 "dcpomatic_time.h" +#include "ffmpeg_stream.h" + +class FFmpegSubtitleStream : public FFmpegStream +{ +public: + FFmpegSubtitleStream (std::string n, int i) + : FFmpegStream (n, i) + {} + + FFmpegSubtitleStream (cxml::ConstNodePtr); + + void as_xml (xmlpp::Node *) const; + + std::vector<ContentTimePeriod> periods; +}; + diff --git a/src/lib/file_group.cc b/src/lib/file_group.cc index 9d042554c..54ec8280c 100644 --- a/src/lib/file_group.cc +++ b/src/lib/file_group.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @file src/lib/file_group.cc + * @brief FileGroup class. + */ + #include <cstdio> #include <sndfile.h> #include "file_group.h" @@ -26,6 +30,7 @@ using std::vector; using std::cout; +/** Construct a FileGroup with no files */ FileGroup::FileGroup () : _current_path (0) , _current_file (0) @@ -33,14 +38,17 @@ FileGroup::FileGroup () } +/** 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) @@ -50,6 +58,7 @@ FileGroup::FileGroup (vector<boost::filesystem::path> const & p) seek (0, SEEK_SET); } +/** Destroy a FileGroup, closing any open file */ FileGroup::~FileGroup () { if (_current_file) { @@ -160,6 +169,7 @@ FileGroup::read (uint8_t* buffer, int amount) const return read; } +/** @return Combined length of all the files */ int64_t FileGroup::length () const { diff --git a/src/lib/file_group.h b/src/lib/file_group.h index 65091c936..5a65de96f 100644 --- a/src/lib/file_group.h +++ b/src/lib/file_group.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,12 +17,19 @@ */ +/** @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: diff --git a/src/lib/film.cc b/src/lib/film.cc index 609003bba..14735d1b1 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -27,15 +27,15 @@ #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" @@ -77,9 +77,9 @@ using boost::to_upper_copy; 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; #define LOG_GENERAL(...) log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); #define LOG_GENERAL_NC(...) log()->log (__VA_ARGS__, Log::TYPE_GENERAL); @@ -92,8 +92,11 @@ using libdcp::raw_convert; * Use <Scale> tag in <VideoContent> rather than <Ratio>. * 8 -> 9 * DCI -> ISDCF + * + * Bumped to 32 for 2.0 branch; some times are expressed in Times rather + * than frames now. */ -int const Film::current_state_version = 9; +int const Film::current_state_version = 32; /** Construct a Film object in a given directory. * @@ -448,7 +451,7 @@ Film::read_metadata () _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) */ @@ -562,7 +565,7 @@ Film::isdcf_name (bool if_created_now) const */ /* The standard says we don't do this for trailers, for some strange reason */ - if (dcp_content_type() && dcp_content_type()->libdcp_kind() != libdcp::TRAILER) { + if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) { ContentList cl = content (); Ratio const * content_ratio = 0; for (ContentList::const_iterator i = cl.begin(); i != cl.end(); ++i) { @@ -854,11 +857,14 @@ Film::cpls () const ) { try { - libdcp::DCP dcp (*i); + dcp::DCP dcp (*i); dcp.read (); out.push_back ( CPLSummary ( - i->path().leaf().string(), dcp.cpls().front()->id(), dcp.cpls().front()->name(), dcp.cpls().front()->filename() + i->path().leaf().string(), + dcp.cpls().front()->id(), + dcp.cpls().front()->annotation_text(), + dcp.cpls().front()->file() ) ); } catch (...) { @@ -957,7 +963,7 @@ Film::move_content_later (shared_ptr<Content> c) _playlist->move_later (c); } -Time +DCPTime Film::length () const { return _playlist->length (); @@ -969,12 +975,18 @@ Film::has_subtitles () const 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) { @@ -993,31 +1005,7 @@ Film::playlist_changed () 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 */ @@ -1033,56 +1021,50 @@ Film::set_sequence_video (bool s) } /** @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 cpl_file, - boost::posix_time::ptime from, - boost::posix_time::ptime until + dcp::LocalTime from, + dcp::LocalTime until ) const { - shared_ptr<const Signer> signer = make_signer (); - - time_t now = time (0); - struct tm* tm = localtime (&now); - string const issue_date = libdcp::tm_to_string (tm); - - return libdcp::KDM (cpl_file, signer, target, key (), from, until, "DCP-o-matic", issue_date); + shared_ptr<const dcp::CPL> cpl (new dcp::CPL (cpl_file)); + return dcp::DecryptedKDM ( + cpl, from, until, "DCP-o-matic", cpl->content_title_text(), dcp::LocalTime().as_string() + ).encrypt (make_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)); @@ -1097,7 +1079,7 @@ Film::make_kdms ( 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 @@ -1115,10 +1097,3 @@ Film::should_be_enough_disk_space (double& required, double& available) const available = double (s.available) / 1073741824.0f; return (available - required) > 1; } - -FrameRateChange -Film::active_frame_rate_change (Time t) const -{ - return _playlist->active_frame_rate_change (t, video_frame_rate ()); -} - diff --git a/src/lib/film.h b/src/lib/film.h index 96ea930cc..67d00aa54 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -1,5 +1,5 @@ /* - 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 @@ -31,8 +31,9 @@ #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 "isdcf_metadata.h" @@ -97,20 +98,15 @@ public: return _dirty; } - libdcp::Size full_frame () const; - libdcp::Size frame_size () const; + dcp::Size full_frame () const; + dcp::Size frame_size () const; std::vector<CPLSummary> cpls () 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; @@ -118,27 +114,27 @@ public: /* Proxies for some Playlist methods */ ContentList content () const; - Time length () const; + DCPTime length () const; bool has_subtitles () const; - OutputVideoFrame best_video_frame_rate () const; - FrameRateChange active_frame_rate_change (Time) 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 cpl_file, - 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 cpl_file, - 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; } @@ -333,7 +329,7 @@ private: bool _three_d; bool _sequence_video; bool _interop; - libdcp::Key _key; + dcp::Key _key; int _state_version; diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc index a36a41f43..5add16d19 100644 --- a/src/lib/filter_graph.cc +++ b/src/lib/filter_graph.cc @@ -45,14 +45,14 @@ using std::make_pair; 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) @@ -122,7 +122,8 @@ FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size throw DecodeError (N_("could not configure filter graph.")); } - /* XXX: leaking `inputs' / `outputs' ? */ + avfilter_inout_free (&inputs); + avfilter_inout_free (&outputs); } FilterGraph::~FilterGraph () @@ -159,7 +160,7 @@ FilterGraph::process (AVFrame* frame) * @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); } diff --git a/src/lib/filter_graph.h b/src/lib/filter_graph.h index 9b403c2bc..45ad5d998 100644 --- a/src/lib/filter_graph.h +++ b/src/lib/filter_graph.h @@ -36,16 +36,16 @@ class FFmpegContent; 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; }; diff --git a/src/lib/frame_rate_change.cc b/src/lib/frame_rate_change.cc index 454938ada..3e9c4b505 100644 --- a/src/lib/frame_rate_change.cc +++ b/src/lib/frame_rate_change.cc @@ -51,10 +51,8 @@ about_equal (float a, float b) } -FrameRateChange::FrameRateChange (float source_, int dcp_) - : source (source_) - , dcp (dcp_) - , skip (false) +FrameRateChange::FrameRateChange (float source, int dcp) + : skip (false) , repeat (1) , change_speed (false) { diff --git a/src/lib/frame_rate_change.h b/src/lib/frame_rate_change.h index f53adc059..92af0ec01 100644 --- a/src/lib/frame_rate_change.h +++ b/src/lib/frame_rate_change.h @@ -37,9 +37,6 @@ struct FrameRateChange return repeat; } - float source; - int dcp; - /** 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) */ diff --git a/src/lib/image.cc b/src/lib/image.cc index 8a8fb1c7b..2ce3738b9 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -1,5 +1,5 @@ /* - 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 @@ -30,6 +30,8 @@ extern "C" { #include "image.h" #include "exceptions.h" #include "scaler.h" +#include "timer.h" +#include "rect.h" #include "md5_digester.h" #include "i18n.h" @@ -38,9 +40,10 @@ using std::string; 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 @@ -84,7 +87,7 @@ Image::components () 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 @@ -100,13 +103,13 @@ Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_s 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) { @@ -140,7 +143,7 @@ Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_s } 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 @@ -171,7 +174,7 @@ Image::scale (libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_fo 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) { @@ -344,10 +347,33 @@ Image::make_black () } 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); + assert (other->pixel_format() == PIX_FMT_RGBA); + int const other_bpp = 4; + + int this_bpp = 0; + switch (_pixel_format) { + case PIX_FMT_BGRA: + case PIX_FMT_RGBA: + this_bpp = 4; + break; + case PIX_FMT_RGB24: + this_bpp = 3; + break; + default: + assert (false); + } int start_tx = position.x; int start_ox = 0; @@ -366,15 +392,17 @@ Image::alpha_blend (shared_ptr<const Image> other, Position<int> position) } 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] + start_tx * 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[0] = op[0] + (tp[0] * (1 - alpha)); + tp[1] = op[1] + (tp[1] * (1 - alpha)); + tp[2] = op[2] + (tp[2] * (1 - alpha)); + tp[3] = op[3] + (tp[3] * (1 - alpha)); + + tp += this_bpp; + op += other_bpp; } } } @@ -458,8 +486,8 @@ Image::bytes_per_pixel (int c) const * @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) { @@ -501,7 +529,7 @@ Image::allocate () } Image::Image (Image const & other) - : libdcp::Image (other) + : dcp::Image (other) , _pixel_format (other._pixel_format) , _aligned (other._aligned) { @@ -519,7 +547,7 @@ Image::Image (Image const & other) } 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) { @@ -538,7 +566,7 @@ Image::Image (AVFrame* frame) } Image::Image (shared_ptr<const Image> other, bool aligned) - : libdcp::Image (other) + : dcp::Image (other) , _pixel_format (other->_pixel_format) , _aligned (aligned) { @@ -571,7 +599,7 @@ Image::operator= (Image const & other) void Image::swap (Image & other) { - libdcp::Image::swap (other); + dcp::Image::swap (other); std::swap (_pixel_format, other._pixel_format); @@ -614,7 +642,7 @@ Image::stride () const return _stride; } -libdcp::Size +dcp::Size Image::size () const { return _size; @@ -626,6 +654,31 @@ Image::aligned () const return _aligned; } +PositionImage +merge (list<PositionImage> images) +{ + if (images.empty ()) { + return PositionImage (); + } + + if (images.size() == 1) { + return images.front (); + } + + 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 - all.position()); + } + + return PositionImage (merged, all.position ()); +} + string Image::digest () const { @@ -637,4 +690,3 @@ Image::digest () const return digester.get (); } - diff --git a/src/lib/image.h b/src/lib/image.h index f83bf6998..23c85e92b 100644 --- a/src/lib/image.h +++ b/src/lib/image.h @@ -31,16 +31,17 @@ extern "C" { #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); @@ -50,19 +51,20 @@ public: 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); @@ -91,4 +93,6 @@ private: bool _aligned; }; +extern PositionImage merge (std::list<PositionImage> images); + #endif diff --git a/src/lib/image_content.cc b/src/lib/image_content.cc index 6acf0bab9..acaedf050 100644 --- a/src/lib/image_content.cc +++ b/src/lib/image_content.cc @@ -20,11 +20,11 @@ #include <libcxml/cxml.h> #include "image_content.h" #include "image_examiner.h" -#include "config.h" #include "compose.hpp" #include "film.h" #include "job.h" #include "frame_rate_change.h" +#include "exceptions.h" #include "i18n.h" @@ -55,7 +55,7 @@ ImageContent::ImageContent (shared_ptr<const Film> f, boost::filesystem::path p) } -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) { @@ -115,7 +115,7 @@ ImageContent::examine (shared_ptr<Job> job) } void -ImageContent::set_video_length (VideoContent::Frame len) +ImageContent::set_video_length (ContentTime len) { { boost::mutex::scoped_lock lm (_mutex); @@ -125,14 +125,12 @@ ImageContent::set_video_length (VideoContent::Frame len) signal_changed (ContentProperty::LENGTH); } -Time +DCPTime ImageContent::full_length () const { shared_ptr<const Film> film = _film.lock (); assert (film); - - FrameRateChange 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 @@ -140,7 +138,7 @@ ImageContent::identifier () const { stringstream s; s << VideoContent::identifier (); - s << "_" << video_length(); + s << "_" << video_length().get(); return s.str (); } diff --git a/src/lib/image_content.h b/src/lib/image_content.h index e56abce4a..a1b1437c8 100644 --- a/src/lib/image_content.h +++ b/src/lib/image_content.h @@ -31,7 +31,7 @@ class ImageContent : public VideoContent { 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 ()); @@ -41,11 +41,11 @@ public: 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); }; diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc index 7a9acd9e4..d3cdbd6f1 100644 --- a/src/lib/image_decoder.cc +++ b/src/lib/image_decoder.cc @@ -31,43 +31,35 @@ 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_content->film()->log ())); } - - shared_ptr<const Film> film = _film.lock (); - assert (film); - - _image.reset (new MagickImageProxy (_image_content->path (_image_content->still() ? 0 : _video_position), film->log ())); - 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 ()); } diff --git a/src/lib/image_decoder.h b/src/lib/image_decoder.h index 5b82dd85c..242f69477 100644 --- a/src/lib/image_decoder.h +++ b/src/lib/image_decoder.h @@ -28,20 +28,19 @@ class ImageContent; 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; }; diff --git a/src/lib/image_examiner.cc b/src/lib/image_examiner.cc index 4ff324f68..004b89e65 100644 --- a/src/lib/image_examiner.cc +++ b/src/lib/image_examiner.cc @@ -36,21 +36,20 @@ using boost::shared_ptr; 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 (); diff --git a/src/lib/image_examiner.h b/src/lib/image_examiner.h index 8887f0d3d..6ae0422cb 100644 --- a/src/lib/image_examiner.h +++ b/src/lib/image_examiner.h @@ -31,14 +31,14 @@ public: 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; }; diff --git a/src/lib/image_proxy.cc b/src/lib/image_proxy.cc index 4e2f8a135..1eb9c169c 100644 --- a/src/lib/image_proxy.cc +++ b/src/lib/image_proxy.cc @@ -18,8 +18,8 @@ */ #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" @@ -51,7 +51,7 @@ RawImageProxy::RawImageProxy (shared_ptr<Image> image, shared_ptr<Log> log) RawImageProxy::RawImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shared_ptr<Log> log) : ImageProxy (log) { - libdcp::Size size ( + dcp::Size size ( xml->number_child<int> ("Width"), xml->number_child<int> ("Height") ); @@ -69,9 +69,9 @@ void 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("PixelFormat")->add_child_text (libdcp::raw_convert<string> (_image->pixel_format ())); + 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)); + node->add_child("PixelFormat")->add_child_text (dcp::raw_convert<string> (_image->pixel_format ())); } void @@ -128,10 +128,9 @@ MagickImageProxy::image () const throw DecodeError (_("Could not decode image file")); } + dcp::Size size (magick_image->columns(), magick_image->rows()); LOG_TIMING ("[%1] MagickImageProxy decode finished", boost::this_thread::get_id ()); - libdcp::Size size (magick_image->columns(), magick_image->rows()); - _image.reset (new Image (PIX_FMT_RGB24, size, true)); /* Write line-by-line here as _image must be aligned, and write() cannot be told about strides */ diff --git a/src/lib/isdcf_metadata.cc b/src/lib/isdcf_metadata.cc index dfba50a9a..7d960b6ac 100644 --- a/src/lib/isdcf_metadata.cc +++ b/src/lib/isdcf_metadata.cc @@ -19,16 +19,16 @@ #include <iostream> #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "isdcf_metadata.h" #include "i18n.h" using std::string; using boost::shared_ptr; -using libdcp::raw_convert; +using dcp::raw_convert; -ISDCFMetadata::ISDCFMetadata (shared_ptr<const cxml::Node> node) +ISDCFMetadata::ISDCFMetadata (cxml::ConstNodePtr node) { content_version = node->number_child<int> ("ContentVersion"); audio_language = node->string_child ("AudioLanguage"); diff --git a/src/lib/isdcf_metadata.h b/src/lib/isdcf_metadata.h index 0fb7e7baa..e63f290e4 100644 --- a/src/lib/isdcf_metadata.h +++ b/src/lib/isdcf_metadata.h @@ -22,10 +22,7 @@ #include <string> #include <libxml++/libxml++.h> - -namespace cxml { - class Node; -} +#include <libcxml/cxml.h> class ISDCFMetadata { @@ -38,7 +35,7 @@ public: , two_d_version_of_three_d (false) {} - ISDCFMetadata (boost::shared_ptr<const cxml::Node>); + ISDCFMetadata (cxml::ConstNodePtr); void as_xml (xmlpp::Node *) const; void read_old_metadata (std::string, std::string); diff --git a/src/lib/job.cc b/src/lib/job.cc index 96aedac65..594c0da34 100644 --- a/src/lib/job.cc +++ b/src/lib/job.cc @@ -1,5 +1,5 @@ /* - 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 @@ -23,12 +23,14 @@ #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" @@ -66,8 +68,8 @@ Job::run_wrapper () 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 { @@ -204,7 +206,7 @@ Job::set_state (State s) } } -/** @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 { @@ -279,6 +281,7 @@ Job::error_summary () const void Job::set_error (string s, string d) { + _film->log()->log (String::compose ("Error in job: %1 (%2)", s, d), Log::TYPE_ERROR); boost::mutex::scoped_lock lm (_state_mutex); _error_summary = s; _error_details = d; diff --git a/src/lib/kdm.cc b/src/lib/kdm.cc index d5d5ec0a0..c08750961 100644 --- a/src/lib/kdm.cc +++ b/src/lib/kdm.cc @@ -21,7 +21,7 @@ #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" @@ -36,13 +36,13 @@ using boost::shared_ptr; 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 @@ -103,16 +103,16 @@ make_screen_kdms ( shared_ptr<const Film> film, list<shared_ptr<Screen> > screens, boost::filesystem::path cpl, - boost::posix_time::ptime from, - boost::posix_time::ptime to + dcp::LocalTime from, + dcp::LocalTime to ) { - list<libdcp::KDM> kdms = film->make_kdms (screens, cpl, from, to); + list<dcp::EncryptedKDM> kdms = film->make_kdms (screens, cpl, 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; @@ -127,8 +127,8 @@ make_cinema_kdms ( shared_ptr<const Film> film, list<shared_ptr<Screen> > screens, boost::filesystem::path cpl, - 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, cpl, from, to); @@ -172,8 +172,8 @@ write_kdm_files ( shared_ptr<const Film> film, list<shared_ptr<Screen> > screens, boost::filesystem::path cpl, - boost::posix_time::ptime from, - boost::posix_time::ptime to, + dcp::LocalTime from, + dcp::LocalTime to, boost::filesystem::path directory ) { @@ -192,8 +192,8 @@ write_kdm_zip_files ( shared_ptr<const Film> film, list<shared_ptr<Screen> > screens, boost::filesystem::path cpl, - boost::posix_time::ptime from, - boost::posix_time::ptime to, + dcp::LocalTime from, + dcp::LocalTime to, boost::filesystem::path directory ) { @@ -211,8 +211,8 @@ email_kdms ( shared_ptr<const Film> film, list<shared_ptr<Screen> > screens, boost::filesystem::path cpl, - 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, cpl, from, to); diff --git a/src/lib/kdm.h b/src/lib/kdm.h index 8aacd7b72..023107a82 100644 --- a/src/lib/kdm.h +++ b/src/lib/kdm.h @@ -27,8 +27,8 @@ extern void write_kdm_files ( boost::shared_ptr<const Film> film, std::list<boost::shared_ptr<Screen> > screens, boost::filesystem::path cpl, - boost::posix_time::ptime from, - boost::posix_time::ptime to, + dcp::LocalTime from, + dcp::LocalTime to, boost::filesystem::path directory ); @@ -36,8 +36,8 @@ extern void write_kdm_zip_files ( boost::shared_ptr<const Film> film, std::list<boost::shared_ptr<Screen> > screens, boost::filesystem::path cpl, - boost::posix_time::ptime from, - boost::posix_time::ptime to, + dcp::LocalTime from, + dcp::LocalTime to, boost::filesystem::path directory ); @@ -45,7 +45,7 @@ extern void email_kdms ( boost::shared_ptr<const Film> film, std::list<boost::shared_ptr<Screen> > screens, boost::filesystem::path cpl, - boost::posix_time::ptime from, - boost::posix_time::ptime to + dcp::LocalTime from, + dcp::LocalTime to ); diff --git a/src/lib/piece.cc b/src/lib/piece.cc deleted file mode 100644 index 494fb17a0..000000000 --- a/src/lib/piece.cc +++ /dev/null @@ -1,84 +0,0 @@ -/* - 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; -} - diff --git a/src/lib/piece.h b/src/lib/piece.h index 17b87b884..976409381 100644 --- a/src/lib/piece.h +++ b/src/lib/piece.h @@ -25,42 +25,19 @@ 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 diff --git a/src/lib/player.cc b/src/lib/player.cc index 7bf78c905..418f360fe 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -18,23 +18,29 @@ */ #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" #include "frame_rate_change.h" @@ -44,23 +50,21 @@ 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) { _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)); @@ -69,404 +73,86 @@ Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p) } void -Player::disable_video () -{ - _video = false; -} - -void -Player::disable_audio () -{ - _audio = false; -} - -bool -Player::pass () -{ - if (!_have_valid_pieces) { - setup_pieces (); - } - - Time earliest_t = TIME_MAX; - shared_ptr<Piece> earliest; - enum { - VIDEO, - AUDIO - } type = VIDEO; - - for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { - if ((*i)->decoder->done () || (*i)->content->length_after_trim() == 0) { - 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; - } - } - - if (_audio && ad && ad->has_audio ()) { - if ((*i)->audio_position < earliest_t) { - earliest_t = (*i)->audio_position; - earliest = *i; - type = AUDIO; - } - } - } - - if (!earliest) { - flush (); - return true; - } - - switch (type) { - case VIDEO: - if (earliest_t > _video_position) { - emit_black (); - } else { - if (earliest->repeating ()) { - earliest->repeat (this); - } else { - earliest->decoder->pass (); - } - } - 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 (), true); - } - } - } - } - 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; - } - - 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 (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 ()); - } - } - - return false; -} - -/** @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) -{ - /* 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); - - FrameRateChange 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)) { - 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 (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()); - - if (frc.repeat > 1 && !piece->repeating ()) { - piece->set_repeat (_last_incoming_video, frc.repeat - 1); - } -} - -/** @param already_resampled true if this data has already been through the chain up to the resampler */ -void -Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame, bool already_resampled) -{ - shared_ptr<Piece> piece = weak_piece.lock (); - if (!piece) { - return; - } - - shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content); - assert (content); - - if (!already_resampled) { - /* Gain */ - if (content->audio_gain() != 0) { - shared_ptr<AudioBuffers> gain (new AudioBuffers (audio)); - gain->apply_gain (content->audio_gain ()); - audio = gain; - } - - /* 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); - - if (content->trimmed (relative_time)) { - return; - } - - Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start (); - - /* 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)) - ); - } - } - } - - audio = dcp_mapped; - - /* 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; - } - - 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 ()); -} - -void -Player::flush () -{ - 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 ()); - } - - while (_video && _video_position < _audio_position) { - emit_black (); - } - - while (_audio && _audio_position < _video_position) { - emit_silence (_film->time_to_audio_frames (_video_position - _audio_position)); - } - -} - -/** 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) -{ - if (!_have_valid_pieces) { - setup_pieces (); - } - - if (_pieces.empty ()) { - return; - } - - 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); - - /* 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 - ); - - (*i)->reset_repeat (); - } - - _video_position = _audio_position = t; - - /* XXX: don't seek audio because we don't need to... */ -} - -void Player::setup_pieces () { list<shared_ptr<Piece> > old_pieces = _pieces; - _pieces.clear (); ContentList content = _playlist->content (); - sort (content.begin(), content.end(), ContentSorter ()); for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { if (!(*i)->paths_valid ()) { continue; } + + 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; + } + + 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; + } + } - shared_ptr<Piece> piece (new Piece (*i)); - - /* XXX: into content? */ + optional<FrameRateChange> best_overlap_frc; + if (best_overlap) { + best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ()); + } else { + /* No video overlap; e.g. if the DCP is just audio */ + best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ()); + } + /* FFmpeg */ 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, false)); - 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; + decoder.reset (new FFmpegDecoder (fc, _film->log())); + frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate()); } - + + /* ImageContent */ 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; + decoder = imd; } } - 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; + if (!decoder) { + decoder.reset (new ImageDecoder (ic)); } + + frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate()); } + /* SndfileContent */ 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, false)); + decoder.reset (new SndfileDecoder (sc)); + frc = best_overlap_frc; + } - piece->decoder = sd; + /* 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 (piece); + _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ()))); } _have_valid_pieces = true; @@ -481,9 +167,12 @@ Player::content_changed (weak_ptr<Content> w, int property, bool frequent) } if ( - property == ContentProperty::POSITION || property == ContentProperty::LENGTH || - property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END || - property == VideoContentProperty::VIDEO_FRAME_TYPE + property == ContentProperty::POSITION || + property == ContentProperty::LENGTH || + property == ContentProperty::TRIM_START || + property == ContentProperty::TRIM_END || + property == ContentProperty::PATH || + property == VideoContentProperty::VIDEO_FRAME_TYPE ) { _have_valid_pieces = false; @@ -492,29 +181,17 @@ Player::content_changed (weak_ptr<Content> w, int property, bool 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); - } - - Changed (frequent); - - } else if ( - property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_SCALE || + property == SubtitleContentProperty::SUBTITLE_SCALE || + 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); } } +/** @param already_resampled true if this data has already been through the chain up to the resampler */ void Player::playlist_changed () { @@ -523,119 +200,341 @@ Player::playlist_changed () } void -Player::set_video_container_size (libdcp::Size s) +Player::set_video_container_size (dcp::Size s) { _video_container_size = s; - shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true)); - im->make_black (); + _black_image.reset (new Image (PIX_FMT_RGB24, _video_container_size, true)); + _black_image->make_black (); +} + +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 (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) { + Changed (false); + } +} + +list<PositionImage> +Player::process_content_image_subtitles (shared_ptr<SubtitleContent> content, list<shared_ptr<ContentImageSubtitle> > subs) const +{ + list<PositionImage> all; - _black_frame.reset ( + 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))) + ) + ) + ); + } + + return all; +} + +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)); + } + } + + return all; +} + +void +Player::set_approximate_size () +{ + _approximate_size = true; +} + +shared_ptr<PlayerVideoFrame> +Player::black_player_video_frame () const +{ + return shared_ptr<PlayerVideoFrame> ( new PlayerVideoFrame ( - shared_ptr<ImageProxy> (new RawImageProxy (im, _film->log ())), - Crop(), + shared_ptr<const ImageProxy> (new RawImageProxy (_black_image, _film->log ())), + Crop (), _video_container_size, _video_container_size, Scaler::from_id ("bicubic"), EYES_BOTH, PART_WHOLE, - ColourConversion () - ) - ); + Config::instance()->colour_conversions().front().conversion + ) + ); } -shared_ptr<Resampler> -Player::resampler (shared_ptr<AudioContent> c, bool create) +/** @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) { - map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c); - if (i != _resamplers.end ()) { - return i->second; + if (!_have_valid_pieces) { + setup_pieces (); } + + list<shared_ptr<Piece> > ov = overlaps<VideoContent> ( + time, + time + DCPTime::from_frames (1, _film->video_frame_rate ()) + ); - if (!create) { - return shared_ptr<Resampler> (); + list<shared_ptr<PlayerVideoFrame> > pvf; + + if (ov.empty ()) { + /* No video content at this time */ + pvf.push_back (black_player_video_frame ()); + } else { + /* Create a PlayerVideoFrame from the content's video at this time */ + + 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); + + 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; + } + + 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; + } + + for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) { + pvf.push_back ( + shared_ptr<PlayerVideoFrame> ( + new PlayerVideoFrame ( + i->image, + content->crop (), + image_size, + _video_container_size, + _film->scaler(), + i->eyes, + i->part, + content->colour_conversion () + ) + ) + ); + } } - LOG_GENERAL ( - "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels() + /* Add subtitles to whatever PlayerVideoFrames we got */ + + list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> ( + time, + time + DCPTime::from_frames (1, _film->video_frame_rate ()) ); - shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels())); - _resamplers[c] = r; - return r; + list<PositionImage> sub_images; + + for (list<shared_ptr<Piece> >::const_iterator j = subs.begin(); j != subs.end(); ++j) { + shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*j)->decoder); + shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*j)->content); + ContentTime const from = dcp_to_content_subtitle (*j, time); + /* XXX: this video_frame_rate() should be the rate that the subtitle content has been prepared for */ + ContentTime const to = from + ContentTime::from_frames (1, _film->video_frame_rate ()); + + list<shared_ptr<ContentImageSubtitle> > image_subtitles = subtitle_decoder->get_image_subtitles (ContentTimePeriod (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)); + } + + list<shared_ptr<ContentTextSubtitle> > text_subtitles = subtitle_decoder->get_text_subtitles (ContentTimePeriod (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 ()) { + for (list<shared_ptr<PlayerVideoFrame> >::const_iterator i = pvf.begin(); i != pvf.end(); ++i) { + (*i)->set_subtitle (merge (sub_images)); + } + } + + return pvf; } -void -Player::emit_black () +shared_ptr<AudioBuffers> +Player::get_audio (DCPTime time, DCPTime length, bool accurate) { -#ifdef DCPOMATIC_DEBUG - _last_video.reset (); -#endif + if (!_have_valid_pieces) { + setup_pieces (); + } - Video (_black_frame, _last_emit_was_black, _video_position); - _video_position += _film->video_frames_to_time (1); - _last_emit_was_black = true; -} + AudioFrame const length_frames = length.frames (_film->audio_frame_rate ()); -void -Player::emit_silence (OutputAudioFrame most) -{ - if (most == 0) { - return; - } + shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames)); + audio->make_silent (); - 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); + list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time, time + length); + if (ov.empty ()) { + return audio; + } + + for (list<shared_ptr<Piece> >::iterator i = ov.begin(); i != ov.end(); ++i) { + + 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; + } + + /* 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 (); + } + + AudioFrame const content_frame = dcp_to_content_audio (*i, request); + + /* 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); + + /* Gain */ + if (content->audio_gain() != 0) { + shared_ptr<AudioBuffers> gain (new AudioBuffers (all->audio)); + gain->apply_gain (content->audio_gain ()); + all->audio = gain; + } + + /* 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)) + ); + } + } + } + + all->audio = dcp_mapped; + + 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 ()) + ); + } + + return audio; } -void -Player::film_changed (Film::Property p) +VideoFrame +Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const { - /* 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); - } + /* Convert this to the content frame */ + return DCPTime (s + piece->content->trim_start()).frames (_film->video_frame_rate()) * piece->frc.factor (); } -void -Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to) +AudioFrame +Player::dcp_to_content_audio (shared_ptr<const Piece> piece, DCPTime t) 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)); - } + /* 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())); + + /* Convert this to the content frame */ + return DCPTime (s + piece->content->trim_start()).frames (_film->audio_frame_rate()); } -/** 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 () +ContentTime +Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const { - if (!_last_incoming_video.image || !_have_valid_pieces) { - return false; - } + /* 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())); - 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 ContentTime (s + piece->content->trim_start(), piece->frc); +} - return true; +void +PlayerStatistics::dump (shared_ptr<Log> log) const +{ + log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat), Log::TYPE_GENERAL); + log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence.seconds()), Log::TYPE_GENERAL); +} + +PlayerStatistics const & +Player::statistics () const +{ + return _statistics; } diff --git a/src/lib/player.h b/src/lib/player.h index 6e70ad707..6805dc8c7 100644 --- a/src/lib/player.h +++ b/src/lib/player.h @@ -27,10 +27,12 @@ #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; @@ -38,42 +40,59 @@ class Playlist; 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 (libdcp::Size); - - bool repeat_last_video (); + void set_video_container_size (dcp::Size); + void set_approximate_size (); - /** 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. @@ -85,51 +104,60 @@ public: 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, bool); - 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; + + /** @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; diff --git a/src/lib/player_video_frame.cc b/src/lib/player_video_frame.cc index 94760e495..5463925bf 100644 --- a/src/lib/player_video_frame.cc +++ b/src/lib/player_video_frame.cc @@ -17,7 +17,7 @@ */ -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "player_video_frame.h" #include "image.h" #include "image_proxy.h" @@ -26,13 +26,13 @@ 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, @@ -54,8 +54,8 @@ PlayerVideoFrame::PlayerVideoFrame (shared_ptr<cxml::Node> node, shared_ptr<Sock { _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"); @@ -65,22 +65,20 @@ PlayerVideoFrame::PlayerVideoFrame (shared_ptr<cxml::Node> node, shared_ptr<Sock 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> @@ -106,12 +104,12 @@ PlayerVideoFrame::image () const break; } - shared_ptr<Image> out = im->crop_scale_window (total_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false); + shared_ptr<Image> out = im->crop_scale_window (total_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, true); 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; @@ -130,11 +128,11 @@ PlayerVideoFrame::add_metadata (xmlpp::Node* node) const 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)); } } @@ -142,7 +140,7 @@ void 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); } } diff --git a/src/lib/player_video_frame.h b/src/lib/player_video_frame.h index b085cb609..4c6a9c630 100644 --- a/src/lib/player_video_frame.h +++ b/src/lib/player_video_frame.h @@ -21,6 +21,7 @@ #include "types.h" #include "position.h" #include "colour_conversion.h" +#include "position_image.h" class Image; class ImageProxy; @@ -35,10 +36,10 @@ class Log; 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>, boost::shared_ptr<Log>); - void set_subtitle (boost::shared_ptr<const Image>, Position<int>); + void set_subtitle (PositionImage); boost::shared_ptr<Image> image () const; @@ -56,12 +57,11 @@ public: 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; }; diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc index dc87fbfab..41ed00b24 100644 --- a/src/lib/playlist.cc +++ b/src/lib/playlist.cc @@ -80,20 +80,20 @@ Playlist::maybe_sequence_video () _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 (); } } @@ -121,7 +121,7 @@ Playlist::video_identifier () const /** @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) { @@ -262,12 +262,12 @@ Playlist::best_dcp_frame_rate () const 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; @@ -287,10 +287,10 @@ Playlist::reconnect () } } -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 ()); @@ -300,6 +300,23 @@ Playlist::video_end () const 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) { + continue; + } + + 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) { @@ -322,7 +339,7 @@ Playlist::content () const 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 ()); @@ -330,7 +347,7 @@ Playlist::repeat (ContentList c, int n) 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 (); @@ -363,7 +380,7 @@ Playlist::move_earlier (shared_ptr<Content> c) 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 ()); @@ -390,27 +407,10 @@ Playlist::move_later (shared_ptr<Content> c) 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 ()); Changed (); } - -FrameRateChange -Playlist::active_frame_rate_change (Time 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) { - continue; - } - - 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); -} diff --git a/src/lib/playlist.h b/src/lib/playlist.h index effc52101..7c29b8588 100644 --- a/src/lib/playlist.h +++ b/src/lib/playlist.h @@ -25,6 +25,7 @@ #include <boost/enable_shared_from_this.hpp> #include "ffmpeg_content.h" #include "audio_mapping.h" +#include "util.h" #include "frame_rate_change.h" class Content; @@ -38,18 +39,15 @@ class Job; 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: @@ -57,7 +55,7 @@ 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>); @@ -71,11 +69,11 @@ public: std::string video_identifier () const; - Time length () const; + DCPTime length () const; int best_dcp_frame_rate () const; - Time video_end () const; - FrameRateChange active_frame_rate_change (Time, int dcp_frame_rate) 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 (); diff --git a/src/lib/position.h b/src/lib/position.h index f9bd0987c..345f7ab4a 100644 --- a/src/lib/position.h +++ b/src/lib/position.h @@ -1,5 +1,5 @@ /* - 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 @@ -21,7 +21,7 @@ #define DCPOMATIC_POSITION_H /** @struct Position - * @brief A position. + * @brief A position (x and y coordinates) */ template <class T> class Position @@ -50,4 +50,18 @@ operator+ (Position<T> const & a, Position<T> const & b) return Position<T> (a.x + b.x, a.y + b.y); } +template<class T> +Position<T> +operator- (Position<T> const & a, Position<T> const & b) +{ + return Position<T> (a.x - b.x, a.y - b.y); +} + +template<class T> +bool +operator== (Position<T> const & a, Position<T> const & b) +{ + return a.x == b.x && a.y == b.y; +} + #endif diff --git a/src/lib/position_image.h b/src/lib/position_image.h new file mode 100644 index 000000000..dbd3c600e --- /dev/null +++ b/src/lib/position_image.h @@ -0,0 +1,41 @@ +/* + 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 diff --git a/src/lib/ratio.cc b/src/lib/ratio.cc index 52577d3bb..fbd702232 100644 --- a/src/lib/ratio.cc +++ b/src/lib/ratio.cc @@ -17,7 +17,7 @@ */ -#include <libdcp/types.h> +#include <dcp/types.h> #include "ratio.h" #include "util.h" diff --git a/src/lib/ratio.h b/src/lib/ratio.h index 8b1a1fc71..22fc7662c 100644 --- a/src/lib/ratio.h +++ b/src/lib/ratio.h @@ -22,7 +22,7 @@ #include <vector> #include <boost/utility.hpp> -#include <libdcp/util.h> +#include <dcp/util.h> class Ratio : public boost::noncopyable { diff --git a/src/lib/rect.h b/src/lib/rect.h index 6f4709c08..1feb8ad4f 100644 --- a/src/lib/rect.h +++ b/src/lib/rect.h @@ -42,6 +42,13 @@ public: , 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_) @@ -54,11 +61,13 @@ public: 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); @@ -69,7 +78,16 @@ public: ); } - 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)); } }; diff --git a/src/lib/render_subtitles.cc b/src/lib/render_subtitles.cc new file mode 100644 index 000000000..5364b8dfe --- /dev/null +++ b/src/lib/render_subtitles.cc @@ -0,0 +1,152 @@ +/* + 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 ())); +} + diff --git a/src/lib/render_subtitles.h b/src/lib/render_subtitles.h new file mode 100644 index 000000000..d83dc119a --- /dev/null +++ b/src/lib/render_subtitles.h @@ -0,0 +1,24 @@ +/* + 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); diff --git a/src/lib/resampler.cc b/src/lib/resampler.cc index e6b1623d9..9a81df499 100644 --- a/src/lib/resampler.cc +++ b/src/lib/resampler.cc @@ -60,11 +60,9 @@ Resampler::~Resampler () 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)); @@ -80,7 +78,7 @@ Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame) } resampled->set_frames (resampled_frames); - return make_pair (resampled, resamp_time); + return resampled; } shared_ptr<const AudioBuffers> diff --git a/src/lib/resampler.h b/src/lib/resampler.h index 69ec83ba9..4ee11a7f0 100644 --- a/src/lib/resampler.h +++ b/src/lib/resampler.h @@ -33,7 +33,7 @@ public: 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: diff --git a/src/lib/server.cc b/src/lib/server.cc index ed7fb6145..66ee2b0e3 100644 --- a/src/lib/server.cc +++ b/src/lib/server.cc @@ -29,7 +29,7 @@ #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" @@ -62,16 +62,37 @@ using boost::thread; 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) + : _terminate (false) + , _log (log) , _verbose (verbose) + , _acceptor (_io_service, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), Config::instance()->server_port_base())) { } +Server::~Server () +{ + { + boost::mutex::scoped_lock lm (_worker_mutex); + _terminate = true; + _worker_condition.notify_all (); + } + + for (vector<boost::thread*>::iterator i = _worker_threads.begin(); i != _worker_threads.end(); ++i) { + (*i)->join (); + delete *i; + } + + _io_service.stop (); + + _broadcast.io_service.stop (); + _broadcast.thread->join (); +} + /** @param after_read Filled in with gettimeofday() after reading the input from the network. * @param after_encode Filled in with gettimeofday() after encoding the image. */ @@ -117,10 +138,14 @@ Server::worker_thread () { while (1) { boost::mutex::scoped_lock lock (_worker_mutex); - while (_queue.empty ()) { + while (_queue.empty () && !_terminate) { _worker_condition.wait (lock); } + if (_terminate) { + return; + } + shared_ptr<Socket> socket = _queue.front (); _queue.pop_front (); @@ -187,39 +212,18 @@ Server::run (int num_threads) _broadcast.thread = new thread (bind (&Server::broadcast_thread, this)); - boost::asio::io_service io_service; - - boost::asio::ip::tcp::acceptor acceptor ( - io_service, - boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), Config::instance()->server_port_base ()) - ); - - while (1) { - shared_ptr<Socket> socket (new Socket); - acceptor.accept (socket->socket ()); - - boost::mutex::scoped_lock lock (_worker_mutex); - - /* Wait until the queue has gone down a bit */ - while (int (_queue.size()) >= num_threads * 2) { - _worker_condition.wait (lock); - } - - _queue.push_back (socket); - _worker_condition.notify_all (); - } + start_accept (); + _io_service.run (); } void Server::broadcast_thread () try { - boost::asio::io_service io_service; - boost::asio::ip::address address = boost::asio::ip::address_v4::any (); boost::asio::ip::udp::endpoint listen_endpoint (address, Config::instance()->server_port_base() + 1); - _broadcast.socket = new boost::asio::ip::udp::socket (io_service); + _broadcast.socket = new boost::asio::ip::udp::socket (_broadcast.io_service); _broadcast.socket->open (listen_endpoint.protocol ()); _broadcast.socket->bind (listen_endpoint); @@ -229,7 +233,7 @@ try boost::bind (&Server::broadcast_received, this) ); - io_service.run (); + _broadcast.io_service.run (); } catch (...) { @@ -264,3 +268,35 @@ Server::broadcast_received () _broadcast.send_endpoint, boost::bind (&Server::broadcast_received, this) ); } + +void +Server::start_accept () +{ + if (_terminate) { + return; + } + + shared_ptr<Socket> socket (new Socket); + _acceptor.async_accept (socket->socket (), boost::bind (&Server::handle_accept, this, socket, boost::asio::placeholders::error)); +} + +void +Server::handle_accept (shared_ptr<Socket> socket, boost::system::error_code const & error) +{ + if (error) { + return; + } + + boost::mutex::scoped_lock lock (_worker_mutex); + + /* Wait until the queue has gone down a bit */ + while (_queue.size() >= _worker_threads.size() * 2 && !_terminate) { + _worker_condition.wait (lock); + } + + _queue.push_back (socket); + _worker_condition.notify_all (); + + start_accept (); +} + diff --git a/src/lib/server.h b/src/lib/server.h index a9b4b1c1c..9f3e99f9c 100644 --- a/src/lib/server.h +++ b/src/lib/server.h @@ -90,6 +90,7 @@ class Server : public ExceptionStore, public boost::noncopyable { public: Server (boost::shared_ptr<Log> log, bool verbose); + ~Server (); void run (int num_threads); @@ -98,14 +99,24 @@ private: int process (boost::shared_ptr<Socket> socket, struct timeval &, struct timeval &); void broadcast_thread (); void broadcast_received (); + void start_accept (); + void handle_accept (boost::shared_ptr<Socket>, boost::system::error_code const &); + + bool _terminate; std::vector<boost::thread *> _worker_threads; std::list<boost::shared_ptr<Socket> > _queue; boost::mutex _worker_mutex; boost::condition _worker_condition; + boost::shared_ptr<Log> _log; bool _verbose; + boost::asio::io_service _io_service; + boost::asio::ip::tcp::acceptor _acceptor; + + int _num_threads; + struct Broadcast { Broadcast () @@ -117,6 +128,7 @@ private: boost::asio::ip::udp::socket* socket; char buffer[64]; boost::asio::ip::udp::endpoint send_endpoint; + boost::asio::io_service io_service; } _broadcast; }; diff --git a/src/lib/server_finder.cc b/src/lib/server_finder.cc index ed6016c67..de8a3852c 100644 --- a/src/lib/server_finder.cc +++ b/src/lib/server_finder.cc @@ -18,7 +18,7 @@ */ #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "server_finder.h" #include "exceptions.h" #include "util.h" @@ -33,7 +33,7 @@ using std::vector; using std::cout; using boost::shared_ptr; using boost::scoped_array; -using libdcp::raw_convert; +using dcp::raw_convert; ServerFinder* ServerFinder::_instance = 0; diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc index 3efba6fd5..37f205535 100644 --- a/src/lib/sndfile_content.cc +++ b/src/lib/sndfile_content.cc @@ -18,7 +18,7 @@ */ #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" @@ -32,7 +32,7 @@ using std::string; using std::stringstream; using std::cout; using boost::shared_ptr; -using libdcp::raw_convert; +using dcp::raw_convert; SndfileContent::SndfileContent (shared_ptr<const Film> f, boost::filesystem::path p) : Content (f, p) @@ -44,13 +44,13 @@ SndfileContent::SndfileContent (shared_ptr<const Film> f, boost::filesystem::pat } -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"); } @@ -81,8 +81,8 @@ SndfileContent::information () const 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 (); @@ -103,10 +103,7 @@ SndfileContent::examine (shared_ptr<Job> job) 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); @@ -137,25 +134,17 @@ SndfileContent::as_xml (xmlpp::Node* node) const 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); - - FrameRateChange frc = film->active_frame_rate_change (position ()); - - OutputAudioFrame const len = divide_with_round ( - audio_length() * output_audio_frame_rate() * frc.source, - content_audio_frame_rate() * film->video_frame_rate() - ); - - return film->audio_frames_to_time (len); + return DCPTime (audio_length(), film->active_frame_rate_change (position ())); } void @@ -166,5 +155,5 @@ SndfileContent::set_audio_mapping (AudioMapping m) _audio_mapping = m; } - signal_changed (AudioContentProperty::AUDIO_MAPPING); + AudioContent::set_audio_mapping (m); } diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h index a32043c5c..dcd6cb55d 100644 --- a/src/lib/sndfile_content.h +++ b/src/lib/sndfile_content.h @@ -33,7 +33,7 @@ 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 ()); @@ -44,7 +44,7 @@ public: 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 { @@ -52,12 +52,12 @@ public: 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; } @@ -73,7 +73,7 @@ public: private: int _audio_channels; - AudioContent::Frame _audio_length; + ContentTime _audio_length; int _audio_frame_rate; AudioMapping _audio_mapping; }; diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc index f66a7c7dc..602014d58 100644 --- a/src/lib/sndfile_decoder.cc +++ b/src/lib/sndfile_decoder.cc @@ -25,7 +25,6 @@ #include <sndfile.h> #include "sndfile_content.h" #include "sndfile_decoder.h" -#include "film.h" #include "exceptions.h" #include "audio_buffers.h" @@ -37,9 +36,8 @@ using std::min; 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) { @@ -66,13 +64,17 @@ SndfileDecoder::~SndfileDecoder () 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 (); @@ -101,9 +103,11 @@ SndfileDecoder::pass () } 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 @@ -112,10 +116,10 @@ SndfileDecoder::audio_channels () const return _info.channels; } -AudioContent::Frame +ContentTime SndfileDecoder::audio_length () const { - return _info.frames; + return ContentTime::from_frames (_info.frames, audio_frame_rate ()); } int @@ -124,8 +128,11 @@ SndfileDecoder::audio_frame_rate () const 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; } diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h index 77fa6d177..52590ef24 100644 --- a/src/lib/sndfile_decoder.h +++ b/src/lib/sndfile_decoder.h @@ -26,21 +26,22 @@ class SndfileContent; 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; }; diff --git a/src/lib/subrip.cc b/src/lib/subrip.cc new file mode 100644 index 000000000..3217a4df0 --- /dev/null +++ b/src/lib/subrip.cc @@ -0,0 +1,236 @@ +/* + 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->period = ContentTimePeriod (convert_time (p[0]), 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().period.to; +} diff --git a/src/lib/subrip.h b/src/lib/subrip.h new file mode 100644 index 000000000..e7d21675f --- /dev/null +++ b/src/lib/subrip.h @@ -0,0 +1,53 @@ +/* + 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 diff --git a/src/lib/subrip_content.cc b/src/lib/subrip_content.cc new file mode 100644 index 000000000..892578ade --- /dev/null +++ b/src/lib/subrip_content.cc @@ -0,0 +1,111 @@ +/* + 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 (); +} diff --git a/src/lib/subrip_content.h b/src/lib/subrip_content.h new file mode 100644 index 000000000..5688f81d5 --- /dev/null +++ b/src/lib/subrip_content.h @@ -0,0 +1,42 @@ +/* + 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; +}; diff --git a/src/lib/subrip_decoder.cc b/src/lib/subrip_decoder.cc new file mode 100644 index 000000000..3d971fd4b --- /dev/null +++ b/src/lib/subrip_decoder.cc @@ -0,0 +1,94 @@ +/* + 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" +#include "subrip_content.h" + +using std::list; +using std::vector; +using boost::shared_ptr; + +SubRipDecoder::SubRipDecoder (shared_ptr<const SubRipContent> content) + : SubtitleDecoder (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].period.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].period.from.seconds() * 250)), + dcp::Time (rint (_subtitles[_next].period.to.seconds() * 250)), + 0.9, + dcp::BOTTOM, + i->text, + dcp::NONE, + dcp::Color (255, 255, 255), + 0, + 0 + ) + ); + } + + text_subtitle (out); + _next++; + return false; +} + +bool +SubRipDecoder::has_subtitle_during (ContentTimePeriod p) const +{ + /* XXX: inefficient */ + + for (vector<SubRipSubtitle>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { + if (p.overlaps (i->period)) { + return true; + } + } + + return false; +} diff --git a/src/lib/subrip_decoder.h b/src/lib/subrip_decoder.h new file mode 100644 index 000000000..d6c6e6bc2 --- /dev/null +++ b/src/lib/subrip_decoder.h @@ -0,0 +1,43 @@ +/* + 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: + bool has_subtitle_during (ContentTimePeriod) const; + + size_t _next; +}; + +#endif diff --git a/src/lib/subrip_subtitle.h b/src/lib/subrip_subtitle.h new file mode 100644 index 000000000..646fc1f7a --- /dev/null +++ b/src/lib/subrip_subtitle.h @@ -0,0 +1,53 @@ +/* + 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 +{ + ContentTimePeriod period; + boost::optional<int> x1; + boost::optional<int> x2; + boost::optional<int> y1; + boost::optional<int> y2; + std::list<SubRipSubtitlePiece> pieces; +}; + +#endif diff --git a/src/lib/subtitle.cc b/src/lib/subtitle.cc deleted file mode 100644 index 0d18861c4..000000000 --- a/src/lib/subtitle.cc +++ /dev/null @@ -1,116 +0,0 @@ -/* - 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 (); - } -} diff --git a/src/lib/subtitle.h b/src/lib/subtitle.h deleted file mode 100644 index c74f5c1b9..000000000 --- a/src/lib/subtitle.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - 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; -}; diff --git a/src/lib/subtitle_content.cc b/src/lib/subtitle_content.cc index 0abb7d491..068bf7516 100644 --- a/src/lib/subtitle_content.cc +++ b/src/lib/subtitle_content.cc @@ -18,7 +18,7 @@ */ #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "subtitle_content.h" #include "util.h" #include "exceptions.h" @@ -27,9 +27,10 @@ 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; @@ -44,7 +45,7 @@ SubtitleContent::SubtitleContent (shared_ptr<const Film> f, boost::filesystem::p } -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) diff --git a/src/lib/subtitle_content.h b/src/lib/subtitle_content.h index 388637688..7d4a385c9 100644 --- a/src/lib/subtitle_content.h +++ b/src/lib/subtitle_content.h @@ -34,9 +34,9 @@ class SubtitleContent : public virtual Content { 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; void set_subtitle_x_offset (double); @@ -57,7 +57,7 @@ public: boost::mutex::scoped_lock lm (_mutex); return _subtitle_scale; } - + private: friend class ffmpeg_pts_offset_test; diff --git a/src/lib/subtitle_decoder.cc b/src/lib/subtitle_decoder.cc index c06f3d718..786199e31 100644 --- a/src/lib/subtitle_decoder.cc +++ b/src/lib/subtitle_decoder.cc @@ -1,5 +1,5 @@ /* - 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 @@ -19,21 +19,81 @@ #include <boost/shared_ptr.hpp> #include "subtitle_decoder.h" +#include "subtitle_content.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 (shared_ptr<const SubtitleContent> c) + : _subtitle_content (c) { } - -/** 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 (ContentTimePeriod period, shared_ptr<Image> image, dcpomatic::Rect<double> rect) +{ + _decoded_image_subtitles.push_back (shared_ptr<ContentImageSubtitle> (new ContentImageSubtitle (period, 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, ContentTimePeriod period) +{ + if (!has_subtitle_during (period)) { + return list<shared_ptr<T> > (); + } + + if (subs.empty() || period.from < subs.front()->period().from || period.to > (subs.back()->period().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 (period.from, true); + } + + /* Now enough pass() calls will either: + * (a) give us what we want, or + * (b) hit the end of the decoder. + */ + while (!pass() && (subs.empty() || (subs.front()->period().from > period.from || period.to < subs.back()->period().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)->period().overlaps (period)) { + out.push_back (*i); + } + } + + return out; +} + +list<shared_ptr<ContentTextSubtitle> > +SubtitleDecoder::get_text_subtitles (ContentTimePeriod period) +{ + return get<ContentTextSubtitle> (_decoded_text_subtitles, period); +} + +list<shared_ptr<ContentImageSubtitle> > +SubtitleDecoder::get_image_subtitles (ContentTimePeriod period) +{ + return get<ContentImageSubtitle> (_decoded_image_subtitles, period); +} + +void +SubtitleDecoder::seek (ContentTime, bool) { - Subtitle (image, rect, from, to); + _decoded_text_subtitles.clear (); + _decoded_image_subtitles.clear (); } diff --git a/src/lib/subtitle_decoder.h b/src/lib/subtitle_decoder.h index eeeadbd3f..c25edad49 100644 --- a/src/lib/subtitle_decoder.h +++ b/src/lib/subtitle_decoder.h @@ -17,22 +17,43 @@ */ -#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::shared_ptr<const SubtitleContent>); - boost::signals2::signal<void (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time)> Subtitle; + std::list<boost::shared_ptr<ContentImageSubtitle> > get_image_subtitles (ContentTimePeriod period); + std::list<boost::shared_ptr<ContentTextSubtitle> > get_text_subtitles (ContentTimePeriod period); protected: - void subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time); + void seek (ContentTime, bool); + + void image_subtitle (ContentTimePeriod period, 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, ContentTimePeriod period); + + virtual bool has_subtitle_during (ContentTimePeriod) const = 0; + + boost::shared_ptr<const SubtitleContent> _subtitle_content; }; + +#endif diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc index ef15f9f5e..4a85fa18a 100644 --- a/src/lib/transcode_job.cc +++ b/src/lib/transcode_job.cc @@ -37,6 +37,7 @@ using std::string; using std::stringstream; using std::fixed; using std::setprecision; +using std::cout; using boost::shared_ptr; /** @param s Film to use. @@ -106,6 +107,7 @@ TranscodeJob::status () const return s.str (); } +/** @return Approximate remaining time in seconds */ int TranscodeJob::remaining_time () const { @@ -123,6 +125,5 @@ 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; } diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc index b11ce8be5..a4cd36a4f 100644 --- a/src/lib/transcoder.cc +++ b/src/lib/transcoder.cc @@ -35,49 +35,43 @@ #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 diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h index d7736d4e8..25b2ef908 100644 --- a/src/lib/transcoder.h +++ b/src/lib/transcoder.h @@ -42,6 +42,7 @@ public: } private: + boost::shared_ptr<const Film> _film; boost::shared_ptr<Player> _player; boost::shared_ptr<Encoder> _encoder; bool _finishing; diff --git a/src/lib/types.cc b/src/lib/types.cc index 83bbf41e4..d052b2a9a 100644 --- a/src/lib/types.cc +++ b/src/lib/types.cc @@ -19,14 +19,14 @@ #include <libxml++/libxml++.h> #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "types.h" using std::max; using std::min; using std::string; using boost::shared_ptr; -using libdcp::raw_convert; +using dcp::raw_convert; bool operator== (Crop const & a, Crop const & b) { diff --git a/src/lib/types.h b/src/lib/types.h index 4eb3d927e..9a6a30b86 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -23,7 +23,9 @@ #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; @@ -46,31 +48,29 @@ namespace xmlpp { */ #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 @@ -120,7 +120,7 @@ struct Crop /** 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; diff --git a/src/lib/update.cc b/src/lib/update.cc index 34eaf385c..c7527ee49 100644 --- a/src/lib/update.cc +++ b/src/lib/update.cc @@ -22,7 +22,7 @@ #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" @@ -33,8 +33,9 @@ using std::cout; 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 @@ -43,6 +44,9 @@ write_callback_wrapper (void* data, size_t size, size_t nmemb, void* user) 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) @@ -74,6 +78,7 @@ UpdateChecker::~UpdateChecker () delete[] _buffer; } +/** Start running the update check */ void UpdateChecker::run () { @@ -86,6 +91,7 @@ void 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); @@ -95,12 +101,16 @@ UpdateChecker::thread () 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; diff --git a/src/lib/update.h b/src/lib/update.h index e96ccec31..c86adb873 100644 --- a/src/lib/update.h +++ b/src/lib/update.h @@ -17,12 +17,17 @@ */ +/** @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: @@ -32,28 +37,31 @@ 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; diff --git a/src/lib/util.cc b/src/lib/util.cc index 86046bcf8..55df5cc83 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -44,13 +44,14 @@ #endif #include <glib.h> #include <openjpeg.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> @@ -69,6 +70,7 @@ extern "C" { #include "job.h" #include "cross.h" #include "video_content.h" +#include "rect.h" #include "md5_digester.h" #ifdef DCPOMATIC_WINDOWS #include "stack.hpp" @@ -100,8 +102,8 @@ using std::set_terminate; 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; @@ -237,24 +239,6 @@ ffmpeg_version_to_string (int v) 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) { @@ -343,7 +327,8 @@ dcpomatic_setup () set_terminate (terminate); - libdcp::init (); + Pango::init (); + dcp::init (); Ratio::setup_ratios (); VideoContentScale::setup_scales (); @@ -725,17 +710,6 @@ ensure_ui_thread () 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) { @@ -786,7 +760,7 @@ tidy_for_filename (string 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 (); @@ -806,37 +780,37 @@ make_signer () 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> @@ -885,14 +859,14 @@ split_get_request (string url) 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 * @@ -923,12 +897,34 @@ divide_with_round (int64_t a, int64_t b) } } +/** 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 (); @@ -936,12 +932,16 @@ ScopedTemporary::~ScopedTemporary () 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) { @@ -949,6 +949,7 @@ ScopedTemporary::open (char const * params) return _open; } +/** Close the file */ void ScopedTemporary::close () { @@ -957,3 +958,16 @@ ScopedTemporary::close () _open = 0; } } + +ContentTimePeriod +subtitle_period (AVSubtitle const & sub) +{ + ContentTime const packet_time = ContentTime::from_seconds (static_cast<double> (sub.pts) / AV_TIME_BASE); + + ContentTimePeriod period ( + packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3), + packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3) + ); + + return period; +} diff --git a/src/lib/util.h b/src/lib/util.h index 70bf495c6..7d3afb022 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -31,7 +31,8 @@ #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> @@ -52,6 +53,7 @@ namespace libdcp { } class Job; +struct AVSubtitle; extern std::string seconds_to_hms (int); extern std::string seconds_to_approximate_hms (int); @@ -69,8 +71,8 @@ extern bool valid_image_file (boost::filesystem::path); 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); extern int dcp_audio_frame_rate (int); @@ -83,6 +85,7 @@ extern int get_optional_int (std::multimap<std::string, std::string> const & kv, extern std::string get_optional_string (std::multimap<std::string, std::string> const & kv, std::string k); extern void* wrapped_av_malloc (size_t); extern int64_t divide_with_round (int64_t a, int64_t b); +extern ContentTimePeriod subtitle_period (AVSubtitle const &); /** @class Socket * @brief A class to wrap a boost::asio::ip::tcp::socket with some things @@ -125,16 +128,20 @@ private: 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 (); diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc index 6f6b2c441..05f5c538e 100644 --- a/src/lib/video_content.cc +++ b/src/lib/video_content.cc @@ -19,8 +19,8 @@ #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" @@ -51,7 +51,7 @@ using std::max; using boost::shared_ptr; using boost::optional; using boost::dynamic_pointer_cast; -using libdcp::raw_convert; +using dcp::raw_convert; vector<VideoContentScale> VideoContentScale::_scales; @@ -65,7 +65,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f) 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) @@ -85,13 +85,20 @@ VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p) 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_size.width = node->number_child<int> ("VideoWidth"); _video_size.height = node->number_child<int> ("VideoHeight"); _video_frame_rate = node->number_child<float> ("VideoFrameRate"); + + if (version < 32) { + /* DCP-o-matic 1.0 branch */ + _video_length = ContentTime::from_frames (node->number_child<int64_t> ("VideoLength"), _video_frame_rate); + } else { + _video_length = ContentTime (node->number_child<int64_t> ("VideoLength")); + } + _video_frame_type = static_cast<VideoFrameType> (node->number_child<int> ("VideoFrameType")); _crop.left = node->number_child<int> ("LeftCrop"); _crop.right = node->number_child<int> ("RightCrop"); @@ -159,7 +166,7 @@ void 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)); @@ -172,14 +179,14 @@ VideoContent::as_xml (xmlpp::Node* node) const 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 (); { @@ -320,14 +327,17 @@ VideoContent::technical_summary () const { 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: @@ -335,9 +345,9 @@ VideoContent::video_size_after_3d_split () const 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); @@ -355,28 +365,21 @@ VideoContent::set_colour_conversion (ColourConversion c) } /** @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); - - FrameRateChange 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())); } void @@ -426,7 +429,7 @@ VideoContentScale::VideoContentScale (bool scale) } -VideoContentScale::VideoContentScale (shared_ptr<cxml::Node> node) +VideoContentScale::VideoContentScale (cxml::NodePtr node) : _ratio (0) , _scale (true) { @@ -479,14 +482,14 @@ VideoContentScale::name () const /** @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) { @@ -496,7 +499,7 @@ VideoContentScale::size (shared_ptr<const VideoContent> c, libdcp::Size display_ /* 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 ); diff --git a/src/lib/video_content.h b/src/lib/video_content.h index f23bf0abe..4206efc2c 100644 --- a/src/lib/video_content.h +++ b/src/lib/video_content.h @@ -43,9 +43,9 @@ public: 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; @@ -81,9 +81,9 @@ public: 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; @@ -91,21 +91,21 @@ public: 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; } @@ -166,10 +166,10 @@ public: 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; void scale_and_crop_to_fit_width (); void scale_and_crop_to_fit_height (); @@ -177,7 +177,7 @@ public: protected: void take_from_video_examiner (boost::shared_ptr<VideoExaminer>); - VideoContent::Frame _video_length; + ContentTime _video_length; float _video_frame_rate; private: @@ -188,7 +188,7 @@ private: void setup_default_colour_conversion (); - libdcp::Size _video_size; + dcp::Size _video_size; VideoFrameType _video_frame_type; Crop _crop; VideoContentScale _scale; diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index 5867ac925..43b1049cc 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -19,46 +19,154 @@ #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 (); } diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h index 42add42aa..2c0028fd1 100644 --- a/src/lib/video_decoder.h +++ b/src/lib/video_decoder.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @file src/lib/video_decoder.h + * @brief VideoDecoder class. + */ + #ifndef DCPOMATIC_VIDEO_DECODER_H #define DCPOMATIC_VIDEO_DECODER_H @@ -25,37 +29,37 @@ #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 diff --git a/src/lib/video_examiner.h b/src/lib/video_examiner.h index 039c494b5..87e9a0428 100644 --- a/src/lib/video_examiner.h +++ b/src/lib/video_examiner.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,15 +17,22 @@ */ -#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; }; diff --git a/src/lib/writer.cc b/src/lib/writer.cc index 2ed55a276..580dfe4f2 100644 --- a/src/lib/writer.cc +++ b/src/lib/writer.cc @@ -19,12 +19,16 @@ #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" @@ -37,6 +41,7 @@ #include "config.h" #include "job.h" #include "cross.h" +#include "audio_buffers.h" #include "md5_digester.h" #include "i18n.h" @@ -56,6 +61,7 @@ using std::cout; 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; @@ -70,7 +76,6 @@ Writer::Writer (shared_ptr<const Film> f, weak_ptr<Job> j) , _last_written_eyes (EYES_RIGHT) , _full_written (0) , _fake_written (0) - , _repeat_written (0) , _pushed_to_disk (0) { /* Remove any old DCP */ @@ -88,36 +93,34 @@ Writer::Writer (shared_ptr<const Film> f, weak_ptr<Job> j) */ 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)); @@ -171,7 +174,7 @@ Writer::fake_write (int frame, Eyes eyes) } FILE* ifi = fopen_boost (_film->info_path (frame, eyes), "r"); - libdcp::FrameInfo info (ifi); + dcp::FrameInfo info (ifi); fclose (ifi); QueueItem qi; @@ -195,8 +198,8 @@ Writer::fake_write (int frame, Eyes eyes) 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()); } } @@ -270,7 +273,7 @@ try 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; @@ -278,39 +281,27 @@ try } case QueueItem::FAKE: LOG_GENERAL (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: - { - LOG_GENERAL (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); } } @@ -384,15 +375,11 @@ Writer::finish () 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(); @@ -410,14 +397,11 @@ Writer::finish () LOG_WARNING_NC ("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 (); @@ -428,78 +412,63 @@ Writer::finish () 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> ()); LOG_GENERAL ( - N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk + 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) { @@ -510,7 +479,7 @@ 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) { LOG_GENERAL ("Existing frame %1 has no info file", f); diff --git a/src/lib/writer.h b/src/lib/writer.h index c0699ad44..2ddf70380 100644 --- a/src/lib/writer.h +++ b/src/lib/writer.h @@ -17,6 +17,10 @@ */ +/** @file src/lib/writer.h + * @brief Writer class. + */ + #include <list> #include <boost/shared_ptr.hpp> #include <boost/thread.hpp> @@ -29,15 +33,15 @@ class EncodedData; 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 @@ -51,8 +55,6 @@ public: 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 */ @@ -67,6 +69,17 @@ public: 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: @@ -123,15 +136,13 @@ private: 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; }; diff --git a/src/lib/wscript b/src/lib/wscript index 933368776..407d9cde4 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -13,10 +13,11 @@ sources = """ config.cc content.cc content_factory.cc + content_subtitle.cc cross.cc dcp_content_type.cc dcp_video_frame.cc - decoder.cc + dcpomatic_time.cc dolby_cp750.cc encoder.cc examine_content_job.cc @@ -24,9 +25,12 @@ sources = """ file_group.cc filter_graph.cc ffmpeg.cc + ffmpeg_audio_stream.cc ffmpeg_content.cc ffmpeg_decoder.cc ffmpeg_examiner.cc + ffmpeg_stream.cc + ffmpeg_subtitle_stream.cc film.cc filter.cc frame_rate_change.cc @@ -43,11 +47,11 @@ sources = """ json_server.cc log.cc md5_digester.cc - piece.cc player.cc player_video_frame.cc playlist.cc ratio.cc + render_subtitles.cc resampler.cc scp_dcp_job.cc scaler.cc @@ -57,7 +61,9 @@ sources = """ 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 @@ -84,7 +90,7 @@ def build(bld): 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: diff --git a/src/tools/dcpomatic.cc b/src/tools/dcpomatic.cc index c9d17eb9b..237fa4e58 100644 --- a/src/tools/dcpomatic.cc +++ b/src/tools/dcpomatic.cc @@ -30,7 +30,7 @@ #include <wx/stdpaths.h> #include <wx/cmdline.h> #include <wx/preferences.h> -#include <libdcp/exceptions.h> +#include <dcp/exceptions.h> #include "wx/film_viewer.h" #include "wx/film_editor.h" #include "wx/job_manager_view.h" @@ -476,7 +476,7 @@ private: shared_ptr<Job> (new SendKDMEmailJob (film, d->screens (), d->cpl (), d->from (), d->until ())) ); } - } catch (libdcp::NotEncryptedError& e) { + } catch (dcp::NotEncryptedError& e) { error_dialog (this, _("CPL's content is not encrypted.")); } catch (exception& e) { error_dialog (this, e.what ()); @@ -643,6 +643,9 @@ static const wxCmdLineEntryDesc command_line_description[] = { { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 } }; +/** @class App + * @brief The magic App class for wxWidgets. + */ class App : public wxApp { bool OnInit () diff --git a/src/tools/dcpomatic_cli.cc b/src/tools/dcpomatic_cli.cc index 8fed28f18..acd1335e6 100644 --- a/src/tools/dcpomatic_cli.cc +++ b/src/tools/dcpomatic_cli.cc @@ -20,7 +20,7 @@ #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" diff --git a/src/tools/dcpomatic_create.cc b/src/tools/dcpomatic_create.cc index c30f4e51e..b0a67c6d9 100644 --- a/src/tools/dcpomatic_create.cc +++ b/src/tools/dcpomatic_create.cc @@ -190,7 +190,7 @@ main (int argc, char* argv[]) 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)); } } diff --git a/src/tools/dcpomatic_kdm.cc b/src/tools/dcpomatic_kdm.cc index 041f6c7ef..0f2d5b8a3 100644 --- a/src/tools/dcpomatic_kdm.cc +++ b/src/tools/dcpomatic_kdm.cc @@ -18,7 +18,7 @@ */ #include <getopt.h> -#include <libdcp/certificates.h> +#include <dcp/certificates.h> #include "lib/film.h" #include "lib/cinema.h" #include "lib/kdm.h" @@ -41,8 +41,8 @@ help () 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" @@ -235,8 +235,8 @@ int main (int argc, char* argv[]) 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, cpl, 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, cpl, valid_from.get(), valid_to.get()); kdm.as_xml (output); if (verbose) { cout << "Generated KDM " << output << " for certificate.\n"; @@ -260,12 +260,13 @@ int main (int argc, char* argv[]) try { if (zip) { - write_kdm_zip_files (film, (*i)->screens(), cpl, valid_from.get(), valid_to.get(), output); + write_kdm_zip_files (film, (*i)->screens(), cpl, 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(), cpl, valid_from.get(), valid_to.get(), output); + write_kdm_files (film, (*i)->screens(), cpl, dcp::LocalTime (valid_from.get()), dcp::LocalTime (valid_to.get()), output); if (verbose) { cout << "Wrote KDM files to " << output << "\n"; } diff --git a/src/tools/server_test.cc b/src/tools/server_test.cc index 896517b27..3c2ea4b36 100644 --- a/src/tools/server_test.cc +++ b/src/tools/server_test.cc @@ -45,18 +45,18 @@ using boost::shared_ptr; 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; @@ -144,12 +144,10 @@ main (int argc, char* argv[]) 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"; diff --git a/src/wx/about_dialog.cc b/src/wx/about_dialog.cc index 63de45120..dd80e94cb 100644 --- a/src/wx/about_dialog.cc +++ b/src/wx/about_dialog.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @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" @@ -207,6 +211,10 @@ AboutDialog::AboutDialog (wxWindow* parent) 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) { diff --git a/src/wx/about_dialog.h b/src/wx/about_dialog.h index a78abb93e..4901cf990 100644 --- a/src/wx/about_dialog.h +++ b/src/wx/about_dialog.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,10 +17,17 @@ */ +/** @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: @@ -29,6 +36,6 @@ public: private: void add_section (wxString, wxArrayString); - wxNotebook* _notebook; + wxNotebook* _notebook; ///< notebook used to keep each list of names for the credits }; diff --git a/src/wx/audio_mapping_view.cc b/src/wx/audio_mapping_view.cc index 6c1508aee..8e92400bd 100644 --- a/src/wx/audio_mapping_view.cc +++ b/src/wx/audio_mapping_view.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,16 +17,21 @@ */ +/** @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 <libdcp/raw_convert.h> +#include <dcp/types.h> +#include <dcp/raw_convert.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; @@ -53,6 +58,9 @@ public: } }; +/** @class ValueRenderer + * @brief wxGridCellRenderer for a gain value. + */ class ValueRenderer : public wxGridCellRenderer { public: @@ -155,7 +163,7 @@ AudioMappingView::left_click (wxGridEvent& ev) 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); @@ -181,28 +189,28 @@ AudioMappingView::right_click (wxGridEvent& ev) 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::minus6dB () { - _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), pow (10, -6.0 / 20)); + _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), pow (10, -6.0 / 20)); 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) { @@ -239,7 +247,7 @@ AudioMappingView::update_cells () _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 (libdcp::raw_convert<string> (_map.get (i, static_cast<libdcp::Channel> (j - 1))))); + _grid->SetCellValue (i, j, std_to_wx (dcp::raw_convert<string> (_map.get (i, static_cast<dcp::Channel> (j - 1))))); } } @@ -343,7 +351,7 @@ AudioMappingView::mouse_moved (wxMouseEvent& ev) 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) { diff --git a/src/wx/audio_mapping_view.h b/src/wx/audio_mapping_view.h index 98375eb9e..7ed699463 100644 --- a/src/wx/audio_mapping_view.h +++ b/src/wx/audio_mapping_view.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,13 @@ */ +/** @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> diff --git a/src/wx/audio_panel.cc b/src/wx/audio_panel.cc index 72cb9fe6a..26237db31 100644 --- a/src/wx/audio_panel.cc +++ b/src/wx/audio_panel.cc @@ -22,6 +22,7 @@ #include "lib/config.h" #include "lib/sound_processor.h" #include "lib/ffmpeg_content.h" +#include "lib/ffmpeg_audio_stream.h" #include "audio_dialog.h" #include "audio_panel.h" #include "audio_mapping_view.h" @@ -239,7 +240,7 @@ AudioPanel::setup_stream_description () } 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); } } diff --git a/src/wx/config_dialog.cc b/src/wx/config_dialog.cc index e65e931d0..b447e0eee 100644 --- a/src/wx/config_dialog.cc +++ b/src/wx/config_dialog.cc @@ -28,13 +28,14 @@ #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" #include "lib/filter.h" #include "lib/dcp_content_type.h" #include "lib/colour_conversion.h" +#include "lib/log.h" #include "config_dialog.h" #include "wx_util.h" #include "editable_list.h" @@ -446,14 +447,14 @@ private: 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); } diff --git a/src/wx/content_widget.h b/src/wx/content_widget.h index ca9485006..9e1338b7c 100644 --- a/src/wx/content_widget.h +++ b/src/wx/content_widget.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @file src/wx/content_widget.h + * @brief ContentWidget class. + */ + #ifndef DCPOMATIC_MULTIPLE_WIDGET_H #define DCPOMATIC_MULTIPLE_WIDGET_H @@ -26,7 +30,8 @@ #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) diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc index 9c980b625..252a89719 100644 --- a/src/wx/film_editor.cc +++ b/src/wx/film_editor.cc @@ -867,7 +867,7 @@ FilmEditor::setup_content_sensitivity () _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); } diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index ed1a2ce41..c848fe09b 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -37,6 +37,7 @@ #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" @@ -51,7 +52,7 @@ using std::make_pair; 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) @@ -62,7 +63,6 @@ FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* 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); @@ -122,7 +122,7 @@ FilmViewer::set_film (shared_ptr<Film> f) _frame.reset (); _slider->SetValue (0); - set_position_text (0); + set_position_text (); if (!_film) { return; @@ -136,30 +136,31 @@ FilmViewer::set_film (shared_ptr<Film> f) 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 (); } @@ -167,16 +168,12 @@ FilmViewer::fetch_current_frame_again () 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); } @@ -219,15 +216,16 @@ FilmViewer::paint_panel () 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 @@ -236,7 +234,7 @@ FilmViewer::panel_sized (wxSizeEvent& ev) _panel_size.width = ev.GetSize().GetWidth(); _panel_size.height = ev.GetSize().GetHeight(); calculate_sizes (); - fetch_current_frame_again (); + get (_position, true); } void @@ -265,6 +263,13 @@ FilmViewer::calculate_sizes () _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); } @@ -289,20 +294,7 @@ FilmViewer::check_play_state () } 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"); @@ -312,9 +304,9 @@ FilmViewer::set_position_text (Time t) 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); @@ -325,35 +317,6 @@ FilmViewer::set_position_text (Time t) _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) { @@ -377,31 +340,18 @@ 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 @@ -412,5 +362,5 @@ FilmViewer::player_changed (bool frequent) } calculate_sizes (); - fetch_current_frame_again (); + get (_position, true); } diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h index 1e5b6d34d..950758f61 100644 --- a/src/wx/film_viewer.h +++ b/src/wx/film_viewer.h @@ -1,5 +1,5 @@ /* - 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 @@ -32,19 +32,6 @@ class PlayerVideoFrame; /** @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 { @@ -59,16 +46,14 @@ private: 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; @@ -84,10 +69,10 @@ private: 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; }; diff --git a/src/wx/properties_dialog.cc b/src/wx/properties_dialog.cc index 28247bc33..801996efa 100644 --- a/src/wx/properties_dialog.cc +++ b/src/wx/properties_dialog.cc @@ -45,8 +45,7 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film) 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")); @@ -64,10 +63,11 @@ PropertiesDialog::frames_already_encoded () const } 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 (); } diff --git a/src/wx/screen_dialog.cc b/src/wx/screen_dialog.cc index b702ae0ad..0d46a46ec 100644 --- a/src/wx/screen_dialog.cc +++ b/src/wx/screen_dialog.cc @@ -19,7 +19,7 @@ #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" @@ -31,7 +31,7 @@ using std::string; 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) { @@ -79,7 +79,7 @@ ScreenDialog::name () const return wx_to_std (_name->GetValue()); } -shared_ptr<libdcp::Certificate> +shared_ptr<dcp::Certificate> ScreenDialog::certificate () const { return _certificate; @@ -89,9 +89,9 @@ void 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())); } } @@ -128,7 +128,7 @@ void 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") || diff --git a/src/wx/screen_dialog.h b/src/wx/screen_dialog.h index 3601a8f6c..5c6d964b8 100644 --- a/src/wx/screen_dialog.h +++ b/src/wx/screen_dialog.h @@ -19,7 +19,7 @@ #include <wx/wx.h> #include <boost/shared_ptr.hpp> -#include <libdcp/certificates.h> +#include <dcp/certificates.h> #include "table_dialog.h" class Progress; @@ -27,10 +27,10 @@ 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 (); @@ -44,5 +44,5 @@ private: wxButton* _download_certificate; wxTextCtrl* _certificate_text; - boost::shared_ptr<libdcp::Certificate> _certificate; + boost::shared_ptr<dcp::Certificate> _certificate; }; diff --git a/src/wx/subtitle_panel.cc b/src/wx/subtitle_panel.cc index 63a18b0ce..aa3d3a735 100644 --- a/src/wx/subtitle_panel.cc +++ b/src/wx/subtitle_panel.cc @@ -1,5 +1,5 @@ /* - 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 @@ -20,9 +20,12 @@ #include <boost/lexical_cast.hpp> #include <wx/spinctrl.h> #include "lib/ffmpeg_content.h" +#include "lib/subrip_content.h" +#include "lib/ffmpeg_subtitle_stream.h" #include "subtitle_panel.h" #include "film_editor.h" #include "wx_util.h" +#include "subtitle_view.h" using std::vector; using std::string; @@ -32,6 +35,7 @@ using boost::dynamic_pointer_cast; 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); @@ -70,6 +74,9 @@ SubtitlePanel::SubtitlePanel (FilmEditor* e) 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); @@ -81,6 +88,7 @@ SubtitlePanel::SubtitlePanel (FilmEditor* e) _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 @@ -164,6 +172,9 @@ SubtitlePanel::setup_sensitivity () _y_offset->Enable (j); _scale->Enable (j); _stream->Enable (j); + + SubtitleContentList c = _editor->selected_subtitle_content (); + _view_button->Enable (c.size() == 1); } void @@ -223,3 +234,21 @@ SubtitlePanel::content_selection_changed () 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 (); +} diff --git a/src/wx/subtitle_panel.h b/src/wx/subtitle_panel.h index 20d7c40c2..1ee775025 100644 --- a/src/wx/subtitle_panel.h +++ b/src/wx/subtitle_panel.h @@ -1,5 +1,5 @@ /* - 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 @@ -21,6 +21,7 @@ class wxCheckBox; class wxSpinCtrl; +class SubtitleView; class SubtitlePanel : public FilmEditorPanel { @@ -37,6 +38,7 @@ private: void y_offset_changed (); void scale_changed (); void stream_changed (); + void view_clicked (); void setup_sensitivity (); @@ -45,4 +47,6 @@ private: wxSpinCtrl* _y_offset; wxSpinCtrl* _scale; wxChoice* _stream; + wxButton* _view_button; + SubtitleView* _view; }; diff --git a/src/wx/subtitle_view.cc b/src/wx/subtitle_view.cc new file mode 100644 index 000000000..d14f260c9 --- /dev/null +++ b/src/wx/subtitle_view.cc @@ -0,0 +1,80 @@ +/* + 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 (ContentTimePeriod (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); +} + diff --git a/src/lib/decoder.cc b/src/wx/subtitle_view.h index 3f4cda6eb..94a25b70c 100644 --- a/src/lib/decoder.cc +++ b/src/wx/subtitle_view.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + 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 @@ -17,22 +17,17 @@ */ -/** @file src/decoder.cc - * @brief Parent class for decoders of content. - */ +#include <boost/shared_ptr.hpp> +#include <wx/wx.h> +#include <wx/listctrl.h> -#include "film.h" -#include "decoder.h" +class SubRipContent; -#include "i18n.h" - -using boost::shared_ptr; - -/** @param f Film. - * @param o Decode options. - */ -Decoder::Decoder (shared_ptr<const Film> f) - : _film (f) +class SubtitleView : public wxDialog { +public: + SubtitleView (wxWindow *, boost::shared_ptr<SubRipContent>); -} +private: + wxListCtrl* _list; +}; diff --git a/src/wx/timecode.cc b/src/wx/timecode.cc index ee5b5604b..1ab4b590b 100644 --- a/src/wx/timecode.cc +++ b/src/wx/timecode.cc @@ -83,12 +83,12 @@ Timecode::Timecode (wxWindow* parent) } 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; @@ -105,18 +105,18 @@ Timecode::set (Time t, int 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; } diff --git a/src/wx/timecode.h b/src/wx/timecode.h index 880b44a31..b13e8c3c0 100644 --- a/src/wx/timecode.h +++ b/src/wx/timecode.h @@ -26,8 +26,8 @@ class Timecode : public wxPanel public: Timecode (wxWindow *); - void set (Time, int); - Time get (int) const; + void set (DCPTime, int); + DCPTime get (int) const; void set_editable (bool); diff --git a/src/wx/timeline.cc b/src/wx/timeline.cc index d4df6d8b2..4b56168a1 100644 --- a/src/wx/timeline.cc +++ b/src/wx/timeline.cc @@ -35,7 +35,9 @@ using boost::dynamic_pointer_cast; 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: @@ -64,9 +66,9 @@ 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().get_value_or (0); + return _timeline.tracks_position().x + t.seconds() * _timeline.pixels_per_second().get_value_or (0); } Timeline& _timeline; @@ -76,7 +78,9 @@ private: }; -/** Parent class for views of pieces of content */ +/** @class ContentView + * @brief Parent class for views of pieces of content. + */ class ContentView : public View { public: @@ -101,7 +105,7 @@ public: return dcpomatic::Rect<int> ( time_x (content->position ()) - 8, y_pos (_track.get()) - 8, - content->length_after_trim () * _timeline.pixels_per_time_unit().get_value_or(0) + 16, + content->length_after_trim().seconds() * _timeline.pixels_per_second().get_value_or(0) + 16, _timeline.track_height() + 16 ); } @@ -142,8 +146,8 @@ private: 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); @@ -172,7 +176,7 @@ private: 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().get_value_or(0), _timeline.track_height())); + gc->Clip (wxRegion (time_x (position), y_pos (_track.get()), len.seconds() * _timeline.pixels_per_second().get_value_or(0), _timeline.track_height())); gc->DrawText (name, time_x (position) + 12, y_pos (_track.get() + 1) - name_height - 4); gc->ResetClip (); } @@ -191,7 +195,7 @@ private: } if (!frequent) { - _timeline.setup_pixels_per_time_unit (); + _timeline.setup_pixels_per_second (); _timeline.Refresh (); } } @@ -246,6 +250,25 @@ private: } }; +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: @@ -269,26 +292,26 @@ private: void do_paint (wxGraphicsContext* gc) { - if (!_timeline.pixels_per_time_unit()) { + if (!_timeline.pixels_per_second()) { return; } - double const pptu = _timeline.pixels_per_time_unit().get (); + double const pps = _timeline.pixels_per_second().get (); gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxPENSTYLE_SOLID)); - int mark_interval = rint (128 / (TIME_HZ * pptu)); + double mark_interval = rint (128 / pps); 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) { @@ -300,14 +323,15 @@ private: path.AddLineToPoint (_timeline.width(), _y); gc->StrokePath (path); - Time t = 0; - while ((t * pptu) < _timeline.width()) { + /* Time in seconds */ + DCPTime t; + while ((t.seconds() * pps) < _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; @@ -321,12 +345,12 @@ private: wxDouble str_leading; gc->GetTextExtent (str, &str_width, &str_height, &str_descent, &str_leading); - int const tx = _timeline.x_offset() + t * pptu; + int const tx = _timeline.x_offset() + t.seconds() * pps; 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); } } @@ -406,10 +430,13 @@ Timeline::playlist_changed () 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 (); } @@ -421,11 +448,11 @@ Timeline::assign_tracks () 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); @@ -471,14 +498,14 @@ Timeline::tracks () const } 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> @@ -580,11 +607,11 @@ Timeline::right_down (wxMouseEvent& ev) void Timeline::set_position_from_event (wxMouseEvent& ev) { - if (!_pixels_per_time_unit) { + if (!_pixels_per_second) { return; } - double const pptu = _pixels_per_time_unit.get (); + double const pps = _pixels_per_second.get (); wxPoint const p = ev.GetPosition(); @@ -603,13 +630,13 @@ Timeline::set_position_from_event (wxMouseEvent& ev) return; } - Time new_position = _down_view_position + (p.x - _down_point.x) / pptu; + DCPTime new_position = _down_view_position + DCPTime::from_seconds ((p.x - _down_point.x) / pps); 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) { @@ -620,7 +647,7 @@ Timeline::set_position_from_event (wxMouseEvent& ev) { /* 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(); @@ -629,7 +656,10 @@ Timeline::set_position_from_event (wxMouseEvent& ev) { /* 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 (); @@ -641,14 +671,14 @@ Timeline::set_position_from_event (wxMouseEvent& ev) if (!first) { /* Snap if it's close; `close' means within a proportion of the time on the timeline */ - if (nearest_distance < (width() / pptu) / 32) { + if (nearest_distance < DCPTime::from_seconds ((width() / pps) / 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); @@ -673,7 +703,7 @@ Timeline::film () const void Timeline::resized () { - setup_pixels_per_time_unit (); + setup_pixels_per_second (); } void diff --git a/src/wx/timeline.h b/src/wx/timeline.h index 7b4d75d09..4ba1cc425 100644 --- a/src/wx/timeline.h +++ b/src/wx/timeline.h @@ -52,8 +52,8 @@ public: return 48; } - boost::optional<double> pixels_per_time_unit () const { - return _pixels_per_time_unit; + boost::optional<double> pixels_per_second () const { + return _pixels_per_second; } Position<int> tracks_position () const { @@ -62,7 +62,7 @@ public: int tracks () const; - void setup_pixels_per_time_unit (); + void setup_pixels_per_second (); void set_snap (bool s) { _snap = s; @@ -96,11 +96,11 @@ private: ViewList _views; boost::shared_ptr<TimeAxisView> _time_axis_view; int _tracks; - boost::optional<double> _pixels_per_time_unit; + boost::optional<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; diff --git a/src/wx/timing_panel.cc b/src/wx/timing_panel.cc index 6d9bf4539..cc0639b4c 100644 --- a/src/wx/timing_panel.cc +++ b/src/wx/timing_panel.cc @@ -87,7 +87,7 @@ TimingPanel::film_content_changed (int property) if (content) { _position->set (content->position (), film_video_frame_rate); } else { - _position->set (0, 24); + _position->set (DCPTime () , 24); } } else if ( property == ContentProperty::LENGTH || @@ -98,24 +98,24 @@ TimingPanel::film_content_changed (int property) _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); } } @@ -155,7 +155,8 @@ TimingPanel::full_length_changed () 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))); } } } diff --git a/src/wx/video_panel.cc b/src/wx/video_panel.cc index 2d874b959..2a5577bc1 100644 --- a/src/wx/video_panel.cc +++ b/src/wx/video_panel.cc @@ -298,8 +298,8 @@ VideoPanel::setup_description () } 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, @@ -308,8 +308,8 @@ VideoPanel::setup_description () ++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 ( diff --git a/src/wx/video_panel.h b/src/wx/video_panel.h index 99633491d..4331a9712 100644 --- a/src/wx/video_panel.h +++ b/src/wx/video_panel.h @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @file src/lib/video_panel.h + * @brief VideoPanel class. + */ + #include "lib/film.h" #include "film_editor_panel.h" #include "content_widget.h" @@ -26,6 +30,9 @@ class wxStaticText; class wxSpinCtrl; class wxButton; +/** @class VideoPanel + * @brief The video tab of the film editor. + */ class VideoPanel : public FilmEditorPanel { public: diff --git a/src/wx/wscript b/src/wx/wscript index 8c142698b..0c87c09be 100644 --- a/src/wx/wscript +++ b/src/wx/wscript @@ -38,6 +38,7 @@ sources = """ server_dialog.cc servers_list_dialog.cc subtitle_panel.cc + subtitle_view.cc table_dialog.cc timecode.cc timeline.cc diff --git a/src/wx/wx_ui_signaller.cc b/src/wx/wx_ui_signaller.cc index f30631960..8fc6670d6 100644 --- a/src/wx/wx_ui_signaller.cc +++ b/src/wx/wx_ui_signaller.cc @@ -1,5 +1,5 @@ /* - 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 diff --git a/src/wx/wx_ui_signaller.h b/src/wx/wx_ui_signaller.h index f7df6fca4..63f2049cd 100644 --- a/src/wx/wx_ui_signaller.h +++ b/src/wx/wx_ui_signaller.h @@ -1,5 +1,5 @@ /* - 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 @@ -21,6 +21,10 @@ class wxEvtHandler; +/** @class wxUISignaller + * @brief UISignaller for the wxWidgets event loop + */ + class wxUISignaller : public UISignaller { public: diff --git a/src/wx/wx_util.cc b/src/wx/wx_util.cc index b73cd490d..aac35e97a 100644 --- a/src/wx/wx_util.cc +++ b/src/wx/wx_util.cc @@ -123,7 +123,7 @@ int const ThreadedStaticText::_update_event_id = 10000; * @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); @@ -139,7 +139,7 @@ ThreadedStaticText::~ThreadedStaticText () /** 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); diff --git a/test/4k_test.cc b/test/4k_test.cc index e65804aa5..fa5b33bb9 100644 --- a/test/4k_test.cc +++ b/test/4k_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,12 @@ */ +/** @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" diff --git a/test/README b/test/README new file mode 100644 index 000000000..a129b6617 --- /dev/null +++ b/test/README @@ -0,0 +1,50 @@ +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 diff --git a/test/audio_analysis_test.cc b/test/audio_analysis_test.cc index 77b2aeaf6..13a8dac84 100644 --- a/test/audio_analysis_test.cc +++ b/test/audio_analysis_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @file test/audio_analysis_test.cc + * @brief Check serialisation of audio analyses. + */ + #include <boost/test/unit_test.hpp> #include "lib/audio_analysis.h" @@ -26,7 +30,6 @@ random_float () return (float (rand ()) / RAND_MAX) * 2 - 1; } -/* Check serialisation of audio analyses */ BOOST_AUTO_TEST_CASE (audio_analysis_test) { int const channels = 3; @@ -50,7 +53,7 @@ BOOST_AUTO_TEST_CASE (audio_analysis_test) 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); diff --git a/test/audio_buffers_test.cc b/test/audio_buffers_test.cc new file mode 100644 index 000000000..15f918470 --- /dev/null +++ b/test/audio_buffers_test.cc @@ -0,0 +1,303 @@ +/* + 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); + } + } + } +} diff --git a/test/audio_decoder_test.cc b/test/audio_decoder_test.cc new file mode 100644 index 000000000..a14e2f9be --- /dev/null +++ b/test/audio_decoder_test.cc @@ -0,0 +1,161 @@ +/* + 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); + } + } +} diff --git a/test/audio_delay_test.cc b/test/audio_delay_test.cc index 8ac5f746c..68e14ff3c 100644 --- a/test/audio_delay_test.cc +++ b/test/audio_delay_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,11 +17,18 @@ */ +/** @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" @@ -53,10 +60,10 @@ void test_audio_delay (int delay_in_ms) 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 */ @@ -66,11 +73,11 @@ void test_audio_delay (int delay_in_ms) /* 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); @@ -86,7 +93,6 @@ void test_audio_delay (int delay_in_ms) } } - /* Test audio delay when specified in a piece of audio content */ BOOST_AUTO_TEST_CASE (audio_delay_test) { diff --git a/test/audio_mapping_test.cc b/test/audio_mapping_test.cc index bfb53b087..fc597b91d 100644 --- a/test/audio_mapping_test.cc +++ b/test/audio_mapping_test.cc @@ -17,13 +17,14 @@ */ +/** @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; @@ -35,10 +36,10 @@ BOOST_AUTO_TEST_CASE (audio_mapping_test) 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); } diff --git a/test/audio_merger_test.cc b/test/audio_merger_test.cc deleted file mode 100644 index 31d055ab7..000000000 --- a/test/audio_merger_test.cc +++ /dev/null @@ -1,107 +0,0 @@ -/* - 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); - } -} diff --git a/test/black_fill_test.cc b/test/black_fill_test.cc index 5c594f68c..148ec9738 100644 --- a/test/black_fill_test.cc +++ b/test/black_fill_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -25,7 +25,7 @@ #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; @@ -46,10 +46,10 @@ BOOST_AUTO_TEST_CASE (black_fill_test) 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 (); diff --git a/test/burnt_subtitle_test.cc b/test/burnt_subtitle_test.cc new file mode 100644 index 000000000..c5d6c4c14 --- /dev/null +++ b/test/burnt_subtitle_test.cc @@ -0,0 +1,49 @@ +/* + 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/burnt_subtitle_test.cc + * @brief Test the burning of subtitles into the DCP. + */ + +#include <boost/test/unit_test.hpp> +#include "lib/subrip_content.h" +#include "lib/film.h" +#include "lib/ratio.h" +#include "lib/dcp_content_type.h" +#include "test.h" + +using std::cout; +using boost::shared_ptr; + +/** Build a small DCP with no picture and a single subtitle overlaid onto it */ +BOOST_AUTO_TEST_CASE (burnt_subtitle_test) +{ + shared_ptr<Film> film = new_test_film ("burnt_subtitle_test"); + film->set_container (Ratio::from_id ("185")); + film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR")); + film->set_name ("frobozz"); + film->set_with_subtitles (true); + shared_ptr<SubRipContent> content (new SubRipContent (film, "test/data/subrip2.srt")); + film->examine_and_add_content (content); + wait_for_jobs (); + film->make_dcp (); + wait_for_jobs (); + + check_dcp ("test/data/burnt_subtitle_test", film->dir (film->dcp_name ())); +} diff --git a/test/client_server_test.cc b/test/client_server_test.cc index 07af1255c..51594a47a 100644 --- a/test/client_server_test.cc +++ b/test/client_server_test.cc @@ -17,6 +17,14 @@ */ +/** @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" @@ -39,12 +47,12 @@ do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription description 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_rgb) { - 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) { @@ -57,7 +65,7 @@ BOOST_AUTO_TEST_CASE (client_server_test_rgb) 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; @@ -76,8 +84,8 @@ BOOST_AUTO_TEST_CASE (client_server_test_rgb) new PlayerVideoFrame ( shared_ptr<ImageProxy> (new RawImageProxy (image, log)), 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, @@ -85,7 +93,7 @@ BOOST_AUTO_TEST_CASE (client_server_test_rgb) ) ); - pvf->set_subtitle (sub_image, Position<int> (50, 60)); + pvf->set_subtitle (PositionImage (sub_image, Position<int> (50, 60))); shared_ptr<DCPVideoFrame> frame ( new DCPVideoFrame ( @@ -122,11 +130,13 @@ BOOST_AUTO_TEST_CASE (client_server_test_rgb) for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) { delete *i; } + + delete server; } BOOST_AUTO_TEST_CASE (client_server_test_yuv) { - 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)); uint8_t* p = image->data()[0]; for (int i = 0; i < image->components(); ++i) { @@ -136,7 +146,7 @@ BOOST_AUTO_TEST_CASE (client_server_test_yuv) } } - 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; @@ -155,8 +165,8 @@ BOOST_AUTO_TEST_CASE (client_server_test_yuv) new PlayerVideoFrame ( shared_ptr<ImageProxy> (new RawImageProxy (image, log)), 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, @@ -164,7 +174,7 @@ BOOST_AUTO_TEST_CASE (client_server_test_yuv) ) ); - pvf->set_subtitle (sub_image, Position<int> (50, 60)); + pvf->set_subtitle (PositionImage (sub_image, Position<int> (50, 60))); shared_ptr<DCPVideoFrame> frame ( new DCPVideoFrame ( @@ -201,5 +211,7 @@ BOOST_AUTO_TEST_CASE (client_server_test_yuv) for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) { delete *i; } + + delete server; } diff --git a/test/colour_conversion_test.cc b/test/colour_conversion_test.cc index f850847b8..7de169dd3 100644 --- a/test/colour_conversion_test.cc +++ b/test/colour_conversion_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,17 +17,20 @@ */ +/** @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(), "1e720d2d99add654d7816f3b72da815e"); BOOST_CHECK_EQUAL (B.identifier(), "18751a247b22682b725bf9c4caf71522"); diff --git a/test/ffmpeg_audio_test.cc b/test/ffmpeg_audio_test.cc index 2e83d45c9..98efe4dd0 100644 --- a/test/ffmpeg_audio_test.cc +++ b/test/ffmpeg_audio_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,12 +17,17 @@ */ +/** @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" @@ -55,56 +60,56 @@ BOOST_AUTO_TEST_CASE (ffmpeg_audio_test) 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); diff --git a/test/ffmpeg_dcp_test.cc b/test/ffmpeg_dcp_test.cc index 4922ec4d4..234bf2c79 100644 --- a/test/ffmpeg_dcp_test.cc +++ b/test/ffmpeg_dcp_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,12 @@ */ +/** @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" @@ -27,10 +33,6 @@ 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"); diff --git a/test/ffmpeg_decoder_seek_test.cc b/test/ffmpeg_decoder_seek_test.cc new file mode 100644 index 000000000..968c3bdf9 --- /dev/null +++ b/test/ffmpeg_decoder_seek_test.cc @@ -0,0 +1,94 @@ +/* + 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); +} + diff --git a/test/ffmpeg_decoder_sequential_test.cc b/test/ffmpeg_decoder_sequential_test.cc new file mode 100644 index 000000000..9a14c5adb --- /dev/null +++ b/test/ffmpeg_decoder_sequential_test.cc @@ -0,0 +1,83 @@ +/* + 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 ()); +#ifdef DCPOMATIC_DEBUG + decoder.test_gaps = 0; +#endif + 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); + } + } +#ifdef DCPOMATIC_DEBUG + BOOST_CHECK_EQUAL (decoder.test_gaps, 0); +#endif +} + +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); +} + diff --git a/test/ffmpeg_examiner_test.cc b/test/ffmpeg_examiner_test.cc index a3b9bb4f6..26834aafc 100644 --- a/test/ffmpeg_examiner_test.cc +++ b/test/ffmpeg_examiner_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,9 +17,15 @@ */ +/** @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" +#include "lib/ffmpeg_audio_stream.h" #include "test.h" using boost::shared_ptr; @@ -30,7 +36,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_examiner_test) 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)); } diff --git a/test/ffmpeg_pts_offset.cc b/test/ffmpeg_pts_offset_test.cc index 6caf0d07a..94e7223ab 100644 --- a/test/ffmpeg_pts_offset.cc +++ b/test/ffmpeg_pts_offset_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,10 +17,15 @@ */ +/** @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 "lib/ffmpeg_audio_stream.h" #include "test.h" using boost::shared_ptr; @@ -34,48 +39,43 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test) { /* 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); + 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 = 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); + 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 = 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); + 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 = 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); + 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 = 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); + 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); } } diff --git a/test/file_group_test.cc b/test/file_group_test.cc index 14c01a976..888834511 100644 --- a/test/file_group_test.cc +++ b/test/file_group_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @file test/file_group_test.cc + * @brief Check that FileGroup works. + */ + #include <stdint.h> #include <cstdio> #include <boost/test/unit_test.hpp> diff --git a/test/film_metadata_test.cc b/test/film_metadata_test.cc index 7440e4338..70f29b998 100644 --- a/test/film_metadata_test.cc +++ b/test/film_metadata_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @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> @@ -33,13 +37,9 @@ using boost::shared_ptr; 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->_isdcf_date = boost::gregorian::from_undelimited_string ("20130211"); BOOST_CHECK (f->container() == 0); BOOST_CHECK (f->dcp_content_type() == 0); @@ -52,9 +52,9 @@ BOOST_AUTO_TEST_CASE (film_metadata_test) 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"); @@ -62,5 +62,5 @@ BOOST_AUTO_TEST_CASE (film_metadata_test) 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); } diff --git a/test/frame_rate_test.cc b/test/frame_rate_test.cc index 7e197dc03..e8ebcea3b 100644 --- a/test/frame_rate_test.cc +++ b/test/frame_rate_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,11 +17,17 @@ */ +/** @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" #include "lib/ffmpeg_content.h" #include "lib/playlist.h" +#include "lib/ffmpeg_audio_stream.h" #include "lib/frame_rate_change.h" #include "test.h" @@ -53,6 +59,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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 (); @@ -61,6 +68,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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 (); @@ -69,6 +77,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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 (); @@ -77,6 +86,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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 (); @@ -85,6 +95,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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 (); @@ -93,6 +104,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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 (); @@ -101,6 +113,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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 (); @@ -109,6 +122,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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 (); @@ -117,6 +131,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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 (); @@ -125,6 +140,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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 (); @@ -133,6 +149,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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. @@ -150,6 +167,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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 (); @@ -158,6 +176,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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 (); @@ -166,6 +185,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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) */ @@ -173,6 +193,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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 */ @@ -187,6 +208,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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 FrameRateChange @@ -233,43 +255,43 @@ BOOST_AUTO_TEST_CASE (audio_sampling_rate_test) 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 FrameRateChange 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)); } diff --git a/test/image_test.cc b/test/image_test.cc index 51ad49ebf..ee4819d6b 100644 --- a/test/image_test.cc +++ b/test/image_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,18 +17,25 @@ */ +/** @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" #include "lib/scaler.h" using std::string; +using std::list; using std::cout; using boost::shared_ptr; 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); @@ -50,12 +57,12 @@ BOOST_AUTO_TEST_CASE (aligned_image_test) 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); @@ -67,9 +74,9 @@ BOOST_AUTO_TEST_CASE (aligned_image_test) 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; @@ -78,7 +85,7 @@ BOOST_AUTO_TEST_CASE (aligned_image_test) 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); @@ -99,12 +106,12 @@ BOOST_AUTO_TEST_CASE (compact_image_test) 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); @@ -116,9 +123,9 @@ BOOST_AUTO_TEST_CASE (compact_image_test) 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; @@ -128,7 +135,7 @@ BOOST_AUTO_TEST_CASE (compact_image_test) 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; @@ -141,7 +148,7 @@ BOOST_AUTO_TEST_CASE (crop_image_test) 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 */ @@ -170,7 +177,7 @@ boost::shared_ptr<Image> 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)); @@ -214,7 +221,7 @@ write_file (shared_ptr<Image> image, string file) 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)); @@ -262,12 +269,134 @@ crop_scale_window_single (AVPixelFormat in_format, libdcp::Size in_size, Crop cr /** 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)); +} + +/** Test Image::alpha_blend */ +BOOST_AUTO_TEST_CASE (alpha_blend_test) +{ + int const stride = 48 * 4; + + shared_ptr<Image> A (new Image (AV_PIX_FMT_RGBA, dcp::Size (48, 48), false)); + A->make_black (); + uint8_t* a = A->data()[0]; + + for (int y = 0; y < 48; ++y) { + uint8_t* p = a + y * stride; + for (int x = 0; x < 16; ++x) { + p[x * 4] = 255; + p[(x + 16) * 4 + 1] = 255; + p[(x + 32) * 4 + 2] = 255; + } + } + + shared_ptr<Image> B (new Image (AV_PIX_FMT_RGBA, dcp::Size (48, 48), true)); + B->make_transparent (); + uint8_t* b = B->data()[0]; + + for (int y = 32; y < 48; ++y) { + uint8_t* p = b + y * stride; + for (int x = 0; x < 48; ++x) { + p[x * 4] = 255; + p[x * 4 + 1] = 255; + p[x * 4 + 2] = 255; + p[x * 4 + 3] = 255; + } + } + + A->alpha_blend (B, Position<int> (0, 0)); + + for (int y = 0; y < 32; ++y) { + uint8_t* p = a + y * stride; + for (int x = 0; x < 16; ++x) { + BOOST_CHECK_EQUAL (p[x * 4], 255); + BOOST_CHECK_EQUAL (p[(x + 16) * 4 + 1], 255); + BOOST_CHECK_EQUAL (p[(x + 32) * 4 + 2], 255); + } + } + + for (int y = 32; y < 48; ++y) { + uint8_t* p = a + y * stride; + for (int x = 0; x < 48; ++x) { + BOOST_CHECK_EQUAL (p[x * 4], 255); + BOOST_CHECK_EQUAL (p[x * 4 + 1], 255); + BOOST_CHECK_EQUAL (p[x * 4 + 2], 255); + BOOST_CHECK_EQUAL (p[x * 4 + 3], 255); + } + } +} + +/** Test merge (list<PositionImage>) with a single image */ +BOOST_AUTO_TEST_CASE (merge_test1) +{ + int const stride = 48 * 4; + + shared_ptr<Image> A (new Image (AV_PIX_FMT_RGBA, dcp::Size (48, 48), false)); + A->make_transparent (); + uint8_t* a = A->data()[0]; + + for (int y = 0; y < 48; ++y) { + uint8_t* p = a + y * stride; + for (int x = 0; x < 16; ++x) { + /* red */ + p[x * 4] = 255; + /* opaque */ + p[x * 4 + 3] = 255; + } + } + + list<PositionImage> all; + all.push_back (PositionImage (A, Position<int> (0, 0))); + PositionImage merged = merge (all); + + BOOST_CHECK (merged.position == Position<int> (0, 0)); + BOOST_CHECK_EQUAL (memcmp (merged.image->data()[0], A->data()[0], stride * 48), 0); +} + +/** Test merge (list<PositionImage>) with two images */ +BOOST_AUTO_TEST_CASE (merge_test2) +{ + shared_ptr<Image> A (new Image (AV_PIX_FMT_RGBA, dcp::Size (48, 1), false)); + A->make_transparent (); + uint8_t* a = A->data()[0]; + for (int x = 0; x < 16; ++x) { + /* red */ + a[x * 4] = 255; + /* opaque */ + a[x * 4 + 3] = 255; + } + + shared_ptr<Image> B (new Image (AV_PIX_FMT_RGBA, dcp::Size (48, 1), false)); + B->make_transparent (); + uint8_t* b = B->data()[0]; + for (int x = 0; x < 16; ++x) { + /* blue */ + b[(x + 32) * 4 + 2] = 255; + /* opaque */ + b[(x + 32) * 4 + 3] = 255; + } + + list<PositionImage> all; + all.push_back (PositionImage (A, Position<int> (0, 0))); + all.push_back (PositionImage (B, Position<int> (0, 0))); + PositionImage merged = merge (all); + + BOOST_CHECK (merged.position == Position<int> (0, 0)); + + uint8_t* m = merged.image->data()[0]; + + for (int x = 0; x < 16; ++x) { + BOOST_CHECK_EQUAL (m[x * 4], 255); + BOOST_CHECK_EQUAL (m[x * 4 + 3], 255); + BOOST_CHECK_EQUAL (m[(x + 16) * 4 + 3], 0); + BOOST_CHECK_EQUAL (m[(x + 32) * 4 + 2], 255); + BOOST_CHECK_EQUAL (m[(x + 32) * 4 + 3], 255); + } } diff --git a/test/job_test.cc b/test/job_test.cc index 4d32b1e0c..2022a8dda 100644 --- a/test/job_test.cc +++ b/test/job_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @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" diff --git a/test/make_black_test.cc b/test/make_black_test.cc index 7c0f92142..0ae2fd137 100644 --- a/test/make_black_test.cc +++ b/test/make_black_test.cc @@ -17,8 +17,15 @@ */ +/** @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> } @@ -27,13 +34,10 @@ extern "C" { 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); diff --git a/test/pixel_formats_test.cc b/test/pixel_formats_test.cc index 1b720d9bf..68d225e6e 100644 --- a/test/pixel_formats_test.cc +++ b/test/pixel_formats_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,13 @@ */ +/** @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" { @@ -28,6 +35,9 @@ 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) diff --git a/test/player_test.cc b/test/player_test.cc new file mode 100644 index 000000000..d48060242 --- /dev/null +++ b/test/player_test.cc @@ -0,0 +1,104 @@ +/* + 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 (280000)); + + 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); + } + } +} + diff --git a/test/ratio_test.cc b/test/ratio_test.cc index f3cbb504f..84e80861b 100644 --- a/test/ratio_test.cc +++ b/test/ratio_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,63 +17,56 @@ */ +/** @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 { - -ostream& -operator<< (ostream& s, libdcp::Size const & t) -{ - s << t.width << "x" << t.height; - return s; -} - -} - BOOST_AUTO_TEST_CASE (ratio_test) { Ratio::setup_ratios (); 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)); } diff --git a/test/recover_test.cc b/test/recover_test.cc index 284895e0a..c9a593241 100644 --- a/test/recover_test.cc +++ b/test/recover_test.cc @@ -17,8 +17,12 @@ */ +/** @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" @@ -30,12 +34,13 @@ using std::string; using boost::shared_ptr; static void -note (libdcp::NoteType, string n) +note (dcp::NoteType t, string n) { - cout << n << "\n"; + if (t == dcp::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"); @@ -52,20 +57,22 @@ BOOST_AUTO_TEST_CASE (recover_test) film->make_dcp (); wait_for_jobs (); + boost::filesystem::path const video = "build/test/recover_test/video/185_2K_e8efb95857b62aa6ff94e3d669e75776_24_bicubic_100000000_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))); } diff --git a/test/repeat_frame_test.cc b/test/repeat_frame_test.cc new file mode 100644 index 000000000..4f66420ea --- /dev/null +++ b/test/repeat_frame_test.cc @@ -0,0 +1,54 @@ +/* + 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 ())); +} + diff --git a/test/resampler_test.cc b/test/resampler_test.cc index 9247159a7..ffd636ac8 100644 --- a/test/resampler_test.cc +++ b/test/resampler_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,11 @@ */ +/** @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" @@ -34,19 +39,16 @@ resampler_test_one (int from, int to) /* 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); diff --git a/test/scaling_test.cc b/test/scaling_test.cc index cdf1653cd..441af6bf3 100644 --- a/test/scaling_test.cc +++ b/test/scaling_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @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" @@ -24,10 +28,6 @@ #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; @@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE (scaling_test) 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"); diff --git a/test/seek_zero_test.cc b/test/seek_zero_test.cc new file mode 100644 index 000000000..682fa9355 --- /dev/null +++ b/test/seek_zero_test.cc @@ -0,0 +1,66 @@ +/* + 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/ffmpeg_audio_stream.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); +} diff --git a/test/silence_padding_test.cc b/test/silence_padding_test.cc index f1136a3f1..d876a0228 100644 --- a/test/silence_padding_test.cc +++ b/test/silence_padding_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,12 +17,17 @@ */ +/** @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" @@ -33,7 +38,8 @@ using std::string; 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); @@ -52,56 +58,56 @@ static void test_silence_padding (int channels) 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); diff --git a/test/skip_frame_test.cc b/test/skip_frame_test.cc new file mode 100644 index 000000000..a77d8459b --- /dev/null +++ b/test/skip_frame_test.cc @@ -0,0 +1,55 @@ +/* + 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 ())); +} + diff --git a/test/stream_test.cc b/test/stream_test.cc index fed3ecabe..16ab83baf 100644 --- a/test/stream_test.cc +++ b/test/stream_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,10 +17,15 @@ */ +/** @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> #include "lib/ffmpeg_content.h" +#include "lib/ffmpeg_audio_stream.h" #include "lib/film.h" using std::pair; @@ -65,7 +70,7 @@ BOOST_AUTO_TEST_CASE (stream_test) 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); @@ -73,11 +78,11 @@ BOOST_AUTO_TEST_CASE (stream_test) 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); } diff --git a/test/subrip_test.cc b/test/subrip_test.cc new file mode 100644 index 000000000..adb548f0c --- /dev/null +++ b/test/subrip_test.cc @@ -0,0 +1,203 @@ +/* + 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->period.from, ContentTime::from_seconds ((1 * 60) + 49.200)); + BOOST_CHECK_EQUAL (i->period.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->period.from, ContentTime::from_seconds ((1 * 60) + 52.440)); + BOOST_CHECK_EQUAL (i->period.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->period.from, ContentTime::from_seconds ((1 * 60) + 54.440)); + BOOST_CHECK_EQUAL (i->period.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->period.from, ContentTime::from_seconds ((1 * 60) + 56.680)); + BOOST_CHECK_EQUAL (i->period.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->period.from, ContentTime::from_seconds ((2 * 60) + 0.840)); + BOOST_CHECK_EQUAL (i->period.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->period.from, ContentTime::from_seconds ((3 * 60) + 54.560)); + BOOST_CHECK_EQUAL (i->period.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 ( + ContentTimePeriod ( + 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"); +} diff --git a/test/test.cc b/test/test.cc index 0b87b8062..32f74a7d2 100644 --- a/test/test.cc +++ b/test/test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,10 +17,15 @@ */ +/** @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" @@ -29,6 +34,7 @@ #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> @@ -41,6 +47,8 @@ using std::cerr; 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: @@ -53,20 +61,26 @@ 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_isdcf_metadata (ISDCFMetadata ()); 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); @@ -98,8 +112,8 @@ void 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); @@ -127,26 +141,26 @@ check_file (boost::filesystem::path ref, boost::filesystem::path check) } static void -note (libdcp::NoteType t, string n) +note (dcp::NoteType t, string n) { - if (t == libdcp::ERROR) { + if (t == dcp::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))); @@ -218,10 +232,19 @@ wait_for_jobs () 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"; } } } @@ -230,3 +253,12 @@ wait_for_jobs () 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 ()); +} diff --git a/test/test.h b/test/test.h index dd007e8c9..57ea32c57 100644 --- a/test/test.h +++ b/test/test.h @@ -20,10 +20,15 @@ #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); diff --git a/test/threed_test.cc b/test/threed_test.cc index 8da9b46a8..5ad78103f 100644 --- a/test/threed_test.cc +++ b/test/threed_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @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" diff --git a/test/util_test.cc b/test/util_test.cc index 750023d9f..5eb3e27f1 100644 --- a/test/util_test.cc +++ b/test/util_test.cc @@ -1,5 +1,5 @@ /* - 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 @@ -17,6 +17,10 @@ */ +/** @file test/util_test.cc + * @brief Test various utility methods. + */ + #include <boost/test/unit_test.hpp> #include "lib/util.h" #include "lib/exceptions.h" @@ -54,6 +58,24 @@ BOOST_AUTO_TEST_CASE (md5_digest_test) 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); diff --git a/test/wscript b/test/wscript index 09df14673..0ece839e6 100644 --- a/test/wscript +++ b/test/wscript @@ -10,23 +10,27 @@ def configure(conf): """, 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 + burnt_subtitle_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 @@ -34,14 +38,18 @@ def build(bld): isdcf_name_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 @@ -49,3 +57,14 @@ def build(bld): 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 = '' @@ -3,7 +3,7 @@ import os import sys APPNAME = 'dcpomatic' -VERSION = '1.69.29devel' +VERSION = '2.0.0devel' def options(opt): opt.load('compiler_cxx') @@ -52,9 +52,9 @@ def dynamic_openjpeg(conf): 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-1.0', 'kumu-libdcp-1.0'] conf.env.LIB_DCP = ['glibmm-2.4', 'ssl', 'crypto', 'bz2', 'xslt'] if static_boost: @@ -78,7 +78,7 @@ def static_dcp(conf, static_boost, static_xmlpp, static_xmlsec, static_ssh): conf.env.LIB_DCP.append('ssh') def dynamic_dcp(conf): - conf.check_cfg(package='libdcp', atleast_version='0.95.0', 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): @@ -299,6 +299,8 @@ def configure(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> @@ -402,4 +404,4 @@ def pot_merge(bld): 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') |
