diff options
301 files changed, 16519 insertions, 6510 deletions
@@ -1,8 +1,37 @@ +2014-10-05 Carl Hetherington <cth@carlh.net> + + * Use a more sensible default position and size for + .srt subs. + 2014-10-08 c.hetherington <cth@carlh.net> * Make server finding more reliable when there are more than a few servers. +2014-10-03 Carl Hetherington <cth@carlh.net> + + * Version 2.0.14 released. + +2014-10-01 Carl Hetherington <cth@carlh.net> + + * Version 2.0.13 released. + +2014-09-30 Carl Hetherington <cth@carlh.net> + + * Version 2.0.12 released. + +2014-09-30 Carl Hetherington <cth@carlh.net> + + * Add basic video fade in/out. + +2014-09-22 Carl Hetherington <cth@carlh.net> + + * Version 2.0.11 released. + +2014-09-18 Carl Hetherington <cth@carlh.net> + + * Version 2.0.10 released. + 2014-10-08 Carl Hetherington <cth@carlh.net> * Version 1.74.2 released. @@ -53,6 +82,19 @@ 2014-09-12 Carl Hetherington <cth@carlh.net> + * Version 2.0.9 released. + +2014-09-12 Carl Hetherington <cth@carlh.net> + + * Add "re-examine" option to content context menu (#339). + +2014-09-11 Carl Hetherington <cth@carlh.net> + + * Restore encoding optimisations for still-image sources. + + * Add option to re-make signing chain with specified organisation, + common names etc. (#354) + * Allow separate X and Y scale for subtitles (#337). 2014-09-10 Carl Hetherington <cth@carlh.net> @@ -62,17 +104,38 @@ * Fix hidden advanced preferences button in some locales. -2014-09-08 Carl Hetherington <cth@carlh.net> + * Version 2.0.8 released. + +2014-09-10 Carl Hetherington <cth@carlh.net> + + * Fix loading of 1.x films. + + * Fix crash on audio analysis in some cases. - * Version 1.73.4 released. +2014-09-09 Carl Hetherington <cth@carlh.net> + + * Version 2.0.7 released. + +2014-09-09 Carl Hetherington <cth@carlh.net> + + * Version 2.0.6 released. + +2014-09-09 Carl Hetherington <cth@carlh.net> + + * Fix missing OS X dependencies. + + * Use a different directory for DCP-o-matic 2 + configuration (not the same as 1.x). 2014-09-08 Carl Hetherington <cth@carlh.net> - * Fix failure to load Targa files. + * Version 2.0.5 released. -2014-09-07 Carl Hetherington <cth@carlh.net> + * Fix hidden advanced preferences button in some locales. + +2014-09-08 Carl Hetherington <cth@carlh.net> - * Version 1.73.3 released. + * Fix failure to load Targa files. 2014-09-07 Carl Hetherington <cth@carlh.net> @@ -83,10 +146,6 @@ 2014-09-03 Carl Hetherington <cth@carlh.net> - * Version 1.73.2 released. - -2014-09-03 Carl Hetherington <cth@carlh.net> - * Fix server certificate downloads on OS X (#376). 2014-09-02 Carl Hetherington <cth@carlh.net> @@ -113,6 +172,33 @@ 2014-08-29 Carl Hetherington <cth@carlh.net> + * Version 2.0.4 released. + +2014-08-24 Carl Hetherington <cth@carlh.net> + + * Version 2.0.3 released. + +2014-08-24 Carl Hetherington <cth@carlh.net> + + * Version 2.0.2 released. + +2014-08-06 Carl Hetherington <cth@carlh.net> + + * Version 2.0.1 released. + +2014-07-15 Carl Hetherington <cth@carlh.net> + + * A variety of changes were made on the 2.0 branch + but not documented in the ChangeLog. Most sigificantly: + + - DCP import + - Creation of DCPs with proper XML subtitles + - Import of .srt and .xml subtitles + - Audio processing framework (with some basic processors). + +2014-03-07 Carl Hetherington <cth@carlh.net> + + * Add subtitle view. * Some improvements to the manual. 2014-08-26 Carl Hetherington <cth@carlh.net> @@ -143,6 +229,7 @@ * Attempt to fix random crashes on OS X (especially during encodes) thought to be caused by multiple threads using (different) stringstreams at the same time; see src/lib/safe_stringstream. +>>>>>>> origin/master 2014-08-09 Carl Hetherington <cth@carlh.net> @@ -207,6 +294,7 @@ 2014-07-10 Carl Hetherington <cth@carlh.net> * Version 1.72.2 released. +>>>>>>> origin/master 2014-07-10 Carl Hetherington <cth@carlh.net> @@ -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 @@ -157,7 +157,8 @@ def make_control(debian_version, bits, filename, debug): def dependencies(target): return (('ffmpeg-cdist', '2dffa11'), - ('libdcp', '2001bef5b3a6256eedf1cada4977a3ba8a3732cd')) + ('libdcp', '1.0'), + ('libsub', None)) def build(target, options): cmd = './waf configure --prefix=%s' % target.directory @@ -230,7 +231,7 @@ def package_centos(target, cpu, version): "%s/SOURCES/dcpomatic-%s.tar.bz2" % (topdir, version) ) - target.command('rpmbuild --define \'_topdir %s\' -bb build/platform/linux/dcpomatic.spec' % topdir) + target.command('rpmbuild --define \'_topdir %s\' -bb build/platform/linux/dcpomatic2.spec' % topdir) rpms = [] if cpu == "amd64": diff --git a/debian/changelog b/debian/changelog index e0ce39fd5..67dbff2a4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -dcpomatic (1.74.2-1) UNRELEASED; urgency=low +dcpomatic (2.0.14-1) UNRELEASED; urgency=low * New upstream release. * New upstream release. diff --git a/doc/design/content.tex b/doc/design/Attic/content.tex index 0f5f17025..0f5f17025 100644 --- a/doc/design/content.tex +++ b/doc/design/Attic/content.tex diff --git a/doc/design/audio_path.svg b/doc/design/audio_path.svg new file mode 100644 index 000000000..c75d505b4 --- /dev/null +++ b/doc/design/audio_path.svg @@ -0,0 +1,408 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1052.3622" + height="744.09448" + id="svg3115" + version="1.1" + inkscape:version="0.48.4 r9939" + sodipodi:docname="audio_path.svg"> + <defs + id="defs3117"> + <marker + inkscape:stockid="Arrow2Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow2Mend" + style="overflow:visible;"> + <path + id="path3860" + style="fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;" + d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z " + transform="scale(0.6) rotate(180) translate(0,0)" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.88221578" + inkscape:cx="342.66212" + inkscape:cy="409.15497" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + showguides="true" + inkscape:guide-bbox="true" + inkscape:object-paths="false" + inkscape:snap-global="true" + inkscape:window-width="1366" + inkscape:window-height="714" + inkscape:window-x="1280" + inkscape:window-y="283" + inkscape:window-maximized="1" + inkscape:snap-bbox="false" + inkscape:snap-nodes="true" + inkscape:object-nodes="true" /> + <metadata + id="metadata3120"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-308.2677)"> + <rect + style="color:#000000;fill:#cdde87;fill-opacity:1;fill-rule:nonzero;stroke:#ff5555;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1, 4;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect3395" + width="861" + height="34" + x="22" + y="326.09448" + transform="translate(0,308.2677)" /> + <rect + style="color:#000000;fill:#ffeeaa;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect3393" + width="861.04535" + height="36.999996" + x="22" + y="597.36218" /> + <rect + style="color:#000000;fill:#ff9955;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1, 4;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect3391" + width="860.48584" + height="37.999996" + x="22" + y="251.09448" + transform="translate(0,308.2677)" /> + <rect + style="color:#000000;fill:#ffaaaa;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect3389" + width="860.78772" + height="29.7075" + x="22" + y="529.65466" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="186" + y="548.36212" + id="text3123" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + x="186" + y="548.36212" + id="tspan3127">AVPacket</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="342" + y="548.36212" + id="text3137" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3139" + x="342" + y="548.36212">AVFrame</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="462" + y="548.36212" + id="text3143" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3145" + x="462" + y="548.36212">AudioBuffers</tspan></text> + <text + xml:space="preserve" + style="font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Roman;-inkscape-font-specification:Latin Modern Roman" + x="31" + y="548.36212" + id="text3165" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3167" + x="31" + y="548.36212">Data type</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="118" + y="656.36218" + id="text3151" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3153" + x="118" + y="656.36218">FFmpegDecoder</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="510.276" + y="656.36218" + id="text3155" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3157" + x="510.276" + y="656.36218">AudioDecoder</tspan></text> + <text + xml:space="preserve" + style="font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Latin Modern Roman;-inkscape-font-specification:Latin Modern Roman" + x="30.747999" + y="656.36218" + id="text3169" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3171" + x="30.747999" + y="656.36218">Class</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="679" + y="656.36218" + id="text3238" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3240" + x="679" + y="656.36218">Player</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="219.51123" + y="584.11017" + id="text3129" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3131" + x="219.51123" + y="584.11017">avcodec_decode_audio4</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="118" + y="584.11017" + id="text3133" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3135" + x="118" + y="584.11017">av_read_frame</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="371.99997" + y="584.11017" + id="text3147" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3149" + x="371.99997" + y="584.11017">deinterleave_audio</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="510" + y="584.11017" + id="text3159" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3161" + x="510" + y="584.11017">audio</tspan><tspan + sodipodi:role="line" + x="510" + y="599.11017" + id="tspan3163" /></text> + <text + xml:space="preserve" + style="font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Latin Modern Roman;-inkscape-font-specification:Latin Modern Roman" + x="30.976" + y="584.11017" + id="text3181" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3183" + x="30.976" + y="584.11017">Method</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="678.96399" + y="584.11017" + id="text3242" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3244" + x="678.96399" + y="584.11017">get_audio</tspan></text> + <text + xml:space="preserve" + style="font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Latin Modern Roman;-inkscape-font-specification:Latin Modern Roman" + x="30.747999" + y="620.27814" + id="text3185" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3187" + x="30.747999" + y="620.27814">Operation</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Roman;-inkscape-font-specification:Latin Modern Roman" + x="191" + y="620.27814" + id="text3222" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3224" + x="191" + y="620.27814">Decode</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Roman;-inkscape-font-specification:Latin Modern Roman" + x="370" + y="620.27814" + id="text3226" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3228" + x="370" + y="620.27814">Deinterleave</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Roman;-inkscape-font-specification:Latin Modern Roman" + x="510.17999" + y="620.27814" + id="text3230" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3232" + x="510.17999" + y="620.27814">Resample</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Roman;-inkscape-font-specification:Latin Modern Roman" + x="573" + y="620.27814" + id="text3234" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3236" + x="573" + y="620.27814">Run Processor</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Roman;-inkscape-font-specification:Latin Modern Roman" + x="678.85602" + y="620.27814" + id="text3246" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3248" + x="678.85602" + y="620.27814">Gain</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Roman;-inkscape-font-specification:Latin Modern Roman" + x="731.56293" + y="620.27814" + id="text3250" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3252" + x="731.56293" + y="620.27814">Channel remap</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Roman;-inkscape-font-specification:Latin Modern Roman" + x="841" + y="620.27814" + id="text3254" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3256" + x="841" + y="620.27814">Mix</tspan></text> + <rect + style="color:#000000;fill:none;stroke:#000000;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect3356" + width="861" + height="138.66901" + x="22" + y="529.69318" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 22,251.09448 860.78771,0" + id="path3358" + inkscape:connector-curvature="0" + transform="translate(0,308.2677)" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 22,289.09448 860.48582,0" + id="path3360" + inkscape:connector-curvature="0" + transform="translate(0,308.2677)" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 22,326.09448 860.69386,0" + id="path3362" + inkscape:connector-curvature="0" + transform="translate(0,308.2677)" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 100,221.37674 0,138.63456" + id="path3364" + inkscape:connector-curvature="0" + transform="translate(0,308.2677)" + sodipodi:nodetypes="cc" /> + <g + id="g4273" + transform="translate(165.08717,-48.74091)"> + <text + transform="translate(0,308.2677)" + sodipodi:linespacing="125%" + id="text3036" + y="437.11526" + x="165.91304" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Latin Modern Roman;-inkscape-font-specification:Latin Modern Roman" + xml:space="preserve"><tspan + y="437.11526" + x="165.91304" + id="tspan3038" + sodipodi:role="line">Data path </tspan></text> + <path + inkscape:connector-curvature="0" + id="path3059" + d="m 223.62193,742.39257 183.54631,0" + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow2Mend)" /> + </g> + </g> +</svg> diff --git a/doc/design/player_get_audio.svg b/doc/design/player_get_audio.svg new file mode 100644 index 000000000..fe7bdd5a3 --- /dev/null +++ b/doc/design/player_get_audio.svg @@ -0,0 +1,399 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + version="1.1" + inkscape:version="0.48.4 r9939" + sodipodi:docname="player_get_audio.svg"> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.2517416" + inkscape:cx="368.22037" + inkscape:cy="938.8543" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + showguides="true" + inkscape:guide-bbox="true" + inkscape:window-width="1366" + inkscape:window-height="714" + inkscape:window-x="0" + inkscape:window-y="27" + inkscape:window-maximized="1" /> + <defs + id="defs4"> + <marker + inkscape:stockid="Arrow1Mstart" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mstart" + style="overflow:visible"> + <path + id="path3983" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt" + transform="scale(0.4) translate(10,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path3986" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1Mstart" + orient="auto" + refY="0" + refX="0" + id="Arrow1Mstart-1" + style="overflow:visible"> + <path + inkscape:connector-curvature="0" + id="path3983-6" + d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" + transform="matrix(0.4,0,0,0.4,4,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0" + refX="0" + id="Arrow1Mend-4" + style="overflow:visible"> + <path + inkscape:connector-curvature="0" + id="path3986-5" + d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" + transform="matrix(-0.4,0,0,-0.4,-4,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1MstartQ" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1MstartQ" + style="overflow:visible"> + <path + id="path4874" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="stroke:#008000;stroke-width:1.0pt;fill:#008000;fill-rule:evenodd" + transform="scale(0.4) translate(10,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1Mendh" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mendh" + style="overflow:visible;"> + <path + id="path4877" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="stroke:#008000;stroke-width:1.0pt;fill:#008000;fill-rule:evenodd" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1Mstart" + orient="auto" + refY="0" + refX="0" + id="Arrow1Mstart-2" + style="overflow:visible"> + <path + id="path3983-60" + d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" + transform="matrix(0.4,0,0,0.4,4,0)" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0" + refX="0" + id="Arrow1Mend-9" + style="overflow:visible"> + <path + id="path3986-52" + d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" + transform="matrix(-0.4,0,0,-0.4,-4,0)" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="Arrow1MstartM" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1MstartM" + style="overflow:visible"> + <path + id="path5026" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="stroke:#ff0000;stroke-width:1.0pt;fill:#ff0000;fill-rule:evenodd" + transform="scale(0.4) translate(10,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1MendT" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1MendT" + style="overflow:visible;"> + <path + id="path5029" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="stroke:#ff0000;stroke-width:1.0pt;fill:#ff0000;fill-rule:evenodd" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1MstartM" + orient="auto" + refY="0" + refX="0" + id="Arrow1MstartM-0" + style="overflow:visible"> + <path + inkscape:connector-curvature="0" + id="path5026-3" + d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" + style="fill:#ff0000;fill-rule:evenodd;stroke:#ff0000;stroke-width:1pt" + transform="matrix(0.4,0,0,0.4,4,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1MendT" + orient="auto" + refY="0" + refX="0" + id="Arrow1MendT-0" + style="overflow:visible"> + <path + inkscape:connector-curvature="0" + id="path5029-9" + d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" + style="fill:#ff0000;fill-rule:evenodd;stroke:#ff0000;stroke-width:1pt" + transform="matrix(-0.4,0,0,-0.4,-4,0)" /> + </marker> + </defs> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 55,34.711899 55,198.7119" + id="path2985" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 47,190.36218 641,0" + id="path2987" + inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="25.429111" + y="210.57095" + id="text2989" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan2991" + x="25.429111" + y="210.57095">DCP time 0</tspan></text> + <rect + style="color:#000000;fill:#000000;fill-opacity:0.15425535;stroke:none;stroke-width:0.99999988px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect2995" + width="267.44409" + height="153.85982" + x="205.08385" + y="-190.34831" + transform="scale(1,-1)" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205.14391,36.525554 0,164.454206" + id="path3783" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 472.50163,36.353991 0,164.625769" + id="path3785" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cc" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="191.90874" + y="212.87964" + id="text3787" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3789" + x="191.90874" + y="212.87964">time</tspan></text> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow1Mstart);marker-mid:none;marker-end:url(#Arrow1Mend)" + d="m 207.02561,30.692594 263.76528,0" + id="path3791" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cc" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="319.72653" + y="23.80699" + id="text4787" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4789" + x="319.72653" + y="23.80699">length</tspan></text> + <path + style="fill:none;stroke:#008000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 206.07602,19.432227 0,210.960753" + id="path4817" + inkscape:connector-curvature="0" /> + <path + style="stroke-linejoin:miter;marker-end:url(#Arrow1Mendh);stroke-opacity:1;marker-start:url(#Arrow1MstartQ);stroke:#008000;stroke-linecap:butt;stroke-width:1px;marker-mid:none;fill:none" + d="m 207.02561,13.263911 263.76528,0" + id="path3791-5" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cc" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#008000;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="297.97812" + y="6.3783064" + id="text4787-3" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4789-8" + x="297.97812" + y="6.3783064">length_frames</tspan></text> + <text + sodipodi:linespacing="125%" + id="text4929" + y="238.74654" + x="205.10674" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#008000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + xml:space="preserve"><tspan + y="238.74654" + x="205.10674" + id="tspan4931" + sodipodi:role="line">out</tspan><tspan + id="tspan4933" + y="253.74654" + x="205.10674" + sodipodi:role="line" /></text> + <rect + y="67.863129" + x="123.76559" + height="71.25898" + width="448.18149" + id="rect4973" + style="color:#000000;fill:#ff0000;fill-opacity:0.30319148;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4975" + d="m 125.66194,148.65781 77.93059,0" + style="color:#000000;fill:none;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-start:url(#Arrow1MstartM);marker-end:url(#Arrow1MendT);visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + sodipodi:nodetypes="cc" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="85.083427" + y="163.35722" + id="text5098" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5100" + x="85.083427" + y="163.35722">dcp_to_content_audio(time)</tspan><tspan + sodipodi:role="line" + x="85.083427" + y="178.35722" + id="tspan5102" /></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="126.75607" + y="78.363503" + id="text5123" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5125" + x="126.75607" + y="78.363503">Content</tspan></text> + <rect + style="color:#000000;fill:#ff0000;fill-opacity:0.48404256;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect5148" + width="204.37869" + height="70.190666" + x="239.47403" + y="68.041344" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="244.01578" + y="78.363503" + id="text5567" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5569" + x="244.01578" + y="78.363503">in</tspan></text> + <path + inkscape:connector-curvature="0" + id="path4975-9" + d="m 125.66194,111.10032 112.28273,0" + style="color:#000000;fill:none;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-start:url(#Arrow1MstartM);marker-end:url(#Arrow1MendT);visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + sodipodi:nodetypes="cc" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Latin Modern Mono;-inkscape-font-specification:Latin Modern Mono" + x="135.21161" + y="106.87954" + id="text5620" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5622" + x="135.21161" + y="106.87954">in->frame</tspan></text> + </g> +</svg> 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/design/timing.svg b/doc/design/timing.svg new file mode 100644 index 000000000..30325e7db --- /dev/null +++ b/doc/design/timing.svg @@ -0,0 +1,645 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1052.3622" + height="744.09448" + id="svg2" + version="1.1" + inkscape:version="0.48.4 r9939" + sodipodi:docname="timing.svg"> + <defs + id="defs4"> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path3830" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0" + refX="0" + id="Arrow1Mend-9" + style="overflow:visible"> + <path + inkscape:connector-curvature="0" + id="path3830-0" + d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" + transform="matrix(-0.4,0,0,-0.4,-4,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0" + refX="0" + id="Arrow1Mend-2" + style="overflow:visible"> + <path + inkscape:connector-curvature="0" + id="path3830-7" + d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" + transform="matrix(-0.4,0,0,-0.4,-4,0)" /> + </marker> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0" + refX="0" + id="Arrow1Mend-7" + style="overflow:visible"> + <path + inkscape:connector-curvature="0" + id="path3830-77" + d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" + transform="matrix(-0.4,0,0,-0.4,-4,0)" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.7392904" + inkscape:cx="260.70009" + inkscape:cy="491.51669" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="1680" + inkscape:window-height="1023" + inkscape:window-x="1366" + inkscape:window-y="0" + inkscape:window-maximized="1" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-308.2677)"> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:TeX Gyre Schola;-inkscape-font-specification:TeX Gyre Schola" + x="17" + y="22.094482" + id="text2985" + sodipodi:linespacing="125%" + transform="translate(0,308.2677)"><tspan + sodipodi:role="line" + id="tspan2987" + x="17" + y="22.094482">FFmpeg sources are by far the most complicated, so we consider those only.</tspan><tspan + sodipodi:role="line" + x="17" + y="37.094482" + id="tspan2989"></tspan><tspan + sodipodi:role="line" + x="17" + y="52.094482" + id="tspan4675">Hardest video case:</tspan><tspan + sodipodi:role="line" + x="17" + y="67.094482" + id="tspan4584" /><tspan + sodipodi:role="line" + x="17" + y="82.094482" + id="tspan4586" /><tspan + sodipodi:role="line" + x="17" + y="97.094482" + id="tspan4588" /><tspan + sodipodi:role="line" + x="17" + y="112.09448" + id="tspan4590" /><tspan + sodipodi:role="line" + x="17" + y="127.09448" + id="tspan4592" /><tspan + sodipodi:role="line" + x="17" + y="142.09448" + id="tspan4594" /><tspan + sodipodi:role="line" + x="17" + y="157.09448" + id="tspan4596" /><tspan + sodipodi:role="line" + x="17" + y="172.09448" + id="tspan4598" /><tspan + sodipodi:role="line" + x="17" + y="187.09448" + id="tspan4600" /><tspan + sodipodi:role="line" + x="17" + y="202.09448" + id="tspan4602" /><tspan + sodipodi:role="line" + x="17" + y="217.09448" + id="tspan4604" /><tspan + sodipodi:role="line" + x="17" + y="232.09448" + id="tspan4606" /><tspan + sodipodi:role="line" + x="17" + y="247.09448" + id="tspan4608" /><tspan + sodipodi:role="line" + x="17" + y="262.09448" + id="tspan4610" /><tspan + sodipodi:role="line" + x="17" + y="277.09448" + id="tspan4612">Content frames arrive with ContentTime; this is converted to DCP time and checked for validity against</tspan><tspan + sodipodi:role="line" + x="17" + y="292.09448" + id="tspan4622">_video_position. _video_position is incremented by one DCP frame period each time a frame is emitted.</tspan><tspan + sodipodi:role="line" + x="17" + y="307.09448" + id="tspan4657">Emission is timestamped with _video_position.</tspan><tspan + sodipodi:role="line" + x="17" + y="322.09448" + id="tspan4661" /><tspan + sodipodi:role="line" + x="17" + y="337.09448" + id="tspan4663">In general, the Decoded::dcp_time is used as a check against the actual position in the DCP (based</tspan><tspan + sodipodi:role="line" + x="17" + y="352.09448" + id="tspan4630">on what has been emitted).</tspan><tspan + sodipodi:role="line" + x="17" + y="367.09448" + id="tspan4671" /><tspan + sodipodi:role="line" + x="17" + y="382.09448" + id="tspan4673">Hardest audio case:</tspan><tspan + sodipodi:role="line" + x="17" + y="397.09448" + id="tspan4785" /><tspan + sodipodi:role="line" + x="17" + y="412.09448" + id="tspan4787" /><tspan + sodipodi:role="line" + x="17" + y="427.09448" + id="tspan4789" /><tspan + sodipodi:role="line" + x="17" + y="442.09448" + id="tspan4791" /><tspan + sodipodi:role="line" + x="17" + y="457.09448" + id="tspan4793" /><tspan + sodipodi:role="line" + x="17" + y="472.09448" + id="tspan4795" /><tspan + sodipodi:role="line" + x="17" + y="487.09448" + id="tspan4797" /><tspan + sodipodi:role="line" + x="17" + y="502.09448" + id="tspan4799">Timestamps are not sample-accurate, here B should be after A but its timestamp says different. Things</tspan><tspan + sodipodi:role="line" + x="17" + y="517.09448" + id="tspan4801">are further complicated by resampling, which renders timestamps invalid. The upshot is that audio</tspan><tspan + sodipodi:role="line" + x="17" + y="532.09448" + id="tspan4803">timestamps are basically useless. However, since audio is (apparently) always continuous in content files</tspan><tspan + sodipodi:role="line" + x="17" + y="547.09448" + id="tspan4805">we can make our own timestamps.</tspan></text> + <rect + style="color:#000000;fill:#de8787;stroke:#000000;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect2991-9" + width="30.424219" + height="37.906269" + x="311.70773" + y="461.45099" /> + <rect + style="color:#000000;fill:#de8787;stroke:#000000;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect2991-9-5" + width="30.424219" + height="37.906269" + x="405.98038" + y="461.45099" /> + <rect + style="color:#000000;fill:#de8787;stroke:#000000;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect2991-9-5-5" + width="30.424219" + height="37.906269" + x="217.43507" + y="461.45099" /> + <g + id="g4368" + transform="translate(185.74311,19.474125)"> + <rect + y="363.08694" + x="61.408226" + height="37.906269" + width="42.547859" + id="rect2991" + style="color:#000000;fill:#ffb380;stroke:#000000;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <text + sodipodi:linespacing="125%" + id="text4319" + y="373.75745" + x="64.765747" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + xml:space="preserve"><tspan + y="373.75745" + x="64.765747" + id="tspan4321" + sodipodi:role="line">A</tspan></text> + </g> + <g + id="g4363" + transform="translate(185.7431,19.474125)"> + <rect + y="363.08694" + x="104.95609" + height="37.906269" + width="42.547859" + id="rect2991-4" + style="color:#000000;fill:#ffb380;stroke:#000000;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <text + sodipodi:linespacing="125%" + id="text4323" + y="373.75745" + x="108.47467" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + xml:space="preserve"><tspan + y="373.75745" + x="108.47467" + id="tspan4325" + sodipodi:role="line">B</tspan></text> + </g> + <g + id="g4358" + transform="translate(185.74312,19.474125)"> + <rect + y="363.08694" + x="148.50394" + height="37.906269" + width="42.547859" + id="rect2991-4-1" + style="color:#000000;fill:#ffb380;stroke:#000000;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <text + sodipodi:linespacing="125%" + id="text4327" + y="373.75745" + x="151.86452" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + xml:space="preserve"><tspan + y="373.75745" + x="151.86452" + id="tspan4329" + sodipodi:role="line">C</tspan></text> + </g> + <g + id="g4353" + transform="translate(183.96221,19.474125)"> + <rect + y="363.08694" + x="237.38057" + height="37.906269" + width="42.547859" + id="rect2991-4-1-7" + style="color:#000000;fill:#ffb380;stroke:#000000;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <text + sodipodi:linespacing="125%" + id="text4331" + y="373.75745" + x="240.5585" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + xml:space="preserve"><tspan + y="373.75745" + x="240.5585" + id="tspan4333" + sodipodi:role="line">D</tspan></text> + </g> + <rect + style="color:#000000;fill:#de8787;stroke:#000000;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect2991-9-5-5-3" + width="30.424219" + height="37.906269" + x="248.85928" + y="461.45099" /> + <rect + style="color:#000000;fill:#de8787;stroke:#000000;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect2991-9-5-5-2" + width="30.424219" + height="37.906269" + x="280.28351" + y="461.45099" /> + <rect + style="color:#000000;fill:#de8787;stroke:#000000;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect2991-9-5-5-1" + width="30.424219" + height="37.906269" + x="343.13196" + y="461.45099" /> + <rect + style="color:#000000;fill:#de8787;stroke:#000000;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect2991-9-5-5-6" + width="30.424219" + height="37.906269" + x="374.55618" + y="461.45099" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + x="-190.66861" + y="507.70966" + id="text4412" + sodipodi:linespacing="125%" + transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,0,0)"><tspan + sodipodi:role="line" + id="tspan4414" + x="-190.66861" + y="507.70966">BLACK</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + x="251.85396" + y="472.95575" + id="text4430" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4432" + x="251.85396" + y="472.95575">A</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + x="376.83502" + y="472.95575" + id="text4438" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4440" + x="376.83502" + y="472.95575">D</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + x="283.88882" + y="472.95575" + id="text4442" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4444" + x="283.88882" + y="472.95575">B</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + x="314.1189" + y="472.95575" + id="text4446" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4448" + x="314.1189" + y="472.95575">C</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + x="346.7153" + y="472.95575" + id="text4446-9" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4448-9" + x="346.7153" + y="472.95575">C</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + x="-56.312008" + y="640.36365" + id="text4412-5" + sodipodi:linespacing="125%" + transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,0,0)"><tspan + sodipodi:role="line" + id="tspan4414-1" + x="-56.312008" + y="640.36365">BLACK</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + x="144.21622" + y="541.08624" + id="text4492" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4494" + x="144.21622" + y="541.08624">(outside content, so black)</tspan></text> + <path + style="color:#000000;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="m 220.72149,530.25751 c 1.80478,-3.15836 7.07827,-26.41646 7.07827,-26.41646" + id="path4496" + inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + x="334.42267" + y="559.13403" + id="text4521" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4523" + x="334.42267" + y="559.13403">(repeat)</tspan></text> + <path + style="color:#000000;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="m 358.45245,547.88911 c -1.80478,-3.15836 -8.88305,-42.65947 -8.88305,-42.65947" + id="path4496-5" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cc" /> + <path + style="color:#000000;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;marker-end:url(#Arrow1Mend);visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="m 227.48942,529.80632 c 57.58855,-44.73312 159.20595,24.81179 194.4651,-25.26693" + id="path4549" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cc" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000080;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + x="68.27523" + y="399.59995" + id="text4574" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4576" + x="68.27523" + y="399.59995">CONTENT</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000080;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + x="68.119232" + y="476.80835" + id="text4578" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4580" + x="68.119232" + y="476.80835">DCP</tspan><tspan + sodipodi:role="line" + x="68.119232" + y="491.80835" + id="tspan4582">(at higher frame rate)</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + x="255.87259" + y="280.20578" + id="text4616" + sodipodi:linespacing="125%" + transform="translate(0,308.2677)"><tspan + sodipodi:role="line" + id="tspan4618" + x="255.87259" + y="280.20578" /></text> + <g + id="g4368-8" + transform="translate(77.477495,337.87916)"> + <rect + y="363.08694" + x="61.408226" + height="37.906269" + width="42.547859" + id="rect2991-5" + style="color:#000000;fill:#ffb380;stroke:#000000;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <text + sodipodi:linespacing="125%" + id="text4319-7" + y="373.75745" + x="64.765747" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + xml:space="preserve"><tspan + y="373.75745" + x="64.765747" + id="tspan4321-8" + sodipodi:role="line">A</tspan></text> + </g> + <g + id="g4368-8-8" + transform="translate(108.10564,376.16434)"> + <rect + y="363.08694" + x="61.408226" + height="37.906269" + width="42.547859" + id="rect2991-5-0" + style="color:#000000;fill:#ffb380;stroke:#000000;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <text + sodipodi:linespacing="125%" + id="text4319-7-8" + y="373.75745" + x="64.765747" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + xml:space="preserve"><tspan + y="373.75745" + x="64.765747" + id="tspan4321-8-1" + sodipodi:role="line">B</tspan></text> + </g> + <g + id="g4368-8-8-9" + transform="translate(166.7546,336.60299)"> + <rect + y="363.08694" + x="61.408226" + height="37.906269" + width="42.547859" + id="rect2991-5-0-9" + style="color:#000000;fill:#ffb380;stroke:#000000;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <text + sodipodi:linespacing="125%" + id="text4319-7-8-0" + y="373.75745" + x="64.765747" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + xml:space="preserve"><tspan + y="373.75745" + x="64.765747" + id="tspan4321-8-1-5" + sodipodi:role="line">C</tspan></text> + </g> + <g + id="g4368-8-8-9-2" + transform="translate(210.30246,336.60299)"> + <rect + y="363.08694" + x="61.408226" + height="37.906269" + width="42.547859" + id="rect2991-5-0-9-0" + style="color:#000000;fill:#ffb380;stroke:#000000;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <text + sodipodi:linespacing="125%" + id="text4319-7-8-0-0" + y="373.75745" + x="64.765747" + style="font-size:12px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium" + xml:space="preserve"><tspan + y="373.75745" + x="64.765747" + id="tspan4321-8-1-5-7" + sodipodi:role="line">D</tspan></text> + </g> + </g> +</svg> diff --git a/doc/design/who_fills_the_gaps.tex b/doc/design/who_fills_the_gaps.tex new file mode 100644 index 000000000..00e8ac39f --- /dev/null +++ b/doc/design/who_fills_the_gaps.tex @@ -0,0 +1,30 @@ +\documentclass{article} +\begin{document} + +There is a lot of dancing about to handle potential gaps and sync +problems in the FFmpeg decoder. It might be nicer if +\texttt{FFmpegDecoder} could just spit out video and audio with +timestamps and let the player sort it out, since the player must +already handle insertion of black and silence. + +The first question would be what time unit the decoder should use to +stamp its output. Initially we have the PTS, in some time base, and +we can convert that to seconds at the content's frame rate; this is +basically a \texttt{Time}. So we could emit video and audio content +with \texttt{Time} stamps. + +Then the player receives video frames, and can fill in gaps. + +The FFmpeg decoder would still have to account for non-zero initial +PTS, as it is expected that such `dead time' is trimmed from the +source implicitly. + +The snag with this is that hitherto \texttt{Time} has meant DCP time, +not time at a content's rates (before the content is potentially sped +up). As it stands, seek takes a \texttt{Time} in the DCP and the +content class converts it to content frames. This is then (rather +grottily) converted back to time again via the content frame rate. +All a bit grim. Everything should probably work in time rather than +frame rates. + +\end{document} diff --git a/doc/mainpage.txt b/doc/mainpage.txt index 649c9c609..1e371852b 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 @@ -22,16 +22,11 @@ * and libsndfile (http://www.mega-nerd.com/libsndfile/) for WAV file manipulation. It * also makes heavy use of the boost libraries (http://www.boost.org/). ImageMagick * (http://www.imagemagick.org/) is used for still-image encoding and decoding, and the GUI is - * built using wxWidgets (http://wxwidgets.org/). It also uses libmhash (http://mhash.sourceforge.net/) - * for debugging purposes. + * built using wxWidgets (http://wxwidgets.org/). * * 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/doc/manual/Makefile b/doc/manual/Makefile index 55d888b2e..5ca5700f9 100644 --- a/doc/manual/Makefile +++ b/doc/manual/Makefile @@ -9,7 +9,7 @@ SCREENSHOTS := file-new.png video-new-film.png still-new-film.png video-select-c still-select-content-file.png examine-thumbs.png examine-content.png timing-tab.png \ calculate-audio-gain.png add-file.png dcp-tab.png colour-conversion.png \ prefs-kdm-email.png prefs-colour-conversions.png prefs-metadata.png prefs-general.png prefs-tms.png \ - prefs-advanced.png prefs-defaults.png prefs-servers.png \ + prefs-advanced.png prefs-defaults.png prefs-servers.png prefs-keys.png \ making-dcp.png filters.png video-tab.png audio-tab.png subtitles-tab.png timing-tab.png \ audio-plot.png audio-map-eg1.png audio-map-eg2.png audio-map-eg3.png kdm.png diff --git a/doc/manual/dcpomatic.xml b/doc/manual/dcpomatic.xml index cd27bef72..489f0ba04 100644 --- a/doc/manual/dcpomatic.xml +++ b/doc/manual/dcpomatic.xml @@ -1376,7 +1376,7 @@ methods to understand it. <para> We suppose that we are trying to distribute a DCP to -Alice's cinema, without a troublemaker called Mallory being able to +Alice's cinema without a troublemaker called Mallory being able to watch it himself. </para> @@ -1533,6 +1533,9 @@ generate the KDMs. <!-- ============================================================== --> +<!-- PREFERENCES --> +<!-- ============================================================== --> + <chapter xml:id="ch-preferences" xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en"> <title>Preferences</title> @@ -1549,7 +1552,7 @@ behaviour. This chapter explains those options. <para> The preferences dialogue is opened by choosing <guilabel>Preferences...</guilabel> from the <guilabel>Edit</guilabel> -menu. The dialogue is split into seven tabs. +menu. The dialogue is split into eight tabs. </para> <!-- ============================================================== --> @@ -1619,7 +1622,7 @@ available <para> The <guilabel>Check for testing updates as well as stable ones</guilabel> option will also check for test updates as well as -those that are formally ‘released’ This is useful if you +those that are formally ‘released’. This is useful if you like to live on the bleeding edge! </para> </section> @@ -1689,6 +1692,61 @@ converting from common input colour spaces to XYZ. <!-- ============================================================== --> +<section> +<title>Keys</title> + +<para> +The Keys tab (shown in <xref linkend="fig-prefs-keys"/>) holds options +related to the keys and certificates used in some parts of DCP +creation. +</para> + +<figure id="fig-prefs-keys"> + <title>Keys preferences</title> + <mediaobject> + <imageobject> + <imagedata fileref="screenshots/prefs-keys&scs;"/> + </imageobject> + </mediaobject> +</figure> + +<para> +At the top of the tab is the chain of certificates that will be used +to sign DCPs and KDMs. DCP-o-matic creates a random chain when you +first run it, so if you are happy to use a randomly-generated chain +you can ignore the preferences. Otherwise, you can add or remove +certificates from the chain using the <guilabel>Add...</guilabel> and +<guilabel>Remove</guilabel> buttons. +</para> + +<para> +If you want DCP-o-matic to re-create the certificate chain (using new, +random certificates) click <guilabel>Re-make +certificates...</guilabel> and specify your organisation and common +names in the dialogue box that opens. +</para> + +<para> +Underneath the certificate chain is the private key that corresponds +to the leaf certificate in the chain. You can specify your own +private key by clicking <guilabel>Load...</guilabel>. You must do +this if you change the leaf certificate, so that the leaf private key +corresponds to the public key held in the leaf certificate. +</para> + +<para> +The bottom of the tab specifies the certificate and private key that +is used to decrypt DCPs if they are imported as sources to +DCP-o-matic. If you want to import an encrypted DCP you will need to +give the decryption certificate to the distributor of the DCP so that +they can generate a DKDM for you. As with the certificate chain, +DCP-o-matic will create a certificate and private key for you. You +can also choose to load your own certificate and key. +</para> + +</section> + +<!-- ============================================================== --> <section xml:id="sec-prefs-tms"> <title>TMS</title> <titleabbrev xml:id="sec-prefs-tms-short">TMS preferences</titleabbrev> @@ -1889,12 +1947,12 @@ with minimal loss in quality. </para> <para> -Video rate conversion is harder. DCP-o-matic's basic strategy to deal +Video rate conversion is harder. DCP-o-matic's strategy to deal with a non-supported content rate is to run it at the wrong speed, and to adjust the audio to keep it in sync. </para> -<para>Let us consider the example of a 25fps source for which you want +<para>Consider the example of a 25fps source for which you want to create a 24fps DCP. DCP-o-matic will put the frames from the source directly into the DCP without modification, but will tell the projector to play them back at 24fps. This means that the DCP's video @@ -1926,7 +1984,7 @@ For very low or high frame rates, DCP-o-matic can also skip or duplicate frames. The <guilabel>Frame Rate</guilabel> control in the <guilabel>DCP</guilabel> tab sets the video frame rate that the DCP will use. Clicking <guilabel>Use best</guilabel> sets the rate to -what DVD-o-matic thinks is the best for your content. With this +what DCP-o-matic thinks is the best for your content. With this button, DCP-o-matic assumes that the whole range of frame rates (24, 25, 30 and 48fps) are allowable. </para> diff --git a/doc/manual/screenshots/prefs-keys.png b/doc/manual/screenshots/prefs-keys.png Binary files differnew file mode 100644 index 000000000..1309e4980 --- /dev/null +++ b/doc/manual/screenshots/prefs-keys.png diff --git a/icons/128x128/dcpomatic.png b/icons/128x128/dcpomatic2.png Binary files differindex 9936b39af..9936b39af 100644 --- a/icons/128x128/dcpomatic.png +++ b/icons/128x128/dcpomatic2.png diff --git a/icons/16x16/dcpomatic.png b/icons/16x16/dcpomatic2.png Binary files differindex 3c5a10f2d..3c5a10f2d 100644 --- a/icons/16x16/dcpomatic.png +++ b/icons/16x16/dcpomatic2.png diff --git a/icons/22x22/dcpomatic.png b/icons/22x22/dcpomatic2.png Binary files differindex dddb86298..dddb86298 100644 --- a/icons/22x22/dcpomatic.png +++ b/icons/22x22/dcpomatic2.png diff --git a/icons/32x32/dcpomatic.png b/icons/32x32/dcpomatic2.png Binary files differindex 8cecf08f8..8cecf08f8 100644 --- a/icons/32x32/dcpomatic.png +++ b/icons/32x32/dcpomatic2.png diff --git a/icons/48x48/dcpomatic.png b/icons/48x48/dcpomatic2.png Binary files differindex 07bf2d10b..07bf2d10b 100644 --- a/icons/48x48/dcpomatic.png +++ b/icons/48x48/dcpomatic2.png diff --git a/icons/64x64/dcpomatic.png b/icons/64x64/dcpomatic2.png Binary files differindex 35564a8a2..35564a8a2 100644 --- a/icons/64x64/dcpomatic.png +++ b/icons/64x64/dcpomatic2.png diff --git a/icons/kdm_email.png b/icons/kdm_email.png Binary files differindex 28701ee49..a6c2157e8 100644 --- a/icons/kdm_email.png +++ b/icons/kdm_email.png diff --git a/icons/kdm_email.svg b/icons/kdm_email.svg index ace413dae..c09d94e81 100644 --- a/icons/kdm_email.svg +++ b/icons/kdm_email.svg @@ -1,197 +1,474 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> <svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - id="svg4217" - viewBox="0 0 744.09449 1052.3622" - version="1.0" - inkscape:version="0.48.4 r9939" - width="100%" - height="100%" - sodipodi:docname="kdm_email.svg" - inkscape:export-filename="/home/carl/src/dcpomatic/icons/kdm_email.png" - inkscape:export-xdpi="6.5100002" - inkscape:export-ydpi="6.5100002"> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1366" - inkscape:window-height="714" - id="namedview6320" - showgrid="false" - inkscape:zoom="0.60313323" - inkscape:cx="-87.032436" - inkscape:cy="195.645" - inkscape:window-x="0" - inkscape:window-y="27" - inkscape:window-maximized="1" - inkscape:current-layer="svg4217" /> + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:ns1="http://sozi.baierouge.fr" + id="svg5816" + viewBox="0 0 48 48" + sodipodi:version="0.32" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + inkscape:version="0.46" + sodipodi:docname="internet-mail.svg" + sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/apps" + > <defs - id="defs4219"> + id="defs3" + > + <radialGradient + id="radialGradient6719" + xlink:href="#linearGradient5060" + gradientUnits="userSpaceOnUse" + cy="486.65" + cx="605.71" + gradientTransform="matrix(-2.7744 0 0 1.9697 112.76 -872.89)" + r="117.14" + inkscape:collect="always" + /> <linearGradient - id="linearGradient3594" - y2="742.5" - gradientUnits="userSpaceOnUse" - x2="-886.76001" - gradientTransform="matrix(-0.84033,-0.84033,-0.84033,0.84033,214.12,-1075.4)" - y1="742.5" - x1="-772.01001"> - <stop - id="stop4687" - stop-color="#fff" - offset="0" /> - <stop - id="stop4689" - stop-color="#fff" - stop-opacity="0" - offset="1" /> - </linearGradient> + id="linearGradient5060" + inkscape:collect="always" + > + <stop + id="stop5062" + style="stop-color:black" + offset="0" + /> + <stop + id="stop5064" + style="stop-color:black;stop-opacity:0" + offset="1" + /> + </linearGradient + > + <radialGradient + id="radialGradient6717" + xlink:href="#linearGradient5060" + gradientUnits="userSpaceOnUse" + cy="486.65" + cx="605.71" + gradientTransform="matrix(2.7744 0 0 1.9697 -1891.6 -872.89)" + r="117.14" + inkscape:collect="always" + /> <linearGradient - id="linearGradient3601" - y2="613.94" - gradientUnits="userSpaceOnUse" - x2="385.04001" - gradientTransform="matrix(0.70711,-0.70711,0.70711,0.70711,-126.18,372.21)" - y1="63.870998" - x1="386.39001"> - <stop - id="stop3797" - stop-color="#ffe800" - offset="0" /> - <stop - id="stop3799" - stop-color="#dfb300" - offset="1" /> - </linearGradient> + id="linearGradient6715" + y2="609.51" + gradientUnits="userSpaceOnUse" + x2="302.86" + gradientTransform="matrix(2.7744 0 0 1.9697 -1892.2 -872.89)" + y1="366.65" + x1="302.86" + inkscape:collect="always" + > + <stop + id="stop5050" + style="stop-color:black;stop-opacity:0" + offset="0" + /> + <stop + id="stop5056" + style="stop-color:black" + offset=".5" + /> + <stop + id="stop5052" + style="stop-color:black;stop-opacity:0" + offset="1" + /> + </linearGradient + > <linearGradient - id="linearGradient3609" - y2="161.84" - gradientUnits="userSpaceOnUse" - x2="212.92999" - y1="358.29999" - x1="409.38"> - <stop - id="stop4034" - stop-color="#dfb300" - offset="0" /> - <stop - id="stop3374" - stop-color="#dfb300" - offset=".5" /> - <stop - id="stop3376" - stop-color="#dfb300" - offset="1" /> - </linearGradient> + id="linearGradient2152" + > + <stop + id="stop2154" + style="stop-color:#9aa29a" + offset="0" + /> + <stop + id="stop2156" + style="stop-color:#b5beb5" + offset="1" + /> + </linearGradient + > <linearGradient - id="linearGradient3632" - y2="448.35001" - gradientUnits="userSpaceOnUse" - x2="382.89999" - gradientTransform="matrix(0.70711,-0.70711,0.70711,0.70711,-126.18,372.21)" - y1="448.35001" - x1="403.63"> - <stop - id="stop3636" - stop-color="#ffe800" - stop-opacity=".39216" - offset="0" /> - <stop - id="stop3638" - stop-color="#dfb300" - stop-opacity=".39216" - offset="1" /> - </linearGradient> - </defs> + id="linearGradient27463" + y2="32.203" + gradientUnits="userSpaceOnUse" + y1="37.785" + gradientTransform="matrix(2.3949 0 0 .78106 2.8795 0.343)" + x2="9.7619" + x1="8.7804" + inkscape:collect="always" + > + <stop + id="stop2276" + style="stop-color:#000000;stop-opacity:.12871" + offset="0" + /> + <stop + id="stop2278" + style="stop-color:#000000;stop-opacity:0" + offset="1" + /> + </linearGradient + > + <linearGradient + id="linearGradient27468" + y2="24.133" + gradientUnits="userSpaceOnUse" + y1="13.686" + gradientTransform="matrix(1.3709 0 0 1.4438 2.4311 -.14079)" + x2="21.112" + x1="11.233" + inkscape:collect="always" + > + <stop + id="stop9751" + style="stop-color:#ffffff" + offset="0" + /> + <stop + id="stop9753" + style="stop-color:#ededed" + offset="1" + /> + </linearGradient + > + <linearGradient + id="linearGradient27471" + y2="52.091" + xlink:href="#linearGradient2152" + gradientUnits="userSpaceOnUse" + y1="37.197" + gradientTransform="matrix(2.4548 0 0 0.762 2.8795 0.343)" + x2="9.8855" + x1="8.9156" + inkscape:collect="always" + /> + <linearGradient + id="linearGradient27477" + y2="29.569" + gradientUnits="userSpaceOnUse" + y1="15.148" + gradientTransform="matrix(1.8193 0 0 1.0282 2.8795 0.343)" + x2="15.311" + x1="10.184" + inkscape:collect="always" + > + <stop + id="stop2168" + style="stop-color:#ffffff" + offset="0" + /> + <stop + id="stop2170" + style="stop-color:#dcdcdc" + offset="1" + /> + </linearGradient + > + <linearGradient + id="linearGradient27483" + y2="17.877" + gradientUnits="userSpaceOnUse" + y1="7.2311" + gradientTransform="matrix(1.5706 0 0 1.191 2.8795 0.343)" + x2="13.467" + x1="5.8266" + inkscape:collect="always" + > + <stop + id="stop18915" + style="stop-color:#ededed" + offset="0" + /> + <stop + id="stop18917" + style="stop-color:#c8c8c8" + offset="1" + /> + </linearGradient + > + <linearGradient + id="linearGradient27486" + y2="26.023" + gradientUnits="userSpaceOnUse" + y1="4.7462" + gradientTransform="matrix(1.3435 0 0 1.4179 2.8795 .31460)" + x2="18.475" + x1="11.573" + inkscape:collect="always" + > + <stop + id="stop15109" + style="stop-color:#ffffff" + offset="0" + /> + <stop + id="stop15111" + style="stop-color:#e2e2e2" + offset="1" + /> + </linearGradient + > + <linearGradient + id="linearGradient27488" + y2="15.257" + gradientUnits="userSpaceOnUse" + y1="15.257" + gradientTransform="matrix(1.3435 0 0 1.4179 2.8795 .31460)" + x2="30.6" + x1="2.0619" + inkscape:collect="always" + > + <stop + id="stop2138" + style="stop-color:#989690" + offset="0" + /> + <stop + id="stop2140" + style="stop-color:#656460" + offset="1" + /> + </linearGradient + > + </defs + > + <sodipodi:namedview + id="base" + bordercolor="#666666" + inkscape:window-x="331" + inkscape:window-y="105" + pagecolor="#ffffff" + inkscape:grid-bbox="true" + inkscape:zoom="1" + inkscape:pageshadow="2" + showgrid="false" + borderopacity="1.0" + inkscape:current-layer="layer1" + inkscape:cx="28.384904" + inkscape:cy="18.816166" + inkscape:window-width="872" + inkscape:pageopacity="0.0" + inkscape:window-height="743" + inkscape:document-units="px" + /> <g - id="layer1" - transform="translate(-77.797413,384.00351)"> + id="layer1" + inkscape:label="Layer 1" + inkscape:groupmode="layer" + > + <g + id="g6707" + transform="matrix(0.0227 0 0 .022979 44.989 37.784)" + > + <rect + id="rect6709" + style="opacity:.40206;color:black;fill:url(#linearGradient6715)" + height="478.36" + width="1339.6" + y="-150.7" + x="-1559.3" + /> + <path + id="path6711" + sodipodi:nodetypes="cccc" + style="opacity:.40206;color:black;fill:url(#radialGradient6717)" + d="m-219.62-150.68v478.33c142.88 0.9 345.4-107.17 345.4-239.2 0-132.02-159.44-239.13-345.4-239.13z" + /> + <path + id="path6713" + sodipodi:nodetypes="cccc" + style="opacity:.40206;color:black;fill:url(#radialGradient6719)" + d="m-1559.3-150.68v478.33c-142.8 0.9-345.4-107.17-345.4-239.2 0-132.02 159.5-239.13 345.4-239.13z" + /> + </g + > + <path + id="path12723" + sodipodi:nodetypes="ccczzzz" + style="stroke-linejoin:round;fill-rule:evenodd;stroke:url(#linearGradient27488);stroke-width:.85660;fill:url(#linearGradient27486)" + d="m6.3334 16.972v24.51h36.973l-0.062-24.392c-0.003-1.378-11.848-14.678-14.033-14.678l-8.552 0.0001c-2.297 0-14.326 13.262-14.326 14.56z" + /> <path - id="path6625" - d="m 227.2,177.73 c -46.65,46.65 -46.67,122.4 -0.03,169.04 30.92,30.92 74.6,41.33 114.12,31.27 l 22.3,22.29 39.67,4.86 4.91,39.73 39.68,4.86 4.89,39.7 39.72,4.91 4.86,39.68 70.53,-5.91 5.66,-0.46 1.07,-12.21 5.62,-63.77 L 558.13,429.65 536.1,407.62 514.05,385.57 492.02,363.55 469.97,341.5 447.92,319.45 425.87,297.4 c 12.55,-40.94 2.66,-87.25 -29.71,-119.62 -46.64,-46.64 -122.32,-46.69 -168.96,-0.05 z m 24.21,24.32 c 21.41,-21.41 52.07,-25.54 68.44,-9.17 16.37,16.37 12.26,47.05 -9.14,68.46 -21.41,21.41 -52.07,25.49 -68.44,9.12 -16.37,-16.37 -12.27,-47.01 9.14,-68.41 z" - style="color:#000000;fill:url(#linearGradient3601)" - inkscape:connector-curvature="0" /> + id="path18153" + sodipodi:nodetypes="czzzccz" + style="fill-rule:evenodd;fill:url(#linearGradient27483)" + d="m6.9231 16.787c-0.3981-0.43 11.887-13.694 13.744-13.694l8.376 0.0005c1.747 0 14.037 13.128 13.427 13.886l-10.861 13.495-12.314-0.318-12.372-13.37z" + /> <path - id="path6871" - d="m 388.43,339.03 c -1.68,1.68 -2.88,3.59 -3.69,5.59 -0.74,1.82 -1.28,3.93 -1.28,6.32 0,2.4 0.52,4.53 1.26,6.35 0.77,1.9 2.01,3.91 3.69,5.59 L 551.53,526 l 3.27,3.27 4.62,-0.38 5.68,-0.46 8.39,-0.71 0.75,-8.4 1.06,-12.19 0.4,-4.64 -3.29,-3.3 -160.16,-160.16 c -1.68,-1.68 -3.68,-2.91 -5.59,-3.69 -1.81,-0.73 -3.92,-1.28 -6.32,-1.28 -2.4,0 -4.51,0.55 -6.32,1.28 -1.91,0.78 -3.91,2.01 -5.59,3.69 z" - style="color:#000000;fill:url(#linearGradient3632)" - inkscape:connector-curvature="0" /> + id="path2164" + sodipodi:nodetypes="ccccc" + style="fill-rule:evenodd;fill:#000000;fill-opacity:.14620" + d="m19.078 30.018l-7.333-8.746 24.818-6.936 3.029 6.216-7.416 9.44" + /> <path - id="path2365" - d="m 239.44,192.85 c -2.29,2.41 -4.43,4.92 -6.43,7.49 2.5,-3.23 5.25,-6.31 8.22,-9.28 -0.6,0.6 -1.21,1.18 -1.79,1.79 z m 3.62,-3.58 c 4.29,-4.07 8.84,-7.67 13.61,-10.83 -4.76,3.15 -9.33,6.77 -13.61,10.83 z m -11.62,13.17 c -0.93,1.27 -1.81,2.53 -2.67,3.82 0.85,-1.29 1.74,-2.56 2.67,-3.82 z m -2.81,4.05 c -0.89,1.35 -1.76,2.74 -2.58,4.13 0.82,-1.4 1.68,-2.77 2.58,-4.13 z m -2.58,4.13 c -1.66,2.8 -3.16,5.65 -4.51,8.57 1.35,-2.91 2.86,-5.78 4.51,-8.57 z m -4.51,8.57 c -1.35,2.92 -2.55,5.9 -3.6,8.91 1.05,-3.01 2.25,-6 3.6,-8.91 z m -3.6,8.91 c -1.05,3 -1.97,6.05 -2.72,9.12 0.75,-3.08 1.67,-6.12 2.72,-9.12 z m -2.72,9.12 c -0.31,1.27 -0.58,2.53 -0.84,3.8 0.26,-1.27 0.53,-2.53 0.84,-3.8 z m 43.53,-60.1 c 1.38,-0.87 2.79,-1.69 4.2,-2.48 -1.41,0.79 -2.82,1.61 -4.2,2.48 z m 8.49,-4.73 c 1.44,-0.71 2.88,-1.37 4.35,-2.01 -1.46,0.63 -2.92,1.3 -4.35,2.01 z m 17.89,-6.76 c 1.53,-0.41 3.06,-0.77 4.6,-1.11 -1.54,0.34 -3.07,0.7 -4.6,1.11 z m 4.62,-1.13 c 1.54,-0.34 3.09,-0.62 4.64,-0.88 -1.55,0.26 -3.1,0.54 -4.64,0.88 z m -75.52,77.34 c -0.16,0.79 -0.29,1.58 -0.42,2.36 0.13,-0.77 0.27,-1.58 0.42,-2.36 z m -0.42,2.36 c -0.13,0.78 -0.27,1.55 -0.38,2.33 0.11,-0.79 0.24,-1.55 0.38,-2.33 z m -0.69,4.67 c -0.09,0.78 -0.17,1.57 -0.24,2.36 0.07,-0.78 0.15,-1.58 0.24,-2.36 z m -0.44,4.73 c -0.06,0.78 -0.1,1.56 -0.13,2.34 0.03,-0.79 0.07,-1.56 0.13,-2.34 z m 93.47,-91.26 c 1.18,-0.06 2.37,-0.1 3.56,-0.12 -1.2,0.02 -2.37,0.06 -3.56,0.12 z m 4.71,-0.12 c 0.78,0 1.57,0.01 2.36,0.03 -0.79,-0.02 -1.57,-0.03 -2.36,-0.03 z m -98.18,105.52 c 0.11,1.58 0.25,3.16 0.44,4.73 -0.19,-1.57 -0.33,-3.16 -0.44,-4.73 z M 322.66,162.93 c 1.56,0.19 3.12,0.4 4.68,0.66 -1.56,-0.26 -3.12,-0.47 -4.68,-0.66 z m -108.85,114.2 c 0.13,0.78 0.26,1.58 0.42,2.36 -0.15,-0.77 -0.29,-1.58 -0.42,-2.36 z m 0.42,2.36 c 0.14,0.77 0.31,1.54 0.48,2.3 -0.17,-0.77 -0.33,-1.52 -0.48,-2.3 z m 1.61,6.92 c 0.21,0.77 0.41,1.55 0.64,2.32 -0.23,-0.76 -0.44,-1.55 -0.64,-2.32 z m 1.35,4.57 c 0.24,0.76 0.49,1.51 0.75,2.26 -0.27,-0.75 -0.51,-1.5 -0.75,-2.26 z m 0.75,2.26 c 0.25,0.71 0.5,1.43 0.77,2.14 -0.26,-0.7 -0.52,-1.43 -0.77,-2.14 z m 1.7,4.48 c 0.3,0.75 0.61,1.48 0.93,2.21 -0.32,-0.73 -0.63,-1.47 -0.93,-2.21 z m 0.93,2.21 c 0.32,0.74 0.63,1.48 0.97,2.21 -0.34,-0.72 -0.65,-1.47 -0.97,-2.21 z m 0.97,2.21 c 0.34,0.73 0.7,1.45 1.06,2.17 -0.36,-0.72 -0.72,-1.44 -1.06,-2.17 z m 2.14,4.31 c 0.38,0.72 0.78,1.43 1.17,2.15 -0.39,-0.71 -0.79,-1.43 -1.17,-2.15 z M 359.25,174.91 c 1.12,0.63 2.23,1.28 3.34,1.97 -1.11,-0.69 -2.22,-1.34 -3.34,-1.97 z M 227.33,312.79 c 0.38,0.61 0.77,1.23 1.17,1.84 -0.4,-0.61 -0.79,-1.22 -1.17,-1.84 z m 1.57,2.46 c 0.42,0.62 0.85,1.24 1.28,1.85 -0.43,-0.62 -0.86,-1.23 -1.28,-1.85 z m 2.72,3.86 c 0.95,1.3 1.95,2.57 2.98,3.83 -1.02,-1.25 -2.03,-2.54 -2.98,-3.83 z M 371.07,182.75 c 1.3,1.01 2.61,2.04 3.87,3.12 -1.27,-1.09 -2.56,-2.1 -3.87,-3.12 z M 244.92,333.79 c 38.64,34.9 98.35,33.74 135.59,-3.49 37.23,-37.24 38.39,-96.96 3.49,-135.59 31.41,35.32 -20.5,44.27 -57.68,81.45 -37.17,37.17 -46.08,89.04 -81.4,57.63 z" - style="color:#000000;fill:url(#linearGradient3609)" - inkscape:connector-curvature="0" /> + id="path2162" + sodipodi:nodetypes="ccccc" + style="fill-rule:evenodd;fill:#000000;fill-opacity:.14620" + d="m18.292 29.836l-7.483-8.81 24.648-6.893 3.174 6.271-7.241 9.407" + /> <path - id="path6632" - d="m 239.44,192.87 c -36.65,38.56 -36.03,99.58 1.8,137.42 38.44,38.43 46.67,-15.69 85.1,-54.13 38.43,-38.43 92.59,-46.69 54.15,-85.12 -38.43,-38.44 -100.81,-38.41 -139.25,0.03 -0.6,0.6 -1.22,1.19 -1.8,1.8 z m 11.99,9.17 c 21.4,-21.41 52.05,-25.51 68.42,-9.14 16.37,16.37 12.27,47.02 -9.14,68.43 -21.4,21.4 -52.05,25.5 -68.42,9.13 -16.37,-16.37 -12.27,-47.02 9.14,-68.42 z" - style="color:#000000;fill:url(#linearGradient3594)" - inkscape:connector-curvature="0" /> - </g> + id="path2160" + sodipodi:nodetypes="ccccc" + style="fill-rule:evenodd;fill:#000000;fill-opacity:.14620" + d="m18.775 29.957l-7.675-8.66 24.968-7.065 3.286 6.593-7.48 9.107" + /> + <path + id="path15105" + sodipodi:nodetypes="ccccc" + style="fill-rule:evenodd;fill:url(#linearGradient27477)" + d="m18.594 30.441l-7.333-8.746 24.712-6.894 3.11 6.388-7.12 8.986" + /> + <path + id="path14245" + sodipodi:nodetypes="ccccccc" + style="fill:url(#linearGradient27471);fill-rule:evenodd" + d="m20.488 29.064l-13.396 10.972 13.909-9.604h9.018l12.42 9.482-11.864-10.85h-10.087z" + /> + <path + id="path14339" + sodipodi:nodetypes="cccc" + style="fill-rule:evenodd;color:#000000;fill:url(#linearGradient27471)" + d="m6.9635 16.885l11.516 14.316 1.068-0.854-12.584-13.462z" + /> + <path + id="path15103" + sodipodi:nodetypes="ccczzzz" + style="stroke:url(#linearGradient27468);stroke-width:.85660;fill:none" + d="m7.3077 17.131l0.0312 23.211h34.945l-0.063-23.084c-0.002-0.75-11.216-13.799-13.384-13.799l-7.895 0.0002c-2.253 0-13.635 12.892-13.634 13.672z" + /> + <path + id="path17393" + sodipodi:nodetypes="cccccc" + style="fill-rule:evenodd;fill:#ffffff" + d="m20.957 30.453l-11.941 8.271 2.219 0.006 9.998-6.869 8.822-1.423-9.098 0.015z" + /> + <path + id="path2174" + sodipodi:nodetypes="ccccccc" + style="fill-rule:evenodd;fill:#ffffff" + d="m11.428 21.67l1.324 1.411 22.791-6.884 2.915 5.682 0.614-0.712-3.069-6.378-24.575 6.881z" + /> + <path + id="path2272" + sodipodi:nodetypes="ccccccc" + style="fill-rule:evenodd;fill:url(#linearGradient27463)" + d="m13.308 23.636l6.026 6.454 1.197-1.026 10.087 0.043 0.812 0.727 3.975-4.744c-1.154-1.411-22.097-1.454-22.097-1.454z" + /> + <path + id="path27492" + sodipodi:nodetypes="cccc" + style="fill-rule:evenodd;color:#000000;fill:#b1b1b1" + d="m41.813 17.848l-9.952 12.631-1.068-0.855 11.02-11.776z" + /> + </g + > <metadata - id="metadata6318"> - <rdf:RDF> - <cc:Work> - <dc:format>image/svg+xml</dc:format> + > + <rdf:RDF + > + <cc:Work + > + <dc:format + >image/svg+xml</dc:format + > <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + rdf:resource="http://purl.org/dc/dcmitype/StillImage" + /> <cc:license - rdf:resource="http://creativecommons.org/licenses/publicdomain/" /> - <dc:publisher> + rdf:resource="http://creativecommons.org/licenses/publicdomain/" + /> + <dc:publisher + > + <cc:Agent + rdf:about="http://openclipart.org/" + > + <dc:title + >Openclipart</dc:title + > + </cc:Agent + > + </dc:publisher + > + <dc:title + >tango internet mail</dc:title + > + <dc:date + >2010-03-29T08:04:16</dc:date + > + <dc:description + >"E-mail" icon from <a href="http://tango.freedesktop.org/Tango_Desktop_Project"> Tango Project </a> \n<br><br> \nSince version 0.8.90 Tango Project icons are Public Domain: <a href="http://tango.freedesktop.org/Frequently_Asked_Questions#Terms_of_Use.3F"> Tango Project FAQ </a></dc:description + > + <dc:source + >https://openclipart.org/detail/35215/tango-internet-mail-by-warszawianka</dc:source + > + <dc:creator + > <cc:Agent - rdf:about="http://openclipart.org/"> - <dc:title>Openclipart</dc:title> - </cc:Agent> - </dc:publisher> - <dc:title>Key</dc:title> - <dc:date>2007-02-27T15:15:43</dc:date> - <dc:description>A key icon.</dc:description> - <dc:source>http://openclipart.org/detail/3330/key-by-barretr</dc:source> - <dc:creator> - <cc:Agent> - <dc:title>barretr</dc:title> - </cc:Agent> - </dc:creator> - <dc:subject> - <rdf:Bag> - <rdf:li>clip art</rdf:li> - <rdf:li>clipart</rdf:li> - <rdf:li>icon</rdf:li> - <rdf:li>image</rdf:li> - <rdf:li>key</rdf:li> - <rdf:li>media</rdf:li> - <rdf:li>png</rdf:li> - <rdf:li>public domain</rdf:li> - <rdf:li>svg</rdf:li> - </rdf:Bag> - </dc:subject> - </cc:Work> + > + <dc:title + >warszawianka</dc:title + > + </cc:Agent + > + </dc:creator + > + <dc:subject + > + <rdf:Bag + > + <rdf:li + >email</rdf:li + > + <rdf:li + >envelope</rdf:li + > + <rdf:li + >externalsource</rdf:li + > + <rdf:li + >icon</rdf:li + > + <rdf:li + >letter</rdf:li + > + <rdf:li + >tango</rdf:li + > + </rdf:Bag + > + </dc:subject + > + </cc:Work + > <cc:License - rdf:about="http://creativecommons.org/licenses/publicdomain/"> + rdf:about="http://creativecommons.org/licenses/publicdomain/" + > <cc:permits - rdf:resource="http://creativecommons.org/ns#Reproduction" /> + rdf:resource="http://creativecommons.org/ns#Reproduction" + /> <cc:permits - rdf:resource="http://creativecommons.org/ns#Distribution" /> + rdf:resource="http://creativecommons.org/ns#Distribution" + /> <cc:permits - rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /> - </cc:License> - </rdf:RDF> - </metadata> - <rect - style="color:#000000;fill:#ff0000;fill-opacity:0;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" - id="rect6322" - width="442.68826" - height="442.68826" - x="84.558434" - y="505.21939" /> -</svg> + rdf:resource="http://creativecommons.org/ns#DerivativeWorks" + /> + </cc:License + > + </rdf:RDF + > + </metadata + > +</svg +> diff --git a/icons/keys.png b/icons/keys.png Binary files differnew file mode 100644 index 000000000..28701ee49 --- /dev/null +++ b/icons/keys.png diff --git a/icons/keys.svg b/icons/keys.svg new file mode 100644 index 000000000..ace413dae --- /dev/null +++ b/icons/keys.svg @@ -0,0 +1,197 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + id="svg4217" + viewBox="0 0 744.09449 1052.3622" + version="1.0" + inkscape:version="0.48.4 r9939" + width="100%" + height="100%" + sodipodi:docname="kdm_email.svg" + inkscape:export-filename="/home/carl/src/dcpomatic/icons/kdm_email.png" + inkscape:export-xdpi="6.5100002" + inkscape:export-ydpi="6.5100002"> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1366" + inkscape:window-height="714" + id="namedview6320" + showgrid="false" + inkscape:zoom="0.60313323" + inkscape:cx="-87.032436" + inkscape:cy="195.645" + inkscape:window-x="0" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg4217" /> + <defs + id="defs4219"> + <linearGradient + id="linearGradient3594" + y2="742.5" + gradientUnits="userSpaceOnUse" + x2="-886.76001" + gradientTransform="matrix(-0.84033,-0.84033,-0.84033,0.84033,214.12,-1075.4)" + y1="742.5" + x1="-772.01001"> + <stop + id="stop4687" + stop-color="#fff" + offset="0" /> + <stop + id="stop4689" + stop-color="#fff" + stop-opacity="0" + offset="1" /> + </linearGradient> + <linearGradient + id="linearGradient3601" + y2="613.94" + gradientUnits="userSpaceOnUse" + x2="385.04001" + gradientTransform="matrix(0.70711,-0.70711,0.70711,0.70711,-126.18,372.21)" + y1="63.870998" + x1="386.39001"> + <stop + id="stop3797" + stop-color="#ffe800" + offset="0" /> + <stop + id="stop3799" + stop-color="#dfb300" + offset="1" /> + </linearGradient> + <linearGradient + id="linearGradient3609" + y2="161.84" + gradientUnits="userSpaceOnUse" + x2="212.92999" + y1="358.29999" + x1="409.38"> + <stop + id="stop4034" + stop-color="#dfb300" + offset="0" /> + <stop + id="stop3374" + stop-color="#dfb300" + offset=".5" /> + <stop + id="stop3376" + stop-color="#dfb300" + offset="1" /> + </linearGradient> + <linearGradient + id="linearGradient3632" + y2="448.35001" + gradientUnits="userSpaceOnUse" + x2="382.89999" + gradientTransform="matrix(0.70711,-0.70711,0.70711,0.70711,-126.18,372.21)" + y1="448.35001" + x1="403.63"> + <stop + id="stop3636" + stop-color="#ffe800" + stop-opacity=".39216" + offset="0" /> + <stop + id="stop3638" + stop-color="#dfb300" + stop-opacity=".39216" + offset="1" /> + </linearGradient> + </defs> + <g + id="layer1" + transform="translate(-77.797413,384.00351)"> + <path + id="path6625" + d="m 227.2,177.73 c -46.65,46.65 -46.67,122.4 -0.03,169.04 30.92,30.92 74.6,41.33 114.12,31.27 l 22.3,22.29 39.67,4.86 4.91,39.73 39.68,4.86 4.89,39.7 39.72,4.91 4.86,39.68 70.53,-5.91 5.66,-0.46 1.07,-12.21 5.62,-63.77 L 558.13,429.65 536.1,407.62 514.05,385.57 492.02,363.55 469.97,341.5 447.92,319.45 425.87,297.4 c 12.55,-40.94 2.66,-87.25 -29.71,-119.62 -46.64,-46.64 -122.32,-46.69 -168.96,-0.05 z m 24.21,24.32 c 21.41,-21.41 52.07,-25.54 68.44,-9.17 16.37,16.37 12.26,47.05 -9.14,68.46 -21.41,21.41 -52.07,25.49 -68.44,9.12 -16.37,-16.37 -12.27,-47.01 9.14,-68.41 z" + style="color:#000000;fill:url(#linearGradient3601)" + inkscape:connector-curvature="0" /> + <path + id="path6871" + d="m 388.43,339.03 c -1.68,1.68 -2.88,3.59 -3.69,5.59 -0.74,1.82 -1.28,3.93 -1.28,6.32 0,2.4 0.52,4.53 1.26,6.35 0.77,1.9 2.01,3.91 3.69,5.59 L 551.53,526 l 3.27,3.27 4.62,-0.38 5.68,-0.46 8.39,-0.71 0.75,-8.4 1.06,-12.19 0.4,-4.64 -3.29,-3.3 -160.16,-160.16 c -1.68,-1.68 -3.68,-2.91 -5.59,-3.69 -1.81,-0.73 -3.92,-1.28 -6.32,-1.28 -2.4,0 -4.51,0.55 -6.32,1.28 -1.91,0.78 -3.91,2.01 -5.59,3.69 z" + style="color:#000000;fill:url(#linearGradient3632)" + inkscape:connector-curvature="0" /> + <path + id="path2365" + d="m 239.44,192.85 c -2.29,2.41 -4.43,4.92 -6.43,7.49 2.5,-3.23 5.25,-6.31 8.22,-9.28 -0.6,0.6 -1.21,1.18 -1.79,1.79 z m 3.62,-3.58 c 4.29,-4.07 8.84,-7.67 13.61,-10.83 -4.76,3.15 -9.33,6.77 -13.61,10.83 z m -11.62,13.17 c -0.93,1.27 -1.81,2.53 -2.67,3.82 0.85,-1.29 1.74,-2.56 2.67,-3.82 z m -2.81,4.05 c -0.89,1.35 -1.76,2.74 -2.58,4.13 0.82,-1.4 1.68,-2.77 2.58,-4.13 z m -2.58,4.13 c -1.66,2.8 -3.16,5.65 -4.51,8.57 1.35,-2.91 2.86,-5.78 4.51,-8.57 z m -4.51,8.57 c -1.35,2.92 -2.55,5.9 -3.6,8.91 1.05,-3.01 2.25,-6 3.6,-8.91 z m -3.6,8.91 c -1.05,3 -1.97,6.05 -2.72,9.12 0.75,-3.08 1.67,-6.12 2.72,-9.12 z m -2.72,9.12 c -0.31,1.27 -0.58,2.53 -0.84,3.8 0.26,-1.27 0.53,-2.53 0.84,-3.8 z m 43.53,-60.1 c 1.38,-0.87 2.79,-1.69 4.2,-2.48 -1.41,0.79 -2.82,1.61 -4.2,2.48 z m 8.49,-4.73 c 1.44,-0.71 2.88,-1.37 4.35,-2.01 -1.46,0.63 -2.92,1.3 -4.35,2.01 z m 17.89,-6.76 c 1.53,-0.41 3.06,-0.77 4.6,-1.11 -1.54,0.34 -3.07,0.7 -4.6,1.11 z m 4.62,-1.13 c 1.54,-0.34 3.09,-0.62 4.64,-0.88 -1.55,0.26 -3.1,0.54 -4.64,0.88 z m -75.52,77.34 c -0.16,0.79 -0.29,1.58 -0.42,2.36 0.13,-0.77 0.27,-1.58 0.42,-2.36 z m -0.42,2.36 c -0.13,0.78 -0.27,1.55 -0.38,2.33 0.11,-0.79 0.24,-1.55 0.38,-2.33 z m -0.69,4.67 c -0.09,0.78 -0.17,1.57 -0.24,2.36 0.07,-0.78 0.15,-1.58 0.24,-2.36 z m -0.44,4.73 c -0.06,0.78 -0.1,1.56 -0.13,2.34 0.03,-0.79 0.07,-1.56 0.13,-2.34 z m 93.47,-91.26 c 1.18,-0.06 2.37,-0.1 3.56,-0.12 -1.2,0.02 -2.37,0.06 -3.56,0.12 z m 4.71,-0.12 c 0.78,0 1.57,0.01 2.36,0.03 -0.79,-0.02 -1.57,-0.03 -2.36,-0.03 z m -98.18,105.52 c 0.11,1.58 0.25,3.16 0.44,4.73 -0.19,-1.57 -0.33,-3.16 -0.44,-4.73 z M 322.66,162.93 c 1.56,0.19 3.12,0.4 4.68,0.66 -1.56,-0.26 -3.12,-0.47 -4.68,-0.66 z m -108.85,114.2 c 0.13,0.78 0.26,1.58 0.42,2.36 -0.15,-0.77 -0.29,-1.58 -0.42,-2.36 z m 0.42,2.36 c 0.14,0.77 0.31,1.54 0.48,2.3 -0.17,-0.77 -0.33,-1.52 -0.48,-2.3 z m 1.61,6.92 c 0.21,0.77 0.41,1.55 0.64,2.32 -0.23,-0.76 -0.44,-1.55 -0.64,-2.32 z m 1.35,4.57 c 0.24,0.76 0.49,1.51 0.75,2.26 -0.27,-0.75 -0.51,-1.5 -0.75,-2.26 z m 0.75,2.26 c 0.25,0.71 0.5,1.43 0.77,2.14 -0.26,-0.7 -0.52,-1.43 -0.77,-2.14 z m 1.7,4.48 c 0.3,0.75 0.61,1.48 0.93,2.21 -0.32,-0.73 -0.63,-1.47 -0.93,-2.21 z m 0.93,2.21 c 0.32,0.74 0.63,1.48 0.97,2.21 -0.34,-0.72 -0.65,-1.47 -0.97,-2.21 z m 0.97,2.21 c 0.34,0.73 0.7,1.45 1.06,2.17 -0.36,-0.72 -0.72,-1.44 -1.06,-2.17 z m 2.14,4.31 c 0.38,0.72 0.78,1.43 1.17,2.15 -0.39,-0.71 -0.79,-1.43 -1.17,-2.15 z M 359.25,174.91 c 1.12,0.63 2.23,1.28 3.34,1.97 -1.11,-0.69 -2.22,-1.34 -3.34,-1.97 z M 227.33,312.79 c 0.38,0.61 0.77,1.23 1.17,1.84 -0.4,-0.61 -0.79,-1.22 -1.17,-1.84 z m 1.57,2.46 c 0.42,0.62 0.85,1.24 1.28,1.85 -0.43,-0.62 -0.86,-1.23 -1.28,-1.85 z m 2.72,3.86 c 0.95,1.3 1.95,2.57 2.98,3.83 -1.02,-1.25 -2.03,-2.54 -2.98,-3.83 z M 371.07,182.75 c 1.3,1.01 2.61,2.04 3.87,3.12 -1.27,-1.09 -2.56,-2.1 -3.87,-3.12 z M 244.92,333.79 c 38.64,34.9 98.35,33.74 135.59,-3.49 37.23,-37.24 38.39,-96.96 3.49,-135.59 31.41,35.32 -20.5,44.27 -57.68,81.45 -37.17,37.17 -46.08,89.04 -81.4,57.63 z" + style="color:#000000;fill:url(#linearGradient3609)" + inkscape:connector-curvature="0" /> + <path + id="path6632" + d="m 239.44,192.87 c -36.65,38.56 -36.03,99.58 1.8,137.42 38.44,38.43 46.67,-15.69 85.1,-54.13 38.43,-38.43 92.59,-46.69 54.15,-85.12 -38.43,-38.44 -100.81,-38.41 -139.25,0.03 -0.6,0.6 -1.22,1.19 -1.8,1.8 z m 11.99,9.17 c 21.4,-21.41 52.05,-25.51 68.42,-9.14 16.37,16.37 12.27,47.02 -9.14,68.43 -21.4,21.4 -52.05,25.5 -68.42,9.13 -16.37,-16.37 -12.27,-47.02 9.14,-68.42 z" + style="color:#000000;fill:url(#linearGradient3594)" + inkscape:connector-curvature="0" /> + </g> + <metadata + id="metadata6318"> + <rdf:RDF> + <cc:Work> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <cc:license + rdf:resource="http://creativecommons.org/licenses/publicdomain/" /> + <dc:publisher> + <cc:Agent + rdf:about="http://openclipart.org/"> + <dc:title>Openclipart</dc:title> + </cc:Agent> + </dc:publisher> + <dc:title>Key</dc:title> + <dc:date>2007-02-27T15:15:43</dc:date> + <dc:description>A key icon.</dc:description> + <dc:source>http://openclipart.org/detail/3330/key-by-barretr</dc:source> + <dc:creator> + <cc:Agent> + <dc:title>barretr</dc:title> + </cc:Agent> + </dc:creator> + <dc:subject> + <rdf:Bag> + <rdf:li>clip art</rdf:li> + <rdf:li>clipart</rdf:li> + <rdf:li>icon</rdf:li> + <rdf:li>image</rdf:li> + <rdf:li>key</rdf:li> + <rdf:li>media</rdf:li> + <rdf:li>png</rdf:li> + <rdf:li>public domain</rdf:li> + <rdf:li>svg</rdf:li> + </rdf:Bag> + </dc:subject> + </cc:Work> + <cc:License + rdf:about="http://creativecommons.org/licenses/publicdomain/"> + <cc:permits + rdf:resource="http://creativecommons.org/ns#Reproduction" /> + <cc:permits + rdf:resource="http://creativecommons.org/ns#Distribution" /> + <cc:permits + rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /> + </cc:License> + </rdf:RDF> + </metadata> + <rect + style="color:#000000;fill:#ff0000;fill-opacity:0;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect6322" + width="442.68826" + height="442.68826" + x="84.558434" + y="505.21939" /> +</svg> diff --git a/platform/linux/dcpomatic.desktop.in b/platform/linux/dcpomatic.desktop.in index 76e629404..a950f35e9 100644 --- a/platform/linux/dcpomatic.desktop.in +++ b/platform/linux/dcpomatic.desktop.in @@ -3,8 +3,8 @@ Encoding=UTF-8 Version=1.0 Type=Application Terminal=false -Exec=@INSTALL_PREFIX@/bin/dcpomatic -Name=DCP-o-matic +Exec=@INSTALL_PREFIX@/bin/dcpomatic2 +Name=DCP-o-matic 2 Icon=dcpomatic Comment=DCP generator Categories=AudioVideo;Video diff --git a/platform/linux/dcpomatic.spec.in b/platform/linux/dcpomatic.spec.in index da179628c..dae0e90b6 100644 --- a/platform/linux/dcpomatic.spec.in +++ b/platform/linux/dcpomatic.spec.in @@ -1,5 +1,5 @@ Summary:A program that generates Digital Cinema Packages (DCPs) from video and audio files -Name:dcpomatic +Name:dcpomatic2 Version:@VERSION@ Release:1%{?dist} License:GPL @@ -13,40 +13,40 @@ files (such as those from DVDs or Blu-Rays) for presentation on DCI-compliant digital projectors. %files -%{_bindir}/dcpomatic -%{_bindir}/dcpomatic_batch -%{_bindir}/dcpomatic_cli -%{_bindir}/dcpomatic_create -%{_bindir}/dcpomatic_kdm -%{_bindir}/dcpomatic_server -%{_bindir}/dcpomatic_server_cli -%{_datadir}/applications/dcpomatic.desktop -%{_datadir}/applications/dcpomatic_batch.desktop -%{_datadir}/applications/dcpomatic_server.desktop -%{_datadir}/dcpomatic/taskbar_icon.png -%{_datadir}/icons/hicolor/128x128/apps/dcpomatic.png -%{_datadir}/icons/hicolor/22x22/apps/dcpomatic.png -%{_datadir}/icons/hicolor/32x32/apps/dcpomatic.png -%{_datadir}/icons/hicolor/48x48/apps/dcpomatic.png -%{_datadir}/icons/hicolor/64x64/apps/dcpomatic.png -%{_datadir}/locale/de_DE/LC_MESSAGES/dcpomatic.mo -%{_datadir}/locale/de_DE/LC_MESSAGES/libdcpomatic-wx.mo -%{_datadir}/locale/de_DE/LC_MESSAGES/libdcpomatic.mo -%{_datadir}/locale/es_ES/LC_MESSAGES/dcpomatic.mo -%{_datadir}/locale/es_ES/LC_MESSAGES/libdcpomatic-wx.mo -%{_datadir}/locale/es_ES/LC_MESSAGES/libdcpomatic.mo -%{_datadir}/locale/fr_FR/LC_MESSAGES/dcpomatic.mo -%{_datadir}/locale/fr_FR/LC_MESSAGES/libdcpomatic-wx.mo -%{_datadir}/locale/fr_FR/LC_MESSAGES/libdcpomatic.mo -%{_datadir}/locale/it_IT/LC_MESSAGES/dcpomatic.mo -%{_datadir}/locale/it_IT/LC_MESSAGES/libdcpomatic-wx.mo -%{_datadir}/locale/it_IT/LC_MESSAGES/libdcpomatic.mo -%{_datadir}/locale/sv_SE/LC_MESSAGES/dcpomatic.mo -%{_datadir}/locale/sv_SE/LC_MESSAGES/libdcpomatic-wx.mo -%{_datadir}/locale/sv_SE/LC_MESSAGES/libdcpomatic.mo -%{_datadir}/locale/nl_NL/LC_MESSAGES/dcpomatic.mo -%{_datadir}/locale/nl_NL/LC_MESSAGES/libdcpomatic-wx.mo -%{_datadir}/locale/nl_NL/LC_MESSAGES/libdcpomatic.mo +%{_bindir}/dcpomatic2 +%{_bindir}/dcpomatic2_batch +%{_bindir}/dcpomatic2_cli +%{_bindir}/dcpomatic2_create +%{_bindir}/dcpomatic2_kdm +%{_bindir}/dcpomatic2_server +%{_bindir}/dcpomatic2_server_cli +%{_datadir}/applications/dcpomatic2.desktop +%{_datadir}/applications/dcpomatic2_batch.desktop +%{_datadir}/applications/dcpomatic2_server.desktop +%{_datadir}/dcpomatic2/taskbar_icon.png +%{_datadir}/icons/hicolor/128x128/apps/dcpomatic2.png +%{_datadir}/icons/hicolor/22x22/apps/dcpomatic2.png +%{_datadir}/icons/hicolor/32x32/apps/dcpomatic2.png +%{_datadir}/icons/hicolor/48x48/apps/dcpomatic2.png +%{_datadir}/icons/hicolor/64x64/apps/dcpomatic2.png +%{_datadir}/locale/de_DE/LC_MESSAGES/dcpomatic2.mo +%{_datadir}/locale/de_DE/LC_MESSAGES/libdcpomatic2-wx.mo +%{_datadir}/locale/de_DE/LC_MESSAGES/libdcpomatic2.mo +%{_datadir}/locale/es_ES/LC_MESSAGES/dcpomatic2.mo +%{_datadir}/locale/es_ES/LC_MESSAGES/libdcpomatic2-wx.mo +%{_datadir}/locale/es_ES/LC_MESSAGES/libdcpomatic2.mo +%{_datadir}/locale/fr_FR/LC_MESSAGES/dcpomatic2.mo +%{_datadir}/locale/fr_FR/LC_MESSAGES/libdcpomatic2-wx.mo +%{_datadir}/locale/fr_FR/LC_MESSAGES/libdcpomatic2.mo +%{_datadir}/locale/it_IT/LC_MESSAGES/dcpomatic2.mo +%{_datadir}/locale/it_IT/LC_MESSAGES/libdcpomatic2-wx.mo +%{_datadir}/locale/it_IT/LC_MESSAGES/libdcpomatic2.mo +%{_datadir}/locale/sv_SE/LC_MESSAGES/dcpomatic2.mo +%{_datadir}/locale/sv_SE/LC_MESSAGES/libdcpomatic2-wx.mo +%{_datadir}/locale/sv_SE/LC_MESSAGES/libdcpomatic2.mo +%{_datadir}/locale/nl_NL/LC_MESSAGES/dcpomatic2.mo +%{_datadir}/locale/nl_NL/LC_MESSAGES/libdcpomatic2-wx.mo +%{_datadir}/locale/nl_NL/LC_MESSAGES/libdcpomatic2.mo %prep rm -rf $RPM_BUILD_DIR/dcpomatic-@VERSION@ diff --git a/platform/linux/wscript b/platform/linux/wscript index 3aab4f7fb..336c1bcb0 100644 --- a/platform/linux/wscript +++ b/platform/linux/wscript @@ -1,25 +1,25 @@ def build(bld): obj = bld(features='subst') obj.source = 'dcpomatic.desktop.in' - obj.target = 'dcpomatic.desktop' + obj.target = 'dcpomatic2.desktop' obj.INSTALL_PREFIX = bld.env.INSTALL_PREFIX obj.VERSION = bld.env.VERSION obj = bld(features='subst') obj.source = 'dcpomatic_batch.desktop.in' - obj.target = 'dcpomatic_batch.desktop' + obj.target = 'dcpomatic2_batch.desktop' obj.INSTALL_PREFIX = bld.env.INSTALL_PREFIX obj.VERSION = bld.env.VERSION obj = bld(features='subst') obj.source = 'dcpomatic_server.desktop.in' - obj.target = 'dcpomatic_server.desktop' + obj.target = 'dcpomatic2_server.desktop' obj.INSTALL_PREFIX = bld.env.INSTALL_PREFIX obj.VERSION = bld.env.VERSION obj = bld(features='subst') obj.source = 'dcpomatic.spec.in' - obj.target = 'dcpomatic.spec' + obj.target = 'dcpomatic2.spec' obj.INSTALL_PREFIX = bld.env.INSTALL_PREFIX obj.VERSION = bld.env.VERSION if bld.env.TARGET_CENTOS_6: @@ -27,4 +27,4 @@ def build(bld): elif bld.env.TARGET_CENTOS_7: obj.CENTOS_VERSION = '7' - bld.install_files('${PREFIX}/share/applications', ['dcpomatic.desktop', 'dcpomatic_batch.desktop', 'dcpomatic_server.desktop']) + bld.install_files('${PREFIX}/share/applications', ['dcpomatic2.desktop', 'dcpomatic2_batch.desktop', 'dcpomatic2_server.desktop']) diff --git a/platform/osx/Info.plist.in b/platform/osx/Info.plist.in index f2675e3f6..e420d3620 100644 --- a/platform/osx/Info.plist.in +++ b/platform/osx/Info.plist.in @@ -5,7 +5,7 @@ <key>CFBundleDevelopmentRegion</key> <string>English</string> <key>CFBundleExecutable</key> - <string>dcpomatic</string> + <string>dcpomatic2</string> <key>CFBundleGetInfoString</key> <string>DCP generator</string> <key>CFBundleIconFile</key> @@ -15,7 +15,7 @@ <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> - <string>DCP-o-matic</string> + <string>DCP-o-matic 2</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersions</key> diff --git a/platform/osx/make_dmg.sh b/platform/osx/make_dmg.sh index 52bff1349..78c3bb819 100644 --- a/platform/osx/make_dmg.sh +++ b/platform/osx/make_dmg.sh @@ -15,7 +15,7 @@ WORK=build/platform/osx ENV=/Users/carl/Environments/osx/10.6 ROOT=$1 -appdir="DCP-o-matic.app" +appdir="DCP-o-matic 2.app" approot="$appdir/Contents" libs="$approot/lib" macos="$approot/MacOS" @@ -53,16 +53,16 @@ function universal_copy_lib { relink="$relink|$2" } -universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic "$WORK/$macos" -universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic_cli "$WORK/$macos" -universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic_server_cli "$WORK/$macos" -universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic_batch "$WORK/$macos" -universal_copy $ROOT src/dcpomatic/build/src/lib/libdcpomatic.dylib "$WORK/$libs" -universal_copy $ROOT src/dcpomatic/build/src/wx/libdcpomatic-wx.dylib "$WORK/$libs" +universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2 "$WORK/$macos" +universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_cli "$WORK/$macos" +universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_server_cli "$WORK/$macos" +universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_batch "$WORK/$macos" +universal_copy $ROOT src/dcpomatic/build/src/lib/libdcpomatic2.dylib "$WORK/$libs" +universal_copy $ROOT src/dcpomatic/build/src/wx/libdcpomatic2-wx.dylib "$WORK/$libs" universal_copy_lib $ROOT libcxml "$WORK/$libs" -universal_copy_lib $ROOT libdcp "$WORK/$libs" -universal_copy_lib $ROOT libasdcp-libdcp "$WORK/$libs" -universal_copy_lib $ROOT libkumu-libdcp "$WORK/$libs" +universal_copy_lib $ROOT libdcp-1.0 "$WORK/$libs" +universal_copy_lib $ROOT libasdcp-libdcp-1.0 "$WORK/$libs" +universal_copy_lib $ROOT libkumu-libdcp-1.0 "$WORK/$libs" universal_copy_lib $ROOT libopenjpeg "$WORK/$libs" universal_copy_lib $ROOT libavdevice "$WORK/$libs" universal_copy_lib $ROOT libavformat "$WORK/$libs" @@ -105,13 +105,16 @@ universal_copy_lib $ENV libffi "$WORK/$libs" universal_copy_lib $ENV libiconv "$WORK/$libs" universal_copy_lib $ENV libpango "$WORK/$libs" universal_copy_lib $ENV libcairo "$WORK/$libs" +universal_copy_lib $ENV libpixman "$WORK/$libs" +universal_copy_lib $ENV libharfbuzz "$WORK/$libs" relink=`echo $relink | sed -e "s/\+//g"` -for obj in "$WORK/$macos/dcpomatic" "$WORK/$macos/dcpomatic_batch" "$WORK/$macos/dcpomatic_cli" "$WORK/$macos/dcpomatic_server_cli" "$WORK/$macos/ffprobe" "$WORK/$libs/"*.dylib; do +for obj in "$WORK/$macos/dcpomatic2" "$WORK/$macos/dcpomatic2_batch" "$WORK/$macos/dcpomatic2_cli" "$WORK/$macos/dcpomatic2_server_cli" "$WORK/$macos/ffprobe" "$WORK/$libs/"*.dylib; do deps=`otool -L "$obj" | awk '{print $1}' | egrep "($relink)" | egrep "($ENV|$ROOT|boost)"` changes="" for dep in $deps; do + echo "Relinking $dep into $obj" base=`basename $dep` # $dep will be a path within 64/; make a 32/ path too dep32=`echo $dep | sed -e "s/\/64\//\/32\//g"` @@ -129,6 +132,7 @@ cp icons/defaults.png "$WORK/$resources" cp icons/kdm_email.png "$WORK/$resources" cp icons/servers.png "$WORK/$resources" cp icons/tms.png "$WORK/$resources" +cp icons/keys.png "$WORK/$resources" # i18n: DCP-o-matic .mo files for lang in de_DE es_ES fr_FR it_IT sv_SE nl_NL; do @@ -169,7 +173,7 @@ echo ' set theViewOptions to the icon view options of container window set arrangement of theViewOptions to not arranged set icon size of theViewOptions to 64 - set position of item "DCP-o-matic.app" of container window to {90, 80} + set position of item "DCP-o-matic 2.app" of container window to {90, 80} set position of item "Applications" of container window to {310, 80} close open diff --git a/platform/windows/wscript b/platform/windows/wscript index 24b6ed099..7e92fb2d5 100644 --- a/platform/windows/wscript +++ b/platform/windows/wscript @@ -22,7 +22,7 @@ def write_installer(bits, version): !define MUI_SPECIALBITMAP "%resources%/dcpomatic.bmp" !include "Sections.nsh" -InstallDir "$PROGRAMFILES\\DCP-o-matic" +InstallDir "$PROGRAMFILES\\DCP-o-matic 2" !insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_LICENSE "../../../COPYING" @@ -40,7 +40,7 @@ ${If} ${RunningX64} ; disable registry redirection (enable access to 64-bit portion of registry) SetRegView 64 ; change install dir - StrCpy $INSTDIR "$PROGRAMFILES64\DCP-o-matic" + StrCpy $INSTDIR "$PROGRAMFILES64\DCP-o-matic 2" ${EndIf} """, file=f) @@ -102,25 +102,34 @@ File "%static_deps%/bin/openssl.exe" File "%static_deps%/bin/libcurl-4.dll" File "%static_deps%/bin/ssleay32.dll" File "%static_deps%/bin/libzip-2.dll" +File "%static_deps%/bin/libcairomm-1.0-1.dll" +File "%static_deps%/bin/libpangomm-1.4-1.dll" +File "%static_deps%/bin/pango-querymodules.exe" -File "%cdist_deps%/bin/asdcp-libdcp.dll" -File "%cdist_deps%/bin/kumu-libdcp.dll" +File "%cdist_deps%/bin/asdcp-libdcp-1.0.dll" +File "%cdist_deps%/bin/kumu-libdcp-1.0.dll" File "%cdist_deps%/bin/avcodec-56.dll" File "%cdist_deps%/bin/avfilter-5.dll" File "%cdist_deps%/bin/avformat-56.dll" File "%cdist_deps%/bin/avutil-54.dll" File "%cdist_deps%/bin/avdevice-56.dll" File "%cdist_deps%/bin/postproc-53.dll" -File "%cdist_deps%/bin/dcp.dll" +File "%cdist_deps%/bin/dcp-1.0.dll" File "%cdist_deps%/bin/libopenjpeg-1.dll" File "%cdist_deps%/bin/swresample-1.dll" File "%cdist_deps%/bin/swscale-3.dll" File "%cdist_deps%/bin/cxml-0.dll" File "%cdist_deps%/bin/ffprobe.exe" -File "%binaries%/src/wx/dcpomatic-wx.dll" -File "%binaries%/src/lib/dcpomatic.dll" +File "%binaries%/src/wx/dcpomatic2-wx.dll" +File "%binaries%/src/lib/dcpomatic2.dll" +SetOutPath "$INSTDIR\\lib\\pango\\1.8.0\\modules" +File "%static_deps%/lib/pango/1.8.0/modules/pango-arabic-lang.dll" +File "%static_deps%/lib/pango/1.8.0/modules/pango-basic-win32.dll" +File "%static_deps%/lib/pango/1.8.0/modules/pango-indic-lang.dll" + +SetOutPath "$INSTDIR\\bin" # I don't know why, but sometimes it seems that # delegates.xml must be in with the binaries, and # sometimes in the $PROFILE. Meh. @@ -129,66 +138,71 @@ SetOutPath "$PROFILE\\.magick" File "%static_deps%/etc/ImageMagick-6/delegates.xml" SetOutPath "$INSTDIR\\locale\\fr\\LC_MESSAGES" -File "%binaries%/src/lib/mo/fr_FR/libdcpomatic.mo" -File "%binaries%/src/wx/mo/fr_FR/libdcpomatic-wx.mo" -File "%binaries%/src/tools/mo/fr_FR/dcpomatic.mo" +File "%binaries%/src/lib/mo/fr_FR/libdcpomatic2.mo" +File "%binaries%/src/wx/mo/fr_FR/libdcpomatic2-wx.mo" +File "%binaries%/src/tools/mo/fr_FR/dcpomatic2.mo" File "%static_deps%/share/locale/fr/LC_MESSAGES/wxstd.mo" SetOutPath "$INSTDIR\\locale\\it\\LC_MESSAGES" -File "%binaries%/src/lib/mo/it_IT/libdcpomatic.mo" -File "%binaries%/src/wx/mo/it_IT/libdcpomatic-wx.mo" -File "%binaries%/src/tools/mo/it_IT/dcpomatic.mo" +File "%binaries%/src/lib/mo/it_IT/libdcpomatic2.mo" +File "%binaries%/src/wx/mo/it_IT/libdcpomatic2-wx.mo" +File "%binaries%/src/tools/mo/it_IT/dcpomatic2.mo" File "%static_deps%/share/locale/it/LC_MESSAGES/wxstd.mo" SetOutPath "$INSTDIR\\locale\\es\\LC_MESSAGES" -File "%binaries%/src/lib/mo/es_ES/libdcpomatic.mo" -File "%binaries%/src/wx/mo/es_ES/libdcpomatic-wx.mo" -File "%binaries%/src/tools/mo/es_ES/dcpomatic.mo" +File "%binaries%/src/lib/mo/es_ES/libdcpomatic2.mo" +File "%binaries%/src/wx/mo/es_ES/libdcpomatic2-wx.mo" +File "%binaries%/src/tools/mo/es_ES/dcpomatic2.mo" File "%static_deps%/share/locale/es/LC_MESSAGES/wxstd.mo" SetOutPath "$INSTDIR\\locale\\sv\\LC_MESSAGES" -File "%binaries%/src/lib/mo/sv_SE/libdcpomatic.mo" -File "%binaries%/src/wx/mo/sv_SE/libdcpomatic-wx.mo" -File "%binaries%/src/tools/mo/sv_SE/dcpomatic.mo" +File "%binaries%/src/lib/mo/sv_SE/libdcpomatic2.mo" +File "%binaries%/src/wx/mo/sv_SE/libdcpomatic2-wx.mo" +File "%binaries%/src/tools/mo/sv_SE/dcpomatic2.mo" File "%static_deps%/share/locale/sv/LC_MESSAGES/wxstd.mo" SetOutPath "$INSTDIR\\locale\\de\\LC_MESSAGES" -File "%binaries%/src/lib/mo/de_DE/libdcpomatic.mo" -File "%binaries%/src/wx/mo/de_DE/libdcpomatic-wx.mo" -File "%binaries%/src/tools/mo/de_DE/dcpomatic.mo" +File "%binaries%/src/lib/mo/de_DE/libdcpomatic2.mo" +File "%binaries%/src/wx/mo/de_DE/libdcpomatic2-wx.mo" +File "%binaries%/src/tools/mo/de_DE/dcpomatic2.mo" File "%static_deps%/share/locale/de/LC_MESSAGES/wxstd.mo" SetOutPath "$INSTDIR\\locale\\nl\\LC_MESSAGES" -File "%binaries%/src/lib/mo/nl_NL/libdcpomatic.mo" -File "%binaries%/src/wx/mo/nl_NL/libdcpomatic-wx.mo" -File "%binaries%/src/tools/mo/nl_NL/dcpomatic.mo" +File "%binaries%/src/lib/mo/nl_NL/libdcpomatic2.mo" +File "%binaries%/src/wx/mo/nl_NL/libdcpomatic2-wx.mo" +File "%binaries%/src/tools/mo/nl_NL/dcpomatic2.mo" File "%static_deps%/share/locale/nl/LC_MESSAGES/wxstd.mo" -WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic" "DisplayName" "DCP-o-matic (remove only)" -WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic" "UninstallString" "$INSTDIR\\Uninstall.exe" +WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic2" "DisplayName" "DCP-o-matic 2 (remove only)" +WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic2" "UninstallString" "$INSTDIR\\Uninstall.exe" WriteUninstaller "$INSTDIR\\Uninstall.exe" +CreateDirectory "$INSTDIR\etc\pango" +ReadEnvStr $0 COMSPEC +SetOutPath "$INSTDIR" +nsExec::ExecToLog '$0 /C bin\pango-querymodules.exe > etc\pango\pango.modules' + SectionEnd Section "DCP-o-matic" SEC_MASTER SetOutPath "$INSTDIR\\bin" -CreateDirectory "$SMPROGRAMS\\DCP-o-matic" -File "%binaries%/src/tools/dcpomatic.exe" -File "%binaries%/src/tools/dcpomatic_batch.exe" -File "%binaries%/src/tools/dcpomatic_cli.exe" -CreateShortCut "$DESKTOP\\DCP-o-matic.lnk" "$INSTDIR\\bin\\dcpomatic.exe" "" -CreateShortCut "$SMPROGRAMS\\DCP-o-matic\\DCP-o-matic.lnk" "$INSTDIR\\bin\\dcpomatic.exe" "" "$INSTDIR\\bin\\dcpomatic.exe" 0 -CreateShortCut "$DESKTOP\\DCP-o-matic batch converter.lnk" "$INSTDIR\\bin\\dcpomatic_batch.exe" "" -CreateShortCut "$SMPROGRAMS\\DCP-o-matic\\DCP-o-matic batch converter.lnk" "$INSTDIR\\bin\\dcpomatic.exe" "" "$INSTDIR\\bin\\dcpomatic_batch.exe" 0 -CreateShortCut "$SMPROGRAMS\\DCP-o-matic\\Uninstall DCP-o-matic.lnk" "$INSTDIR\\Uninstall.exe" "" "$INSTDIR\\Uninstall.exe" 0 -WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic" "DisplayName" "DCP-o-matic (remove only)" -WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic" "UninstallString" "$INSTDIR\\Uninstall.exe" +CreateDirectory "$SMPROGRAMS\\DCP-o-matic 2" +File "%binaries%/src/tools/dcpomatic2.exe" +File "%binaries%/src/tools/dcpomatic2_batch.exe" +File "%binaries%/src/tools/dcpomatic2_cli.exe" +CreateShortCut "$DESKTOP\\DCP-o-matic 2.lnk" "$INSTDIR\\bin\\dcpomatic2.exe" "" +CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2.lnk" "$INSTDIR\\bin\\dcpomatic2.exe" "" "$INSTDIR\\bin\\dcpomatic2.exe" 0 +CreateShortCut "$DESKTOP\\DCP-o-matic 2 batch converter.lnk" "$INSTDIR\\bin\\dcpomatic2_batch.exe" "" +CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2 batch converter.lnk" "$INSTDIR\\bin\\dcpomatic2.exe" "" "$INSTDIR\\bin\\dcpomatic2_batch.exe" 0 +CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\Uninstall DCP-o-matic 2.lnk" "$INSTDIR\\Uninstall.exe" "" "$INSTDIR\\Uninstall.exe" 0 +WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic2" "DisplayName" "DCP-o-matic 2 (remove only)" +WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic2" "UninstallString" "$INSTDIR\\Uninstall.exe" WriteUninstaller "$INSTDIR\\Uninstall.exe" SectionEnd Section "Encode server" SEC_SERVER SetOutPath "$INSTDIR\\bin" -CreateDirectory "$SMPROGRAMS\\DCP-o-matic" -File "%binaries%/src/tools/dcpomatic_server_cli.exe" -File "%binaries%/src/tools/dcpomatic_server.exe" -CreateShortCut "$DESKTOP\\DCP-o-matic encode server.lnk" "$INSTDIR\\bin\\dcpomatic_server.exe" "" -CreateShortCut "$SMPROGRAMS\\DCP-o-matic\\DCP-o-matic encode server.lnk" "$INSTDIR\\bin\\dcpomatic_server.exe" "" "$INSTDIR\\bin\\dcpomatic_server.exe" 0 -CreateShortCut "$SMPROGRAMS\\DCP-o-matic\\Uninstall DCP-o-matic.lnk" "$INSTDIR\\Uninstall.exe" "" "$INSTDIR\\Uninstall.exe" 0 +CreateDirectory "$SMPROGRAMS\\DCP-o-matic 2" +File "%binaries%/src/tools/dcpomatic2_server_cli.exe" +File "%binaries%/src/tools/dcpomatic2_server.exe" +CreateShortCut "$DESKTOP\\DCP-o-matic 2 encode server.lnk" "$INSTDIR\\bin\\dcpomatic2_server.exe" "" +CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2 encode server.lnk" "$INSTDIR\\bin\\dcpomatic_server.exe" "" "$INSTDIR\\bin\\dcpomatic2_server.exe" 0 +CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\Uninstall DCP-o-matic 2.lnk" "$INSTDIR\\Uninstall.exe" "" "$INSTDIR\\Uninstall.exe" 0 SectionEnd LangString DESC_SEC_MASTER ${LANG_ENGLISH} "DCP-o-matic" @@ -211,13 +225,13 @@ LangString DESC_SEC_SERVER ${LANG_ENGLISH} "DCP-o-matic encode server" Section "Uninstall" RMDir /r "$INSTDIR\\*.*" RMDir "$INSTDIR" -Delete "$DESKTOP\\DCP-o-matic.lnk" -Delete "$DESKTOP\\DCP-o-matic batch converter.lnk" -Delete "$DESKTOP\\DCP-o-matic encode server.lnk" -Delete "$SMPROGRAMS\\DCP-o-matic\\*.*" -RmDir "$SMPROGRAMS\\DCP-o-matic" -DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\\DCP-o-matic" -DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic" +Delete "$DESKTOP\\DCP-o-matic 2.lnk" +Delete "$DESKTOP\\DCP-o-matic 2 batch converter.lnk" +Delete "$DESKTOP\\DCP-o-matic 2 encode server.lnk" +Delete "$SMPROGRAMS\\DCP-o-matic 2\\*.*" +RmDir "$SMPROGRAMS\\DCP-o-matic 2" +DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\\DCP-o-matic2" +DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic2" SectionEnd """, file=f) diff --git a/run/dcpomatic b/run/dcpomatic index 278ee8c5f..74714865a 100755 --- a/run/dcpomatic +++ b/run/dcpomatic @@ -90,24 +90,24 @@ else export LD_LIBRARY_PATH=build/src/lib:build/src/wx:build/src/asdcplib/src:$LD_LIBRARY_PATH if [ "$1" == "--debug" ]; then shift - gdb --args build/src/tools/dcpomatic $* + gdb --args build/src/tools/dcpomatic2 $* elif [ "$1" == "--valgrind" ]; then shift - valgrind --tool="memcheck" build/src/tools/dcpomatic $* + valgrind --tool="memcheck" build/src/tools/dcpomatic2 $* elif [ "$1" == "--callgrind" ]; then shift - valgrind --tool="callgrind" build/src/tools/dcpomatic $* + valgrind --tool="callgrind" build/src/tools/dcpomatic2 $* elif [ "$1" == "--massif" ]; then shift - valgrind --tool="massif" build/src/tools/dcpomatic $* + valgrind --tool="massif" build/src/tools/dcpomatic2 $* elif [ "$1" == "--i18n" ]; then shift - LANGUAGE=fr_FR.UTF8 LANG=fr_FR.UTF8 LC_ALL=fr_FR.UTF8 build/src/tools/dcpomatic "$*" + LANGUAGE=fr_FR.UTF8 LANG=fr_FR.UTF8 LC_ALL=fr_FR.UTF8 build/src/tools/dcpomatic2 "$*" elif [ "$1" == "--perf" ]; then shift - perf record build/src/tools/dcpomatic $* + perf record build/src/tools/dcpomatic2 $* else - build/src/tools/dcpomatic $* + build/src/tools/dcpomatic2 $* fi fi diff --git a/run/dcpomatic_cli b/run/dcpomatic_cli index ea2a98763..e1c0ebd14 100755 --- a/run/dcpomatic_cli +++ b/run/dcpomatic_cli @@ -3,11 +3,11 @@ export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH:build/src if [ "$1" == "--debug" ]; then shift - gdb --args build/src/tools/dcpomatic_cli "$@" + gdb --args build/src/tools/dcpomatic2_cli "$@" elif [ "$1" == "--valgrind" ]; then shift - valgrind --tool="memcheck" --num-callers=24 --suppressions=suppressions build/src/tools/dcpomatic_cli "$@" -# valgrind --tool="memcheck" --leak-check=full --show-reachable=yes --num-callers=24 --suppressions=suppressions build/src/tools/dcpomatic_cli "$@" + valgrind --tool="memcheck" --num-callers=24 --suppressions=suppressions build/src/tools/dcpomatic2_cli "$@" +# valgrind --tool="memcheck" --leak-check=full --show-reachable=yes --num-callers=24 --suppressions=suppressions build/src/tools/dcpomatic2_cli "$@" else - build/src/tools/dcpomatic_cli "$@" + build/src/tools/dcpomatic2_cli "$@" fi diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc index ab985bdf7..60b10e7b6 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" @@ -59,19 +60,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 ()); @@ -81,7 +81,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 3d4881983..a218cb340 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: @@ -33,10 +45,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..56ca7a94b 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) { @@ -275,3 +285,29 @@ AudioBuffers::apply_gain (float dB) } } } + +/** @param c Channel index. + * @return AudioBuffers object containing only channel `c' from this AudioBuffers. + */ +shared_ptr<AudioBuffers> +AudioBuffers::channel (int c) const +{ + shared_ptr<AudioBuffers> o (new AudioBuffers (1, frames ())); + o->copy_channel_from (this, c, 0); + return o; +} + +void +AudioBuffers::copy_channel_from (AudioBuffers const * from, int from_channel, int to_channel) +{ + assert (from->frames() == frames()); + memcpy (data(to_channel), from->data(from_channel), frames() * sizeof (float)); +} + +shared_ptr<AudioBuffers> +AudioBuffers::clone () const +{ + shared_ptr<AudioBuffers> b (new AudioBuffers (channels (), frames ())); + b->copy_from (this, frames (), 0, 0); + return b; +} diff --git a/src/lib/audio_buffers.h b/src/lib/audio_buffers.h index c9030dbfa..8cd67aaa7 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> @@ -35,6 +39,9 @@ public: AudioBuffers & operator= (AudioBuffers const &); + boost::shared_ptr<AudioBuffers> clone () const; + boost::shared_ptr<AudioBuffers> channel (int) const; + void ensure_size (int); float** data () const { @@ -60,8 +67,9 @@ public: void apply_gain (float); void copy_from (AudioBuffers const * from, int frames_to_copy, int read_offset, int write_offset); + void copy_channel_from (AudioBuffers const * from, int from_channel, int to_channel); 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..d02728b00 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" @@ -26,6 +26,7 @@ #include "exceptions.h" #include "config.h" #include "frame_rate_change.h" +#include "audio_processor.h" #include "i18n.h" @@ -34,7 +35,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; @@ -42,11 +43,22 @@ int const AudioContentProperty::AUDIO_FRAME_RATE = 202; int const AudioContentProperty::AUDIO_GAIN = 203; int const AudioContentProperty::AUDIO_DELAY = 204; int const AudioContentProperty::AUDIO_MAPPING = 205; +int const AudioContentProperty::AUDIO_PROCESSOR = 206; -AudioContent::AudioContent (shared_ptr<const Film> f, Time s) +AudioContent::AudioContent (shared_ptr<const Film> f) + : Content (f) + , _audio_gain (0) + , _audio_delay (Config::instance()->default_audio_delay ()) + , _audio_processor (0) +{ + +} + +AudioContent::AudioContent (shared_ptr<const Film> f, DCPTime s) : Content (f, s) , _audio_gain (0) , _audio_delay (Config::instance()->default_audio_delay ()) + , _audio_processor (0) { } @@ -55,15 +67,20 @@ AudioContent::AudioContent (shared_ptr<const Film> f, boost::filesystem::path p) : Content (f, p) , _audio_gain (0) , _audio_delay (Config::instance()->default_audio_delay ()) + , _audio_processor (0) { } -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_processor (0) { _audio_gain = node->number_child<float> ("AudioGain"); _audio_delay = node->number_child<int> ("AudioDelay"); + if (node->optional_string_child ("AudioProcessor")) { + _audio_processor = AudioProcessor::from_id (node->string_child ("AudioProcessor")); + } } AudioContent::AudioContent (shared_ptr<const Film> f, vector<shared_ptr<Content> > c) @@ -86,6 +103,7 @@ AudioContent::AudioContent (shared_ptr<const Film> f, vector<shared_ptr<Content> _audio_gain = ref->audio_gain (); _audio_delay = ref->audio_delay (); + _audio_processor = ref->audio_processor (); } void @@ -94,6 +112,9 @@ AudioContent::as_xml (xmlpp::Node* node) const boost::mutex::scoped_lock lm (_mutex); node->add_child("AudioGain")->add_child_text (raw_convert<string> (_audio_gain)); node->add_child("AudioDelay")->add_child_text (raw_convert<string> (_audio_delay)); + if (_audio_processor) { + node->add_child("AudioProcessor")->add_child_text (_audio_processor->id ()); + } } @@ -119,6 +140,22 @@ AudioContent::set_audio_delay (int d) signal_changed (AudioContentProperty::AUDIO_DELAY); } +void +AudioContent::set_audio_processor (AudioProcessor const * p) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _audio_processor = p; + } + + /* The channel count might have changed, so reset the mapping */ + AudioMapping m (processed_audio_channels ()); + m.make_default (); + set_audio_mapping (m); + + signal_changed (AudioContentProperty::AUDIO_PROCESSOR); +} + boost::signals2::connection AudioContent::analyse_audio (boost::function<void()> finished) { @@ -148,24 +185,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) { @@ -175,3 +226,13 @@ AudioContent::output_audio_frame_rate () const return rint (t); } +int +AudioContent::processed_audio_channels () const +{ + if (!audio_processor ()) { + return audio_channels (); + } + + return audio_processor()->out_channels (audio_channels ()); +} + diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h index 2c324a3a4..57085a765 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 @@ -27,6 +31,11 @@ namespace cxml { class Node; } +class AudioProcessor; + +/** @class AudioContentProperty + * @brief Names for properties of AudioContent. + */ class AudioContentProperty { public: @@ -36,34 +45,44 @@ public: static int const AUDIO_GAIN; static int const AUDIO_DELAY; static int const AUDIO_MAPPING; + static int const AUDIO_PROCESSOR; }; +/** @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>); + 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; + int processed_audio_channels () const; + boost::signals2::connection analyse_audio (boost::function<void()>); void set_audio_gain (double); void set_audio_delay (int); + void set_audio_processor (AudioProcessor const *); double audio_gain () const { boost::mutex::scoped_lock lm (_mutex); @@ -75,11 +94,17 @@ public: return _audio_delay; } + AudioProcessor const * audio_processor () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_processor; + } + private: /** Gain to apply to audio in dB */ double _audio_gain; /** Delay to apply to audio (positive moves audio later) in milliseconds */ int _audio_delay; + AudioProcessor const * _audio_processor; }; #endif diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index 30bd2540a..f3251f306 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 @@ -20,39 +20,217 @@ #include <iostream> #include "audio_decoder.h" #include "audio_buffers.h" -#include "exceptions.h" -#include "log.h" +#include "audio_processor.h" #include "resampler.h" +#include "util.h" #include "i18n.h" using std::list; using std::pair; using std::cout; +using std::min; +using std::max; using boost::optional; using boost::shared_ptr; -AudioDecoder::AudioDecoder (shared_ptr<const Film> film, shared_ptr<const AudioContent> content) - : Decoder (film) - , _audio_content (content) - , _audio_position (0) +AudioDecoder::AudioDecoder (shared_ptr<const AudioContent> content) + : _audio_content (content) { + if (content->resampled_audio_frame_rate() != content->audio_frame_rate() && content->audio_channels ()) { + _resampler.reset (new Resampler (content->audio_frame_rate(), content->resampled_audio_frame_rate(), content->audio_channels ())); + } + if (content->audio_processor ()) { + _processor = content->audio_processor()->clone (content->resampled_audio_frame_rate ()); + } + + 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->processed_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 ((_decoded_audio.frame > frame || (_decoded_audio.frame + _decoded_audio.audio->frames()) < end) && !pass ()) {} + decoded_offset = frame - _decoded_audio.frame; + } else { + while (_decoded_audio.audio->frames() < length && !pass ()) {} + /* 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 (_processor) { + data = _processor->run (data); + } + + AudioFrame const frame_rate = _audio_content->resampled_audio_frame_rate (); + + if (_seek_reference) { + /* We've had an accurate seek and now we're seeing some data */ + ContentTime const delta = time - _seek_reference.get (); + AudioFrame const delta_frames = delta.frames (frame_rate); + if (delta_frames > 0) { + /* This data comes after the seek time. Pad the data with some silence. */ + shared_ptr<AudioBuffers> padded (new AudioBuffers (data->channels(), data->frames() + delta_frames)); + padded->make_silent (); + padded->copy_from (data.get(), data->frames(), 0, delta_frames); + data = padded; + time -= delta; + } else if (delta_frames < 0) { + /* This data comes before the seek time. Throw some data away */ + AudioFrame const to_discard = min (-delta_frames, static_cast<AudioFrame> (data->frames())); + AudioFrame const to_keep = data->frames() - to_discard; + if (to_keep == 0) { + /* We have to throw all this data away, so keep _seek_reference and + try again next time some data arrives. + */ + return; + } + shared_ptr<AudioBuffers> trimmed (new AudioBuffers (data->channels(), to_keep)); + trimmed->copy_from (data.get(), to_keep, to_discard, 0); + data = trimmed; + time += ContentTime::from_frames (to_discard, frame_rate); + } + _seek_reference = optional<ContentTime> (); + } + + if (!_audio_position) { + _audio_position = time.frames (frame_rate); + } + + assert (_audio_position.get() >= (_decoded_audio.frame + _decoded_audio.audio->frames())); + + add (data); +} + +void +AudioDecoder::add (shared_ptr<const AudioBuffers> data) +{ + if (!_audio_position) { + /* This should only happen when there is a seek followed by a flush, but + we need to cope with it. + */ + return; + } + + /* Resize _decoded_audio to fit the new data */ + int new_size = 0; + if (_decoded_audio.audio->frames() == 0) { + /* There's nothing in there, so just store the new data */ + new_size = data->frames (); + _decoded_audio.frame = _audio_position.get (); + } else { + /* Otherwise we need to extend _decoded_audio to include the new stuff */ + 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 (); + + /* Limit the amount of data we keep in case nobody is asking for it */ + int const max_frames = _audio_content->resampled_audio_frame_rate () * 10; + if (_decoded_audio.audio->frames() > max_frames) { + int const to_remove = _decoded_audio.audio->frames() - max_frames; + _decoded_audio.frame += to_remove; + _decoded_audio.audio->move (to_remove, 0, max_frames); + _decoded_audio.audio->set_frames (max_frames); + } +} + +void +AudioDecoder::flush () +{ + if (!_resampler) { + return; + } + + shared_ptr<const AudioBuffers> b = _resampler->flush (); + if (b) { + add (b); + } +} + +void +AudioDecoder::seek (ContentTime t, bool accurate) { - return _audio_content->audio_channels () > 0; + _audio_position.reset (); + reset_decoded_audio (); + if (accurate) { + _seek_reference = t; + } + if (_processor) { + _processor->flush (); + } } diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index ab6c4b8a9..f8438df52 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,38 @@ 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 (after resampling, if applicable) + * @param length Frames to get (after resampling, if applicable) + * @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 (); + void add (boost::shared_ptr<const AudioBuffers>); + boost::shared_ptr<const AudioContent> _audio_content; - AudioContent::Frame _audio_position; + boost::shared_ptr<Resampler> _resampler; + boost::shared_ptr<AudioProcessor> _processor; + boost::optional<AudioFrame> _audio_position; + /** Currently-available decoded audio data */ + ContentAudio _decoded_audio; + /** The time of an accurate seek after which we have not yet received any actual + data at the seek time. + */ + boost::optional<ContentTime> _seek_reference; }; #endif diff --git a/src/lib/audio_examiner.h b/src/lib/audio_examiner.h new file mode 100644 index 000000000..d6d4dbe97 --- /dev/null +++ b/src/lib/audio_examiner.h @@ -0,0 +1,35 @@ +/* + 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/audio_examiner.h + * @brief AudioExaminer class. + */ + +/** @class AudioExaminer + * @brief Parent for classes which examine AudioContent for their pertinent details. + */ +class AudioExaminer +{ +public: + virtual ~AudioExaminer () {} + + virtual int audio_channels () const = 0; + virtual ContentTime audio_length () const = 0; + virtual int audio_frame_rate () const = 0; +}; diff --git a/src/lib/audio_filter.cc b/src/lib/audio_filter.cc new file mode 100644 index 000000000..59b5684ea --- /dev/null +++ b/src/lib/audio_filter.cc @@ -0,0 +1,139 @@ +/* + 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 <cmath> +#include "audio_filter.h" +#include "audio_buffers.h" + +using std::vector; +using std::min; +using boost::shared_ptr; + +vector<float> +AudioFilter::sinc_blackman (float cutoff, bool invert) const +{ + vector<float> ir (_M + 1); + + /* Impulse response */ + + for (int i = 0; i <= _M; ++i) { + if (i == (_M / 2)) { + ir[i] = 2 * M_PI * cutoff; + } else { + /* sinc */ + ir[i] = sin (2 * M_PI * cutoff * (i - _M / 2)) / (i - _M / 2); + /* Blackman window */ + ir[i] *= (0.42 - 0.5 * cos (2 * M_PI * i / _M) + 0.08 * cos (4 * M_PI * i / _M)); + } + } + + /* Normalise */ + + float sum = 0; + for (int i = 0; i <= _M; ++i) { + sum += ir[i]; + } + + for (int i = 0; i <= _M; ++i) { + ir[i] /= sum; + } + + /* Frequency inversion (swapping low-pass for high-pass, or whatever) */ + + if (invert) { + for (int i = 0; i <= _M; ++i) { + ir[i] = -ir[i]; + } + ir[_M / 2] += 1; + } + + return ir; +} + +shared_ptr<AudioBuffers> +AudioFilter::run (shared_ptr<AudioBuffers> in) +{ + shared_ptr<AudioBuffers> out (new AudioBuffers (in->channels(), in->frames())); + + if (!_tail) { + _tail.reset (new AudioBuffers (in->channels(), _M + 1)); + _tail->make_silent (); + } + + for (int i = 0; i < in->channels(); ++i) { + for (int j = 0; j < in->frames(); ++j) { + float s = 0; + for (int k = 0; k <= _M; ++k) { + if ((j - k) < 0) { + s += _tail->data(i)[j - k + _M + 1] * _ir[k]; + } else { + s += in->data(i)[j - k] * _ir[k]; + } + } + + out->data(i)[j] = s; + } + } + + int const amount = min (in->frames(), _tail->frames()); + if (amount < _tail->frames ()) { + _tail->move (amount, 0, _tail->frames() - amount); + } + _tail->copy_from (in.get(), amount, in->frames() - amount, _tail->frames () - amount); + + return out; +} + +void +AudioFilter::flush () +{ + _tail.reset (); +} + +LowPassAudioFilter::LowPassAudioFilter (float transition_bandwidth, float cutoff) + : AudioFilter (transition_bandwidth) +{ + _ir = sinc_blackman (cutoff, false); +} + + +HighPassAudioFilter::HighPassAudioFilter (float transition_bandwidth, float cutoff) + : AudioFilter (transition_bandwidth) +{ + _ir = sinc_blackman (cutoff, true); +} + +BandPassAudioFilter::BandPassAudioFilter (float transition_bandwidth, float lower, float higher) + : AudioFilter (transition_bandwidth) +{ + vector<float> lpf = sinc_blackman (lower, false); + vector<float> hpf = sinc_blackman (higher, true); + + _ir.resize (_M + 1); + for (int i = 0; i <= _M; ++i) { + _ir[i] = lpf[i] + hpf[i]; + } + + /* We now have a band-stop, so invert for band-pass */ + for (int i = 0; i <= _M; ++i) { + _ir[i] = -_ir[i]; + } + + _ir[_M / 2] += 1; +} diff --git a/src/lib/audio_filter.h b/src/lib/audio_filter.h new file mode 100644 index 000000000..9fc69daad --- /dev/null +++ b/src/lib/audio_filter.h @@ -0,0 +1,82 @@ +/* + 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 <vector> +#include <boost/shared_ptr.hpp> + +class AudioBuffers; +class audio_filter_impulse_kernel_test; +struct audio_filter_impulse_input_test; + +class AudioFilter +{ +public: + AudioFilter (float transition_bandwidth) + { + _M = 4 / transition_bandwidth; + if (_M % 2) { + ++_M; + } + } + + boost::shared_ptr<AudioBuffers> run (boost::shared_ptr<AudioBuffers> in); + + void flush (); + +protected: + friend struct audio_filter_impulse_kernel_test; + friend struct audio_filter_impulse_input_test; + + std::vector<float> sinc_blackman (float cutoff, bool invert) const; + + std::vector<float> _ir; + int _M; + boost::shared_ptr<AudioBuffers> _tail; +}; + +class LowPassAudioFilter : public AudioFilter +{ +public: + /** Construct a windowed-sinc low-pass filter using the Blackman window. + * @param transition_bandwidth Transition bandwidth as a fraction of the sampling rate. + * @param cutoff Cutoff frequency as a fraction of the sampling rate. + */ + LowPassAudioFilter (float transition_bandwidth, float cutoff); +}; + +class HighPassAudioFilter : public AudioFilter +{ +public: + /** Construct a windowed-sinc high-pass filter using the Blackman window. + * @param transition_bandwidth Transition bandwidth as a fraction of the sampling rate. + * @param cutoff Cutoff frequency as a fraction of the sampling rate. + */ + HighPassAudioFilter (float transition_bandwidth, float cutoff); +}; + +class BandPassAudioFilter : public AudioFilter +{ +public: + /** Construct a windowed-sinc band-pass filter using the Blackman window. + * @param transition_bandwidth Transition bandwidth as a fraction of the sampling rate. + * @param lower Lower cutoff frequency as a fraction of the sampling rate. + * @param higher Higher cutoff frequency as a fraction of the sampling rate. + */ + BandPassAudioFilter (float transition_bandwidth, float lower, float higher); +}; diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc index e35c1ae94..e86e2e2ac 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) @@ -40,12 +40,12 @@ AudioMapping::AudioMapping () } -/** Create a default AudioMapping for a given channel count. - * @param c Number of channels. +/** Create an empty AudioMapping for a given channel count. + * @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..a76d83a37 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 @@ -17,12 +17,17 @@ */ +/** @file src/lib/audio_mapping.h + * @brief AudioMapping class. + */ + #ifndef DCPOMATIC_AUDIO_MAPPING_H #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; @@ -32,7 +37,9 @@ namespace cxml { class Node; } -/** A many-to-many mapping from some content channels to DCP channels. +/** @class AudioMapping. + * @brief A many-to-many mapping from some content channels to DCP channels. + * * The number of content channels is set on construction and fixed, * and then each of those content channels are mapped to each DCP channel * by a linear gain. @@ -41,8 +48,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 +57,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/audio_processor.cc b/src/lib/audio_processor.cc new file mode 100644 index 000000000..f350cc2aa --- /dev/null +++ b/src/lib/audio_processor.cc @@ -0,0 +1,52 @@ +/* + 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 "audio_processor.h" +#include "mid_side_decoder.h" +#include "upmixer_a.h" + +using std::string; +using std::list; + +list<AudioProcessor const *> AudioProcessor::_all; + +void +AudioProcessor::setup_audio_processors () +{ + _all.push_back (new MidSideDecoder ()); + _all.push_back (new UpmixerA (48000)); +} + +AudioProcessor const * +AudioProcessor::from_id (string id) +{ + for (list<AudioProcessor const *>::const_iterator i = _all.begin(); i != _all.end(); ++i) { + if ((*i)->id() == id) { + return *i; + } + } + + return 0; +} + +list<AudioProcessor const *> +AudioProcessor::all () +{ + return _all; +} diff --git a/src/lib/audio_processor.h b/src/lib/audio_processor.h new file mode 100644 index 000000000..9b332e7fe --- /dev/null +++ b/src/lib/audio_processor.h @@ -0,0 +1,51 @@ +/* + 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_AUDIO_PROCESSOR_H +#define DCPOMATIC_AUDIO_PROCESSOR_H + +#include <list> +#include <string> +#include <boost/shared_ptr.hpp> +#include "channel_count.h" + +class AudioBuffers; + +class AudioProcessor +{ +public: + virtual ~AudioProcessor () {} + + virtual std::string name () const = 0; + virtual std::string id () const = 0; + virtual ChannelCount in_channels () const = 0; + virtual int out_channels (int) const = 0; + virtual boost::shared_ptr<AudioProcessor> clone (int sampling_rate) const = 0; + virtual boost::shared_ptr<AudioBuffers> run (boost::shared_ptr<const AudioBuffers>) = 0; + virtual void flush () {} + + static std::list<AudioProcessor const *> all (); + static void setup_audio_processors (); + static AudioProcessor const * from_id (std::string); + +private: + static std::list<AudioProcessor const *> _all; +}; + +#endif diff --git a/src/lib/channel_count.h b/src/lib/channel_count.h new file mode 100644 index 000000000..4247fc063 --- /dev/null +++ b/src/lib/channel_count.h @@ -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. + +*/ + +#ifndef DCPOMATIC_CHANNEL_COUNT_H +#define DCPOMATIC_CHANNEL_COUNT_H + +class ChannelCount +{ +public: + ChannelCount () + : min (0) + , max (0) + {} + + ChannelCount (int n) + : min (n) + , max (n) + {} + + ChannelCount (int min_, int max_) + : min (min_) + , max (max_) + {} + + bool includes (int c) { + return min <= c && c <= max; + } + + int min; + int max; +}; + +#endif diff --git a/src/lib/cinema.cc b/src/lib/cinema.cc index fca6b6afd..620236186 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,17 +67,21 @@ 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"))); + if (node->optional_string_child ("Certificate")) { + certificate = dcp::Certificate (node->string_child ("Certificate")); + } } void Screen::as_xml (xmlpp::Element* parent) const { parent->add_child("Name")->add_child_text (name); - parent->add_child("Certificate")->add_child_text (certificate->certificate (true)); + if (certificate) { + parent->add_child("Certificate")->add_child_text (certificate->certificate (true)); + } } diff --git a/src/lib/cinema.h b/src/lib/cinema.h index 40dc15ae0..8421f4687 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::optional<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::optional<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/sound_processor.cc b/src/lib/cinema_sound_processor.cc index 9be6621cc..6a7905114 100644 --- a/src/lib/sound_processor.cc +++ b/src/lib/cinema_sound_processor.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 @@ -18,22 +18,22 @@ */ /** @file src/sound_processor.cc - * @brief A class to describe a sound processor. + * @brief CinemaSoundProcessor class. */ #include <iostream> #include <cassert> -#include "sound_processor.h" +#include "cinema_sound_processor.h" #include "dolby_cp750.h" using namespace std; -vector<SoundProcessor const *> SoundProcessor::_sound_processors; +vector<CinemaSoundProcessor const *> CinemaSoundProcessor::_cinema_sound_processors; /** @param i Our id. * @param n User-visible name. */ -SoundProcessor::SoundProcessor (string i, string n) +CinemaSoundProcessor::CinemaSoundProcessor (string i, string n) : _id (i) , _name (n) { @@ -41,33 +41,33 @@ SoundProcessor::SoundProcessor (string i, string n) } /** @return All available sound processors */ -vector<SoundProcessor const *> -SoundProcessor::all () +vector<CinemaSoundProcessor const *> +CinemaSoundProcessor::all () { - return _sound_processors; + return _cinema_sound_processors; } /** Set up the static _sound_processors vector; must be called before from_* * methods are used. */ void -SoundProcessor::setup_sound_processors () +CinemaSoundProcessor::setup_cinema_sound_processors () { - _sound_processors.push_back (new DolbyCP750); + _cinema_sound_processors.push_back (new DolbyCP750); } /** @param id One of our ids. * @return Corresponding sound processor, or 0. */ -SoundProcessor const * -SoundProcessor::from_id (string id) +CinemaSoundProcessor const * +CinemaSoundProcessor::from_id (string id) { - vector<SoundProcessor const *>::iterator i = _sound_processors.begin (); - while (i != _sound_processors.end() && (*i)->id() != id) { + vector<CinemaSoundProcessor const *>::iterator i = _cinema_sound_processors.begin (); + while (i != _cinema_sound_processors.end() && (*i)->id() != id) { ++i; } - if (i == _sound_processors.end ()) { + if (i == _cinema_sound_processors.end ()) { return 0; } @@ -78,14 +78,14 @@ SoundProcessor::from_id (string id) * @return Index of the sound processor with the list, or -1. */ int -SoundProcessor::as_index (SoundProcessor const * s) +CinemaSoundProcessor::as_index (CinemaSoundProcessor const * s) { - vector<SoundProcessor*>::size_type i = 0; - while (i < _sound_processors.size() && _sound_processors[i] != s) { + vector<CinemaSoundProcessor*>::size_type i = 0; + while (i < _cinema_sound_processors.size() && _cinema_sound_processors[i] != s) { ++i; } - if (i == _sound_processors.size ()) { + if (i == _cinema_sound_processors.size ()) { return -1; } @@ -95,9 +95,9 @@ SoundProcessor::as_index (SoundProcessor const * s) /** @param i An index returned from as_index(). * @return Corresponding sound processor. */ -SoundProcessor const * -SoundProcessor::from_index (int i) +CinemaSoundProcessor const * +CinemaSoundProcessor::from_index (int i) { - assert (i <= int(_sound_processors.size ())); - return _sound_processors[i]; + assert (i <= int(_cinema_sound_processors.size ())); + return _cinema_sound_processors[i]; } diff --git a/src/lib/sound_processor.h b/src/lib/cinema_sound_processor.h index 8f2652243..f735b1227 100644 --- a/src/lib/sound_processor.h +++ b/src/lib/cinema_sound_processor.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,24 +17,27 @@ */ -/** @file src/sound_processor.h - * @brief A class to describe a sound processor. +/** @file src/cinema_sound_processor.h + * @brief CinemaSoundProcessor class */ -#ifndef DCPOMATIC_SOUND_PROCESSOR_H -#define DCPOMATIC_SOUND_PROCESSOR_H +#ifndef DCPOMATIC_CINEMA_SOUND_PROCESSOR_H +#define DCPOMATIC_CINEMA_SOUND_PROCESSOR_H #include <string> #include <vector> #include <boost/utility.hpp> -/** @class SoundProcessor - * @brief Class to describe a sound processor. +/** @class CinemaSoundProcessor + * @brief Class to describe a cimema's sound processor. + * + * In other words, the box in the rack that handles sound decoding and processing + * in a cinema. */ -class SoundProcessor : public boost::noncopyable +class CinemaSoundProcessor : public boost::noncopyable { public: - SoundProcessor (std::string i, std::string n); + CinemaSoundProcessor (std::string i, std::string n); virtual float db_for_fader_change (float from, float to) const = 0; @@ -48,11 +51,11 @@ public: return _name; } - static std::vector<SoundProcessor const *> all (); - static void setup_sound_processors (); - static SoundProcessor const * from_id (std::string id); - static SoundProcessor const * from_index (int); - static int as_index (SoundProcessor const *); + static std::vector<CinemaSoundProcessor const *> all (); + static void setup_cinema_sound_processors (); + static CinemaSoundProcessor const * from_id (std::string id); + static CinemaSoundProcessor const * from_index (int); + static int as_index (CinemaSoundProcessor const *); private: /** id for our use */ @@ -60,8 +63,8 @@ private: /** user-visible name for this sound processor */ std::string _name; - /** sll available sound processors */ - static std::vector<SoundProcessor const *> _sound_processors; + /** sll available cinema sound processors */ + static std::vector<CinemaSoundProcessor const *> _cinema_sound_processors; }; #endif diff --git a/src/lib/colour_conversion.cc b/src/lib/colour_conversion.cc index e5b1104ff..c836cc271 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" @@ -34,7 +34,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) @@ -44,7 +44,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 878fedaa4..2b7b81cfe 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -22,8 +22,10 @@ #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 <dcp/signer.h> +#include <dcp/certificate_chain.h> #include <libcxml/cxml.h> #include "config.h" #include "server.h" @@ -31,10 +33,11 @@ #include "filter.h" #include "ratio.h" #include "dcp_content_type.h" -#include "sound_processor.h" +#include "cinema_sound_processor.h" #include "colour_conversion.h" #include "cinema.h" #include "util.h" +#include "cross.h" #include "i18n.h" @@ -51,7 +54,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; @@ -61,7 +64,7 @@ Config::Config () , _server_port_base (6192) , _use_any_servers (true) , _tms_path (".") - , _sound_processor (SoundProcessor::from_id (N_("dolby_cp750"))) + , _cinema_sound_processor (CinemaSoundProcessor::from_id (N_("dolby_cp750"))) , _allow_any_dcp_frame_rate (false) , _default_still_length (10) , _default_scale (VideoContentScale (Ratio::from_id ("185"))) @@ -73,6 +76,9 @@ Config::Config () , _check_for_test_updates (false) , _maximum_j2k_bandwidth (250000000) , _log_types (Log::TYPE_GENERAL | Log::TYPE_WARNING | Log::TYPE_ERROR) +#ifdef DCPOMATIC_WINDOWS + , _win32_console (false) +#endif { _allowed_dcp_frame_rates.push_back (24); _allowed_dcp_frame_rates.push_back (25); @@ -81,9 +87,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)); reset_kdm_email (); } @@ -91,13 +97,16 @@ Config::Config () void Config::read () { - if (!boost::filesystem::exists (file (false))) { - read_old_metadata (); + if (!boost::filesystem::exists (file ())) { + /* Make a new set of signing certificates and key */ + _signer.reset (new dcp::Signer (openssl_path ())); + /* And decryption keys */ + make_decryption_keys (); return; } cxml::Document f ("Config"); - f.read_file (file (false)); + f.read_file (file ()); optional<string> c; optional<int> version = f.optional_number_child<int> ("Version"); @@ -130,7 +139,11 @@ Config::read () c = f.optional_string_child ("SoundProcessor"); if (c) { - _sound_processor = SoundProcessor::from_id (c.get ()); + _cinema_sound_processor = CinemaSoundProcessor::from_id (c.get ()); + } + c = f.optional_string_child ("CinemaSoundProcessor"); + if (c) { + _cinema_sound_processor = CinemaSoundProcessor::from_id (c.get ()); } _language = f.optional_string_child ("Language"); @@ -180,7 +193,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"); @@ -209,99 +222,64 @@ Config::read () _allow_any_dcp_frame_rate = f.optional_bool_child ("AllowAnyDCPFrameRate"); _log_types = f.optional_number_child<int> ("LogTypes").get_value_or (Log::TYPE_GENERAL | Log::TYPE_WARNING | Log::TYPE_ERROR); +#ifdef DCPOMATIC_WINDOWS + _win32_console = f.optional_bool_child ("Win32Console").get_value_or (false); +#endif list<cxml::NodePtr> his = f.node_children ("History"); for (list<cxml::NodePtr>::const_iterator i = his.begin(); i != his.end(); ++i) { _history.push_back ((*i)->content ()); } -} - -void -Config::read_old_metadata () -{ - /* XXX: this won't work with non-Latin filenames */ - ifstream f (file(true).string().c_str ()); - string line; - while (getline (f, line)) { - if (line.empty ()) { - continue; + cxml::NodePtr signer = f.optional_node_child ("Signer"); + dcp::CertificateChain signer_chain; + if (signer) { + /* Read the signing certificates and private key in from the config file */ + list<cxml::NodePtr> certificates = signer->node_children ("Certificate"); + for (list<cxml::NodePtr>::const_iterator i = certificates.begin(); i != certificates.end(); ++i) { + signer_chain.add (dcp::Certificate ((*i)->content ())); } - if (line[0] == '#') { - continue; - } + _signer.reset (new dcp::Signer (signer_chain, signer->string_child ("PrivateKey"))); + } else { + /* Make a new set of signing certificates and key */ + _signer.reset (new dcp::Signer (openssl_path ())); + } - size_t const s = line.find (' '); - if (s == string::npos) { - continue; - } - - string const k = line.substr (0, s); - string const v = line.substr (s + 1); - - if (k == N_("num_local_encoding_threads")) { - _num_local_encoding_threads = atoi (v.c_str ()); - } else if (k == N_("default_directory")) { - _default_directory = v; - } else if (k == N_("server_port")) { - _server_port_base = atoi (v.c_str ()); - } else if (k == N_("server")) { - vector<string> b; - split (b, v, is_any_of (" ")); - if (b.size() == 2) { - _servers.push_back (b[0]); - } - } else if (k == N_("tms_ip")) { - _tms_ip = v; - } else if (k == N_("tms_path")) { - _tms_path = v; - } else if (k == N_("tms_user")) { - _tms_user = v; - } else if (k == N_("tms_password")) { - _tms_password = v; - } else if (k == N_("sound_processor")) { - _sound_processor = SoundProcessor::from_id (v); - } else if (k == "language") { - _language = v; - } else if (k == "default_container") { - _default_container = Ratio::from_id (v); - } else if (k == "default_dcp_content_type") { - _default_dcp_content_type = DCPContentType::from_isdcf_name (v); - } else if (k == "dcp_metadata_issuer") { - _dcp_issuer = v; - } + if (f.optional_string_child ("DecryptionCertificate")) { + _decryption_certificate = dcp::Certificate (f.string_child ("DecryptionCertificate")); + } - _default_isdcf_metadata.read_old_metadata (k, v); + if (f.optional_string_child ("DecryptionPrivateKey")) { + _decryption_private_key = f.string_child ("DecryptionPrivateKey"); + } + + if (!f.optional_string_child ("DecryptionCertificate") || !f.optional_string_child ("DecryptionPrivateKey")) { + /* Generate our own decryption certificate and key if either is not present in config */ + make_decryption_keys (); } } -/** @return Filename to write configuration to */ -boost::filesystem::path -Config::file (bool old) const +void +Config::make_decryption_keys () { - boost::filesystem::path p; - p /= g_get_user_config_dir (); - boost::system::error_code ec; - boost::filesystem::create_directory (p, ec); - if (old) { - p /= ".dvdomatic"; - } else { - p /= "dcpomatic"; - boost::filesystem::create_directory (p, ec); - p /= "config.xml"; - } - return p; + boost::filesystem::path p = dcp::make_certificate_chain (openssl_path ()); + _decryption_certificate = dcp::Certificate (dcp::file_to_string (p / "leaf.signed.pem")); + _decryption_private_key = dcp::file_to_string (p / "leaf.key"); + boost::filesystem::remove_all (p); } +/** @return Filename to write configuration to */ boost::filesystem::path -Config::signer_chain_directory () const +Config::file () const { boost::filesystem::path p; p /= g_get_user_config_dir (); - p /= "dcpomatic"; - p /= "crypt"; - boost::filesystem::create_directories (p); + boost::system::error_code ec; + boost::filesystem::create_directory (p, ec); + p /= "dcpomatic2"; + boost::filesystem::create_directory (p, ec); + p /= "config.xml"; return p; } @@ -347,8 +325,8 @@ Config::write () const root->add_child("TMSPath")->add_child_text (_tms_path); root->add_child("TMSUser")->add_child_text (_tms_user); root->add_child("TMSPassword")->add_child_text (_tms_password); - if (_sound_processor) { - root->add_child("SoundProcessor")->add_child_text (_sound_processor->id ()); + if (_cinema_sound_processor) { + root->add_child("CinemaSoundProcessor")->add_child_text (_cinema_sound_processor->id ()); } if (_language) { root->add_child("Language")->add_child_text (_language.get()); @@ -391,12 +369,25 @@ Config::write () const root->add_child("MaximumJ2KBandwidth")->add_child_text (raw_convert<string> (_maximum_j2k_bandwidth)); root->add_child("AllowAnyDCPFrameRate")->add_child_text (_allow_any_dcp_frame_rate ? "1" : "0"); root->add_child("LogTypes")->add_child_text (raw_convert<string> (_log_types)); +#ifdef DCPOMATIC_WINDOWS + root->add_child("Win32Console")->add_child_text (_win32_console ? "1" : "0"); +#endif + + xmlpp::Element* signer = root->add_child ("Signer"); + dcp::CertificateChain::List certs = _signer->certificates().root_to_leaf (); + for (dcp::CertificateChain::List::const_iterator i = certs.begin(); i != certs.end(); ++i) { + signer->add_child("Certificate")->add_child_text (i->certificate (true)); + } + signer->add_child("PrivateKey")->add_child_text (_signer->key ()); + + root->add_child("DecryptionCertificate")->add_child_text (_decryption_certificate.certificate (true)); + root->add_child("DecryptionPrivateKey")->add_child_text (_decryption_private_key); for (vector<boost::filesystem::path>::const_iterator i = _history.begin(); i != _history.end(); ++i) { root->add_child("History")->add_child_text (i->string ()); } - doc.write_to_file_formatted (file(false).string ()); + doc.write_to_file_formatted (file().string ()); } boost::filesystem::path diff --git a/src/lib/config.h b/src/lib/config.h index 0639382a0..55a172d78 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -28,15 +28,17 @@ #include <boost/shared_ptr.hpp> #include <boost/signals2.hpp> #include <boost/filesystem.hpp> +#include <dcp/metadata.h> +#include <dcp/certificates.h> +#include <dcp/signer.h> #include "isdcf_metadata.h" #include "colour_conversion.h" -#include "server.h" #include "video_content.h" class ServerDescription; class Scaler; class Filter; -class SoundProcessor; +class CinemaSoundProcessor; class DCPContentType; class Ratio; class Cinema; @@ -104,9 +106,9 @@ public: return _tms_password; } - /** @return The sound processor that we are using */ - SoundProcessor const * sound_processor () const { - return _sound_processor; + /** @return The cinema sound processor that we are using */ + CinemaSoundProcessor const * cinema_sound_processor () const { + return _cinema_sound_processor; } std::list<boost::shared_ptr<Cinema> > cinemas () const { @@ -193,6 +195,18 @@ public: return _kdm_email; } + boost::shared_ptr<const dcp::Signer> signer () const { + return _signer; + } + + dcp::Certificate decryption_certificate () const { + return _decryption_certificate; + } + + std::string decryption_private_key () const { + return _decryption_private_key; + } + bool check_for_updates () const { return _check_for_updates; } @@ -209,6 +223,12 @@ public: return _log_types; } +#ifdef DCPOMATIC_WINDOWS + bool win32_console () const { + return _win32_console; + } +#endif + std::vector<boost::filesystem::path> history () const { return _history; } @@ -371,6 +391,21 @@ public: void reset_kdm_email (); + void set_signer (boost::shared_ptr<const dcp::Signer> s) { + _signer = s; + changed (); + } + + void set_decryption_certificate (dcp::Certificate c) { + _decryption_certificate = c; + changed (); + } + + void set_decryption_private_key (std::string k) { + _decryption_private_key = k; + changed (); + } + void set_check_for_updates (bool c) { _check_for_updates = c; changed (); @@ -391,6 +426,13 @@ public: changed (); } +#ifdef DCPOMATIC_WINDOWS + void set_win32_console (bool c) { + _win32_console = c; + changed (); + } +#endif + void clear_history () { _history.clear (); changed (); @@ -398,8 +440,6 @@ public: void add_to_history (boost::filesystem::path p); - boost::filesystem::path signer_chain_directory () const; - void changed (); boost::signals2::signal<void ()> Changed; @@ -408,10 +448,10 @@ public: private: Config (); - boost::filesystem::path file (bool) const; + boost::filesystem::path file () const; void read (); - void read_old_metadata (); void write () const; + void make_decryption_keys (); /** number of threads to use for J2K encoding on the local machine */ int _num_local_encoding_threads; @@ -433,8 +473,8 @@ private: std::string _tms_user; /** Password to log into the TMS with */ std::string _tms_password; - /** Our sound processor */ - SoundProcessor const * _sound_processor; + /** Our cinema sound processor */ + CinemaSoundProcessor const * _cinema_sound_processor; std::list<int> _allowed_dcp_frame_rates; /** Allow any video frame rate for the DCP; if true, overrides _allowed_dcp_frame_rates */ bool _allow_any_dcp_frame_rate; @@ -458,12 +498,18 @@ private: std::string _kdm_cc; std::string _kdm_bcc; std::string _kdm_email; + boost::shared_ptr<const dcp::Signer> _signer; + dcp::Certificate _decryption_certificate; + std::string _decryption_private_key; /** true to check for updates on startup */ bool _check_for_updates; bool _check_for_test_updates; /** maximum allowed J2K bandwidth in bits per second */ int _maximum_j2k_bandwidth; int _log_types; +#ifdef DCPOMATIC_WINDOWS + bool _win32_console; +#endif std::vector<boost::filesystem::path> _history; /** Singleton instance, or 0 */ diff --git a/src/lib/content.cc b/src/lib/content.cc index 11a4b21cc..21e49a2c9 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 SafeStringStream 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..f7e97feac 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,25 +51,38 @@ 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 () {} + + /** Examine the content to establish digest, frame rates and any other + * useful metadata. + * @param job Job to use to report progress, or 0. + */ + virtual void examine (boost::shared_ptr<Job> job); - virtual void examine (boost::shared_ptr<Job>); + /** @return Quick one-line summary of the content, as will be presented in the + * film editor. + */ virtual std::string summary () const = 0; + /** @return Technical details of this content; these are written to logs to * help with debugging. */ 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 +114,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 +160,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 +169,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..16340adb4 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,30 @@ */ +/** @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 "dcp_content.h" +#include "dcp_subtitle_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 +54,38 @@ 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)); + } else if (type == "DCP") { + content.reset (new DCPContent (film, node, version)); + } else if (type == "DCPSubtitle") { + content.reset (new DCPSubtitleContent (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 if (ext == ".xml") { + content.reset (new DCPSubtitleContent (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..8868618ad --- /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" +#include "image_subtitle.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) + : sub (im, r) + , _period (p) + {} + + ContentTimePeriod period () const { + return _period; + } + + /* Our subtitle, with its rectangle unmodified by any offsets or scales that the content specifies */ + ImageSubtitle sub; + +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 9b7d5594f..d84c17c55 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.cc b/src/lib/dcp_content.cc new file mode 100644 index 000000000..a5b5f37e1 --- /dev/null +++ b/src/lib/dcp_content.cc @@ -0,0 +1,162 @@ +/* + 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/dcp.h> +#include <dcp/exceptions.h> +#include "dcp_content.h" +#include "dcp_examiner.h" +#include "job.h" +#include "film.h" +#include "config.h" +#include "compose.hpp" + +#include "i18n.h" + +using std::string; +using std::cout; +using boost::shared_ptr; +using boost::optional; + +int const DCPContentProperty::CAN_BE_PLAYED = 600; + +DCPContent::DCPContent (shared_ptr<const Film> f, boost::filesystem::path p) + : Content (f) + , VideoContent (f) + , SingleStreamAudioContent (f) + , SubtitleContent (f) + , _has_subtitles (false) + , _encrypted (false) + , _directory (p) + , _kdm_valid (false) +{ + read_directory (p); +} + +DCPContent::DCPContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version) + : Content (f, node) + , VideoContent (f, node, version) + , SingleStreamAudioContent (f, node, version) + , SubtitleContent (f, node, version) +{ + _name = node->string_child ("Name"); + _has_subtitles = node->bool_child ("HasSubtitles"); + _directory = node->string_child ("Directory"); + _encrypted = node->bool_child ("Encrypted"); + if (node->optional_node_child ("KDM")) { + _kdm = dcp::EncryptedKDM (node->string_child ("KDM")); + } + _kdm_valid = node->bool_child ("KDMValid"); +} + +void +DCPContent::read_directory (boost::filesystem::path p) +{ + for (boost::filesystem::directory_iterator i(p); i != boost::filesystem::directory_iterator(); ++i) { + if (boost::filesystem::is_regular_file (i->path ())) { + _paths.push_back (i->path ()); + } else if (boost::filesystem::is_directory (i->path ())) { + read_directory (i->path ()); + } + } +} + +void +DCPContent::examine (shared_ptr<Job> job) +{ + bool const could_be_played = can_be_played (); + + job->set_progress_unknown (); + Content::examine (job); + + shared_ptr<DCPExaminer> examiner (new DCPExaminer (shared_from_this ())); + take_from_video_examiner (examiner); + take_from_audio_examiner (examiner); + + boost::mutex::scoped_lock lm (_mutex); + _name = examiner->name (); + _has_subtitles = examiner->has_subtitles (); + _encrypted = examiner->encrypted (); + _kdm_valid = examiner->kdm_valid (); + + if (could_be_played != can_be_played ()) { + signal_changed (DCPContentProperty::CAN_BE_PLAYED); + } +} + +string +DCPContent::summary () const +{ + boost::mutex::scoped_lock lm (_mutex); + return String::compose (_("%1 [DCP]"), _name); +} + +string +DCPContent::technical_summary () const +{ + return Content::technical_summary() + " - " + + VideoContent::technical_summary() + " - " + + AudioContent::technical_summary() + " - "; +} + +void +DCPContent::as_xml (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text ("DCP"); + + Content::as_xml (node); + VideoContent::as_xml (node); + SingleStreamAudioContent::as_xml (node); + SubtitleContent::as_xml (node); + + boost::mutex::scoped_lock lm (_mutex); + node->add_child("Name")->add_child_text (_name); + node->add_child("HasSubtitles")->add_child_text (_has_subtitles ? "1" : "0"); + node->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0"); + node->add_child("Directory")->add_child_text (_directory.string ()); + if (_kdm) { + node->add_child("KDM")->add_child_text (_kdm->as_xml ()); + } + node->add_child("KDMValid")->add_child_text (_kdm_valid ? "1" : "0"); +} + +DCPTime +DCPContent::full_length () const +{ + shared_ptr<const Film> film = _film.lock (); + assert (film); + return DCPTime (video_length (), FrameRateChange (video_frame_rate (), film->video_frame_rate ())); +} + +string +DCPContent::identifier () const +{ + return SubtitleContent::identifier (); +} + +void +DCPContent::add_kdm (dcp::EncryptedKDM k) +{ + _kdm = k; +} + +bool +DCPContent::can_be_played () const +{ + return !_encrypted || _kdm_valid; +} diff --git a/src/lib/dcp_content.h b/src/lib/dcp_content.h new file mode 100644 index 000000000..da78e6d72 --- /dev/null +++ b/src/lib/dcp_content.h @@ -0,0 +1,98 @@ +/* + 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_DCP_CONTENT_H +#define DCPOMATIC_DCP_CONTENT_H + +/** @file src/lib/dcp_content.h + * @brief DCPContent class. + */ + +#include <libcxml/cxml.h> +#include <dcp/encrypted_kdm.h> +#include <dcp/decrypted_kdm.h> +#include "video_content.h" +#include "single_stream_audio_content.h" +#include "subtitle_content.h" + +class DCPContentProperty +{ +public: + static int const CAN_BE_PLAYED; +}; + +/** @class DCPContent + * @brief An existing DCP used as input. + */ +class DCPContent : public VideoContent, public SingleStreamAudioContent, public SubtitleContent +{ +public: + DCPContent (boost::shared_ptr<const Film> f, boost::filesystem::path p); + DCPContent (boost::shared_ptr<const Film> f, cxml::ConstNodePtr, int version); + + boost::shared_ptr<DCPContent> shared_from_this () { + return boost::dynamic_pointer_cast<DCPContent> (Content::shared_from_this ()); + } + + DCPTime full_length () const; + + void examine (boost::shared_ptr<Job>); + std::string summary () const; + std::string technical_summary () const; + void as_xml (xmlpp::Node *) const; + std::string identifier () const; + + /* SubtitleContent */ + bool has_subtitles () const { + boost::mutex::scoped_lock lm (_mutex); + return _has_subtitles; + } + + boost::filesystem::path directory () const { + boost::mutex::scoped_lock lm (_mutex); + return _directory; + } + + bool encrypted () const { + boost::mutex::scoped_lock lm (_mutex); + return _encrypted; + } + + void add_kdm (dcp::EncryptedKDM); + + boost::optional<dcp::EncryptedKDM> kdm () const { + return _kdm; + } + + bool can_be_played () const; + +private: + void read_directory (boost::filesystem::path); + + std::string _name; + bool _has_subtitles; + /** true if our DCP is encrypted */ + bool _encrypted; + boost::filesystem::path _directory; + boost::optional<dcp::EncryptedKDM> _kdm; + /** true if _kdm successfully decrypts the first frame of our DCP */ + bool _kdm_valid; +}; + +#endif 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_decoder.cc b/src/lib/dcp_decoder.cc new file mode 100644 index 000000000..bf016ef87 --- /dev/null +++ b/src/lib/dcp_decoder.cc @@ -0,0 +1,140 @@ +/* + 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/dcp.h> +#include <dcp/cpl.h> +#include <dcp/reel.h> +#include <dcp/mono_picture_mxf.h> +#include <dcp/stereo_picture_mxf.h> +#include <dcp/reel_picture_asset.h> +#include <dcp/reel_sound_asset.h> +#include <dcp/mono_picture_frame.h> +#include <dcp/stereo_picture_frame.h> +#include <dcp/sound_frame.h> +#include "dcp_decoder.h" +#include "dcp_content.h" +#include "j2k_image_proxy.h" +#include "image.h" +#include "config.h" + +using std::list; +using std::cout; +using boost::shared_ptr; +using boost::dynamic_pointer_cast; + +DCPDecoder::DCPDecoder (shared_ptr<const DCPContent> c, shared_ptr<Log> log) + : VideoDecoder (c) + , AudioDecoder (c) + , SubtitleDecoder (c) + , _log (log) + , _dcp_content (c) +{ + dcp::DCP dcp (c->directory ()); + dcp.read (); + if (c->kdm ()) { + dcp.add (dcp::DecryptedKDM (c->kdm().get (), Config::instance()->decryption_private_key ())); + } + assert (dcp.cpls().size() == 1); + _reels = dcp.cpls().front()->reels (); + _reel = _reels.begin (); +} + +bool +DCPDecoder::pass () +{ + if (_reel == _reels.end () || !_dcp_content->can_be_played ()) { + return true; + } + + float const vfr = _dcp_content->video_frame_rate (); + int64_t const frame = _next.frames (vfr); + + if ((*_reel)->main_picture ()) { + shared_ptr<dcp::PictureMXF> mxf = (*_reel)->main_picture()->mxf (); + shared_ptr<dcp::MonoPictureMXF> mono = dynamic_pointer_cast<dcp::MonoPictureMXF> (mxf); + shared_ptr<dcp::StereoPictureMXF> stereo = dynamic_pointer_cast<dcp::StereoPictureMXF> (mxf); + int64_t const entry_point = (*_reel)->main_picture()->entry_point (); + if (mono) { + video (shared_ptr<ImageProxy> (new J2KImageProxy (mono->get_frame (entry_point + frame), mxf->size(), _log)), frame); + } else { + video ( + shared_ptr<ImageProxy> (new J2KImageProxy (stereo->get_frame (entry_point + frame), mxf->size(), dcp::EYE_LEFT, _log)), + frame + ); + + video ( + shared_ptr<ImageProxy> (new J2KImageProxy (stereo->get_frame (entry_point + frame), mxf->size(), dcp::EYE_RIGHT, _log)), + frame + ); + } + } + + if ((*_reel)->main_sound ()) { + int64_t const entry_point = (*_reel)->main_sound()->entry_point (); + shared_ptr<const dcp::SoundFrame> sf = (*_reel)->main_sound()->mxf()->get_frame (entry_point + frame); + uint8_t const * from = sf->data (); + + int const channels = _dcp_content->audio_channels (); + int const frames = sf->size() / (3 * channels); + shared_ptr<AudioBuffers> data (new AudioBuffers (channels, frames)); + for (int i = 0; i < frames; ++i) { + for (int j = 0; j < channels; ++j) { + data->data()[j][i] = float (from[0] | (from[1] << 8) | (from[2] << 16)) / (1 << 23); + from += 3; + } + } + + audio (data, _next); + } + + /* XXX: subtitle */ + + _next += ContentTime::from_frames (1, vfr); + + if ((*_reel)->main_picture ()) { + if (_next.frames (vfr) >= (*_reel)->main_picture()->duration()) { + ++_reel; + } + } + + return false; +} + +void +DCPDecoder::seek (ContentTime t, bool accurate) +{ + VideoDecoder::seek (t, accurate); + AudioDecoder::seek (t, accurate); + SubtitleDecoder::seek (t, accurate); + + _reel = _reels.begin (); + while (_reel != _reels.end() && t >= ContentTime::from_frames ((*_reel)->main_picture()->duration(), _dcp_content->video_frame_rate ())) { + t -= ContentTime::from_frames ((*_reel)->main_picture()->duration(), _dcp_content->video_frame_rate ()); + ++_reel; + } + + _next = t; +} + + +list<ContentTimePeriod> +DCPDecoder::subtitles_during (ContentTimePeriod, bool starting) const +{ + return list<ContentTimePeriod> (); +} diff --git a/src/lib/dcp_decoder.h b/src/lib/dcp_decoder.h new file mode 100644 index 000000000..d81b20b5c --- /dev/null +++ b/src/lib/dcp_decoder.h @@ -0,0 +1,46 @@ +/* + 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 "video_decoder.h" +#include "audio_decoder.h" +#include "subtitle_decoder.h" + +namespace dcp { + class Reel; +} + +class DCPContent; +class Log; + +class DCPDecoder : public VideoDecoder, public AudioDecoder, public SubtitleDecoder +{ +public: + DCPDecoder (boost::shared_ptr<const DCPContent>, boost::shared_ptr<Log>); + +private: + void seek (ContentTime t, bool accurate); + bool pass (); + std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const; + + ContentTime _next; + std::list<boost::shared_ptr<dcp::Reel> > _reels; + std::list<boost::shared_ptr<dcp::Reel> >::iterator _reel; + boost::shared_ptr<Log> _log; + boost::shared_ptr<const DCPContent> _dcp_content; +}; diff --git a/src/lib/dcp_examiner.cc b/src/lib/dcp_examiner.cc new file mode 100644 index 000000000..1e4cc899d --- /dev/null +++ b/src/lib/dcp_examiner.cc @@ -0,0 +1,136 @@ +/* + 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/dcp.h> +#include <dcp/cpl.h> +#include <dcp/reel.h> +#include <dcp/reel_picture_asset.h> +#include <dcp/reel_sound_asset.h> +#include <dcp/mono_picture_mxf.h> +#include <dcp/mono_picture_frame.h> +#include <dcp/stereo_picture_mxf.h> +#include <dcp/stereo_picture_frame.h> +#include <dcp/sound_mxf.h> +#include "dcp_examiner.h" +#include "dcp_content.h" +#include "exceptions.h" +#include "image.h" +#include "config.h" + +#include "i18n.h" + +using std::list; +using std::cout; +using boost::shared_ptr; +using boost::dynamic_pointer_cast; + +DCPExaminer::DCPExaminer (shared_ptr<const DCPContent> content) + : _video_length (0) + , _audio_length (0) + , _has_subtitles (false) + , _encrypted (false) + , _kdm_valid (false) +{ + dcp::DCP dcp (content->directory ()); + dcp.read (); + + if (content->kdm ()) { + dcp.add (dcp::DecryptedKDM (content->kdm().get(), Config::instance()->decryption_private_key ())); + } + + if (dcp.cpls().size() == 0) { + throw DCPError ("No CPLs found in DCP"); + } else if (dcp.cpls().size() > 1) { + throw DCPError ("Multiple CPLs found in DCP"); + } + + _name = dcp.cpls().front()->content_title_text (); + + list<shared_ptr<dcp::Reel> > reels = dcp.cpls().front()->reels (); + for (list<shared_ptr<dcp::Reel> >::const_iterator i = reels.begin(); i != reels.end(); ++i) { + + if ((*i)->main_picture ()) { + dcp::Fraction const frac = (*i)->main_picture()->frame_rate (); + float const fr = float(frac.numerator) / frac.denominator; + if (!_video_frame_rate) { + _video_frame_rate = fr; + } else if (_video_frame_rate.get() != fr) { + throw DCPError (_("Mismatched frame rates in DCP")); + } + + shared_ptr<dcp::PictureMXF> mxf = (*i)->main_picture()->mxf (); + if (!_video_size) { + _video_size = mxf->size (); + } else if (_video_size.get() != mxf->size ()) { + throw DCPError (_("Mismatched video sizes in DCP")); + } + + _video_length += ContentTime::from_frames ((*i)->main_picture()->duration(), _video_frame_rate.get ()); + } + + if ((*i)->main_sound ()) { + shared_ptr<dcp::SoundMXF> mxf = (*i)->main_sound()->mxf (); + + if (!_audio_channels) { + _audio_channels = mxf->channels (); + } else if (_audio_channels.get() != mxf->channels ()) { + throw DCPError (_("Mismatched audio channel counts in DCP")); + } + + if (!_audio_frame_rate) { + _audio_frame_rate = mxf->sampling_rate (); + } else if (_audio_frame_rate.get() != mxf->sampling_rate ()) { + throw DCPError (_("Mismatched audio frame rates in DCP")); + } + + _audio_length += ContentTime::from_frames ((*i)->main_sound()->duration(), _video_frame_rate.get ()); + } + + if ((*i)->main_subtitle ()) { + _has_subtitles = true; + } + } + + _encrypted = dcp.encrypted (); + _kdm_valid = true; + + /* Check that we can read the first picture frame */ + try { + if (!dcp.cpls().empty () && !dcp.cpls().front()->reels().empty ()) { + shared_ptr<dcp::PictureMXF> mxf = dcp.cpls().front()->reels().front()->main_picture()->mxf (); + shared_ptr<dcp::MonoPictureMXF> mono = dynamic_pointer_cast<dcp::MonoPictureMXF> (mxf); + shared_ptr<dcp::StereoPictureMXF> stereo = dynamic_pointer_cast<dcp::StereoPictureMXF> (mxf); + + shared_ptr<Image> image (new Image (PIX_FMT_RGB24, _video_size.get(), false)); + + if (mono) { + mono->get_frame(0)->rgb_frame (image->data()[0]); + } else { + stereo->get_frame(0)->rgb_frame (dcp::EYE_LEFT, image->data()[0]); + } + + } + } catch (dcp::DCPReadError& e) { + _kdm_valid = false; + if (_encrypted && content->kdm ()) { + /* XXX: maybe don't use an exception for this */ + throw StringError (_("The KDM does not decrypt the DCP. Perhaps it is targeted at the wrong CPL")); + } + } +} diff --git a/src/lib/dcp_examiner.h b/src/lib/dcp_examiner.h new file mode 100644 index 000000000..03d43d0f6 --- /dev/null +++ b/src/lib/dcp_examiner.h @@ -0,0 +1,81 @@ +/* + 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 "video_examiner.h" +#include "audio_examiner.h" + +class DCPContent; + +class DCPExaminer : public VideoExaminer, public AudioExaminer +{ +public: + DCPExaminer (boost::shared_ptr<const DCPContent>); + + float video_frame_rate () const { + return _video_frame_rate.get_value_or (24); + } + + dcp::Size video_size () const { + return _video_size.get_value_or (dcp::Size (1998, 1080)); + } + + ContentTime video_length () const { + return _video_length; + } + + std::string name () const { + return _name; + } + + bool has_subtitles () const { + return _has_subtitles; + } + + bool encrypted () const { + return _encrypted; + } + + int audio_channels () const { + return _audio_channels.get_value_or (0); + } + + ContentTime audio_length () const { + return _audio_length; + } + + int audio_frame_rate () const { + return _audio_frame_rate.get_value_or (48000); + } + + bool kdm_valid () const { + return _kdm_valid; + } + +private: + boost::optional<float> _video_frame_rate; + boost::optional<dcp::Size> _video_size; + ContentTime _video_length; + boost::optional<int> _audio_channels; + boost::optional<int> _audio_frame_rate; + ContentTime _audio_length; + std::string _name; + bool _has_subtitles; + bool _encrypted; + bool _kdm_valid; +}; diff --git a/src/lib/dcp_subtitle_content.cc b/src/lib/dcp_subtitle_content.cc new file mode 100644 index 000000000..83b0d200c --- /dev/null +++ b/src/lib/dcp_subtitle_content.cc @@ -0,0 +1,88 @@ +/* + 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_content.h> +#include <dcp/raw_convert.h> +#include "dcp_subtitle_content.h" + +#include "i18n.h" + +using std::string; +using std::list; +using boost::shared_ptr; +using dcp::raw_convert; + +DCPSubtitleContent::DCPSubtitleContent (shared_ptr<const Film> film, boost::filesystem::path path) + : Content (film, path) + , SubtitleContent (film, path) +{ + +} + +DCPSubtitleContent::DCPSubtitleContent (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version) + : Content (film, node) + , SubtitleContent (film, node, version) + , _length (node->number_child<DCPTime::Type> ("Length")) +{ + +} + +void +DCPSubtitleContent::examine (shared_ptr<Job> job) +{ + Content::examine (job); + dcp::SubtitleContent sc (path (0), false); + _length = DCPTime::from_seconds (sc.latest_subtitle_out().to_seconds ()); +} + +DCPTime +DCPSubtitleContent::full_length () const +{ + /* XXX: this assumes that the timing of the subtitle file is appropriate + for the DCP's frame rate. + */ + return _length; +} + +string +DCPSubtitleContent::summary () const +{ + return path_summary() + " " + _("[subtitles]"); +} + +string +DCPSubtitleContent::technical_summary () const +{ + return Content::technical_summary() + " - " + _("DCP XML subtitles"); +} + +string +DCPSubtitleContent::information () const +{ + +} + +void +DCPSubtitleContent::as_xml (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text ("DCPSubtitle"); + Content::as_xml (node); + SubtitleContent::as_xml (node); + node->add_child("Length")->add_child_text (raw_convert<string> (_length.get ())); +} diff --git a/src/lib/dcp_subtitle_content.h b/src/lib/dcp_subtitle_content.h new file mode 100644 index 000000000..5794b5951 --- /dev/null +++ b/src/lib/dcp_subtitle_content.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. + +*/ + +#include "subtitle_content.h" + +class DCPSubtitleContent : public SubtitleContent +{ +public: + DCPSubtitleContent (boost::shared_ptr<const Film>, boost::filesystem::path); + DCPSubtitleContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int); + + /* Content */ + 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; + + /* SubtitleContent */ + bool has_subtitles () const { + return true; + } + +private: + DCPTime _length; +}; diff --git a/src/lib/dcp_subtitle_decoder.cc b/src/lib/dcp_subtitle_decoder.cc new file mode 100644 index 000000000..20a9f32fe --- /dev/null +++ b/src/lib/dcp_subtitle_decoder.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. + +*/ + +#include <dcp/subtitle_content.h> +#include "dcp_subtitle_decoder.h" +#include "dcp_subtitle_content.h" + +using std::list; +using std::cout; +using boost::shared_ptr; + +DCPSubtitleDecoder::DCPSubtitleDecoder (shared_ptr<const DCPSubtitleContent> content) + : SubtitleDecoder (content) +{ + dcp::SubtitleContent c (content->path (0), false); + _subtitles = c.subtitles (); + _next = _subtitles.begin (); +} + +void +DCPSubtitleDecoder::seek (ContentTime time, bool accurate) +{ + SubtitleDecoder::seek (time, accurate); + + _next = _subtitles.begin (); + list<dcp::SubtitleString>::const_iterator i = _subtitles.begin (); + while (i != _subtitles.end() && ContentTime::from_seconds (_next->in().to_seconds()) < time) { + ++i; + } +} + +bool +DCPSubtitleDecoder::pass () +{ + if (_next == _subtitles.end ()) { + return true; + } + + list<dcp::SubtitleString> s; + s.push_back (*_next); + text_subtitle (s); + ++_next; + + return false; +} + +list<ContentTimePeriod> +DCPSubtitleDecoder::subtitles_during (ContentTimePeriod p, bool starting) const +{ + /* XXX: inefficient */ + + list<ContentTimePeriod> d; + + for (list<dcp::SubtitleString>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { + ContentTimePeriod period ( + ContentTime::from_seconds (i->in().to_seconds ()), + ContentTime::from_seconds (i->out().to_seconds ()) + ); + + if ((starting && p.contains (period.from)) || (!starting && p.overlaps (period))) { + d.push_back (period); + } + } + + return d; +} + diff --git a/src/lib/dcp_subtitle_decoder.h b/src/lib/dcp_subtitle_decoder.h new file mode 100644 index 000000000..070562458 --- /dev/null +++ b/src/lib/dcp_subtitle_decoder.h @@ -0,0 +1,38 @@ +/* + 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_decoder.h" + +class DCPSubtitleContent; + +class DCPSubtitleDecoder : public SubtitleDecoder +{ +public: + DCPSubtitleDecoder (boost::shared_ptr<const DCPSubtitleContent>); + +protected: + void seek (ContentTime time, bool accurate); + bool pass (); + +private: + std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const; + + std::list<dcp::SubtitleString> _subtitles; + std::list<dcp::SubtitleString>::const_iterator _next; +}; diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video.cc index bb7eaa064..ccfc800c8 100644 --- a/src/lib/dcp_video_frame.cc +++ b/src/lib/dcp_video.cc @@ -41,16 +41,15 @@ #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" +#include "dcp_video.h" #include "config.h" #include "exceptions.h" #include "server.h" @@ -59,7 +58,8 @@ #include "image.h" #include "log.h" #include "cross.h" -#include "player_video_frame.h" +#include "player_video.h" +#include "encoded_data.h" #define LOG_GENERAL(...) _log->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); @@ -68,8 +68,9 @@ using std::string; 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) @@ -79,20 +80,21 @@ using libdcp::raw_convert; * @param bw J2K bandwidth to use (see Config::j2k_bandwidth ()) * @param l Log to write to. */ -DCPVideoFrame::DCPVideoFrame ( - shared_ptr<const PlayerVideoFrame> frame, int index, int dcp_fps, int bw, Resolution r, shared_ptr<Log> l +DCPVideo::DCPVideo ( + shared_ptr<const PlayerVideo> frame, int index, int dcp_fps, int bw, Resolution r, bool b, shared_ptr<Log> l ) : _frame (frame) , _index (index) , _frames_per_second (dcp_fps) , _j2k_bandwidth (bw) , _resolution (r) + , _burn_subtitles (b) , _log (l) { } -DCPVideoFrame::DCPVideoFrame (shared_ptr<const PlayerVideoFrame> frame, shared_ptr<const cxml::Node> node, shared_ptr<Log> log) +DCPVideo::DCPVideo (shared_ptr<const PlayerVideo> frame, shared_ptr<const cxml::Node> node, shared_ptr<Log> log) : _frame (frame) , _log (log) { @@ -100,21 +102,19 @@ DCPVideoFrame::DCPVideoFrame (shared_ptr<const PlayerVideoFrame> frame, shared_p _frames_per_second = node->number_child<int> ("FramesPerSecond"); _j2k_bandwidth = node->number_child<int> ("J2KBandwidth"); _resolution = Resolution (node->optional_number_child<int>("Resolution").get_value_or (RESOLUTION_2K)); + _burn_subtitles = node->bool_child ("BurnSubtitles"); } /** J2K-encode this frame on the local host. * @return Encoded data. */ shared_ptr<EncodedData> -DCPVideoFrame::encode_locally () +DCPVideo::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]; @@ -124,10 +124,10 @@ DCPVideoFrame::encode_locally () } } - shared_ptr<libdcp::XYZFrame> xyz = libdcp::rgb_to_xyz ( - _frame->image(), + shared_ptr<dcp::XYZFrame> xyz = dcp::rgb_to_xyz ( + _frame->image (_burn_subtitles), 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 ); @@ -260,7 +260,7 @@ DCPVideoFrame::encode_locally () * @return Encoded data. */ shared_ptr<EncodedData> -DCPVideoFrame::encode_remotely (ServerDescription serv) +DCPVideo::encode_remotely (ServerDescription serv) { boost::asio::io_service io_service; boost::asio::ip::tcp::resolver resolver (io_service); @@ -285,7 +285,7 @@ DCPVideoFrame::encode_remotely (ServerDescription serv) socket->write ((uint8_t *) xml.c_str(), xml.length() + 1); /* Send binary data */ - _frame->send_binary (socket); + _frame->send_binary (socket, _burn_subtitles); /* Read the response (JPEG2000-encoded data); this blocks until the data is ready and sent back. @@ -299,105 +299,34 @@ DCPVideoFrame::encode_remotely (ServerDescription serv) } void -DCPVideoFrame::add_metadata (xmlpp::Element* el) const +DCPVideo::add_metadata (xmlpp::Element* el) const { el->add_child("Index")->add_child_text (raw_convert<string> (_index)); el->add_child("FramesPerSecond")->add_child_text (raw_convert<string> (_frames_per_second)); el->add_child("J2KBandwidth")->add_child_text (raw_convert<string> (_j2k_bandwidth)); el->add_child("Resolution")->add_child_text (raw_convert<string> (int (_resolution))); - _frame->add_metadata (el); + el->add_child("BurnSubtitles")->add_child_text (_burn_subtitles ? "1" : "0"); + _frame->add_metadata (el, _burn_subtitles); } Eyes -DCPVideoFrame::eyes () const +DCPVideo::eyes () const { return _frame->eyes (); } -EncodedData::EncodedData (int s) - : _data (new uint8_t[s]) - , _size (s) -{ - -} - -EncodedData::EncodedData (boost::filesystem::path file) -{ - _size = boost::filesystem::file_size (file); - _data = new uint8_t[_size]; - - FILE* f = fopen_boost (file, "rb"); - if (!f) { - throw FileError (_("could not open file for reading"), file); - } - - size_t const r = fread (_data, 1, _size, f); - if (r != size_t (_size)) { - fclose (f); - throw FileError (_("could not read encoded data"), file); - } - - fclose (f); -} - - -EncodedData::~EncodedData () -{ - delete[] _data; -} - -/** Write this data to a J2K file. - * @param Film Film. - * @param frame DCP frame index. +/** @return true if this DCPVideo is definitely the same as another; + * (apart from the frame index), false if it is probably not. */ -void -EncodedData::write (shared_ptr<const Film> film, int frame, Eyes eyes) const +bool +DCPVideo::same (shared_ptr<const DCPVideo> other) const { - boost::filesystem::path const tmp_j2c = film->j2c_path (frame, eyes, true); - - FILE* f = fopen_boost (tmp_j2c, "wb"); - - if (!f) { - throw WriteFileError (tmp_j2c, errno); + if (_frames_per_second != other->_frames_per_second || + _j2k_bandwidth != other->_j2k_bandwidth || + _resolution != other->_resolution || + _burn_subtitles != other->_burn_subtitles) { + return false; } - fwrite (_data, 1, _size, f); - fclose (f); - - boost::filesystem::path const real_j2c = film->j2c_path (frame, eyes, false); - - /* Rename the file from foo.j2c.tmp to foo.j2c now that it is complete */ - boost::filesystem::rename (tmp_j2c, real_j2c); -} - -void -EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, libdcp::FrameInfo fin) const -{ - boost::filesystem::path const info = film->info_path (frame, eyes); - FILE* h = fopen_boost (info, "w"); - fin.write (h); - fclose (h); -} - -/** Send this data to a socket. - * @param socket Socket - */ -void -EncodedData::send (shared_ptr<Socket> socket) -{ - socket->write (_size); - socket->write (_data, _size); -} - -LocallyEncodedData::LocallyEncodedData (uint8_t* d, int s) - : EncodedData (s) -{ - memcpy (_data, d, s); -} - -/** @param s Size of data in bytes */ -RemotelyEncodedData::RemotelyEncodedData (int s) - : EncodedData (s) -{ - + return _frame->same (other->_frame); } diff --git a/src/lib/dcp_video.h b/src/lib/dcp_video.h new file mode 100644 index 000000000..d517a8f02 --- /dev/null +++ b/src/lib/dcp_video.h @@ -0,0 +1,75 @@ +/* + Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net> + Taken from code Copyright (C) 2010-2011 Terrence Meiczinger + + 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/picture_mxf_writer.h> +#include "util.h" + +/** @file src/dcp_video_frame.h + * @brief A single frame of video destined for a DCP. + */ + +class Film; +class ServerDescription; +class Scaler; +class Image; +class Log; +class Subtitle; +class PlayerVideo; +class EncodedData; + +/** @class DCPVideo + * @brief A single frame of video destined for a DCP. + * + * Given an Image and some settings, this class knows how to encode + * the image to J2K either on the local host or on a remote server. + * + * Objects of this class are used for the queue that we keep + * of images that require encoding. + */ +class DCPVideo : public boost::noncopyable +{ +public: + DCPVideo (boost::shared_ptr<const PlayerVideo>, int, int, int, Resolution, bool b, boost::shared_ptr<Log>); + DCPVideo (boost::shared_ptr<const PlayerVideo>, cxml::ConstNodePtr, boost::shared_ptr<Log>); + + boost::shared_ptr<EncodedData> encode_locally (); + boost::shared_ptr<EncodedData> encode_remotely (ServerDescription); + + int index () const { + return _index; + } + + Eyes eyes () const; + + bool same (boost::shared_ptr<const DCPVideo> other) const; + +private: + + void add_metadata (xmlpp::Element *) const; + + boost::shared_ptr<const PlayerVideo> _frame; + int _index; ///< frame index within the DCP's intrinsic duration + int _frames_per_second; ///< Frames per second that we will use for the DCP + int _j2k_bandwidth; ///< J2K bandwidth to use + Resolution _resolution; ///< Resolution (2K or 4K) + bool _burn_subtitles; ///< true to burn subtitles into the image + + boost::shared_ptr<Log> _log; ///< log +}; diff --git a/src/lib/dcpomatic_time.cc b/src/lib/dcpomatic_time.cc new file mode 100644 index 000000000..812c756ec --- /dev/null +++ b/src/lib/dcpomatic_time.cc @@ -0,0 +1,63 @@ +/* + 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); +} + +bool +ContentTimePeriod::contains (ContentTime const & other) const +{ + return (from <= other && other < to); +} diff --git a/src/lib/dcpomatic_time.h b/src/lib/dcpomatic_time.h new file mode 100644 index 000000000..55476d5b5 --- /dev/null +++ b/src/lib/dcpomatic_time.h @@ -0,0 +1,299 @@ +/* + 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 <sstream> +#include <iomanip> +#include <stdint.h> +#include "frame_rate_change.h" +#include "safe_stringstream.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) + {} + + typedef int64_t Type; + + explicit Time (Type t) + : _t (t) + {} + + virtual ~Time () {} + + Type 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); + } + + template <typename T> + void split (T r, int& h, int& m, int& s, int& f) const + { + /* Do this calculation with frames so that we can round + to a frame boundary at the start rather than the end. + */ + int64_t ff = frames (r); + + h = ff / (3600 * r); + ff -= h * 3600 * r; + m = ff / (60 * r); + ff -= m * 60 * r; + s = ff / r; + ff -= s * r; + + f = static_cast<int> (ff); + } + + template <typename T> + std::string timecode (T r) const { + int h; + int m; + int s; + int f; + split (r, h, m, s, f); + + SafeStringStream o; + o.width (2); + o.fill ('0'); + o << std::setw(2) << std::setfill('0') << h << ":" + << std::setw(2) << std::setfill('0') << m << ":" + << std::setw(2) << std::setfill('0') << s << ":" + << std::setw(2) << std::setfill('0') << f; + return o.str (); + } + +protected: + friend struct dcptime_round_up_test; + + Type _t; + static const int HZ = 96000; +}; + +class DCPTime; + +class ContentTime : public Time +{ +public: + ContentTime () : Time () {} + explicit ContentTime (Type t) : Time (t) {} + ContentTime (Type n, Type 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) { + Type const n = rint (HZ / r); + Type 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; + bool contains (ContentTime const & o) const; +}; + +class DCPTime : public Time +{ +public: + DCPTime () : Time () {} + explicit DCPTime (Type 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) { + Type const n = rint (HZ / r); + Type 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..583a92636 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,19 @@ 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 ensure that, at worst, + * the next thing we yield comes before `time'. This may entail + * seeking some way before `time' to be on the safe side. + * Alternatively, if seeking is 100% accurate for this decoder, + * it may seek to just the right spot. */ - 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/dolby_cp750.cc b/src/lib/dolby_cp750.cc index aeb469d29..317d129d9 100644 --- a/src/lib/dolby_cp750.cc +++ b/src/lib/dolby_cp750.cc @@ -24,7 +24,7 @@ using namespace std; DolbyCP750::DolbyCP750 () - : SoundProcessor ("dolby_cp750", _("Dolby CP650 and CP750")) + : CinemaSoundProcessor ("dolby_cp750", _("Dolby CP650 and CP750")) { } diff --git a/src/lib/dolby_cp750.h b/src/lib/dolby_cp750.h index b6c0e7df2..c545844fe 100644 --- a/src/lib/dolby_cp750.h +++ b/src/lib/dolby_cp750.h @@ -17,9 +17,9 @@ */ -#include "sound_processor.h" +#include "cinema_sound_processor.h" -class DolbyCP750 : public SoundProcessor +class DolbyCP750 : public CinemaSoundProcessor { public: DolbyCP750 (); diff --git a/src/lib/encoded_data.cc b/src/lib/encoded_data.cc new file mode 100644 index 000000000..61d2644da --- /dev/null +++ b/src/lib/encoded_data.cc @@ -0,0 +1,122 @@ +/* + Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "encoded_data.h" +#include "cross.h" +#include "exceptions.h" +#include "film.h" + +#include "i18n.h" + +using boost::shared_ptr; + +EncodedData::EncodedData (int s) + : _data (new uint8_t[s]) + , _size (s) +{ + +} + +EncodedData::EncodedData (uint8_t const * d, int s) + : _data (new uint8_t[s]) + , _size (s) +{ + memcpy (_data, d, s); +} + +EncodedData::EncodedData (boost::filesystem::path file) +{ + _size = boost::filesystem::file_size (file); + _data = new uint8_t[_size]; + + FILE* f = fopen_boost (file, "rb"); + if (!f) { + throw FileError (_("could not open file for reading"), file); + } + + size_t const r = fread (_data, 1, _size, f); + if (r != size_t (_size)) { + fclose (f); + throw FileError (_("could not read encoded data"), file); + } + + fclose (f); +} + + +EncodedData::~EncodedData () +{ + delete[] _data; +} + +/** Write this data to a J2K file. + * @param Film Film. + * @param frame DCP frame index. + */ +void +EncodedData::write (shared_ptr<const Film> film, int frame, Eyes eyes) const +{ + boost::filesystem::path const tmp_j2c = film->j2c_path (frame, eyes, true); + + FILE* f = fopen_boost (tmp_j2c, "wb"); + + if (!f) { + throw WriteFileError (tmp_j2c, errno); + } + + fwrite (_data, 1, _size, f); + fclose (f); + + boost::filesystem::path const real_j2c = film->j2c_path (frame, eyes, false); + + /* Rename the file from foo.j2c.tmp to foo.j2c now that it is complete */ + boost::filesystem::rename (tmp_j2c, real_j2c); +} + +void +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"); + fin.write (h); + fclose (h); +} + +/** Send this data to a socket. + * @param socket Socket + */ +void +EncodedData::send (shared_ptr<Socket> socket) +{ + socket->write (_size); + socket->write (_data, _size); +} + +LocallyEncodedData::LocallyEncodedData (uint8_t* d, int s) + : EncodedData (s) +{ + memcpy (_data, d, s); +} + +/** @param s Size of data in bytes */ +RemotelyEncodedData::RemotelyEncodedData (int s) + : EncodedData (s) +{ + +} diff --git a/src/lib/dcp_video_frame.h b/src/lib/encoded_data.h index e4006d986..232ed6e8a 100644 --- a/src/lib/dcp_video_frame.h +++ b/src/lib/encoded_data.h @@ -1,6 +1,5 @@ /* Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net> - Taken from code Copyright (C) 2010-2011 Terrence Meiczinger 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,22 +17,13 @@ */ -#include <openjpeg.h> -#include <libdcp/picture_asset.h> -#include <libdcp/picture_asset_writer.h> -#include "util.h" - -/** @file src/dcp_video_frame.h - * @brief A single frame of video destined for a DCP. - */ +#include <boost/noncopyable.hpp> +#include <boost/filesystem.hpp> +#include <dcp/picture_mxf_writer.h> +#include "types.h" +class Socket; class Film; -class ServerDescription; -class Scaler; -class Image; -class Log; -class Subtitle; -class PlayerVideoFrame; /** @class EncodedData * @brief Container for J2K-encoded data. @@ -43,6 +33,7 @@ class EncodedData : public boost::noncopyable public: /** @param s Size of data, in bytes */ EncodedData (int s); + EncodedData (uint8_t const * d, int s); EncodedData (boost::filesystem::path); @@ -50,7 +41,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 { @@ -90,40 +81,3 @@ class RemotelyEncodedData : public EncodedData public: RemotelyEncodedData (int s); }; - -/** @class DCPVideoFrame - * @brief A single frame of video destined for a DCP. - * - * Given an Image and some settings, this class knows how to encode - * the image to J2K either on the local host or on a remote server. - * - * Objects of this class are used for the queue that we keep - * of images that require encoding. - */ -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>); - - boost::shared_ptr<EncodedData> encode_locally (); - boost::shared_ptr<EncodedData> encode_remotely (ServerDescription); - - int index () const { - return _index; - } - - Eyes eyes () const; - -private: - - void add_metadata (xmlpp::Element *) const; - - boost::shared_ptr<const PlayerVideoFrame> _frame; - int _index; ///< frame index within the DCP's intrinsic duration - int _frames_per_second; ///< Frames per second that we will use for the DCP - int _j2k_bandwidth; ///< J2K bandwidth to use - Resolution _resolution; ///< Resolution (2K or 4K) - - boost::shared_ptr<Log> _log; ///< log -}; diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc index 693fd587e..0c9faa70d 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -29,13 +29,13 @@ #include "film.h" #include "log.h" #include "config.h" -#include "dcp_video_frame.h" +#include "dcp_video.h" #include "server.h" #include "cross.h" #include "writer.h" #include "server_finder.h" #include "player.h" -#include "player_video_frame.h" +#include "player_video.h" #include "i18n.h" @@ -58,15 +58,14 @@ using boost::scoped_array; int const Encoder::_history_size = 25; /** @param f Film that we are encoding */ -Encoder::Encoder (shared_ptr<const Film> f, weak_ptr<Job> j) +Encoder::Encoder (shared_ptr<const Film> f, weak_ptr<Job> j, shared_ptr<Writer> writer) : _film (f) , _job (j) , _video_frames_out (0) , _terminate (false) + , _writer (writer) { - _have_a_real_frame[EYES_BOTH] = false; - _have_a_real_frame[EYES_LEFT] = false; - _have_a_real_frame[EYES_RIGHT] = false; + } Encoder::~Encoder () @@ -88,18 +87,17 @@ Encoder::add_worker_threads (ServerDescription d) } void -Encoder::process_begin () +Encoder::begin () { for (int i = 0; i < Config::instance()->num_local_encoding_threads (); ++i) { _threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, optional<ServerDescription> ()))); } - _writer.reset (new Writer (_film, _job)); ServerFinder::instance()->connect (boost::bind (&Encoder::server_found, this, _1)); } void -Encoder::process_end () +Encoder::end () { boost::mutex::scoped_lock lock (_mutex); @@ -126,7 +124,7 @@ Encoder::process_end () So just mop up anything left in the queue here. */ - for (list<shared_ptr<DCPVideoFrame> >::iterator i = _queue.begin(); i != _queue.end(); ++i) { + for (list<shared_ptr<DCPVideo> >::iterator i = _queue.begin(); i != _queue.end(); ++i) { LOG_GENERAL (N_("Encode left-over frame %1"), (*i)->index ()); try { _writer->write ((*i)->encode_locally(), (*i)->index (), (*i)->eyes ()); @@ -135,9 +133,6 @@ Encoder::process_end () LOG_ERROR (N_("Local encode failed (%1)"), e.what ()); } } - - _writer->finish (); - _writer.reset (); } /** @return an estimate of the current number of frames we are encoding per second, @@ -181,8 +176,11 @@ Encoder::frame_done () } } +/** Called in order, so each time this is called the supplied frame is the one + * after the previous one. + */ void -Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf, bool same) +Encoder::enqueue (shared_ptr<PlayerVideo> pv) { _waker.nudge (); @@ -209,20 +207,24 @@ Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf, bool same) rethrow (); 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()); + /* We can fake-write this frame */ + _writer->fake_write (_video_frames_out, pv->eyes ()); frame_done (); + } else if (pv->has_j2k ()) { + /* This frame already has JPEG2000 data, so just write it */ + _writer->write (pv->j2k(), _video_frames_out, pv->eyes ()); } 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() + _queue.push_back (shared_ptr<DCPVideo> ( + new DCPVideo ( + pv, + _video_frames_out, + _film->video_frame_rate(), + _film->j2k_bandwidth(), + _film->resolution(), + _film->burn_subtitles(), + _film->log() ) )); @@ -230,21 +232,14 @@ Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf, bool same) waiting on that. */ _empty_condition.notify_all (); - _have_a_real_frame[pvf->eyes()] = true; } - if (pvf->eyes() != EYES_LEFT) { + if (pv->eyes() != EYES_LEFT) { ++_video_frames_out; } } void -Encoder::process_audio (shared_ptr<const AudioBuffers> data) -{ - _writer->write (data); -} - -void Encoder::terminate_threads () { { @@ -273,6 +268,8 @@ try encodings. */ int remote_backoff = 0; + shared_ptr<DCPVideo> last_dcp_video; + shared_ptr<EncodedData> last_encoded; while (true) { @@ -287,7 +284,7 @@ try } LOG_TIMING ("[%1] encoder thread wakes with queue of %2", boost::this_thread::get_id(), _queue.size()); - shared_ptr<DCPVideoFrame> vf = _queue.front (); + shared_ptr<DCPVideo> vf = _queue.front (); LOG_TIMING ("[%1] encoder thread pops frame %2 (%3) from queue", boost::this_thread::get_id(), vf->index(), vf->eyes ()); _queue.pop_front (); @@ -295,38 +292,47 @@ try shared_ptr<EncodedData> encoded; - if (server) { - try { - encoded = vf->encode_remotely (server.get ()); - - if (remote_backoff > 0) { - LOG_GENERAL ("%1 was lost, but now she is found; removing backoff", server->host_name ()); + if (last_dcp_video && vf->same (last_dcp_video)) { + /* We already have encoded data for the same input as this one, so take a short-cut */ + encoded = last_encoded; + } else { + /* We need to encode this input */ + if (server) { + try { + encoded = vf->encode_remotely (server.get ()); + + if (remote_backoff > 0) { + LOG_GENERAL ("%1 was lost, but now she is found; removing backoff", server->host_name ()); + } + + /* This job succeeded, so remove any backoff */ + remote_backoff = 0; + + } catch (std::exception& e) { + if (remote_backoff < 60) { + /* back off more */ + remote_backoff += 10; + } + LOG_ERROR ( + N_("Remote encode of %1 on %2 failed (%3); thread sleeping for %4s"), + vf->index(), server->host_name(), e.what(), remote_backoff + ); } - /* This job succeeded, so remove any backoff */ - remote_backoff = 0; - - } catch (std::exception& e) { - if (remote_backoff < 60) { - /* back off more */ - remote_backoff += 10; + } else { + try { + LOG_TIMING ("[%1] encoder thread begins local encode of %2", boost::this_thread::get_id(), vf->index()); + encoded = vf->encode_locally (); + LOG_TIMING ("[%1] encoder thread finishes local encode of %2", boost::this_thread::get_id(), vf->index()); + } catch (std::exception& e) { + LOG_ERROR (N_("Local encode failed (%1)"), e.what ()); } - LOG_ERROR ( - N_("Remote encode of %1 on %2 failed (%3); thread sleeping for %4s"), - vf->index(), server->host_name(), e.what(), remote_backoff - ); - } - - } else { - try { - LOG_TIMING ("[%1] encoder thread begins local encode of %2", boost::this_thread::get_id(), vf->index()); - encoded = vf->encode_locally (); - LOG_TIMING ("[%1] encoder thread finishes local encode of %2", boost::this_thread::get_id(), vf->index()); - } catch (std::exception& e) { - LOG_ERROR (N_("Local encode failed (%1)"), e.what ()); } } + last_dcp_video = vf; + last_encoded = encoded; + if (encoded) { _writer->write (encoded, vf->index (), vf->eyes ()); frame_done (); diff --git a/src/lib/encoder.h b/src/lib/encoder.h index 8d5aa2c40..51df0176b 100644 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@ -38,45 +38,42 @@ extern "C" { #include "util.h" #include "config.h" #include "cross.h" +#include "exceptions.h" class Image; class AudioBuffers; class Film; class ServerDescription; -class DCPVideoFrame; +class DCPVideo; class EncodedData; class Writer; class Job; class ServerFinder; -class PlayerVideoFrame; +class PlayerVideo; /** @class Encoder - * @brief Encoder to J2K and WAV for DCP. + * @brief Class to manage encoding to JPEG2000. * - * Video is supplied to process_video as RGB frames, and audio - * is supplied as uncompressed PCM in blocks of various sizes. + * This class keeps a queue of frames to be encoded and distributes + * the work around threads and encoding servers. */ class Encoder : public boost::noncopyable, public ExceptionStore { public: - Encoder (boost::shared_ptr<const Film> f, boost::weak_ptr<Job>); + Encoder (boost::shared_ptr<const Film> f, boost::weak_ptr<Job>, boost::shared_ptr<Writer>); virtual ~Encoder (); /** Called to indicate that a processing run is about to begin */ - void process_begin (); + void 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); - - /** Call with some audio data */ - void process_audio (boost::shared_ptr<const AudioBuffers>); + void enqueue (boost::shared_ptr<PlayerVideo> f); /** Called when a processing run has finished */ - void process_end (); + void end (); float current_encoding_rate () const; int video_frames_out () const; @@ -106,9 +103,8 @@ 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::shared_ptr<DCPVideo> > _queue; std::list<boost::thread *> _threads; mutable boost::mutex _mutex; /** condition to manage thread wakeups when we have nothing to do */ diff --git a/src/lib/exceptions.cc b/src/lib/exceptions.cc index 8144f41b9..95d4c8cce 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,20 @@ 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.empty() ? "[nothing]" : saw, expecting), f) +{ + +} + +InvalidSignerError::InvalidSignerError () + : StringError (_("The certificate chain for signing is invalid")) +{ + +} diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h index 3423a5754..52f257a8d 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,44 @@ 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 DCPError : public StringError +{ +public: + DCPError (std::string s) + : StringError (s) + {} +}; + +class InvalidSignerError : public StringError +{ +public: + InvalidSignerError (); +}; + +/** @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 { @@ -269,6 +301,4 @@ private: mutable boost::mutex _mutex; }; - - #endif diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc index ebe62b51f..fa369dda4 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,11 @@ 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 "ffmpeg_subtitle_stream.h" #include "exceptions.h" #include "util.h" @@ -33,7 +35,7 @@ extern "C" { using std::string; using std::cout; using boost::shared_ptr; -using libdcp::raw_convert; +using dcp::raw_convert; boost::mutex FFmpeg::_mutex; @@ -47,8 +49,7 @@ FFmpeg::FFmpeg (boost::shared_ptr<const FFmpegContent> c) , _video_stream (-1) { setup_general (); - setup_video (); - setup_audio (); + setup_decoders (); } FFmpeg::~FFmpeg () @@ -56,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); } @@ -143,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 { @@ -192,9 +167,23 @@ 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; } +AVCodecContext * +FFmpeg::subtitle_codec_context () const +{ + if (!_ffmpeg_content->subtitle_stream ()) { + return 0; + } + + return _ffmpeg_content->subtitle_stream()->stream(_format_context)->codec; +} + int FFmpeg::avio_read (uint8_t* buffer, int const amount) { diff --git a/src/lib/ffmpeg.h b/src/lib/ffmpeg.h index 760918437..8aaa54f84 100644 --- a/src/lib/ffmpeg.h +++ b/src/lib/ffmpeg.h @@ -56,6 +56,7 @@ public: protected: AVCodecContext* video_codec_context () const; AVCodecContext* audio_codec_context () const; + AVCodecContext* subtitle_codec_context () const; boost::shared_ptr<const FFmpegContent> _ffmpeg_content; @@ -79,8 +80,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..d8666e89e --- /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) + , _frame_rate (node->number_child<int> ("FrameRate")) + , _channels (node->number_child<int64_t> ("Channels")) + , _mapping (node->node_child ("Mapping"), version) +{ + 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..1587afcae --- /dev/null +++ b/src/lib/ffmpeg_audio_stream.h @@ -0,0 +1,74 @@ +/* + 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" + +struct 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 () const { + return _frame_rate; + } + + int channels () const { + return _channels; + } + + AudioMapping mapping () const { + return _mapping; + } + + void set_mapping (AudioMapping m) { + _mapping = m; + } + + boost::optional<ContentTime> first_audio; + +private: + friend struct ffmpeg_pts_offset_test; + + /* Constructor for tests */ + FFmpegAudioStream () + : FFmpegStream ("", 0) + , _frame_rate (0) + , _channels (0) + , _mapping (1) + {} + + int _frame_rate; + int _channels; + AudioMapping _mapping; +}; diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index a4209f5b6..bb4e02230 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) @@ -108,7 +110,7 @@ FFmpegContent::FFmpegContent (shared_ptr<const Film> f, vector<boost::shared_ptr for (size_t i = 0; i < c.size(); ++i) { shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c[i]); - if (f->with_subtitles() && *(fc->_subtitle_stream.get()) != *(ref->_subtitle_stream.get())) { + if (fc->use_subtitles() && *(fc->_subtitle_stream.get()) != *(ref->_subtitle_stream.get())) { throw JoinError (_("Content to be joined must use the same subtitle stream.")); } @@ -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,20 +169,15 @@ 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); + shared_ptr<const Film> film = _film.lock (); + assert (film); { boost::mutex::scoped_lock lm (_mutex); - _video_length = video_length; - _subtitle_streams = examiner->subtitle_streams (); if (!_subtitle_streams.empty ()) { _subtitle_stream = _subtitle_streams.front (); @@ -194,9 +191,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); signal_changed (FFmpegContentProperty::AUDIO_STREAMS); @@ -237,13 +231,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 ""; } SafeStringStream 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 +265,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 @@ -295,11 +284,11 @@ FFmpegContent::audio_channels () const return 0; } - return _audio_stream->channels; + return _audio_stream->channels (); } int -FFmpegContent::content_audio_frame_rate () const +FFmpegContent::audio_frame_rate () const { boost::mutex::scoped_lock lm (_mutex); @@ -307,7 +296,7 @@ FFmpegContent::content_audio_frame_rate () const return 0; } - return _audio_stream->frame_rate; + return _audio_stream->frame_rate (); } bool @@ -322,94 +311,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 @@ -421,7 +328,7 @@ FFmpegContent::audio_mapping () const return AudioMapping (); } - return _audio_stream->mapping; + return _audio_stream->mapping (); } void @@ -438,8 +345,8 @@ FFmpegContent::set_filters (vector<Filter const *> const & filters) void FFmpegContent::set_audio_mapping (AudioMapping m) { - audio_stream()->mapping = m; - signal_changed (AudioContentProperty::AUDIO_MAPPING); + audio_stream()->set_mapping (m); + AudioContent::set_audio_mapping (m); } string @@ -482,3 +389,29 @@ FFmpegContent::audio_analysis_path () const p /= name; return p; } + +list<ContentTimePeriod> +FFmpegContent::subtitles_during (ContentTimePeriod period, bool starting) const +{ + list<ContentTimePeriod> d; + + shared_ptr<FFmpegSubtitleStream> stream = subtitle_stream (); + if (!stream) { + return d; + } + + /* XXX: inefficient */ + for (vector<ContentTimePeriod>::const_iterator i = stream->periods.begin(); i != stream->periods.end(); ++i) { + if ((starting && period.contains (i->from)) || (!starting && period.overlaps (*i))) { + d.push_back (*i); + } + } + + return d; +} + +bool +FFmpegContent::has_subtitles () const +{ + return !subtitle_streams().empty (); +} diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h index c15e5c10e..da8152c0d 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,88 +31,9 @@ struct AVFormatContext; struct AVStream; class Filter; -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 FFmpegSubtitleStream; +class FFmpegAudioStream; +struct ffmpeg_pts_offset_test; class FFmpegContentProperty : public VideoContentProperty { @@ -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,18 +61,21 @@ 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; + /* SubtitleContent */ + bool has_subtitles () const; + void set_filters (std::vector<Filter const *> const &); std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const { @@ -181,19 +106,21 @@ 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; } + std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const; + private: - friend class ffmpeg_pts_offset_test; + friend struct ffmpeg_pts_offset_test; std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams; 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 d40b798ba..15443c346 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -31,23 +31,26 @@ 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 "raw_image_proxy.h" +#include "film.h" +#include "timer.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; @@ -55,26 +58,19 @@ using std::vector; using std::list; using std::min; using std::pair; +using std::make_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) - , _subtitle_codec_context (0) - , _subtitle_codec (0) - , _decode_video (video) - , _decode_audio (audio) - , _pts_offset (0) - , _just_sought (false) + , _log (log) { - setup_subtitle (); - /* Audio and video frame PTS values may not start with 0. We want to fiddle them so that: @@ -84,13 +80,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 */ @@ -104,24 +98,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; - } -} - -FFmpegDecoder::~FFmpegDecoder () -{ - boost::mutex::scoped_lock lm (_mutex); - - if (_subtitle_codec_context) { - avcodec_close (_subtitle_codec_context); + 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; } } @@ -135,20 +114,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); @@ -158,29 +132,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. @@ -313,82 +283,40 @@ FFmpegDecoder::bytes_per_audio_sample () const } void -FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate) +FFmpegDecoder::seek (ContentTime time, bool accurate) { - double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base); - - /* 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. + VideoDecoder::seek (time, accurate); + AudioDecoder::seek (time, accurate); + + /* If we are doing an `accurate' seek, we need to use pre-roll, as + we don't really know what the seek will give us. */ - int initial = frame; - if (accurate) { - initial -= 5; - } + ContentTime pre_roll = accurate ? ContentTime::from_seconds (2) : ContentTime (0); + time -= pre_roll; - if (initial < 0) { - initial = 0; - } - - /* Initial seek time in the stream's timebase */ - int64_t const initial_vt = ((initial / _ffmpeg_content->original_video_frame_rate()) - _pts_offset) / time_base; - - av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD); - - avcodec_flush_buffers (video_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. + /* XXX: it seems debatable whether PTS should be used here... + http://www.mjbshaw.com/2012/04/seeking-in-ffmpeg-know-your-timestamp.html */ - if (!accurate) { - _just_sought = true; - } - - _video_position = frame; - if (frame == 0 || !accurate) { - /* We're already there, or we're as close as we need to be */ - return; - } + ContentTime const u = time - _pts_offset; + int64_t s = u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base); - while (true) { - int r = av_read_frame (_format_context, &_packet); - if (r < 0) { - return; - } + if (_ffmpeg_content->audio_stream ()) { + s = min ( + s, int64_t (u.seconds() / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base)) + ); + } - 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->original_video_frame_rate() - ); + av_seek_frame (_format_context, _video_stream, s, 0); - if (_video_position >= (frame - 1)) { - av_free_packet (&_packet); - break; - } - } - - av_free_packet (&_packet); + 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 ()); } - - /* _video_position should be the next thing to be emitted, which will the one after the thing - we just saw. - */ - _video_position++; } void @@ -404,42 +332,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; @@ -460,17 +369,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; @@ -478,56 +383,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->original_video_frame_rate ()); - _just_sought = false; - } - - double const next = _video_position / _ffmpeg_content->original_video_frame_rate(); - double const one_frame = 1 / _ffmpeg_content->original_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"); } @@ -535,47 +400,13 @@ 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 () { int got_subtitle; AVSubtitle sub; - if (avcodec_decode_subtitle2 (_subtitle_codec_context, &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) { + if (avcodec_decode_subtitle2 (subtitle_codec_context(), &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) { return; } @@ -583,31 +414,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]; @@ -629,20 +458,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); } + +list<ContentTimePeriod> +FFmpegDecoder::subtitles_during (ContentTimePeriod p, bool starting) const +{ + return _ffmpeg_content->subtitles_during (p, starting); +} diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index d4b4fa1c0..9f85c2dca 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,15 @@ 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 (); - - void pass (); - void seek (VideoContent::Frame, bool); - bool done () const; + FFmpegDecoder (boost::shared_ptr<const FFmpegContent>, boost::shared_ptr<Log>); private: - friend class ::ffmpeg_pts_offset_test; - - static double compute_pts_offset (double, double, float); + friend struct ::ffmpeg_pts_offset_test; + 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 +66,12 @@ private: void maybe_add_subtitle (); boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size); - 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<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const; + + boost::shared_ptr<Log> _log; 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 5ccc8028b..48d85da6f 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 "safe_stringstream.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 (true) { 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) / AV_TIME_BASE) * video_frame_rate(); - return max (1, length); + ContentTime const length = ContentTime::from_seconds (double (_format_context->duration) / AV_TIME_BASE); + return ContentTime (max (ContentTime::Type (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 048f69233..9c8d43204 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 54503ef72..268109921 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -26,15 +26,14 @@ #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/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 +76,10 @@ using boost::ends_with; using boost::starts_with; using boost::optional; using boost::is_any_of; -using libdcp::Size; -using libdcp::Signer; -using libdcp::raw_convert; +using dcp::Size; +using dcp::Signer; +using dcp::raw_convert; +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); @@ -91,11 +91,14 @@ using libdcp::raw_convert; * 7 -> 8 * Use <Scale> tag in <VideoContent> rather than <Ratio>. * 8 -> 9 - * DCI -> ISDCF. + * DCI -> ISDCF * 9 -> 10 * Subtitle X and Y scale. + * + * Bumped to 32 for 2.0 branch; some times are expressed in Times rather + * than frames now. */ -int const Film::current_state_version = 10; +int const Film::current_state_version = 32; /** Construct a Film object in a given directory. * @@ -109,7 +112,6 @@ Film::Film (boost::filesystem::path dir, bool log) , _container (Config::instance()->default_container ()) , _resolution (RESOLUTION_2K) , _scaler (Scaler::from_id ("bicubic")) - , _with_subtitles (false) , _signed (true) , _encrypted (false) , _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ()) @@ -119,6 +121,7 @@ Film::Film (boost::filesystem::path dir, bool log) , _three_d (false) , _sequence_video (true) , _interop (false) + , _burn_subtitles (false) , _state_version (current_state_version) , _dirty (false) { @@ -182,12 +185,12 @@ Film::video_identifier () const s << "_S"; } - if (_three_d) { - s << "_3D"; + if (_burn_subtitles) { + s << "_B"; } - if (_with_subtitles) { - s << "_WS"; + if (_three_d) { + s << "_3D"; } return s.str (); @@ -227,6 +230,12 @@ Film::audio_mxf_filename () const return filename_safe_name() + "_audio.mxf"; } +boost::filesystem::path +Film::subtitle_xml_filename () const +{ + return filename_safe_name() + "_subtitle.xml"; +} + string Film::filename_safe_name () const { @@ -369,7 +378,6 @@ Film::metadata () const root->add_child("Resolution")->add_child_text (resolution_to_string (_resolution)); root->add_child("Scaler")->add_child_text (_scaler->id ()); - root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0"); root->add_child("J2KBandwidth")->add_child_text (raw_convert<string> (_j2k_bandwidth)); _isdcf_metadata.as_xml (root->add_child ("ISDCFMetadata")); root->add_child("VideoFrameRate")->add_child_text (raw_convert<string> (_video_frame_rate)); @@ -378,6 +386,7 @@ Film::metadata () const root->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0"); root->add_child("SequenceVideo")->add_child_text (_sequence_video ? "1" : "0"); root->add_child("Interop")->add_child_text (_interop ? "1" : "0"); + root->add_child("BurnSubtitles")->add_child_text (_burn_subtitles ? "1" : "0"); root->add_child("Signed")->add_child_text (_signed ? "1" : "0"); root->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0"); root->add_child("Key")->add_child_text (_key.hex ()); @@ -441,7 +450,6 @@ Film::read_metadata () _resolution = string_to_resolution (f.string_child ("Resolution")); _scaler = Scaler::from_id (f.string_child ("Scaler")); - _with_subtitles = f.bool_child ("WithSubtitles"); _j2k_bandwidth = f.number_child<int> ("J2KBandwidth"); _video_frame_rate = f.number_child<int> ("VideoFrameRate"); _signed = f.optional_bool_child("Signed").get_value_or (true); @@ -450,7 +458,10 @@ 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")); + if (_state_version >= 32) { + _burn_subtitles = f.bool_child ("BurnSubtitles"); + } + _key = dcp::Key (f.string_child ("Key")); list<string> notes; /* This method is the only one that can return notes (so far) */ @@ -585,9 +596,9 @@ Film::isdcf_name (bool if_created_now) const /* XXX: this uses the first bit of content only */ /* 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) { - Ratio const * content_ratio = 0; + if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) { ContentList cl = content (); + Ratio const * content_ratio = 0; for (ContentList::iterator i = cl.begin(); i != cl.end(); ++i) { shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i); if (vc) { @@ -689,7 +700,6 @@ Film::dcp_name (bool if_created_now) const return name(); } - void Film::set_directory (boost::filesystem::path d) { @@ -740,13 +750,6 @@ Film::set_scaler (Scaler const * s) } void -Film::set_with_subtitles (bool w) -{ - _with_subtitles = w; - signal_changed (WITH_SUBTITLES); -} - -void Film::set_j2k_bandwidth (int b) { _j2k_bandwidth = b; @@ -789,6 +792,13 @@ Film::set_interop (bool i) } void +Film::set_burn_subtitles (bool b) +{ + _burn_subtitles = b; + signal_changed (BURN_SUBTITLES); +} + +void Film::signal_changed (Property p) { _dirty = true; @@ -869,7 +879,7 @@ Film::j2c_path (int f, Eyes e, bool t) const return file (p); } -/** Find all the DCPs in our directory that can be libdcp::DCP::read() and return details of their CPLs */ +/** Find all the DCPs in our directory that can be dcp::DCP::read() and return details of their CPLs */ vector<CPLSummary> Film::cpls () const { @@ -883,11 +893,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 (...) { @@ -932,6 +945,13 @@ Film::content () const } void +Film::examine_content (shared_ptr<Content> c) +{ + shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c)); + JobManager::instance()->add (j); +} + +void Film::examine_and_add_content (shared_ptr<Content> c) { if (dynamic_pointer_cast<FFmpegContent> (c)) { @@ -986,22 +1006,22 @@ Film::move_content_later (shared_ptr<Content> c) _playlist->move_later (c); } -Time +DCPTime Film::length () const { return _playlist->length (); } -bool -Film::has_subtitles () const +int +Film::best_video_frame_rate () const { - return _playlist->has_subtitles (); + return _playlist->best_dcp_frame_rate (); } -OutputVideoFrame -Film::best_video_frame_rate () const +FrameRateChange +Film::active_frame_rate_change (DCPTime t) const { - return _playlist->best_dcp_frame_rate (); + return _playlist->active_frame_rate_change (t, video_frame_rate ()); } void @@ -1022,31 +1042,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 */ @@ -1062,61 +1058,62 @@ 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 ()); + return fit_ratio_within (container()->ratio(), full_frame (), 1); } -/** @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, + dcp::Certificate target, boost::filesystem::path cpl_file, - boost::posix_time::ptime from, - boost::posix_time::ptime until, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime until, + dcp::Formulation formulation ) 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); + shared_ptr<const dcp::CPL> cpl (new dcp::CPL (cpl_file)); + shared_ptr<const dcp::Signer> signer = Config::instance()->signer(); + if (!signer->valid ()) { + throw InvalidSignerError (); + } - return libdcp::KDM (cpl_file, signer, target, key (), from, until, "DCP-o-matic", issue_date, formulation); + return dcp::DecryptedKDM ( + cpl, key(), from, until, "DCP-o-matic", cpl->content_title_text(), dcp::LocalTime().as_string() + ).encrypt (signer, target, formulation); } -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, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime until, + dcp::Formulation formulation ) 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, formulation)); + if ((*i)->certificate) { + kdms.push_back (make_kdm ((*i)->certificate.get(), dcp, from, until, formulation)); + } } return kdms; @@ -1128,7 +1125,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 @@ -1146,10 +1143,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 b7d105688..8a0823094 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -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" @@ -46,7 +47,7 @@ class Playlist; class AudioContent; class Scaler; class Screen; -class isdcf_name_test; +struct isdcf_name_test; /** @class Film * @@ -69,6 +70,7 @@ public: boost::filesystem::path video_mxf_filename () const; boost::filesystem::path audio_mxf_filename () const; + boost::filesystem::path subtitle_xml_filename () const; void send_dcp_to_tms (); void make_dcp (); @@ -97,20 +99,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,29 +115,28 @@ public: /* Proxies for some Playlist methods */ ContentList content () const; - Time length () const; - bool has_subtitles () const; - OutputVideoFrame best_video_frame_rate () const; - FrameRateChange active_frame_rate_change (Time) const; + DCPTime length () 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, + dcp::Certificate target, boost::filesystem::path cpl_file, - boost::posix_time::ptime from, - boost::posix_time::ptime until, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime until, + dcp::Formulation formulation ) 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, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime until, + dcp::Formulation formulation ) const; - libdcp::Key key () const { + dcp::Key key () const { return _key; } @@ -161,17 +157,18 @@ public: CONTAINER, RESOLUTION, SCALER, - WITH_SUBTITLES, SIGNED, ENCRYPTED, J2K_BANDWIDTH, ISDCF_METADATA, VIDEO_FRAME_RATE, AUDIO_CHANNELS, - /** The setting of _three_d has been changed */ + /** The setting of _three_d has changed */ THREE_D, SEQUENCE_VIDEO, INTEROP, + /** The setting of _burn_subtitles has changed */ + BURN_SUBTITLES, }; @@ -205,10 +202,6 @@ public: return _scaler; } - bool with_subtitles () const { - return _with_subtitles; - } - /* signed is a reserved word */ bool is_signed () const { return _signed; @@ -246,6 +239,10 @@ public: bool interop () const { return _interop; } + + bool burn_subtitles () const { + return _burn_subtitles; + } /* SET */ @@ -253,6 +250,7 @@ public: void set_directory (boost::filesystem::path); void set_name (std::string); void set_use_isdcf_name (bool); + void examine_content (boost::shared_ptr<Content>); void examine_and_add_content (boost::shared_ptr<Content>); void add_content (boost::shared_ptr<Content>); void remove_content (boost::shared_ptr<Content>); @@ -262,7 +260,6 @@ public: void set_container (Ratio const *); void set_resolution (Resolution); void set_scaler (Scaler const *); - void set_with_subtitles (bool); void set_signed (bool); void set_encrypted (bool); void set_j2k_bandwidth (int); @@ -273,6 +270,7 @@ public: void set_isdcf_date_today (); void set_sequence_video (bool); void set_interop (bool); + void set_burn_subtitles (bool); /** Emitted when some property has of the Film has changed */ mutable boost::signals2::signal<void (Property)> Changed; @@ -285,7 +283,7 @@ public: private: - friend class ::isdcf_name_test; + friend struct ::isdcf_name_test; void signal_changed (Property); std::string video_identifier () const; @@ -315,8 +313,6 @@ private: Resolution _resolution; /** Scaler algorithm to use */ Scaler const * _scaler; - /** True if subtitles should be shown for this film */ - bool _with_subtitles; bool _signed; bool _encrypted; /** bandwidth for J2K files in bits per second */ @@ -335,15 +331,16 @@ private: bool _three_d; bool _sequence_video; bool _interop; - libdcp::Key _key; + bool _burn_subtitles; + dcp::Key _key; int _state_version; /** true if our state has changed since we last saved it */ mutable bool _dirty; - friend class paths_test; - friend class film_metadata_test; + friend struct paths_test; + friend struct film_metadata_test; }; #endif diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc index 639992d70..2d8f83aa7 100644 --- a/src/lib/filter_graph.cc +++ b/src/lib/filter_graph.cc @@ -45,26 +45,29 @@ 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) - : _buffer_src_context (0) +FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, dcp::Size s, AVPixelFormat p) + : _copy (false) + , _buffer_src_context (0) , _buffer_sink_context (0) , _size (s) , _pixel_format (p) + , _frame (0) { - _frame = av_frame_alloc (); - - string filters = Filter::ffmpeg_string (content->filters()); + string const filters = Filter::ffmpeg_string (content->filters()); if (filters.empty ()) { - filters = "copy"; + _copy = true; + return; } + _frame = av_frame_alloc (); + AVFilterGraph* graph = avfilter_graph_alloc(); if (graph == 0) { throw DecodeError (N_("could not create filter graph.")); @@ -122,12 +125,15 @@ 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 () { - av_frame_free (&_frame); + if (_frame) { + av_frame_free (&_frame); + } } /** Take an AVFrame and process it using our configured filters, returning a @@ -138,19 +144,23 @@ FilterGraph::process (AVFrame* frame) { list<pair<shared_ptr<Image>, int64_t> > images; - if (av_buffersrc_write_frame (_buffer_src_context, frame) < 0) { - throw DecodeError (N_("could not push buffer into filter chain.")); - } - - while (true) { - if (av_buffersink_get_frame (_buffer_sink_context, _frame) < 0) { - break; + if (_copy) { + images.push_back (make_pair (shared_ptr<Image> (new Image (frame)), av_frame_get_best_effort_timestamp (frame))); + } else { + if (av_buffersrc_write_frame (_buffer_src_context, frame) < 0) { + throw DecodeError (N_("could not push buffer into filter chain.")); + } + + while (true) { + if (av_buffersink_get_frame (_buffer_sink_context, _frame) < 0) { + break; + } + + images.push_back (make_pair (shared_ptr<Image> (new Image (_frame)), av_frame_get_best_effort_timestamp (_frame))); + av_frame_unref (_frame); } - - images.push_back (make_pair (shared_ptr<Image> (new Image (_frame)), av_frame_get_best_effort_timestamp (_frame))); - av_frame_unref (_frame); } - + return images; } @@ -159,7 +169,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..5b43c5512 100644 --- a/src/lib/filter_graph.h +++ b/src/lib/filter_graph.h @@ -36,16 +36,18 @@ 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: + /** true if this graph has no filters in, so it just copies stuff straight through */ + bool _copy; 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/image.cc b/src/lib/image.cc index 066f12c07..0da2d1e48 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,8 +40,9 @@ using std::string; using std::min; using std::cout; using std::cerr; +using std::list; using boost::shared_ptr; -using libdcp::Size; +using dcp::Size; int Image::line_factor (int n) const @@ -83,7 +86,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 @@ -99,13 +102,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) { @@ -139,7 +142,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 @@ -170,7 +173,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,137 @@ Image::digest () const return digester.get (); } - + +bool +operator== (Image const & a, Image const & b) +{ + if (a.components() != b.components() || a.pixel_format() != b.pixel_format() || a.aligned() != b.aligned()) { + return false; + } + + for (int c = 0; c < a.components(); ++c) { + if (a.lines(c) != b.lines(c) || a.line_size()[c] != b.line_size()[c] || a.stride()[c] != b.stride()[c]) { + return false; + } + + uint8_t* p = a.data()[c]; + uint8_t* q = b.data()[c]; + for (int y = 0; y < a.lines(c); ++y) { + if (memcmp (p, q, a.line_size()[c]) != 0) { + return false; + } + + p += a.stride()[c]; + q += b.stride()[c]; + } + } + + return true; +} + +void +Image::fade (float f) +{ + switch (_pixel_format) { + case PIX_FMT_YUV420P: + case PIX_FMT_YUV422P: + case PIX_FMT_YUV444P: + case PIX_FMT_YUV411P: + case PIX_FMT_YUVJ420P: + case PIX_FMT_YUVJ422P: + case PIX_FMT_YUVJ444P: + case PIX_FMT_RGB24: + case PIX_FMT_ARGB: + case PIX_FMT_RGBA: + case PIX_FMT_ABGR: + case PIX_FMT_BGRA: + case PIX_FMT_RGB555LE: + /* 8-bit */ + for (int c = 0; c < 3; ++c) { + uint8_t* p = data()[c]; + for (int y = 0; y < lines(c); ++y) { + uint8_t* q = p; + for (int x = 0; x < line_size()[c]; ++x) { + *q = int (float (*q) * f); + ++q; + } + p += stride()[c]; + } + } + break; + + case PIX_FMT_YUV422P9LE: + case PIX_FMT_YUV444P9LE: + case PIX_FMT_YUV422P10LE: + case PIX_FMT_YUV444P10LE: + case PIX_FMT_YUV422P16LE: + case PIX_FMT_YUV444P16LE: + case AV_PIX_FMT_YUVA420P9LE: + case AV_PIX_FMT_YUVA422P9LE: + case AV_PIX_FMT_YUVA444P9LE: + case AV_PIX_FMT_YUVA420P10LE: + case AV_PIX_FMT_YUVA422P10LE: + case AV_PIX_FMT_YUVA444P10LE: + /* 16-bit little-endian */ + for (int c = 0; c < 3; ++c) { + int const stride_pixels = stride()[c] / 2; + int const line_size_pixels = line_size()[c] / 2; + uint16_t* p = reinterpret_cast<uint16_t*> (data()[c]); + for (int y = 0; y < lines(c); ++y) { + uint16_t* q = p; + for (int x = 0; x < line_size_pixels; ++x) { + *q = int (float (*q) * f); + ++q; + } + p += stride_pixels; + } + } + break; + + case PIX_FMT_YUV422P9BE: + case PIX_FMT_YUV444P9BE: + case PIX_FMT_YUV444P10BE: + case PIX_FMT_YUV422P10BE: + case AV_PIX_FMT_YUVA420P9BE: + case AV_PIX_FMT_YUVA422P9BE: + case AV_PIX_FMT_YUVA444P9BE: + case AV_PIX_FMT_YUVA420P10BE: + case AV_PIX_FMT_YUVA422P10BE: + case AV_PIX_FMT_YUVA444P10BE: + case AV_PIX_FMT_YUVA420P16BE: + case AV_PIX_FMT_YUVA422P16BE: + case AV_PIX_FMT_YUVA444P16BE: + /* 16-bit big-endian */ + for (int c = 0; c < 3; ++c) { + int const stride_pixels = stride()[c] / 2; + int const line_size_pixels = line_size()[c] / 2; + uint16_t* p = reinterpret_cast<uint16_t*> (data()[c]); + for (int y = 0; y < lines(c); ++y) { + uint16_t* q = p; + for (int x = 0; x < line_size_pixels; ++x) { + *q = swap_16 (int (float (swap_16 (*q)) * f)); + ++q; + } + p += stride_pixels; + } + } + break; + + case PIX_FMT_UYVY422: + { + int const Y = lines(0); + int const X = line_size()[0]; + uint8_t* p = data()[0]; + for (int y = 0; y < Y; ++y) { + for (int x = 0; x < X; ++x) { + *p = int (float (*p) * f); + ++p; + } + } + break; + } + + default: + throw PixelFormatError ("fade()", _pixel_format); + } +} diff --git a/src/lib/image.h b/src/lib/image.h index f83bf6998..172250eb1 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,21 +51,23 @@ 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); + void fade (float); void read_from_socket (boost::shared_ptr<Socket>); void write_to_socket (boost::shared_ptr<Socket>) const; @@ -76,7 +79,7 @@ public: std::string digest () const; private: - friend class pixel_formats_test; + friend struct pixel_formats_test; void allocate (); void swap (Image &); @@ -91,4 +94,7 @@ private: bool _aligned; }; +extern PositionImage merge (std::list<PositionImage> images); +extern bool operator== (Image const & a, Image const & b); + #endif diff --git a/src/lib/image_content.cc b/src/lib/image_content.cc index 915da7beb..84b0b75c9 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 "safe_stringstream.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) { @@ -109,13 +109,11 @@ ImageContent::examine (shared_ptr<Job> job) assert (film); shared_ptr<ImageExaminer> examiner (new ImageExaminer (film, shared_from_this(), job)); - take_from_video_examiner (examiner); - set_video_length (examiner->video_length ()); } void -ImageContent::set_video_length (VideoContent::Frame len) +ImageContent::set_video_length (ContentTime len) { { boost::mutex::scoped_lock lm (_mutex); @@ -125,14 +123,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 +136,7 @@ ImageContent::identifier () const { SafeStringStream 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..8702c1a33 100644 --- a/src/lib/image_decoder.cc +++ b/src/lib/image_decoder.cc @@ -23,7 +23,7 @@ #include "image_content.h" #include "image_decoder.h" #include "image.h" -#include "image_proxy.h" +#include "magick_image_proxy.h" #include "film.h" #include "exceptions.h" @@ -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 3aba6cf7c..b6b387b76 100644 --- a/src/lib/image_proxy.cc +++ b/src/lib/image_proxy.cc @@ -17,10 +17,12 @@ */ -#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 "raw_image_proxy.h" +#include "magick_image_proxy.h" +#include "j2k_image_proxy.h" #include "image.h" #include "exceptions.h" #include "cross.h" @@ -40,147 +42,6 @@ ImageProxy::ImageProxy (shared_ptr<Log> log) } -RawImageProxy::RawImageProxy (shared_ptr<Image> image, shared_ptr<Log> log) - : ImageProxy (log) - , _image (image) -{ - -} - -RawImageProxy::RawImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shared_ptr<Log> log) - : ImageProxy (log) -{ - libdcp::Size size ( - xml->number_child<int> ("Width"), xml->number_child<int> ("Height") - ); - - _image.reset (new Image (static_cast<AVPixelFormat> (xml->number_child<int> ("PixelFormat")), size, true)); - _image->read_from_socket (socket); -} - -shared_ptr<Image> -RawImageProxy::image () const -{ - return _image; -} - -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 ())); -} - -void -RawImageProxy::send_binary (shared_ptr<Socket> socket) const -{ - _image->write_to_socket (socket); -} - -MagickImageProxy::MagickImageProxy (boost::filesystem::path path, shared_ptr<Log> log) - : ImageProxy (log) -{ - /* Read the file into a Blob */ - - boost::uintmax_t const size = boost::filesystem::file_size (path); - FILE* f = fopen_boost (path, "rb"); - if (!f) { - throw OpenFileError (path); - } - - uint8_t* data = new uint8_t[size]; - if (fread (data, 1, size, f) != size) { - delete[] data; - throw ReadFileError (path); - } - - fclose (f); - _blob.update (data, size); - delete[] data; -} - -MagickImageProxy::MagickImageProxy (shared_ptr<cxml::Node>, shared_ptr<Socket> socket, shared_ptr<Log> log) - : ImageProxy (log) -{ - uint32_t const size = socket->read_uint32 (); - uint8_t* data = new uint8_t[size]; - socket->read (data, size); - _blob.update (data, size); - delete[] data; -} - -shared_ptr<Image> -MagickImageProxy::image () const -{ - if (_image) { - return _image; - } - - LOG_TIMING ("[%1] MagickImageProxy begins decode and convert of %2 bytes", boost::this_thread::get_id(), _blob.length()); - - Magick::Image* magick_image = 0; - string error; - try { - magick_image = new Magick::Image (_blob); - } catch (Magick::Exception& e) { - error = e.what (); - } - - if (!magick_image) { - /* ImageMagick cannot auto-detect Targa files, it seems, so try here with an - explicit format. I can't find it documented that passing a (0, 0) geometry - is allowed, but it seems to work. - */ - try { - magick_image = new Magick::Image (_blob, Magick::Geometry (0, 0), "TGA"); - } catch (...) { - - } - } - - if (!magick_image) { - /* If we failed both an auto-detect and a forced-Targa we give the error from - the auto-detect. - */ - throw DecodeError (String::compose (_("Could not decode image file (%1)"), error)); - } - - 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 */ - uint8_t* p = _image->data()[0]; - for (int i = 0; i < size.height; ++i) { - using namespace MagickCore; - magick_image->write (0, i, size.width, 1, "RGB", CharPixel, p); - p += _image->stride()[0]; - } - - delete magick_image; - - LOG_TIMING ("[%1] MagickImageProxy completes decode and convert of %2 bytes", boost::this_thread::get_id(), _blob.length()); - - return _image; -} - -void -MagickImageProxy::add_metadata (xmlpp::Node* node) const -{ - node->add_child("Type")->add_child_text (N_("Magick")); -} - -void -MagickImageProxy::send_binary (shared_ptr<Socket> socket) const -{ - socket->write (_blob.length ()); - socket->write ((uint8_t *) _blob.data (), _blob.length ()); -} - shared_ptr<ImageProxy> image_proxy_factory (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shared_ptr<Log> log) { @@ -188,6 +49,8 @@ image_proxy_factory (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shar return shared_ptr<ImageProxy> (new RawImageProxy (xml, socket, log)); } else if (xml->string_child("Type") == N_("Magick")) { return shared_ptr<MagickImageProxy> (new MagickImageProxy (xml, socket, log)); + } else if (xml->string_child("Type") == N_("J2K")) { + return shared_ptr<J2KImageProxy> (new J2KImageProxy (xml, socket, log)); } throw NetworkError (_("Unexpected image type received by server")); diff --git a/src/lib/image_proxy.h b/src/lib/image_proxy.h index c0ccd9125..7ff28e174 100644 --- a/src/lib/image_proxy.h +++ b/src/lib/image_proxy.h @@ -17,6 +17,9 @@ */ +#ifndef DCPOMATIC_IMAGE_PROXY_H +#define DCPOMATIC_IMAGE_PROXY_H + /** @file src/lib/image_proxy.h * @brief ImageProxy and subclasses. */ @@ -34,6 +37,11 @@ namespace cxml { class Node; } +namespace dcp { + class MonoPictureFrame; + class StereoPictureFrame; +} + /** @class ImageProxy * @brief A class which holds an Image, and can produce it on request. * @@ -42,7 +50,7 @@ namespace cxml { * of happening in a single-threaded decoder. * * For example, large TIFFs are slow to decode, so this class will keep - * the TIFF data TIFF until such a time that the actual image is needed. + * the TIFF data compressed until the decompressed image is needed. * At this point, the class decodes the TIFF to an Image. */ class ImageProxy : public boost::noncopyable @@ -55,38 +63,15 @@ public: virtual boost::shared_ptr<Image> image () const = 0; virtual void add_metadata (xmlpp::Node *) const = 0; virtual void send_binary (boost::shared_ptr<Socket>) const = 0; + /** @return true if our image is definitely the same as another, false if it is probably not */ + virtual bool same (boost::shared_ptr<const ImageProxy>) const { + return false; + } protected: boost::shared_ptr<Log> _log; }; -class RawImageProxy : public ImageProxy -{ -public: - RawImageProxy (boost::shared_ptr<Image>, boost::shared_ptr<Log> log); - RawImageProxy (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket, boost::shared_ptr<Log> log); - - boost::shared_ptr<Image> image () const; - void add_metadata (xmlpp::Node *) const; - void send_binary (boost::shared_ptr<Socket>) const; - -private: - boost::shared_ptr<Image> _image; -}; - -class MagickImageProxy : public ImageProxy -{ -public: - MagickImageProxy (boost::filesystem::path, boost::shared_ptr<Log> log); - MagickImageProxy (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket, boost::shared_ptr<Log> log); - - boost::shared_ptr<Image> image () const; - void add_metadata (xmlpp::Node *) const; - void send_binary (boost::shared_ptr<Socket>) const; - -private: - Magick::Blob _blob; - mutable boost::shared_ptr<Image> _image; -}; - boost::shared_ptr<ImageProxy> image_proxy_factory (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket, boost::shared_ptr<Log> log); + +#endif diff --git a/src/lib/image_subtitle.h b/src/lib/image_subtitle.h new file mode 100644 index 000000000..b25943ae1 --- /dev/null +++ b/src/lib/image_subtitle.h @@ -0,0 +1,46 @@ +/* + 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_IMAGE_SUBTITLE_H +#define DCPOMATIC_IMAGE_SUBTITLE_H + +#include "rect.h" + +class Image; + +class ImageSubtitle +{ +public: + ImageSubtitle (boost::shared_ptr<Image> i, dcpomatic::Rect<double> r) + : image (i) + , rectangle (r) + {} + + boost::shared_ptr<Image> image; + /** Area that the subtitle covers on its corresponding video, expressed in + * proportions of the image size; e.g. rectangle.x = 0.5 would mean that + * the rectangle starts half-way across the video. + * + * This rectangle may or may not have had a SubtitleContent's offsets and + * scale applied to it, depending on context. + */ + dcpomatic::Rect<double> rectangle; +}; + +#endif 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/j2k_image_proxy.cc b/src/lib/j2k_image_proxy.cc new file mode 100644 index 000000000..6924fad79 --- /dev/null +++ b/src/lib/j2k_image_proxy.cc @@ -0,0 +1,123 @@ +/* + 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 <libcxml/cxml.h> +#include <dcp/raw_convert.h> +#include <dcp/mono_picture_frame.h> +#include <dcp/stereo_picture_frame.h> +#include "j2k_image_proxy.h" +#include "util.h" +#include "image.h" +#include "encoded_data.h" + +#include "i18n.h" + +using std::string; +using boost::shared_ptr; + +J2KImageProxy::J2KImageProxy (shared_ptr<const dcp::MonoPictureFrame> frame, dcp::Size size, shared_ptr<Log> log) + : ImageProxy (log) + , _mono (frame) + , _size (size) +{ + +} + +J2KImageProxy::J2KImageProxy (shared_ptr<const dcp::StereoPictureFrame> frame, dcp::Size size, dcp::Eye eye, shared_ptr<Log> log) + : ImageProxy (log) + , _stereo (frame) + , _size (size) + , _eye (eye) +{ + +} + +J2KImageProxy::J2KImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shared_ptr<Log> log) + : ImageProxy (log) +{ + _size = dcp::Size (xml->number_child<int> ("Width"), xml->number_child<int> ("Height")); + if (xml->optional_number_child<int> ("Eye")) { + _eye = static_cast<dcp::Eye> (xml->number_child<int> ("Eye")); + int const left_size = xml->number_child<int> ("LeftSize"); + int const right_size = xml->number_child<int> ("RightSize"); + shared_ptr<dcp::StereoPictureFrame> f (new dcp::StereoPictureFrame ()); + socket->read (f->left_j2k_data(), left_size); + socket->read (f->right_j2k_data(), right_size); + _stereo = f; + } else { + int const size = xml->number_child<int> ("Size"); + shared_ptr<dcp::MonoPictureFrame> f (new dcp::MonoPictureFrame ()); + socket->read (f->j2k_data (), size); + _mono = f; + } +} + +shared_ptr<Image> +J2KImageProxy::image () const +{ + shared_ptr<Image> image (new Image (PIX_FMT_RGB24, _size, false)); + + if (_mono) { + _mono->rgb_frame (image->data()[0]); + } else { + _stereo->rgb_frame (_eye, image->data()[0]); + } + + return shared_ptr<Image> (new Image (image, true)); +} + +void +J2KImageProxy::add_metadata (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text (N_("J2K")); + node->add_child("Width")->add_child_text (dcp::raw_convert<string> (_size.width)); + node->add_child("Height")->add_child_text (dcp::raw_convert<string> (_size.height)); + if (_stereo) { + node->add_child("Eye")->add_child_text (dcp::raw_convert<string> (_eye)); + node->add_child("LeftSize")->add_child_text (dcp::raw_convert<string> (_stereo->left_j2k_size ())); + node->add_child("RightSize")->add_child_text (dcp::raw_convert<string> (_stereo->right_j2k_size ())); + } else { + node->add_child("Size")->add_child_text (dcp::raw_convert<string> (_mono->j2k_size ())); + } +} + +void +J2KImageProxy::send_binary (shared_ptr<Socket> socket) const +{ + if (_mono) { + socket->write (_mono->j2k_data(), _mono->j2k_size ()); + } else { + socket->write (_stereo->left_j2k_data(), _stereo->left_j2k_size ()); + socket->write (_stereo->right_j2k_data(), _stereo->right_j2k_size ()); + } +} + +shared_ptr<EncodedData> +J2KImageProxy::j2k () const +{ + if (_mono) { + return shared_ptr<EncodedData> (new EncodedData (_mono->j2k_data(), _mono->j2k_size())); + } else { + if (_eye == dcp::EYE_LEFT) { + return shared_ptr<EncodedData> (new EncodedData (_stereo->left_j2k_data(), _stereo->left_j2k_size())); + } else { + return shared_ptr<EncodedData> (new EncodedData (_stereo->right_j2k_data(), _stereo->right_j2k_size())); + } + } +} diff --git a/src/lib/j2k_image_proxy.h b/src/lib/j2k_image_proxy.h new file mode 100644 index 000000000..d7b5c83fc --- /dev/null +++ b/src/lib/j2k_image_proxy.h @@ -0,0 +1,46 @@ +/* + 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/util.h> +#include "image_proxy.h" + +class EncodedData; + +class J2KImageProxy : public ImageProxy +{ +public: + J2KImageProxy (boost::shared_ptr<const dcp::MonoPictureFrame> frame, dcp::Size, boost::shared_ptr<Log> log); + J2KImageProxy (boost::shared_ptr<const dcp::StereoPictureFrame> frame, dcp::Size, dcp::Eye, boost::shared_ptr<Log> log); + J2KImageProxy (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket, boost::shared_ptr<Log> log); + + boost::shared_ptr<Image> image () const; + void add_metadata (xmlpp::Node *) const; + void send_binary (boost::shared_ptr<Socket>) const; + + boost::shared_ptr<EncodedData> j2k () const; + dcp::Size size () const { + return _size; + } + +private: + boost::shared_ptr<const dcp::MonoPictureFrame> _mono; + boost::shared_ptr<const dcp::StereoPictureFrame> _stereo; + dcp::Size _size; + dcp::Eye _eye; +}; diff --git a/src/lib/job.cc b/src/lib/job.cc index 52ec1426c..1d3cb73b6 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,13 +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 "safe_stringstream.h" +#include "film.h" +#include "log.h" #include "i18n.h" @@ -66,8 +67,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 +205,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 +280,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 f5054b8ed..108860594 100644 --- a/src/lib/kdm.cc +++ b/src/lib/kdm.cc @@ -21,7 +21,8 @@ #include <boost/shared_ptr.hpp> #include <quickmail.h> #include <zip.h> -#include <libdcp/kdm.h> +#include <dcp/encrypted_kdm.h> +#include <dcp/types.h> #include "kdm.h" #include "cinema.h" #include "exceptions.h" @@ -37,13 +38,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 @@ -104,17 +105,17 @@ 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, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation ) { - list<libdcp::KDM> kdms = film->make_kdms (screens, cpl, from, to, formulation); + list<dcp::EncryptedKDM> kdms = film->make_kdms (screens, cpl, from, to, formulation); 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; @@ -129,9 +130,9 @@ 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, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation ) { list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, cpl, from, to, formulation); @@ -175,9 +176,9 @@ 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, - libdcp::KDM::Formulation formulation, + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation, boost::filesystem::path directory ) { @@ -196,9 +197,9 @@ 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, - libdcp::KDM::Formulation formulation, + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation, boost::filesystem::path directory ) { @@ -216,9 +217,9 @@ 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, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation ) { list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, cpl, from, to, formulation); diff --git a/src/lib/kdm.h b/src/lib/kdm.h index 8fb4ec494..b80ef454f 100644 --- a/src/lib/kdm.h +++ b/src/lib/kdm.h @@ -27,9 +27,9 @@ 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, - libdcp::KDM::Formulation formulation, + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation, boost::filesystem::path directory ); @@ -37,9 +37,9 @@ 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, - libdcp::KDM::Formulation formulation, + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation, boost::filesystem::path directory ); @@ -47,8 +47,8 @@ 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, - libdcp::KDM::Formulation formulation + dcp::LocalTime from, + dcp::LocalTime to, + dcp::Formulation formulation ); diff --git a/src/lib/magick_image_proxy.cc b/src/lib/magick_image_proxy.cc new file mode 100644 index 000000000..e5265187f --- /dev/null +++ b/src/lib/magick_image_proxy.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 <Magick++.h> +#include "magick_image_proxy.h" +#include "cross.h" +#include "exceptions.h" +#include "util.h" +#include "log.h" +#include "image.h" +#include "log.h" + +#include "i18n.h" + +#define LOG_TIMING(...) _log->microsecond_log (String::compose (__VA_ARGS__), Log::TYPE_TIMING); + +using std::string; +using std::cout; +using boost::shared_ptr; +using boost::dynamic_pointer_cast; + +MagickImageProxy::MagickImageProxy (boost::filesystem::path path, shared_ptr<Log> log) + : ImageProxy (log) +{ + /* Read the file into a Blob */ + + boost::uintmax_t const size = boost::filesystem::file_size (path); + FILE* f = fopen_boost (path, "rb"); + if (!f) { + throw OpenFileError (path); + } + + uint8_t* data = new uint8_t[size]; + if (fread (data, 1, size, f) != size) { + delete[] data; + throw ReadFileError (path); + } + + fclose (f); + _blob.update (data, size); + delete[] data; +} + +MagickImageProxy::MagickImageProxy (shared_ptr<cxml::Node>, shared_ptr<Socket> socket, shared_ptr<Log> log) + : ImageProxy (log) +{ + uint32_t const size = socket->read_uint32 (); + uint8_t* data = new uint8_t[size]; + socket->read (data, size); + _blob.update (data, size); + delete[] data; +} + +shared_ptr<Image> +MagickImageProxy::image () const +{ + if (_image) { + return _image; + } + + LOG_TIMING ("[%1] MagickImageProxy begins decode and convert of %2 bytes", boost::this_thread::get_id(), _blob.length()); + + Magick::Image* magick_image = 0; + string error; + try { + magick_image = new Magick::Image (_blob); + } catch (Magick::Exception& e) { + error = e.what (); + } + + if (!magick_image) { + /* ImageMagick cannot auto-detect Targa files, it seems, so try here with an + explicit format. I can't find it documented that passing a (0, 0) geometry + is allowed, but it seems to work. + */ + try { + magick_image = new Magick::Image (_blob, Magick::Geometry (0, 0), "TGA"); + } catch (...) { + + } + } + + if (!magick_image) { + /* If we failed both an auto-detect and a forced-Targa we give the error from + the auto-detect. + */ + throw DecodeError (String::compose (_("Could not decode image file (%1)"), error)); + } + + dcp::Size size (magick_image->columns(), magick_image->rows()); + LOG_TIMING ("[%1] MagickImageProxy decode finished", boost::this_thread::get_id ()); + + _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 */ + uint8_t* p = _image->data()[0]; + for (int i = 0; i < size.height; ++i) { + using namespace MagickCore; + magick_image->write (0, i, size.width, 1, "RGB", CharPixel, p); + p += _image->stride()[0]; + } + + delete magick_image; + + LOG_TIMING ("[%1] MagickImageProxy completes decode and convert of %2 bytes", boost::this_thread::get_id(), _blob.length()); + + return _image; +} + +void +MagickImageProxy::add_metadata (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text (N_("Magick")); +} + +void +MagickImageProxy::send_binary (shared_ptr<Socket> socket) const +{ + socket->write (_blob.length ()); + socket->write ((uint8_t *) _blob.data (), _blob.length ()); +} + +bool +MagickImageProxy::same (shared_ptr<const ImageProxy> other) const +{ + shared_ptr<const MagickImageProxy> mp = dynamic_pointer_cast<const MagickImageProxy> (other); + if (!mp) { + return false; + } + + if (_blob.length() != mp->_blob.length()) { + return false; + } + + return memcmp (_blob.data(), mp->_blob.data(), _blob.length()) == 0; +} diff --git a/src/lib/magick_image_proxy.h b/src/lib/magick_image_proxy.h new file mode 100644 index 000000000..8b43d0a00 --- /dev/null +++ b/src/lib/magick_image_proxy.h @@ -0,0 +1,36 @@ +/* + 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 "image_proxy.h" + +class MagickImageProxy : public ImageProxy +{ +public: + MagickImageProxy (boost::filesystem::path, boost::shared_ptr<Log> log); + MagickImageProxy (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket, boost::shared_ptr<Log> log); + + boost::shared_ptr<Image> image () const; + void add_metadata (xmlpp::Node *) const; + void send_binary (boost::shared_ptr<Socket>) const; + bool same (boost::shared_ptr<const ImageProxy> other) const; + +private: + Magick::Blob _blob; + mutable boost::shared_ptr<Image> _image; +}; diff --git a/src/lib/mid_side_decoder.cc b/src/lib/mid_side_decoder.cc new file mode 100644 index 000000000..be82f6754 --- /dev/null +++ b/src/lib/mid_side_decoder.cc @@ -0,0 +1,72 @@ +/* + 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 "mid_side_decoder.h" +#include "audio_buffers.h" + +#include "i18n.h" + +using std::string; +using boost::shared_ptr; + +string +MidSideDecoder::name () const +{ + return _("Mid-side decoder"); +} + +string +MidSideDecoder::id () const +{ + return N_("mid-side-decoder"); +} + +ChannelCount +MidSideDecoder::in_channels () const +{ + return ChannelCount (2); +} + +int +MidSideDecoder::out_channels (int) const +{ + return 3; +} + +shared_ptr<AudioProcessor> +MidSideDecoder::clone (int) const +{ + return shared_ptr<AudioProcessor> (new MidSideDecoder ()); +} + +shared_ptr<AudioBuffers> +MidSideDecoder::run (shared_ptr<const AudioBuffers> in) +{ + shared_ptr<AudioBuffers> out (new AudioBuffers (3, in->frames ())); + for (int i = 0; i < in->frames(); ++i) { + float const left = in->data()[0][i]; + float const right = in->data()[1][i]; + float const mid = (left + right) / 2; + out->data()[0][i] = left - mid; + out->data()[1][i] = right - mid; + out->data()[2][i] = mid; + } + + return out; +} diff --git a/src/lib/mid_side_decoder.h b/src/lib/mid_side_decoder.h new file mode 100644 index 000000000..dac6cb7d9 --- /dev/null +++ b/src/lib/mid_side_decoder.h @@ -0,0 +1,33 @@ +/* + 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 "audio_processor.h" + +class MidSideDecoder : public AudioProcessor +{ +public: + std::string name () const; + std::string id () const; + ChannelCount in_channels () const; + int out_channels (int) const; + boost::shared_ptr<AudioProcessor> clone (int) const; + boost::shared_ptr<AudioBuffers> run (boost::shared_ptr<const AudioBuffers>); +}; + + 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 8063d1212..db99cd2ad 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -17,26 +17,38 @@ */ -#include <stdint.h> #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 "dcp_content.h" #include "playlist.h" #include "job.h" #include "image.h" -#include "image_proxy.h" +#include "raw_image_proxy.h" #include "ratio.h" -#include "resampler.h" #include "log.h" #include "scaler.h" -#include "player_video_frame.h" +#include "render_subtitles.h" +#include "config.h" +#include "content_video.h" +#include "player_video.h" #include "frame_rate_change.h" +#include "dcp_content.h" +#include "dcp_decoder.h" +#include "dcp_subtitle_content.h" +#include "dcp_subtitle_decoder.h" +#include <boost/foreach.hpp> +#include <stdint.h> +#include <algorithm> #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); @@ -44,23 +56,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,579 +79,508 @@ Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p) } void -Player::disable_video () -{ - _video = false; -} - -void -Player::disable_audio () +Player::setup_pieces () { - _audio = false; -} + list<shared_ptr<Piece> > old_pieces = _pieces; + _pieces.clear (); -bool -Player::pass () -{ - if (!_have_valid_pieces) { - setup_pieces (); - } + ContentList content = _playlist->content (); - Time earliest_t = TIME_MAX; - shared_ptr<Piece> earliest; - enum { - VIDEO, - AUDIO - } type = VIDEO; + for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { - for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { - if ((*i)->decoder->done () || (*i)->content->length_after_trim() == 0) { + if (!(*i)->paths_valid ()) { continue; } - - shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder); - shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder); - - if (_video && vd) { - if ((*i)->video_position < earliest_t) { - earliest_t = (*i)->video_position; - earliest = *i; - type = VIDEO; + + shared_ptr<Decoder> decoder; + optional<FrameRateChange> frc; + + /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */ + DCPTime best_overlap_t; + shared_ptr<VideoContent> best_overlap; + for (ContentList::iterator j = content.begin(); j != content.end(); ++j) { + shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j); + if (!vc) { + continue; + } + + DCPTime const overlap = max (vc->position(), (*i)->position()) - min (vc->end(), (*i)->end()); + if (overlap > best_overlap_t) { + best_overlap = vc; + best_overlap_t = overlap; } } - if (_audio && ad && ad->has_audio ()) { - if ((*i)->audio_position < earliest_t) { - earliest_t = (*i)->audio_position; - earliest = *i; - type = AUDIO; - } + 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 ()); } - } - if (!earliest) { - flush (); - return true; - } + /* FFmpeg */ + shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i); + if (fc) { + decoder.reset (new FFmpegDecoder (fc, _film->log())); + frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate()); + } - switch (type) { - case VIDEO: - if (earliest_t > _video_position) { - emit_black (); - } else { - if (earliest->repeating ()) { - earliest->repeat (this); - } else { - earliest->decoder->pass (); - } + shared_ptr<const DCPContent> dc = dynamic_pointer_cast<const DCPContent> (*i); + if (dc) { + decoder.reset (new DCPDecoder (dc, _film->log ())); + frc = FrameRateChange (dc->video_frame_rate(), _film->video_frame_rate()); } - break; - case AUDIO: - if (earliest_t > _audio_position) { - emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position)); - } else { - earliest->decoder->pass (); - - if (earliest->decoder->done()) { - shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content); - assert (ac); - shared_ptr<Resampler> re = resampler (ac, false); - if (re) { - shared_ptr<const AudioBuffers> b = re->flush (); - if (b->frames ()) { - process_audio ( - earliest, - b, - ac->audio_length() * ac->output_audio_frame_rate() / ac->content_audio_frame_rate(), - true - ); - } + /* ImageContent */ + shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i); + if (ic) { + /* See if we can re-use an old ImageDecoder */ + for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) { + shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder); + if (imd && imd->content() == ic) { + decoder = imd; } } - } - 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; + if (!decoder) { + decoder.reset (new ImageDecoder (ic)); } - 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); - } + frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate()); } - if (audio_done_up_to) { - TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to.get ()); - Audio (tb.audio, tb.time); - _audio_position += _film->audio_frames_to_time (tb.audio->frames ()); + /* SndfileContent */ + shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i); + if (sc) { + decoder.reset (new SndfileDecoder (sc)); + frc = best_overlap_frc; } - } - - 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); - } + /* SubRipContent */ + shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i); + if (rc) { + decoder.reset (new SubRipDecoder (rc)); + frc = best_overlap_frc; } - } - /* 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); + /* DCPSubtitleContent */ + shared_ptr<const DCPSubtitleContent> dsc = dynamic_pointer_cast<const DCPSubtitleContent> (*i); + if (dsc) { + decoder.reset (new DCPSubtitleDecoder (dsc)); + frc = best_overlap_frc; } - i = j; + _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ()))); } -#ifdef DCPOMATIC_DEBUG - _last_video = piece->content; -#endif + _have_valid_pieces = true; +} - Video (pi, same, time); +void +Player::content_changed (weak_ptr<Content> w, int property, bool frequent) +{ + shared_ptr<Content> c = w.lock (); + if (!c) { + return; + } - _last_emit_was_black = false; - _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate()); + if ( + property == ContentProperty::POSITION || + property == ContentProperty::LENGTH || + property == ContentProperty::TRIM_START || + property == ContentProperty::TRIM_END || + property == ContentProperty::PATH || + property == VideoContentProperty::VIDEO_FRAME_TYPE || + property == DCPContentProperty::CAN_BE_PLAYED + ) { + + _have_valid_pieces = false; + Changed (frequent); - if (frc.repeat > 1 && !piece->repeating ()) { - piece->set_repeat (_last_incoming_video, frc.repeat - 1); + } else if ( + property == SubtitleContentProperty::USE_SUBTITLES || + property == SubtitleContentProperty::SUBTITLE_X_OFFSET || + property == SubtitleContentProperty::SUBTITLE_Y_OFFSET || + property == SubtitleContentProperty::SUBTITLE_X_SCALE || + property == SubtitleContentProperty::SUBTITLE_Y_SCALE || + property == VideoContentProperty::VIDEO_CROP || + property == VideoContentProperty::VIDEO_SCALE || + property == VideoContentProperty::VIDEO_FRAME_RATE + ) { + + Changed (frequent); } } /** @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) +Player::playlist_changed () { - shared_ptr<Piece> piece = weak_piece.lock (); - if (!piece) { - return; - } + _have_valid_pieces = false; + Changed (false); +} - shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content); - assert (content); +void +Player::set_video_container_size (dcp::Size s) +{ + _video_container_size = s; - 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); + _black_image.reset (new Image (PIX_FMT_RGB24, _video_container_size, true)); + _black_image->make_black (); +} - if (content->trimmed (relative_time)) { - return; - } +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. + */ - 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)) - ); - } - } + if (p == Film::SCALER || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) { + Changed (false); } +} - 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; +list<PositionImage> +Player::transform_image_subtitles (list<ImageSubtitle> subs) const +{ + list<PositionImage> all; + + for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) { + if (!i->image) { + continue; } - 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; + /* We will scale the subtitle up to fit _video_container_size */ + dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height); + + /* 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_x_scale) / 2) and + * (height_before_subtitle_scale * (1 - subtitle_y_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 * i->rectangle.x), + rint (_video_container_size.height * i->rectangle.y) + ) + ) + ); } - _audio_merger.push (audio, time); - piece->audio_position += _film->audio_frames_to_time (audio->frames ()); + return all; } void -Player::flush () +Player::set_approximate_size () { - TimedAudioBuffers<Time> tb = _audio_merger.flush (); - if (_audio && tb.audio) { - Audio (tb.audio, tb.time); - _audio_position += _film->audio_frames_to_time (tb.audio->frames ()); - } - - while (_video && _video_position < _audio_position) { - emit_black (); - } + _approximate_size = true; +} - while (_audio && _audio_position < _video_position) { - emit_silence (_film->time_to_audio_frames (_video_position - _audio_position)); - } - +shared_ptr<PlayerVideo> +Player::black_player_video_frame (DCPTime time) const +{ + return shared_ptr<PlayerVideo> ( + new PlayerVideo ( + shared_ptr<const ImageProxy> (new RawImageProxy (_black_image, _film->log ())), + time, + Crop (), + optional<float> (), + _video_container_size, + _video_container_size, + Scaler::from_id ("bicubic"), + EYES_BOTH, + PART_WHOLE, + Config::instance()->colour_conversions().front().conversion + ) + ); } -/** Seek so that the next pass() will yield (approximately) the requested frame. - * Pass accurate = true to try harder to get close to the request. - * @return true on error - */ -void -Player::seek (Time t, bool accurate) +/** @return All PlayerVideos at the given time (there may be two frames for 3D) */ +list<shared_ptr<PlayerVideo> > +Player::get_video (DCPTime time, bool accurate) { if (!_have_valid_pieces) { setup_pieces (); } + + list<shared_ptr<Piece> > ov = overlaps<VideoContent> ( + time, + time + DCPTime::from_frames (1, _film->video_frame_rate ()) + ); - if (_pieces.empty ()) { - return; - } + list<shared_ptr<PlayerVideo> > pvf; - for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { - shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content); - if (!vc) { - continue; + if (ov.empty ()) { + /* No video content at this time */ + pvf.push_back (black_player_video_frame (time)); + } else { + /* Create a PlayerVideo 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 (time)); + return pvf; } + + dcp::Size image_size = content->scale().size (content, _video_container_size, _film->frame_size (), _approximate_size ? 4 : 1); + 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<PlayerVideo> ( + new PlayerVideo ( + i->image, + content_video_to_dcp (piece, i->frame), + content->crop (), + content->fade (i->frame), + image_size, + _video_container_size, + _film->scaler(), + i->eyes, + i->part, + content->colour_conversion () + ) + ) + ); + } + } - /* 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; + /* Add subtitles (for possible burn-in) to whatever PlayerVideos we got */ - /* And seek the decoder */ - dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek ( - vc->time_to_content_video_frames (s + vc->trim_start ()), accurate - ); + PlayerSubtitles ps = get_subtitles (time, DCPTime::from_frames (1, _film->video_frame_rate ()), false); - (*i)->reset_repeat (); - } + list<PositionImage> sub_images; - _video_position = _audio_position = t; + /* Image subtitles */ + list<PositionImage> c = transform_image_subtitles (ps.image); + copy (c.begin(), c.end(), back_inserter (sub_images)); - /* XXX: don't seek audio because we don't need to... */ + /* Text subtitles (rendered to images) */ + sub_images.push_back (render_subtitles (ps.text, _video_container_size)); + + if (!sub_images.empty ()) { + for (list<shared_ptr<PlayerVideo> >::const_iterator i = pvf.begin(); i != pvf.end(); ++i) { + (*i)->set_subtitle (merge (sub_images)); + } + } + + return pvf; } -void -Player::setup_pieces () +shared_ptr<AudioBuffers> +Player::get_audio (DCPTime time, DCPTime length, bool accurate) { - list<shared_ptr<Piece> > old_pieces = _pieces; + if (!_have_valid_pieces) { + setup_pieces (); + } - _pieces.clear (); + AudioFrame const length_frames = length.frames (_film->audio_frame_rate ()); - ContentList content = _playlist->content (); - sort (content.begin(), content.end(), ContentSorter ()); + shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames)); + audio->make_silent (); + + list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time, time + length); + if (ov.empty ()) { + return audio; + } - for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { + for (list<shared_ptr<Piece> >::iterator i = ov.begin(); i != ov.end(); ++i) { - if (!(*i)->paths_valid ()) { + shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> ((*i)->content); + assert (content); + shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder); + assert (decoder); + + if (content->audio_frame_rate() == 0) { + /* This AudioContent has no audio (e.g. if it is an FFmpegContent with no + * audio stream). + */ continue; } - shared_ptr<Piece> piece (new Piece (*i)); + /* 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 (); + } - /* XXX: into content? */ + AudioFrame const content_frame = dcp_to_content_audio (*i, request); - 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)); + /* 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); - fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true); - piece->decoder = fd; + /* Gain */ + if (content->audio_gain() != 0) { + shared_ptr<AudioBuffers> gain (new AudioBuffers (all->audio)); + gain->apply_gain (content->audio_gain ()); + all->audio = gain; } - - shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i); - if (ic) { - bool reusing = false; - - /* See if we can re-use an old ImageDecoder */ - for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) { - shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder); - if (imd && imd->content() == ic) { - piece = *j; - reusing = true; - } - } - if (!reusing) { - shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic)); - id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, _5, 0)); - piece->decoder = id; + /* 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; - 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)); - - piece->decoder = sd; - } - - _pieces.push_back (piece); + 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 ()) + ); } - _have_valid_pieces = true; + return audio; } -void -Player::content_changed (weak_ptr<Content> w, int property, bool frequent) +VideoFrame +Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const { - shared_ptr<Content> c = w.lock (); - if (!c) { - return; - } + /* s is the offset of t from the start position of this content */ + DCPTime s = t - piece->content->position (); + s = DCPTime (max (DCPTime::Type (0), s.get ())); + s = DCPTime (min (piece->content->length_after_trim().get(), s.get())); - if ( - property == ContentProperty::POSITION || property == ContentProperty::LENGTH || - property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END || - property == VideoContentProperty::VIDEO_FRAME_TYPE - ) { - - _have_valid_pieces = false; - Changed (frequent); - - } else if ( - property == SubtitleContentProperty::SUBTITLE_X_OFFSET || - property == SubtitleContentProperty::SUBTITLE_Y_OFFSET || - property == SubtitleContentProperty::SUBTITLE_X_SCALE || - property == SubtitleContentProperty::SUBTITLE_Y_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 == VideoContentProperty::VIDEO_FRAME_RATE - ) { - - Changed (frequent); - - } else if (property == ContentProperty::PATH) { - - _have_valid_pieces = false; - Changed (frequent); - } + /* Convert this to the content frame */ + return DCPTime (s + piece->content->trim_start()).frames (_film->video_frame_rate()) * piece->frc.factor (); } -void -Player::playlist_changed () +DCPTime +Player::content_video_to_dcp (shared_ptr<const Piece> piece, VideoFrame f) const { - _have_valid_pieces = false; - Changed (false); + DCPTime t = DCPTime::from_frames (f / piece->frc.factor (), _film->video_frame_rate()) - piece->content->trim_start () + piece->content->position (); + if (t < DCPTime ()) { + t = DCPTime (); + } + + return t; } -void -Player::set_video_container_size (libdcp::Size s) +AudioFrame +Player::dcp_to_content_audio (shared_ptr<const Piece> piece, DCPTime t) const { - _video_container_size = s; + /* s is the offset of t from the start position of this content */ + DCPTime s = t - piece->content->position (); + s = DCPTime (max (DCPTime::Type (0), s.get ())); + s = DCPTime (min (piece->content->length_after_trim().get(), s.get())); - shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true)); - im->make_black (); - - _black_frame.reset ( - new PlayerVideoFrame ( - shared_ptr<ImageProxy> (new RawImageProxy (im, _film->log ())), - Crop(), - _video_container_size, - _video_container_size, - Scaler::from_id ("bicubic"), - EYES_BOTH, - PART_WHOLE, - ColourConversion () - ) - ); + /* Convert this to the content frame */ + return DCPTime (s + piece->content->trim_start()).frames (_film->audio_frame_rate()); } -shared_ptr<Resampler> -Player::resampler (shared_ptr<AudioContent> c, bool create) +ContentTime +Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const { - map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c); - if (i != _resamplers.end ()) { - return i->second; - } - - if (!create) { - return shared_ptr<Resampler> (); - } - - 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() - ); + /* s is the offset of t from the start position of this content */ + DCPTime s = t - piece->content->position (); + s = DCPTime (max (DCPTime::Type (0), s.get ())); + s = DCPTime (min (piece->content->length_after_trim().get(), s.get())); - shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels())); - _resamplers[c] = r; - return r; + return ContentTime (s + piece->content->trim_start(), piece->frc); } void -Player::emit_black () +PlayerStatistics::dump (shared_ptr<Log> log) const { -#ifdef DCPOMATIC_DEBUG - _last_video.reset (); -#endif - - Video (_black_frame, _last_emit_was_black, _video_position); - _video_position += _film->video_frames_to_time (1); - _last_emit_was_black = true; + 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); } -void -Player::emit_silence (OutputAudioFrame most) +PlayerStatistics const & +Player::statistics () const { - if (most == 0) { - return; - } - - OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2); - shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N)); - silence->make_silent (); - Audio (silence, _audio_position); - _audio_position += _film->audio_frames_to_time (N); + return _statistics; } -void -Player::film_changed (Film::Property p) +PlayerSubtitles +Player::get_subtitles (DCPTime time, DCPTime length, bool starting) { - /* 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. - */ + list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> (time, time + length); - if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) { - Changed (false); - } -} + PlayerSubtitles ps (time, length); -void -Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to) -{ - 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); + for (list<shared_ptr<Piece> >::const_iterator j = subs.begin(); j != subs.end(); ++j) { + shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*j)->content); + if (!subtitle_content->use_subtitles ()) { + continue; } - } else { - _subtitles.push_back (Subtitle (_film, _video_container_size, weak_piece, image, rect, from, to)); - } -} -/** 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 () -{ - if (!_last_incoming_video.image || !_have_valid_pieces) { - return false; - } + shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*j)->decoder); + 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 ()); - 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 - ); + list<ContentImageSubtitle> image = subtitle_decoder->get_image_subtitles (ContentTimePeriod (from, to), starting); + for (list<ContentImageSubtitle>::iterator i = image.begin(); i != image.end(); ++i) { + + /* Apply content's subtitle offsets */ + i->sub.rectangle.x += subtitle_content->subtitle_x_offset (); + i->sub.rectangle.y += subtitle_content->subtitle_y_offset (); + + /* Apply content's subtitle scale */ + i->sub.rectangle.width *= subtitle_content->subtitle_x_scale (); + i->sub.rectangle.height *= subtitle_content->subtitle_y_scale (); + + /* Apply a corrective translation to keep the subtitle centred after that scale */ + i->sub.rectangle.x -= i->sub.rectangle.width * (subtitle_content->subtitle_x_scale() - 1); + i->sub.rectangle.y -= i->sub.rectangle.height * (subtitle_content->subtitle_y_scale() - 1); + + ps.image.push_back (i->sub); + } + + list<ContentTextSubtitle> text = subtitle_decoder->get_text_subtitles (ContentTimePeriod (from, to), starting); + BOOST_FOREACH (ContentTextSubtitle& ts, text) { + BOOST_FOREACH (dcp::SubtitleString& s, ts.subs) { + s.set_v_position (s.v_position() + subtitle_content->subtitle_y_offset ()); + s.set_size (s.size() * max (subtitle_content->subtitle_x_scale(), subtitle_content->subtitle_y_scale())); + ps.text.push_back (s); + } + } + } - return true; + return ps; } diff --git a/src/lib/player.h b/src/lib/player.h index 6e70ad707..a3b745c8e 100644 --- a/src/lib/player.h +++ b/src/lib/player.h @@ -27,10 +27,13 @@ #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" +#include "player_subtitles.h" class Job; class Film; @@ -38,42 +41,60 @@ class Playlist; class AudioContent; class Piece; class Image; +class Decoder; class Resampler; -class PlayerVideoFrame; +class PlayerVideo; 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<PlayerVideo> > get_video (DCPTime time, bool accurate); + boost::shared_ptr<AudioBuffers> get_audio (DCPTime time, DCPTime length, bool accurate); + PlayerSubtitles get_subtitles (DCPTime time, DCPTime length, bool starting); - 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 +106,58 @@ public: private: friend class PlayerWrapper; friend class Piece; + friend struct 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> transform_image_subtitles (std::list<ImageSubtitle>) const; + void update_subtitle_from_text (); + VideoFrame dcp_to_content_video (boost::shared_ptr<const Piece> piece, DCPTime t) const; + DCPTime content_video_to_dcp (boost::shared_ptr<const Piece> piece, VideoFrame f) 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<PlayerVideo> black_player_video_frame (DCPTime) 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_subtitles.h b/src/lib/player_subtitles.h new file mode 100644 index 000000000..46994ea3b --- /dev/null +++ b/src/lib/player_subtitles.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. + +*/ + +#ifndef DCPOMATIC_PLAYER_SUBTITLES_H +#define DCPOMATIC_PLAYER_SUBTITLES_H + +#include <dcp/subtitle_string.h> +#include "image_subtitle.h" + +class PlayerSubtitles +{ +public: + PlayerSubtitles (DCPTime f, DCPTime t) + : from (f) + , to (t) + {} + + DCPTime from; + DCPTime to; + + /** ImageSubtitles, with their rectangles transformed as specified by their content */ + std::list<ImageSubtitle> image; + std::list<dcp::SubtitleString> text; +}; + +#endif diff --git a/src/lib/player_video.cc b/src/lib/player_video.cc new file mode 100644 index 000000000..2feb52f42 --- /dev/null +++ b/src/lib/player_video.cc @@ -0,0 +1,209 @@ +/* + 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 <dcp/raw_convert.h> +#include "player_video.h" +#include "image.h" +#include "image_proxy.h" +#include "j2k_image_proxy.h" +#include "scaler.h" + +using std::string; +using std::cout; +using dcp::raw_convert; +using boost::shared_ptr; +using boost::dynamic_pointer_cast; + +PlayerVideo::PlayerVideo ( + shared_ptr<const ImageProxy> in, + DCPTime time, + Crop crop, + boost::optional<float> fade, + dcp::Size inter_size, + dcp::Size out_size, + Scaler const * scaler, + Eyes eyes, + Part part, + ColourConversion colour_conversion + ) + : _in (in) + , _time (time) + , _crop (crop) + , _fade (fade) + , _inter_size (inter_size) + , _out_size (out_size) + , _scaler (scaler) + , _eyes (eyes) + , _part (part) + , _colour_conversion (colour_conversion) +{ + +} + +PlayerVideo::PlayerVideo (shared_ptr<cxml::Node> node, shared_ptr<Socket> socket, shared_ptr<Log> log) +{ + _time = DCPTime (node->number_child<DCPTime::Type> ("Time")); + _crop = Crop (node); + _fade = node->optional_number_child<float> ("Fade"); + + _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"); + _colour_conversion = ColourConversion (node); + + _in = image_proxy_factory (node->node_child ("In"), socket, log); + + if (node->optional_number_child<int> ("SubtitleX")) { + + _subtitle.position = Position<int> (node->number_child<int> ("SubtitleX"), node->number_child<int> ("SubtitleY")); + + _subtitle.image.reset ( + new Image (PIX_FMT_RGBA, dcp::Size (node->number_child<int> ("SubtitleWidth"), node->number_child<int> ("SubtitleHeight")), true) + ); + + _subtitle.image->read_from_socket (socket); + } +} + +void +PlayerVideo::set_subtitle (PositionImage image) +{ + _subtitle = image; +} + +shared_ptr<Image> +PlayerVideo::image (bool burn_subtitle) const +{ + shared_ptr<Image> im = _in->image (); + + Crop total_crop = _crop; + switch (_part) { + case PART_LEFT_HALF: + total_crop.right += im->size().width / 2; + break; + case PART_RIGHT_HALF: + total_crop.left += im->size().width / 2; + break; + case PART_TOP_HALF: + total_crop.bottom += im->size().height / 2; + break; + case PART_BOTTOM_HALF: + total_crop.top += im->size().height / 2; + break; + default: + break; + } + + shared_ptr<Image> out = im->crop_scale_window (total_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, true); + + if (burn_subtitle && _subtitle.image) { + out->alpha_blend (_subtitle.image, _subtitle.position); + } + + if (_fade) { + out->fade (_fade.get ()); + } + + return out; +} + +void +PlayerVideo::add_metadata (xmlpp::Node* node, bool send_subtitles) const +{ + node->add_child("Time")->add_child_text (raw_convert<string> (_time.get ())); + _crop.as_xml (node); + if (_fade) { + node->add_child("Fade")->add_child_text (raw_convert<string> (_fade.get ())); + } + _in->add_metadata (node->add_child ("In")); + node->add_child("InterWidth")->add_child_text (raw_convert<string> (_inter_size.width)); + node->add_child("InterHeight")->add_child_text (raw_convert<string> (_inter_size.height)); + node->add_child("OutWidth")->add_child_text (raw_convert<string> (_out_size.width)); + node->add_child("OutHeight")->add_child_text (raw_convert<string> (_out_size.height)); + node->add_child("Scaler")->add_child_text (_scaler->id ()); + 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 (send_subtitles && _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)); + } +} + +void +PlayerVideo::send_binary (shared_ptr<Socket> socket, bool send_subtitles) const +{ + _in->send_binary (socket); + if (send_subtitles && _subtitle.image) { + _subtitle.image->write_to_socket (socket); + } +} + +bool +PlayerVideo::has_j2k () const +{ + /* XXX: burnt-in subtitle; maybe other things */ + + shared_ptr<const J2KImageProxy> j2k = dynamic_pointer_cast<const J2KImageProxy> (_in); + if (!j2k) { + return false; + } + + return _crop == Crop () && _inter_size == j2k->size(); +} + +shared_ptr<EncodedData> +PlayerVideo::j2k () const +{ + shared_ptr<const J2KImageProxy> j2k = dynamic_pointer_cast<const J2KImageProxy> (_in); + assert (j2k); + return j2k->j2k (); +} + +Position<int> +PlayerVideo::inter_position () const +{ + return Position<int> ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.height) / 2); +} + +/** @return true if this PlayerVideo is definitely the same as another + * (apart from _time), false if it is probably not + */ +bool +PlayerVideo::same (shared_ptr<const PlayerVideo> other) const +{ + if (_in != other->_in || + _crop != other->_crop || + _fade.get_value_or(0) != other->_fade.get_value_or(0) || + _inter_size != other->_inter_size || + _out_size != other->_out_size || + _scaler != other->_scaler || + _eyes != other->_eyes || + _part != other->_part || + _colour_conversion != other->_colour_conversion || + !_subtitle.same (other->_subtitle)) { + return false; + } + + return _in->same (other->_in); +} diff --git a/src/lib/player_video_frame.h b/src/lib/player_video.h index b085cb609..0f5e83b10 100644 --- a/src/lib/player_video_frame.h +++ b/src/lib/player_video.h @@ -21,29 +21,50 @@ #include "types.h" #include "position.h" #include "colour_conversion.h" +#include "position_image.h" class Image; class ImageProxy; class Scaler; class Socket; class Log; +class EncodedData; /** Everything needed to describe a video frame coming out of the player, but with the * bits still their raw form. We may want to combine the bits on a remote machine, * or maybe not even bother to combine them at all. */ -class PlayerVideoFrame +class PlayerVideo { public: - PlayerVideoFrame (boost::shared_ptr<const ImageProxy>, Crop, libdcp::Size, libdcp::Size, Scaler const *, Eyes, Part, ColourConversion); - PlayerVideoFrame (boost::shared_ptr<cxml::Node>, boost::shared_ptr<Socket>, boost::shared_ptr<Log>); + PlayerVideo ( + boost::shared_ptr<const ImageProxy>, + DCPTime, + Crop, + boost::optional<float>, + dcp::Size, + dcp::Size, + Scaler const *, + Eyes, + Part, + ColourConversion + ); + + PlayerVideo (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; + boost::shared_ptr<Image> image (bool burn_subtitle) const; + + void add_metadata (xmlpp::Node* node, bool send_subtitles) const; + void send_binary (boost::shared_ptr<Socket> socket, bool send_subtitles) const; - void add_metadata (xmlpp::Node* node) const; - void send_binary (boost::shared_ptr<Socket> socket) const; + bool has_j2k () const; + boost::shared_ptr<EncodedData> j2k () const; + + DCPTime time () const { + return _time; + } Eyes eyes () const { return _eyes; @@ -53,15 +74,26 @@ public: return _colour_conversion; } + /** @return Position of the content within the overall image once it has been scaled up */ + Position<int> inter_position () const; + + /** @return Size of the content within the overall image once it has been scaled up */ + dcp::Size inter_size () const { + return _inter_size; + } + + bool same (boost::shared_ptr<const PlayerVideo> other) const; + private: boost::shared_ptr<const ImageProxy> _in; + DCPTime _time; Crop _crop; - libdcp::Size _inter_size; - libdcp::Size _out_size; + boost::optional<float> _fade; + 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/player_video_frame.cc b/src/lib/player_video_frame.cc deleted file mode 100644 index 94760e495..000000000 --- a/src/lib/player_video_frame.cc +++ /dev/null @@ -1,148 +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 <libdcp/raw_convert.h> -#include "player_video_frame.h" -#include "image.h" -#include "image_proxy.h" -#include "scaler.h" - -using std::string; -using std::cout; -using boost::shared_ptr; -using libdcp::raw_convert; - -PlayerVideoFrame::PlayerVideoFrame ( - shared_ptr<const ImageProxy> in, - Crop crop, - libdcp::Size inter_size, - libdcp::Size out_size, - Scaler const * scaler, - Eyes eyes, - Part part, - ColourConversion colour_conversion - ) - : _in (in) - , _crop (crop) - , _inter_size (inter_size) - , _out_size (out_size) - , _scaler (scaler) - , _eyes (eyes) - , _part (part) - , _colour_conversion (colour_conversion) -{ - -} - -PlayerVideoFrame::PlayerVideoFrame (shared_ptr<cxml::Node> node, shared_ptr<Socket> socket, shared_ptr<Log> log) -{ - _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")); - _scaler = Scaler::from_id (node->string_child ("Scaler")); - _eyes = (Eyes) node->number_child<int> ("Eyes"); - _part = (Part) node->number_child<int> ("Part"); - _colour_conversion = ColourConversion (node); - - _in = image_proxy_factory (node->node_child ("In"), socket, log); - - if (node->optional_number_child<int> ("SubtitleX")) { - - _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) - ); - - image->read_from_socket (socket); - _subtitle_image = image; - } -} - -void -PlayerVideoFrame::set_subtitle (shared_ptr<const Image> image, Position<int> pos) -{ - _subtitle_image = image; - _subtitle_position = pos; -} - -shared_ptr<Image> -PlayerVideoFrame::image () const -{ - shared_ptr<Image> im = _in->image (); - - Crop total_crop = _crop; - switch (_part) { - case PART_LEFT_HALF: - total_crop.right += im->size().width / 2; - break; - case PART_RIGHT_HALF: - total_crop.left += im->size().width / 2; - break; - case PART_TOP_HALF: - total_crop.bottom += im->size().height / 2; - break; - case PART_BOTTOM_HALF: - total_crop.top += im->size().height / 2; - break; - default: - break; - } - - shared_ptr<Image> out = im->crop_scale_window (total_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false); - - 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); - } - - return out; -} - -void -PlayerVideoFrame::add_metadata (xmlpp::Node* node) const -{ - _crop.as_xml (node); - _in->add_metadata (node->add_child ("In")); - node->add_child("InterWidth")->add_child_text (raw_convert<string> (_inter_size.width)); - node->add_child("InterHeight")->add_child_text (raw_convert<string> (_inter_size.height)); - node->add_child("OutWidth")->add_child_text (raw_convert<string> (_out_size.width)); - node->add_child("OutHeight")->add_child_text (raw_convert<string> (_out_size.height)); - node->add_child("Scaler")->add_child_text (_scaler->id ()); - 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)); - } -} - -void -PlayerVideoFrame::send_binary (shared_ptr<Socket> socket) const -{ - _in->send_binary (socket); - if (_subtitle_image) { - _subtitle_image->write_to_socket (socket); - } -} diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc index c3e430082..22412da4a 100644 --- a/src/lib/playlist.cc +++ b/src/lib/playlist.cc @@ -79,20 +79,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 (); } } @@ -120,7 +120,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) { @@ -185,19 +185,6 @@ Playlist::remove (ContentList c) Changed (); } -bool -Playlist::has_subtitles () const -{ - for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) { - shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i); - if (fc && !fc->subtitle_streams().empty()) { - return true; - } - } - - return false; -} - class FrameRateCandidate { public: @@ -261,12 +248,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; @@ -286,10 +273,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 ()); @@ -299,6 +286,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) { @@ -321,7 +325,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 ()); @@ -329,7 +333,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 +367,7 @@ Playlist::move_earlier (shared_ptr<Content> c) } - 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 ()); @@ -392,20 +396,3 @@ Playlist::move_later (shared_ptr<Content> c) c->set_position (c->position() + c->length_after_trim ()); sort (_content.begin(), _content.end(), ContentSorter ()); } - -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 12380696b..9e3dbb6df 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>); @@ -65,17 +63,15 @@ public: void move_earlier (boost::shared_ptr<Content>); void move_later (boost::shared_ptr<Content>); - bool has_subtitles () const; - ContentList content () const; 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..3c561d85c 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,25 @@ 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; +} + +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/decoder.cc b/src/lib/position_image.cc index 3f4cda6eb..44c1262b3 100644 --- a/src/lib/decoder.cc +++ b/src/lib/position_image.cc @@ -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,21 @@ */ -/** @file src/decoder.cc - * @brief Parent class for decoders of content. - */ +#include "position_image.h" +#include "image.h" -#include "film.h" -#include "decoder.h" +using std::cout; -#include "i18n.h" - -using boost::shared_ptr; - -/** @param f Film. - * @param o Decode options. - */ -Decoder::Decoder (shared_ptr<const Film> f) - : _film (f) +bool +PositionImage::same (PositionImage const & other) const { + if (image != other.image || position != other.position) { + return false; + } + + if (!image) { + return true; + } + return *image == *(other.image); } diff --git a/src/lib/position_image.h b/src/lib/position_image.h new file mode 100644 index 000000000..c0c65d1da --- /dev/null +++ b/src/lib/position_image.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. + +*/ + +#ifndef DCPOMATIC_POSITION_IMAGE_H +#define DCPOMATIC_POSITION_IMAGE_H + +#include "position.h" +#include <boost/shared_ptr.hpp> + +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; + + bool same (PositionImage const & other) const; +}; + +#endif diff --git a/src/lib/ratio.cc b/src/lib/ratio.cc index 554d3c36c..fc36415c5 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 ab157a9bc..69e3726c8 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/raw_image_proxy.cc b/src/lib/raw_image_proxy.cc new file mode 100644 index 000000000..7e0688d13 --- /dev/null +++ b/src/lib/raw_image_proxy.cc @@ -0,0 +1,71 @@ +/* + 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. + +*/ + +extern "C" { +#include <libavutil/pixfmt.h> +} +#include <libcxml/cxml.h> +#include <dcp/util.h> +#include <dcp/raw_convert.h> +#include "raw_image_proxy.h" +#include "image.h" + +#include "i18n.h" + +using std::string; +using boost::shared_ptr; + +RawImageProxy::RawImageProxy (shared_ptr<Image> image, shared_ptr<Log> log) + : ImageProxy (log) + , _image (image) +{ + +} + +RawImageProxy::RawImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shared_ptr<Log> log) + : ImageProxy (log) +{ + dcp::Size size ( + xml->number_child<int> ("Width"), xml->number_child<int> ("Height") + ); + + _image.reset (new Image (static_cast<AVPixelFormat> (xml->number_child<int> ("PixelFormat")), size, true)); + _image->read_from_socket (socket); +} + +shared_ptr<Image> +RawImageProxy::image () const +{ + return _image; +} + +void +RawImageProxy::add_metadata (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text (N_("Raw")); + 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 +RawImageProxy::send_binary (shared_ptr<Socket> socket) const +{ + _image->write_to_socket (socket); +} diff --git a/src/lib/raw_image_proxy.h b/src/lib/raw_image_proxy.h new file mode 100644 index 000000000..6707f689c --- /dev/null +++ b/src/lib/raw_image_proxy.h @@ -0,0 +1,34 @@ +/* + 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 "image_proxy.h" + +class RawImageProxy : public ImageProxy +{ +public: + RawImageProxy (boost::shared_ptr<Image>, boost::shared_ptr<Log> log); + RawImageProxy (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket, boost::shared_ptr<Log> log); + + boost::shared_ptr<Image> image () const; + void add_metadata (xmlpp::Node *) const; + void send_binary (boost::shared_ptr<Socket>) const; + +private: + boost::shared_ptr<Image> _image; +}; 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..6f103246c --- /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 * target_height - offset; + case dcp::CENTER: + return (0.5 + v_position) * target_height - offset; + case dcp::BOTTOM: + return (1.0 - v_position) * 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 e7f50c4b7..e414436e8 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/safe_stringstream.h b/src/lib/safe_stringstream.h index e455de964..0ffcb6224 100644 --- a/src/lib/safe_stringstream.h +++ b/src/lib/safe_stringstream.h @@ -84,6 +84,11 @@ public: _stream.width (w); } + void fill (int f) + { + _stream.fill (f); + } + void precision (int p) { _stream.precision (p); diff --git a/src/lib/send_kdm_email_job.cc b/src/lib/send_kdm_email_job.cc index 164dfe987..541307f5a 100644 --- a/src/lib/send_kdm_email_job.cc +++ b/src/lib/send_kdm_email_job.cc @@ -34,7 +34,7 @@ SendKDMEmailJob::SendKDMEmailJob ( boost::filesystem::path dcp, boost::posix_time::ptime from, boost::posix_time::ptime to, - libdcp::KDM::Formulation formulation + dcp::Formulation formulation ) : Job (f) , _screens (screens) diff --git a/src/lib/send_kdm_email_job.h b/src/lib/send_kdm_email_job.h index 778d3927a..af84a13af 100644 --- a/src/lib/send_kdm_email_job.h +++ b/src/lib/send_kdm_email_job.h @@ -18,7 +18,7 @@ */ #include <boost/filesystem.hpp> -#include <libdcp/kdm.h> +#include <dcp/types.h> #include "job.h" class Screen; @@ -32,7 +32,7 @@ public: boost::filesystem::path, boost::posix_time::ptime, boost::posix_time::ptime, - libdcp::KDM::Formulation + dcp::Formulation ); std::string name () const; @@ -43,5 +43,5 @@ private: boost::filesystem::path _dcp; boost::posix_time::ptime _from; boost::posix_time::ptime _to; - libdcp::KDM::Formulation _formulation; + dcp::Formulation _formulation; }; diff --git a/src/lib/server.cc b/src/lib/server.cc index 9428ba611..a699be577 100644 --- a/src/lib/server.cc +++ b/src/lib/server.cc @@ -28,15 +28,16 @@ #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" #include "image.h" -#include "dcp_video_frame.h" +#include "dcp_video.h" #include "config.h" #include "cross.h" -#include "player_video_frame.h" +#include "player_video.h" +#include "encoded_data.h" #include "safe_stringstream.h" #include "i18n.h" @@ -61,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; + _empty_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. */ @@ -90,9 +112,9 @@ Server::process (shared_ptr<Socket> socket, struct timeval& after_read, struct t return -1; } - shared_ptr<PlayerVideoFrame> pvf (new PlayerVideoFrame (xml, socket, _log)); + shared_ptr<PlayerVideo> pvf (new PlayerVideo (xml, socket, _log)); - DCPVideoFrame dcp_video_frame (pvf, xml, _log); + DCPVideo dcp_video_frame (pvf, xml, _log); gettimeofday (&after_read, 0); @@ -116,10 +138,14 @@ Server::worker_thread () { while (true) { boost::mutex::scoped_lock lock (_worker_mutex); - while (_queue.empty ()) { + while (_queue.empty () && !_terminate) { _empty_condition.wait (lock); } + if (_terminate) { + return; + } + shared_ptr<Socket> socket = _queue.front (); _queue.pop_front (); @@ -186,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 (true) { - 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) { - _full_condition.wait (lock); - } - - _queue.push_back (socket); - _empty_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); @@ -228,7 +233,7 @@ try boost::bind (&Server::broadcast_received, this) ); - io_service.run (); + _broadcast.io_service.run (); } catch (...) { @@ -265,3 +270,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) { + _full_condition.wait (lock); + } + + _queue.push_back (socket); + _empty_condition.notify_all (); + + start_accept (); +} + diff --git a/src/lib/server.h b/src/lib/server.h index b925031eb..e2e1d46ec 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,6 +99,10 @@ 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; @@ -107,6 +112,9 @@ private: boost::shared_ptr<Log> _log; bool _verbose; + boost::asio::io_service _io_service; + boost::asio::ip::tcp::acceptor _acceptor; + struct Broadcast { Broadcast () @@ -118,6 +126,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 a082f3bab..637103591 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" @@ -32,7 +32,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/single_stream_audio_content.cc b/src/lib/single_stream_audio_content.cc new file mode 100644 index 000000000..521597606 --- /dev/null +++ b/src/lib/single_stream_audio_content.cc @@ -0,0 +1,106 @@ +/* + 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/raw_convert.h> +#include "single_stream_audio_content.h" +#include "audio_examiner.h" +#include "film.h" + +using std::string; +using std::cout; +using boost::shared_ptr; +using dcp::raw_convert; + +SingleStreamAudioContent::SingleStreamAudioContent (shared_ptr<const Film> f) + : Content (f) + , AudioContent (f) + , _audio_channels (0) + , _audio_length (0) + , _audio_frame_rate (0) +{ + +} + +SingleStreamAudioContent::SingleStreamAudioContent (shared_ptr<const Film> f, boost::filesystem::path p) + : Content (f, p) + , AudioContent (f, p) + , _audio_channels (0) + , _audio_length (0) + , _audio_frame_rate (0) +{ + +} + +SingleStreamAudioContent::SingleStreamAudioContent (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 = ContentTime (node->number_child<ContentTime::Type> ("AudioLength")); + _audio_frame_rate = node->number_child<int> ("AudioFrameRate"); +} + +void +SingleStreamAudioContent::set_audio_mapping (AudioMapping m) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _audio_mapping = m; + } + + AudioContent::set_audio_mapping (m); +} + + +void +SingleStreamAudioContent::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().get ())); + node->add_child("AudioFrameRate")->add_child_text (raw_convert<string> (audio_frame_rate ())); + _audio_mapping.as_xml (node->add_child("AudioMapping")); +} + +void +SingleStreamAudioContent::take_from_audio_examiner (shared_ptr<AudioExaminer> examiner) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _audio_channels = examiner->audio_channels (); + _audio_length = examiner->audio_length (); + _audio_frame_rate = examiner->audio_frame_rate (); + } + + signal_changed (AudioContentProperty::AUDIO_CHANNELS); + signal_changed (AudioContentProperty::AUDIO_LENGTH); + signal_changed (AudioContentProperty::AUDIO_FRAME_RATE); + + int const p = processed_audio_channels (); + + { + boost::mutex::scoped_lock lm (_mutex); + /* XXX: do this in signal_changed...? */ + _audio_mapping = AudioMapping (p); + _audio_mapping.make_default (); + } + + signal_changed (AudioContentProperty::AUDIO_MAPPING); +} diff --git a/src/lib/single_stream_audio_content.h b/src/lib/single_stream_audio_content.h new file mode 100644 index 000000000..fcfaf14ca --- /dev/null +++ b/src/lib/single_stream_audio_content.h @@ -0,0 +1,74 @@ +/* + 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/single_stream_audio_content.h + * @brief SingleStreamAudioContent class. + */ + +#ifndef DCPOMATIC_SINGLE_STREAM_AUDIO_CONTENT_H +#define DCPOMATIC_SINGLE_STREAM_AUDIO_CONTENT_H + +#include "audio_content.h" + +class AudioExaminer; + +/** @class SingleStreamAudioContent + * @brief A piece of AudioContent that has a single audio stream. + */ +class SingleStreamAudioContent : public AudioContent +{ +public: + SingleStreamAudioContent (boost::shared_ptr<const Film>); + SingleStreamAudioContent (boost::shared_ptr<const Film>, boost::filesystem::path); + SingleStreamAudioContent (boost::shared_ptr<const Film> f, cxml::ConstNodePtr node, int version); + + void as_xml (xmlpp::Node* node) const; + + int audio_channels () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_channels; + } + + ContentTime audio_length () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_length; + } + + int audio_frame_rate () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_frame_rate; + } + + AudioMapping audio_mapping () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_mapping; + } + + void set_audio_mapping (AudioMapping); + + void take_from_audio_examiner (boost::shared_ptr<AudioExaminer>); + +protected: + int _audio_channels; + ContentTime _audio_length; + int _audio_frame_rate; + AudioMapping _audio_mapping; +}; + +#endif diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc index ba3bd0a77..1a1797665 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,28 +32,31 @@ using std::string; 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) - , AudioContent (f, p) - , _audio_channels (0) - , _audio_length (0) - , _audio_frame_rate (0) + , SingleStreamAudioContent (f, p) { } -SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version) +SndfileContent::SndfileContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version) : Content (f, node) - , AudioContent (f, node) - , _audio_mapping (node->node_child ("AudioMapping"), version) + , SingleStreamAudioContent (f, node, version) { - _audio_channels = node->number_child<int> ("AudioChannels"); - _audio_length = node->number_child<AudioContent::Frame> ("AudioLength"); - _audio_frame_rate = node->number_child<int> ("AudioFrameRate"); + } +void +SndfileContent::as_xml (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text ("Sndfile"); + Content::as_xml (node); + SingleStreamAudioContent::as_xml (node); +} + + string SndfileContent::summary () const { @@ -81,8 +84,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 (); @@ -102,69 +105,15 @@ 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()); - - { - boost::mutex::scoped_lock lm (_mutex); - _audio_channels = dec.audio_channels (); - _audio_length = dec.audio_length (); - _audio_frame_rate = dec.audio_frame_rate (); - } - - signal_changed (AudioContentProperty::AUDIO_CHANNELS); - signal_changed (AudioContentProperty::AUDIO_LENGTH); - signal_changed (AudioContentProperty::AUDIO_FRAME_RATE); - - { - boost::mutex::scoped_lock lm (_mutex); - /* XXX: do this in signal_changed...? */ - _audio_mapping = AudioMapping (_audio_channels); - _audio_mapping.make_default (); - } - - signal_changed (AudioContentProperty::AUDIO_MAPPING); -} - -void -SndfileContent::as_xml (xmlpp::Node* node) const -{ - node->add_child("Type")->add_child_text ("Sndfile"); - Content::as_xml (node); - 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 ())); - _audio_mapping.as_xml (node->add_child("AudioMapping")); + shared_ptr<AudioExaminer> dec (new SndfileDecoder (shared_from_this())); + take_from_audio_examiner (dec); } -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 -SndfileContent::set_audio_mapping (AudioMapping m) -{ - { - boost::mutex::scoped_lock lm (_mutex); - _audio_mapping = m; - } - - signal_changed (AudioContentProperty::AUDIO_MAPPING); -} diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h index a32043c5c..75c723518 100644 --- a/src/lib/sndfile_content.h +++ b/src/lib/sndfile_content.h @@ -23,59 +23,31 @@ extern "C" { #include <libavutil/audioconvert.h> } -#include "audio_content.h" +#include "single_stream_audio_content.h" namespace cxml { class Node; } -class SndfileContent : public AudioContent +class SndfileContent : public SingleStreamAudioContent { 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 ()); } + DCPTime full_length () const; + 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; - Time full_length () const; - - /* AudioContent */ - int audio_channels () const { - boost::mutex::scoped_lock lm (_mutex); - return _audio_channels; - } - - AudioContent::Frame audio_length () const { - boost::mutex::scoped_lock lm (_mutex); - return _audio_length; - } - - int content_audio_frame_rate () const { - boost::mutex::scoped_lock lm (_mutex); - return _audio_frame_rate; - } - - AudioMapping audio_mapping () const { - boost::mutex::scoped_lock lm (_mutex); - return _audio_mapping; - } - - void set_audio_mapping (AudioMapping); static bool valid_file (boost::filesystem::path); - -private: - int _audio_channels; - AudioContent::Frame _audio_length; - int _audio_frame_rate; - AudioMapping _audio_mapping; }; #endif 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..41d5faf08 100644 --- a/src/lib/sndfile_decoder.h +++ b/src/lib/sndfile_decoder.h @@ -20,27 +20,29 @@ #include <sndfile.h> #include "decoder.h" #include "audio_decoder.h" +#include "audio_examiner.h" class SndfileContent; -class SndfileDecoder : public AudioDecoder +class SndfileDecoder : public AudioDecoder, public AudioExaminer { 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..8a7423698 --- /dev/null +++ b/src/lib/subrip.cc @@ -0,0 +1,51 @@ +/* + 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.h" +#include "cross.h" +#include "exceptions.h" +#include "subrip_content.h" +#include <libsub/subrip_reader.h> +#include <libsub/collect.h> + +#include "i18n.h" + +using std::vector; +using boost::shared_ptr; + +SubRip::SubRip (shared_ptr<const SubRipContent> content) +{ + FILE* f = fopen_boost (content->path (0), "r"); + if (!f) { + throw OpenFileError (content->path (0)); + } + + sub::SubripReader reader (f); + _subtitles = sub::collect<vector<sub::Subtitle> > (reader.subtitles ()); +} + +ContentTime +SubRip::length () const +{ + if (_subtitles.empty ()) { + return ContentTime (); + } + + return ContentTime::from_seconds (_subtitles.back().to.metric().get().all_as_seconds ()); +} diff --git a/src/lib/subrip.h b/src/lib/subrip.h new file mode 100644 index 000000000..14bc360c0 --- /dev/null +++ b/src/lib/subrip.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_H +#define DCPOMATIC_SUBRIP_H + +#include "subrip_subtitle.h" +#include <libsub/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<sub::Subtitle> _subtitles; +}; + +#endif diff --git a/src/lib/subrip_content.cc b/src/lib/subrip_content.cc new file mode 100644 index 000000000..14cb50b86 --- /dev/null +++ b/src/lib/subrip_content.cc @@ -0,0 +1,98 @@ +/* + 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::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<DCPTime::Type> ("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; +} diff --git a/src/lib/subrip_content.h b/src/lib/subrip_content.h new file mode 100644 index 000000000..d2dcdee00 --- /dev/null +++ b/src/lib/subrip_content.h @@ -0,0 +1,47 @@ +/* + 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 ()); + } + + /* Content */ + 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; + + /* SubtitleContent */ + bool has_subtitles () const { + return true; + } + +private: + DCPTime _length; +}; diff --git a/src/lib/subrip_decoder.cc b/src/lib/subrip_decoder.cc new file mode 100644 index 000000000..411e7542d --- /dev/null +++ b/src/lib/subrip_decoder.cc @@ -0,0 +1,105 @@ +/* + 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; + while (_next < _subtitles.size() && ContentTime::from_seconds (_subtitles[_next].from.metric().get().all_as_seconds ()) < time) { + ++_next; + } +} + +bool +SubRipDecoder::pass () +{ + if (_next >= _subtitles.size ()) { + return true; + } + + /* XXX: we are ignoring positioning specified in the file */ + + list<dcp::SubtitleString> out; + for (list<sub::Line>::const_iterator i = _subtitles[_next].lines.begin(); i != _subtitles[_next].lines.end(); ++i) { + for (list<sub::Block>::const_iterator j = i->blocks.begin(); j != i->blocks.end(); ++j) { + out.push_back ( + dcp::SubtitleString ( + "Arial", + j->italic, + dcp::Color (255, 255, 255), + /* .srt files don't specify size, so this is an arbitrary value */ + 48, + dcp::Time (rint (_subtitles[_next].from.metric().get().all_as_milliseconds() / 4)), + dcp::Time (rint (_subtitles[_next].to.metric().get().all_as_milliseconds() / 4)), + i->vertical_position.line.get() * (1.5 / 22) + 0.8, + dcp::TOP, + j->text, + dcp::NONE, + dcp::Color (255, 255, 255), + 0, + 0 + ) + ); + } + } + + text_subtitle (out); + ++_next; + return false; +} + +list<ContentTimePeriod> +SubRipDecoder::subtitles_during (ContentTimePeriod p, bool starting) const +{ + /* XXX: inefficient */ + + list<ContentTimePeriod> d; + + for (vector<sub::Subtitle>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { + + ContentTimePeriod t ( + ContentTime::from_seconds (i->from.metric().get().all_as_seconds()), + ContentTime::from_seconds (i->to.metric().get().all_as_seconds()) + ); + + if ((starting && p.contains (t.from)) || (!starting && p.overlaps (t))) { + d.push_back (t); + } + } + + return d; +} diff --git a/src/lib/subrip_decoder.h b/src/lib/subrip_decoder.h new file mode 100644 index 000000000..ad9d04e40 --- /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: + std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) 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.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 3702eef41..5b370847b 100644 --- a/src/lib/subtitle_content.cc +++ b/src/lib/subtitle_content.cc @@ -18,26 +18,41 @@ */ #include <libcxml/cxml.h> -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "subtitle_content.h" #include "util.h" #include "exceptions.h" +#include "safe_stringstream.h" #include "i18n.h" using std::string; using std::vector; +using std::cout; using boost::shared_ptr; using boost::dynamic_pointer_cast; -using libdcp::raw_convert; +using dcp::raw_convert; int const SubtitleContentProperty::SUBTITLE_X_OFFSET = 500; int const SubtitleContentProperty::SUBTITLE_Y_OFFSET = 501; int const SubtitleContentProperty::SUBTITLE_X_SCALE = 502; int const SubtitleContentProperty::SUBTITLE_Y_SCALE = 503; +int const SubtitleContentProperty::USE_SUBTITLES = 504; + +SubtitleContent::SubtitleContent (shared_ptr<const Film> f) + : Content (f) + , _use_subtitles (false) + , _subtitle_x_offset (0) + , _subtitle_y_offset (0) + , _subtitle_x_scale (1) + , _subtitle_y_scale (1) +{ + +} SubtitleContent::SubtitleContent (shared_ptr<const Film> f, boost::filesystem::path p) : Content (f, p) + , _use_subtitles (false) , _subtitle_x_offset (0) , _subtitle_y_offset (0) , _subtitle_x_scale (1) @@ -46,13 +61,20 @@ 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) + , _use_subtitles (false) , _subtitle_x_offset (0) , _subtitle_y_offset (0) , _subtitle_x_scale (1) , _subtitle_y_scale (1) { + if (version >= 32) { + _use_subtitles = node->bool_child ("UseSubtitles"); + } else { + _use_subtitles = false; + } + if (version >= 7) { _subtitle_x_offset = node->number_child<float> ("SubtitleXOffset"); _subtitle_y_offset = node->number_child<float> ("SubtitleYOffset"); @@ -77,6 +99,10 @@ SubtitleContent::SubtitleContent (shared_ptr<const Film> f, vector<shared_ptr<Co for (size_t i = 0; i < c.size(); ++i) { shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (c[i]); + if (sc->use_subtitles() != ref->use_subtitles()) { + throw JoinError (_("Content to be joined must have the same 'use subtitles' setting.")); + } + if (sc->subtitle_x_offset() != ref->subtitle_x_offset()) { throw JoinError (_("Content to be joined must have the same subtitle X offset.")); } @@ -94,6 +120,7 @@ SubtitleContent::SubtitleContent (shared_ptr<const Film> f, vector<shared_ptr<Co } } + _use_subtitles = ref->use_subtitles (); _subtitle_x_offset = ref->subtitle_x_offset (); _subtitle_y_offset = ref->subtitle_y_offset (); _subtitle_x_scale = ref->subtitle_x_scale (); @@ -103,6 +130,7 @@ SubtitleContent::SubtitleContent (shared_ptr<const Film> f, vector<shared_ptr<Co void SubtitleContent::as_xml (xmlpp::Node* root) const { + root->add_child("UseSubtitles")->add_child_text (raw_convert<string> (_use_subtitles)); root->add_child("SubtitleXOffset")->add_child_text (raw_convert<string> (_subtitle_x_offset)); root->add_child("SubtitleYOffset")->add_child_text (raw_convert<string> (_subtitle_y_offset)); root->add_child("SubtitleXScale")->add_child_text (raw_convert<string> (_subtitle_x_scale)); @@ -110,6 +138,16 @@ SubtitleContent::as_xml (xmlpp::Node* root) const } void +SubtitleContent::set_use_subtitles (bool u) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _use_subtitles = u; + } + signal_changed (SubtitleContentProperty::USE_SUBTITLES); +} + +void SubtitleContent::set_subtitle_x_offset (double o) { { @@ -148,3 +186,16 @@ SubtitleContent::set_subtitle_y_scale (double s) } signal_changed (SubtitleContentProperty::SUBTITLE_Y_SCALE); } + +string +SubtitleContent::identifier () const +{ + SafeStringStream s; + s << Content::identifier() + << "_" << raw_convert<string> (subtitle_x_scale()) + << "_" << raw_convert<string> (subtitle_y_scale()) + << "_" << raw_convert<string> (subtitle_x_offset()) + << "_" << raw_convert<string> (subtitle_y_offset()); + + return s.str (); +} diff --git a/src/lib/subtitle_content.h b/src/lib/subtitle_content.h index 329368e44..c3c25232f 100644 --- a/src/lib/subtitle_content.h +++ b/src/lib/subtitle_content.h @@ -29,22 +29,39 @@ public: static int const SUBTITLE_Y_OFFSET; static int const SUBTITLE_X_SCALE; static int const SUBTITLE_Y_SCALE; + static int const USE_SUBTITLES; }; +/** @class SubtitleContent + * @brief Parent for content which has the potential to include subtitles. + * + * Although inheriting from this class indicates that the content could + * have subtitles, it may not. ::has_subtitles() will tell you. + */ class SubtitleContent : public virtual Content { public: + SubtitleContent (boost::shared_ptr<const Film>); 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; + std::string identifier () const; + + virtual bool has_subtitles () const = 0; + void set_use_subtitles (bool); void set_subtitle_x_offset (double); void set_subtitle_y_offset (double); void set_subtitle_x_scale (double); void set_subtitle_y_scale (double); + bool use_subtitles () const { + boost::mutex::scoped_lock lm (_mutex); + return _use_subtitles; + } + double subtitle_x_offset () const { boost::mutex::scoped_lock lm (_mutex); return _subtitle_x_offset; @@ -64,10 +81,11 @@ public: boost::mutex::scoped_lock lm (_mutex); return _subtitle_y_scale; } - + private: - friend class ffmpeg_pts_offset_test; + friend struct ffmpeg_pts_offset_test; + bool _use_subtitles; /** x offset for placing subtitles, as a proportion of the container width; * +ve is further right, -ve is further left. */ diff --git a/src/lib/subtitle_decoder.cc b/src/lib/subtitle_decoder.cc index c06f3d718..e1485e221 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,84 @@ #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 (ContentImageSubtitle (period, image, rect)); +} + +void +SubtitleDecoder::text_subtitle (list<dcp::SubtitleString> s) +{ + _decoded_text_subtitles.push_back (ContentTextSubtitle (s)); +} + +template <class T> +list<T> +SubtitleDecoder::get (list<T> const & subs, ContentTimePeriod period, bool starting) +{ + /* Get the full periods of the subtitles that are showing or starting during the specified period */ + list<ContentTimePeriod> sp = subtitles_during (period, starting); + if (sp.empty ()) { + /* Nothing in this period */ + return list<T> (); + } + + /* Seek if what we want is before what we have, or more than a reasonable amount after */ + if (subs.empty() || sp.back().to < subs.front().period().from || sp.front().from > (subs.back().period().to + ContentTime::from_seconds (5))) { + seek (sp.front().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.back().period().to < sp.back().to))) {} + + /* Now look for what we wanted in the data we have collected */ + /* XXX: inefficient */ + + list<T> out; + for (typename list<T>::const_iterator i = subs.begin(); i != subs.end(); ++i) { + if ((starting && period.contains (i->period().from)) || (!starting && period.overlaps (i->period ()))) { + out.push_back (*i); + } + } + + return out; +} + +list<ContentTextSubtitle> +SubtitleDecoder::get_text_subtitles (ContentTimePeriod period, bool starting) +{ + return get<ContentTextSubtitle> (_decoded_text_subtitles, period, starting); +} + +list<ContentImageSubtitle> +SubtitleDecoder::get_image_subtitles (ContentTimePeriod period, bool starting) +{ + return get<ContentImageSubtitle> (_decoded_image_subtitles, period, starting); +} + +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..142cfa42b 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<ContentImageSubtitle> get_image_subtitles (ContentTimePeriod period, bool starting); + std::list<ContentTextSubtitle> get_text_subtitles (ContentTimePeriod period, bool starting); 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<ContentImageSubtitle> _decoded_image_subtitles; + std::list<ContentTextSubtitle> _decoded_text_subtitles; + +private: + template <class T> + std::list<T> get (std::list<T> const & subs, ContentTimePeriod period, bool starting); + + virtual std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) 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 831c74b3b..23a46d06d 100644 --- a/src/lib/transcode_job.cc +++ b/src/lib/transcode_job.cc @@ -37,6 +37,7 @@ using std::string; using std::fixed; using std::setprecision; +using std::cout; using boost::shared_ptr; /** @param s Film to use. @@ -100,6 +101,7 @@ TranscodeJob::status () const return s.str (); } +/** @return Approximate remaining time in seconds */ int TranscodeJob::remaining_time () const { @@ -117,6 +119,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..9d8eebe25 100644 --- a/src/lib/transcoder.cc +++ b/src/lib/transcoder.cc @@ -33,51 +33,52 @@ #include "audio_decoder.h" #include "player.h" #include "job.h" +#include "writer.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 ()) - , _encoder (new Encoder (f, j)) + : _film (f) + , _player (f->make_player ()) + , _writer (new Writer (f, j)) + , _encoder (new Encoder (f, j, _writer)) , _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 ()) {} + _encoder->begin (); + + DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate ()); + DCPTime const length = _film->length (); + for (DCPTime t; t < length; t += frame) { + list<shared_ptr<PlayerVideo> > v = _player->get_video (t, true); + for (list<shared_ptr<PlayerVideo> >::const_iterator i = v.begin(); i != v.end(); ++i) { + _encoder->enqueue (*i); + } + _writer->write (_player->get_audio (t, frame, true)); + if (!_film->burn_subtitles ()) { + _writer->write (_player->get_subtitles (t, frame, true)); + } + } _finishing = true; - _encoder->process_end (); + _encoder->end (); + _writer->finish (); + + _player->statistics().dump (_film->log ()); } float diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h index d7736d4e8..ed0a6b1b5 100644 --- a/src/lib/transcoder.h +++ b/src/lib/transcoder.h @@ -42,7 +42,9 @@ public: } private: + boost::shared_ptr<const Film> _film; boost::shared_ptr<Player> _player; + boost::shared_ptr<Writer> _writer; 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 7bec061e9..c50022091 100644 --- a/src/lib/update.cc +++ b/src/lib/update.cc @@ -21,7 +21,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" @@ -32,8 +32,9 @@ using std::cout; using std::min; using std::string; -using libdcp::raw_convert; +using dcp::raw_convert; +/** Singleton instance */ UpdateChecker* UpdateChecker::_instance = 0; static size_t @@ -42,6 +43,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) @@ -73,6 +77,7 @@ UpdateChecker::~UpdateChecker () delete[] _buffer; } +/** Start running the update check */ void UpdateChecker::run () { @@ -85,6 +90,7 @@ void UpdateChecker::thread () { while (true) { + /* Block until there is something to do */ boost::mutex::scoped_lock lock (_process_mutex); while (_to_do == 0) { _condition.wait (lock); @@ -94,12 +100,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'; string s (_buffer); 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/upmixer_a.cc b/src/lib/upmixer_a.cc new file mode 100644 index 000000000..dce08fe37 --- /dev/null +++ b/src/lib/upmixer_a.cc @@ -0,0 +1,109 @@ +/* + 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 "upmixer_a.h" +#include "audio_buffers.h" + +#include "i18n.h" + +using std::string; +using boost::shared_ptr; + +UpmixerA::UpmixerA (int sampling_rate) + : _left (0.02, 1900.0 / sampling_rate, 4800.0 / sampling_rate) + , _right (0.02, 1900.0 / sampling_rate, 4800.0 / sampling_rate) + , _centre (0.02, 150.0 / sampling_rate, 1900.0 / sampling_rate) + , _lfe (0.02, 20.0 / sampling_rate, 150.0 / sampling_rate) + , _ls (0.02, 4800.0 / sampling_rate, 20000.0 / sampling_rate) + , _rs (0.02, 4800.0 / sampling_rate, 20000.0 / sampling_rate) +{ + +} + +string +UpmixerA::name () const +{ + return _("Stereo to 5.1 up-mixer A"); +} + + +string +UpmixerA::id () const +{ + return N_("stereo-5.1-upmix-a"); +} + +ChannelCount +UpmixerA::in_channels () const +{ + return ChannelCount (2); +} + +int +UpmixerA::out_channels (int) const +{ + return 6; +} + +shared_ptr<AudioProcessor> +UpmixerA::clone (int sampling_rate) const +{ + return shared_ptr<AudioProcessor> (new UpmixerA (sampling_rate)); +} + +shared_ptr<AudioBuffers> +UpmixerA::run (shared_ptr<const AudioBuffers> in) +{ + /* Input L and R */ + shared_ptr<AudioBuffers> in_L = in->channel (0); + shared_ptr<AudioBuffers> in_R = in->channel (1); + + /* Mix of L and R */ + shared_ptr<AudioBuffers> in_LR = in_L->clone (); + in_LR->accumulate_frames (in_R.get(), 0, 0, in_R->frames ()); + in_LR->apply_gain (0.5); + + /* Run filters */ + shared_ptr<AudioBuffers> L = _left.run (in_L); + shared_ptr<AudioBuffers> R = _right.run (in_R); + shared_ptr<AudioBuffers> C = _centre.run (in_LR); + shared_ptr<AudioBuffers> Lfe = _lfe.run (in_LR); + shared_ptr<AudioBuffers> Ls = _ls.run (in_L); + shared_ptr<AudioBuffers> Rs = _rs.run (in_R); + + shared_ptr<AudioBuffers> out (new AudioBuffers (6, in->frames ())); + out->copy_channel_from (L.get(), 0, 0); + out->copy_channel_from (R.get(), 0, 1); + out->copy_channel_from (C.get(), 0, 2); + out->copy_channel_from (Lfe.get(), 0, 3); + out->copy_channel_from (Ls.get(), 0, 4); + out->copy_channel_from (Rs.get(), 0, 5); + return out; +} + +void +UpmixerA::flush () +{ + _left.flush (); + _right.flush (); + _centre.flush (); + _lfe.flush (); + _ls.flush (); + _rs.flush (); +} diff --git a/src/lib/upmixer_a.h b/src/lib/upmixer_a.h new file mode 100644 index 000000000..32e3f5fb6 --- /dev/null +++ b/src/lib/upmixer_a.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. + +*/ + +#include "audio_processor.h" +#include "audio_filter.h" + +class UpmixerA : public AudioProcessor +{ +public: + UpmixerA (int sampling_rate); + + std::string name () const; + std::string id () const; + ChannelCount in_channels () const; + int out_channels (int) const; + boost::shared_ptr<AudioProcessor> clone (int) const; + boost::shared_ptr<AudioBuffers> run (boost::shared_ptr<const AudioBuffers>); + void flush (); + +private: + BandPassAudioFilter _left; + BandPassAudioFilter _right; + BandPassAudioFilter _centre; + BandPassAudioFilter _lfe; + BandPassAudioFilter _ls; + BandPassAudioFilter _rs; +}; diff --git a/src/lib/util.cc b/src/lib/util.cc index 290dd20ef..e0db5de2e 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -43,13 +43,13 @@ #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.h> +#include <dcp/raw_convert.h> extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> @@ -62,13 +62,15 @@ extern "C" { #include "scaler.h" #include "dcp_content_type.h" #include "filter.h" -#include "sound_processor.h" +#include "cinema_sound_processor.h" #include "config.h" #include "ratio.h" #include "job.h" #include "cross.h" #include "video_content.h" +#include "rect.h" #include "md5_digester.h" +#include "audio_processor.h" #include "safe_stringstream.h" #ifdef DCPOMATIC_WINDOWS #include "stack.hpp" @@ -99,8 +101,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; @@ -265,24 +267,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 () -{ - SafeStringStream 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) { @@ -371,14 +355,16 @@ dcpomatic_setup () set_terminate (terminate); - libdcp::init (); + Pango::init (); + dcp::init (); Ratio::setup_ratios (); VideoContentScale::setup_scales (); DCPContentType::setup_dcp_content_types (); Scaler::setup_scalers (); Filter::setup_filters (); - SoundProcessor::setup_sound_processors (); + CinemaSoundProcessor::setup_cinema_sound_processors (); + AudioProcessor::setup_audio_processors (); ui_thread = boost::this_thread::get_id (); } @@ -401,7 +387,7 @@ mo_path () boost::filesystem::path mo_path () { - return "DCP-o-matic.app/Contents/Resources"; + return "DCP-o-matic 2.app/Contents/Resources"; } #endif @@ -486,7 +472,10 @@ md5_digest (vector<boost::filesystem::path> files, shared_ptr<Job> job) while (remaining > 0) { int const t = min (remaining, buffer_size); - fread (buffer, 1, t, f); + int const r = fread (buffer, 1, t, f); + if (r != t) { + throw ReadFileError (files[i], errno); + } digester.add (buffer, t); remaining -= t; @@ -656,6 +645,17 @@ stride_round_up (int c, int const * stride, int t) return a - (a % t); } +/** @param n A number. + * @param r Rounding `boundary' (must be a power of 2) + * @return n rounded to the nearest r + */ +int +round_to (float n, int r) +{ + assert (r == 1 || r == 2 || r == 4); + return int (n + float(r) / 2) &~ (r - 1); +} + /** Read a sequence of key / value pairs from a text stream; * the keys are the first words on the line, and the values are * the remainder of the line following the key. Lines beginning @@ -760,17 +760,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) { @@ -821,59 +810,6 @@ tidy_for_filename (string f) return t; } -shared_ptr<const libdcp::Signer> -make_signer () -{ - boost::filesystem::path const sd = Config::instance()->signer_chain_directory (); - - /* Remake the chain if any of it is missing */ - - list<boost::filesystem::path> files; - files.push_back ("ca.self-signed.pem"); - files.push_back ("intermediate.signed.pem"); - files.push_back ("leaf.signed.pem"); - files.push_back ("leaf.key"); - - list<boost::filesystem::path>::const_iterator i = files.begin(); - while (i != files.end()) { - boost::filesystem::path p (sd); - p /= *i; - if (!boost::filesystem::exists (p)) { - boost::filesystem::remove_all (sd); - boost::filesystem::create_directories (sd); - libdcp::make_signer_chain (sd, openssl_path ()); - break; - } - - ++i; - } - - libdcp::CertificateChain chain; - - { - boost::filesystem::path p (sd); - p /= "ca.self-signed.pem"; - chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p))); - } - - { - boost::filesystem::path p (sd); - p /= "intermediate.signed.pem"; - chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p))); - } - - { - boost::filesystem::path p (sd); - p /= "leaf.signed.pem"; - chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p))); - } - - boost::filesystem::path signer_key (sd); - signer_key /= "leaf.key"; - - return shared_ptr<const libdcp::Signer> (new libdcp::Signer (chain, signer_key)); -} - map<string, string> split_get_request (string url) { @@ -920,14 +856,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, int round) { if (ratio < full_frame.ratio ()) { - return libdcp::Size (rint (full_frame.height * ratio), full_frame.height); + return dcp::Size (round_to (full_frame.height * ratio, round), full_frame.height); } - return libdcp::Size (full_frame.width, rint (full_frame.width / ratio)); + return dcp::Size (full_frame.width, round_to (full_frame.width / ratio, round)); } void * @@ -958,12 +894,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 () +{ + SafeStringStream 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 (); @@ -971,12 +929,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) { @@ -984,6 +946,7 @@ ScopedTemporary::open (char const * params) return _open; } +/** Close the file */ void ScopedTemporary::close () { @@ -992,3 +955,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 675c8d03e..724e8937c 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -31,7 +31,7 @@ #include <boost/asio.hpp> #include <boost/optional.hpp> #include <boost/filesystem.hpp> -#include <libdcp/util.h> +#include <dcp/util.h> extern "C" { #include <libavcodec/avcodec.h> #include <libavfilter/avfilter.h> @@ -47,11 +47,8 @@ extern "C" { #define DCPOMATIC_HELLO "Boys, you gotta learn not to talk to nuns that way" #define HISTORY_SIZE 10 -namespace libdcp { - class Signer; -} - class Job; +struct AVSubtitle; extern std::string seconds_to_hms (int); extern std::string seconds_to_approximate_hms (int); @@ -69,12 +66,12 @@ 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 dcp::Size fit_ratio_within (float ratio, dcp::Size, int); 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); extern int stride_round_up (int, int const *, int); +extern int round_to (float n, int r); extern std::multimap<std::string, std::string> read_key_value (std::istream& s); extern int get_required_int (std::multimap<std::string, std::string> const & kv, std::string k); extern float get_required_float (std::multimap<std::string, std::string> const & kv, std::string k); @@ -83,6 +80,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 +123,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 13f2cf516..ba656e4c2 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" @@ -31,16 +31,21 @@ #include "film.h" #include "exceptions.h" #include "frame_rate_change.h" +#include "log.h" #include "safe_stringstream.h" #include "i18n.h" +#define LOG_GENERAL(...) film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); + int const VideoContentProperty::VIDEO_SIZE = 0; int const VideoContentProperty::VIDEO_FRAME_RATE = 1; int const VideoContentProperty::VIDEO_FRAME_TYPE = 2; int const VideoContentProperty::VIDEO_CROP = 3; int const VideoContentProperty::VIDEO_SCALE = 4; int const VideoContentProperty::COLOUR_CONVERSION = 5; +int const VideoContentProperty::VIDEO_FADE_IN = 6; +int const VideoContentProperty::VIDEO_FADE_OUT = 7; using std::string; using std::setprecision; @@ -51,12 +56,11 @@ using std::max; using boost::shared_ptr; using boost::optional; using boost::dynamic_pointer_cast; -using libdcp::raw_convert; +using dcp::raw_convert; VideoContent::VideoContent (shared_ptr<const Film> f) : Content (f) , _video_length (0) - , _original_video_frame_rate (0) , _video_frame_rate (0) , _video_frame_type (VIDEO_FRAME_TYPE_2D) , _scale (Config::instance()->default_scale ()) @@ -64,10 +68,9 @@ 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) - , _original_video_frame_rate (0) , _video_frame_rate (0) , _video_frame_type (VIDEO_FRAME_TYPE_2D) , _scale (Config::instance()->default_scale ()) @@ -78,7 +81,6 @@ VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Fram VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p) : Content (f, p) , _video_length (0) - , _original_video_frame_rate (0) , _video_frame_rate (0) , _video_frame_type (VIDEO_FRAME_TYPE_2D) , _scale (Config::instance()->default_scale ()) @@ -86,14 +88,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"); - _original_video_frame_rate = node->optional_number_child<float> ("OriginalVideoFrameRate").get_value_or (_video_frame_rate); + + 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<ContentTime::Type> ("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"); @@ -110,6 +118,10 @@ VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Nod } _colour_conversion = ColourConversion (node->node_child ("ColourConversion")); + if (version >= 32) { + _fade_in = ContentTime (node->number_child<int64_t> ("FadeIn")); + _fade_out = ContentTime (node->number_child<int64_t> ("FadeOut")); + } } VideoContent::VideoContent (shared_ptr<const Film> f, vector<shared_ptr<Content> > c) @@ -146,55 +158,67 @@ VideoContent::VideoContent (shared_ptr<const Film> f, vector<shared_ptr<Content> throw JoinError (_("Content to be joined must have the same colour conversion.")); } + if (vc->fade_in() != ref->fade_in() || vc->fade_out() != ref->fade_out()) { + throw JoinError (_("Content to be joined must have the same fades.")); + } + _video_length += vc->video_length (); } _video_size = ref->video_size (); - _original_video_frame_rate = ref->original_video_frame_rate (); _video_frame_rate = ref->video_frame_rate (); _video_frame_type = ref->video_frame_type (); _crop = ref->crop (); _scale = ref->scale (); _colour_conversion = ref->colour_conversion (); + _fade_in = ref->fade_in (); + _fade_out = ref->fade_out (); } 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)); - node->add_child("OriginalVideoFrameRate")->add_child_text (raw_convert<string> (_original_video_frame_rate)); node->add_child("VideoFrameType")->add_child_text (raw_convert<string> (static_cast<int> (_video_frame_type))); _crop.as_xml (node); _scale.as_xml (node->add_child("Scale")); _colour_conversion.as_xml (node->add_child("ColourConversion")); + node->add_child("FadeIn")->add_child_text (raw_convert<string> (_fade_in.get ())); + node->add_child("FadeOut")->add_child_text (raw_convert<string> (_fade_out.get ())); } 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 (); - + ContentTime vl = d->video_length (); + { boost::mutex::scoped_lock lm (_mutex); _video_size = vs; _video_frame_rate = vfr; - _original_video_frame_rate = vfr; + _video_length = vl; } + + 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)); signal_changed (VideoContentProperty::VIDEO_SIZE); signal_changed (VideoContentProperty::VIDEO_FRAME_RATE); + signal_changed (ContentProperty::LENGTH); } @@ -325,14 +349,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: @@ -340,9 +367,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); @@ -359,29 +386,44 @@ VideoContent::set_colour_conversion (ColourConversion c) signal_changed (VideoContentProperty::COLOUR_CONVERSION); } +void +VideoContent::set_fade_in (ContentTime t) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _fade_in = t; + } + + signal_changed (VideoContentProperty::VIDEO_FADE_IN); +} + +void +VideoContent::set_fade_out (ContentTime t) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _fade_out = t; + } + + signal_changed (VideoContentProperty::VIDEO_FADE_OUT); +} + /** @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 @@ -425,3 +467,19 @@ VideoContent::set_video_frame_rate (float r) signal_changed (VideoContentProperty::VIDEO_FRAME_RATE); } +optional<float> +VideoContent::fade (VideoFrame f) const +{ + assert (f >= 0); + + if (f < fade_in().frames (video_frame_rate ())) { + return float (f) / _fade_in.frames (video_frame_rate ()); + } + + VideoFrame fade_out_start = ContentTime (video_length() - fade_out()).frames (video_frame_rate ()); + if (f >= fade_out_start) { + return 1 - float (f - fade_out_start) / fade_out().frames (video_frame_rate ()); + } + + return optional<float> (); +} diff --git a/src/lib/video_content.h b/src/lib/video_content.h index 3a7b44306..e88fb0227 100644 --- a/src/lib/video_content.h +++ b/src/lib/video_content.h @@ -36,6 +36,8 @@ public: static int const VIDEO_CROP; static int const VIDEO_SCALE; static int const COLOUR_CONVERSION; + static int const VIDEO_FADE_IN; + static int const VIDEO_FADE_OUT; }; class VideoContent : public virtual Content @@ -44,9 +46,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; @@ -54,21 +56,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; } @@ -78,11 +80,6 @@ public: return _video_frame_rate; } - float original_video_frame_rate () const { - boost::mutex::scoped_lock lm (_mutex); - return _original_video_frame_rate; - } - void set_video_frame_type (VideoFrameType); void set_video_frame_rate (float); @@ -93,6 +90,9 @@ public: void set_scale (VideoContentScale); void set_colour_conversion (ColourConversion); + + void set_fade_in (ContentTime); + void set_fade_out (ContentTime); VideoFrameType video_frame_type () const { boost::mutex::scoped_lock lm (_mutex); @@ -135,10 +135,22 @@ public: return _colour_conversion; } - libdcp::Size video_size_after_3d_split () const; - libdcp::Size video_size_after_crop () const; + ContentTime fade_in () const { + boost::mutex::scoped_lock lm (_mutex); + return _fade_in; + } + + ContentTime fade_out () const { + boost::mutex::scoped_lock lm (_mutex); + return _fade_out; + } + + dcp::Size video_size_after_3d_split () const; + dcp::Size video_size_after_crop () const; + + ContentTime dcp_time_to_content_time (DCPTime) const; - VideoContent::Frame time_to_content_video_frames (Time) const; + boost::optional<float> fade (VideoFrame) const; void scale_and_crop_to_fit_width (); void scale_and_crop_to_fit_height (); @@ -146,23 +158,24 @@ public: protected: void take_from_video_examiner (boost::shared_ptr<VideoExaminer>); - VideoContent::Frame _video_length; - float _original_video_frame_rate; + ContentTime _video_length; float _video_frame_rate; private: - friend class ffmpeg_pts_offset_test; - friend class best_dcp_frame_rate_test_single; - friend class best_dcp_frame_rate_test_double; - friend class audio_sampling_rate_test; + friend struct ffmpeg_pts_offset_test; + friend struct best_dcp_frame_rate_test_single; + friend struct best_dcp_frame_rate_test_double; + friend struct audio_sampling_rate_test; void setup_default_colour_conversion (); - libdcp::Size _video_size; + dcp::Size _video_size; VideoFrameType _video_frame_type; Crop _crop; VideoContentScale _scale; ColourConversion _colour_conversion; + ContentTime _fade_in; + ContentTime _fade_out; }; #endif diff --git a/src/lib/video_content_scale.cc b/src/lib/video_content_scale.cc index e603582b8..418c46eec 100644 --- a/src/lib/video_content_scale.cc +++ b/src/lib/video_content_scale.cc @@ -123,24 +123,24 @@ VideoContentScale::from_id (string id) /** @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, int round) const { if (_ratio) { - return fit_ratio_within (_ratio->ratio (), display_container); + return fit_ratio_within (_ratio->ratio (), display_container, round); } - 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) { - return fit_ratio_within (ac.ratio (), display_container); + return fit_ratio_within (ac.ratio (), display_container, round); } /* 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_scale.h b/src/lib/video_content_scale.h index 87dd2f1fa..6b718d574 100644 --- a/src/lib/video_content_scale.h +++ b/src/lib/video_content_scale.h @@ -22,7 +22,7 @@ #include <vector> #include <boost/shared_ptr.hpp> -#include <libdcp/util.h> +#include <dcp/util.h> namespace cxml { class Node; @@ -43,7 +43,7 @@ public: VideoContentScale (bool); VideoContentScale (boost::shared_ptr<cxml::Node>); - 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, int round) const; std::string id () const; std::string name () const; void as_xml (xmlpp::Node *) const; diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index 5867ac925..64c66ea55 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -19,46 +19,164 @@ #include "video_decoder.h" #include "image.h" +#include "image_proxy.h" +#include "content_video.h" #include "i18n.h" using std::cout; +using std::list; +using std::max; 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 + , _same (false) { } +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) +{ + /* At this stage, if we have get_video()ed before, _decoded_video will contain the last frame that this + method returned (and possibly a few more). If the requested frame is not in _decoded_video and it is not the next + one after the end of _decoded_video we need to seek. + */ + + if (_decoded_video.empty() || frame < _decoded_video.front().frame || frame > (_decoded_video.back().frame + 1)) { + 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) give us something after what we want, indicating that we will never get what we want, or + * (c) 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; keep the frame we are returning, but nothing before that */ + while (!_decoded_video.empty() && _decoded_video.front().frame < dec.front().frame) { + _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 may receive the same frame index twice for 3D, and we need to know + when that happens. + */ + _same = (!_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, _same ? 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..f5c3cd743 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,38 @@ #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; + bool _same; }; #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 5af1aea1e..6262525c8 100644 --- a/src/lib/writer.cc +++ b/src/lib/writer.cc @@ -19,25 +19,32 @@ #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/reel_subtitle_asset.h> +#include <dcp/dcp.h> +#include <dcp/cpl.h> +#include <dcp/signer.h> #include "writer.h" #include "compose.hpp" #include "film.h" #include "ratio.h" #include "log.h" -#include "dcp_video_frame.h" +#include "dcp_video.h" #include "dcp_content_type.h" -#include "player.h" #include "audio_mapping.h" #include "config.h" #include "job.h" #include "cross.h" +#include "audio_buffers.h" #include "md5_digester.h" +#include "encoded_data.h" #include "version.h" #include "i18n.h" @@ -57,6 +64,7 @@ using std::list; using std::cout; 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; @@ -71,7 +79,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 */ @@ -89,36 +96,39 @@ 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); + } + + /* Check that the signer is OK if we need one */ + if (_film->is_signed() && !Config::instance()->signer()->valid ()) { + throw InvalidSignerError (); } _thread = new boost::thread (boost::bind (&Writer::thread, this)); @@ -175,7 +185,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; @@ -200,8 +210,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()); } } @@ -277,7 +287,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; @@ -285,39 +295,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); } } @@ -392,15 +390,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(); @@ -421,14 +415,11 @@ Writer::finish () } } - /* 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 (); @@ -439,80 +430,86 @@ 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 ()); + + 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<libdcp::Reel> (new libdcp::Reel ( - _picture_asset, - _sound_asset, - shared_ptr<libdcp::SubtitleAsset> () - ) - )); + if (_subtitle_content) { + _subtitle_content->write_xml (_film->dir (_film->dcp_name ()) / _film->subtitle_xml_filename ()); + reel->add (shared_ptr<dcp::ReelSubtitleAsset> ( + new dcp::ReelSubtitleAsset ( + _subtitle_content, + dcp::Fraction (_film->video_frame_rate(), 1), + _picture_mxf->intrinsic_duration (), + 0 + ) + )); + + dcp.add (_subtitle_content); + } + + 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; + dcp::XMLMetadata meta; meta.issuer = Config::instance()->dcp_issuer (); meta.creator = String::compose ("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit); meta.set_issue_date_now (); - dcp.write_xml (_film->interop (), meta, _film->is_signed() ? make_signer () : shared_ptr<const libdcp::Signer> ()); - - LOG_GENERAL ( - N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_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) { - /* The queue is too big; wait until that is sorted out */ - _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); + shared_ptr<const dcp::Signer> signer; + if (_film->is_signed ()) { + signer = Config::instance()->signer (); + /* We did check earlier, but check again here to be on the safe side */ + if (!signer->valid ()) { + throw InvalidSignerError (); + } } - /* Now there's something to do: wake anything wait()ing on _empty_condition */ - _empty_condition.notify_all (); + dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, signer); + + LOG_GENERAL ( + N_("Wrote %1 FULL, %2 FAKE, %3 pushed to disk"), _full_written, _fake_written, _pushed_to_disk + ); } bool @@ -525,7 +522,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); @@ -610,6 +607,24 @@ Writer::can_fake_write (int frame) const return (frame != 0 && frame < _first_nonexistant_frame); } +void +Writer::write (PlayerSubtitles subs) +{ + if (subs.text.empty ()) { + return; + } + + if (!_subtitle_content) { + _subtitle_content.reset ( + new dcp::SubtitleContent (_film->name(), _film->isdcf_metadata().subtitle_language) + ); + } + + for (list<dcp::SubtitleString>::const_iterator i = subs.text.begin(); i != subs.text.end(); ++i) { + _subtitle_content->add (*i); + } +} + bool operator< (QueueItem const & a, QueueItem const & b) { diff --git a/src/lib/writer.h b/src/lib/writer.h index c0699ad44..66fe98ec7 100644 --- a/src/lib/writer.h +++ b/src/lib/writer.h @@ -17,27 +17,33 @@ */ +/** @file src/lib/writer.h + * @brief Writer class. + */ + #include <list> #include <boost/shared_ptr.hpp> #include <boost/thread.hpp> #include <boost/thread/condition.hpp> +#include <dcp/subtitle_content.h> #include "exceptions.h" #include "types.h" +#include "player_subtitles.h" class Film; 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 +57,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 +71,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: @@ -78,6 +93,7 @@ public: void write (boost::shared_ptr<const EncodedData>, int, Eyes); void fake_write (int, Eyes); void write (boost::shared_ptr<const AudioBuffers>); + void write (PlayerSubtitles); void repeat (int f, Eyes); void finish (); @@ -123,15 +139,14 @@ 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; + boost::shared_ptr<dcp::SubtitleContent> _subtitle_content; }; diff --git a/src/lib/wscript b/src/lib/wscript index 6c1da1772..d62c22bae 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -7,26 +7,39 @@ sources = """ audio_buffers.cc audio_content.cc audio_decoder.cc + audio_filter.cc audio_mapping.cc + audio_processor.cc cinema.cc + cinema_sound_processor.cc colour_conversion.cc config.cc content.cc content_factory.cc + content_subtitle.cc cross.cc + dcp_content.cc dcp_content_type.cc - dcp_video_frame.cc - decoder.cc + dcp_decoder.cc + dcp_examiner.cc + dcp_subtitle_content.cc + dcp_subtitle_decoder.cc + dcp_video.cc + dcpomatic_time.cc dolby_cp750.cc encoder.cc + encoded_data.cc examine_content_job.cc exceptions.cc 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 @@ -37,16 +50,21 @@ sources = """ image_examiner.cc image_proxy.cc isdcf_metadata.cc + j2k_image_proxy.cc job.cc job_manager.cc kdm.cc log.cc + magick_image_proxy.cc md5_digester.cc - piece.cc + mid_side_decoder.cc player.cc - player_video_frame.cc + player_video.cc playlist.cc + position_image.cc ratio.cc + raw_image_proxy.cc + render_subtitles.cc resampler.cc safe_stringstream.cc scp_dcp_job.cc @@ -54,10 +72,12 @@ sources = """ send_kdm_email_job.cc server.cc server_finder.cc + single_stream_audio_content.cc sndfile_content.cc sndfile_decoder.cc - sound_processor.cc - subtitle.cc + subrip.cc + subrip_content.cc + subrip_decoder.cc subtitle_content.cc subtitle_decoder.cc timer.cc @@ -66,6 +86,7 @@ sources = """ types.cc ui_signaller.cc update.cc + upmixer_a.cc util.cc video_content.cc video_content_scale.cc @@ -79,13 +100,13 @@ def build(bld): else: obj = bld(features = 'cxx cxxshlib') - obj.name = 'libdcpomatic' + obj.name = 'libdcpomatic2' obj.export_includes = ['..'] obj.uselib = """ AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 - SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XMLPP - CURL ZIP QUICKMAIL XMLSEC + SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++ + CURL ZIP QUICKMAIL PANGOMM CAIROMM XMLSEC SUB """ if bld.env.TARGET_OSX: @@ -99,9 +120,9 @@ def build(bld): if bld.env.BUILD_STATIC: obj.uselib += ' XMLPP' - obj.target = 'dcpomatic' + obj.target = 'dcpomatic2' - i18n.po_to_mo(os.path.join('src', 'lib'), 'libdcpomatic', bld) + i18n.po_to_mo(os.path.join('src', 'lib'), 'libdcpomatic2', bld) def pot(bld): i18n.pot(os.path.join('src', 'lib'), sources, 'libdcpomatic') diff --git a/src/tools/dcpomatic.cc b/src/tools/dcpomatic.cc index fa89a4871..d08a11ea9 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" @@ -45,6 +45,7 @@ #include "wx/servers_list_dialog.h" #include "wx/hints_dialog.h" #include "wx/update_dialog.h" +#include "wx/content_panel.h" #include "lib/film.h" #include "lib/config.h" #include "lib/util.h" @@ -72,8 +73,6 @@ using std::exception; using boost::shared_ptr; using boost::dynamic_pointer_cast; -// #define DCPOMATIC_WINDOWS_CONSOLE 1 - class FilmChangedDialog { public: @@ -145,20 +144,24 @@ public: , _history_position (0) , _history_separator (0) { -#if defined(DCPOMATIC_WINDOWS) && defined(DCPOMATIC_WINDOWS_CONSOLE) - AllocConsole(); - - HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE); - int hCrt = _open_osfhandle((intptr_t) handle_out, _O_TEXT); - FILE* hf_out = _fdopen(hCrt, "w"); - setvbuf(hf_out, NULL, _IONBF, 1); - *stdout = *hf_out; - - HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE); - hCrt = _open_osfhandle((intptr_t) handle_in, _O_TEXT); - FILE* hf_in = _fdopen(hCrt, "r"); - setvbuf(hf_in, NULL, _IONBF, 128); - *stdin = *hf_in; +#if defined(DCPOMATIC_WINDOWS) + if (Config::instance()->win32_console ()) { + AllocConsole(); + + HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE); + int hCrt = _open_osfhandle((intptr_t) handle_out, _O_TEXT); + FILE* hf_out = _fdopen(hCrt, "w"); + setvbuf(hf_out, NULL, _IONBF, 1); + *stdout = *hf_out; + + HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE); + hCrt = _open_osfhandle((intptr_t) handle_in, _O_TEXT); + FILE* hf_in = _fdopen(hCrt, "r"); + setvbuf(hf_in, NULL, _IONBF, 128); + *stdin = *hf_in; + + cout << "DCP-o-matic is starting." << "\n"; + } #endif wxMenuBar* bar = new wxMenuBar; @@ -188,12 +191,6 @@ public: Bind (wxEVT_CLOSE_WINDOW, boost::bind (&Frame::close, this, _1)); - wxAcceleratorEntry accel[1]; - accel[0].Set (wxACCEL_CTRL, static_cast<int>('A'), ID_add_file); - Bind (wxEVT_MENU, boost::bind (&FilmEditor::content_add_file_clicked, _film_editor), ID_add_file); - wxAcceleratorTable accel_table (1, accel); - SetAcceleratorTable (accel_table); - /* Use a panel as the only child of the Frame so that we avoid the dark-grey background on Windows. */ @@ -219,6 +216,12 @@ public: JobManager::instance()->ActiveJobsChanged.connect (boost::bind (&Frame::set_menu_sensitivity, this)); overall_panel->SetSizer (main_sizer); + + wxAcceleratorEntry accel[1]; + accel[0].Set (wxACCEL_CTRL, static_cast<int>('A'), ID_add_file); + Bind (wxEVT_MENU, boost::bind (&ContentPanel::add_file_clicked, _film_editor->content_panel()), ID_add_file); + wxAcceleratorTable accel_table (1, accel); + SetAcceleratorTable (accel_table); } void new_film (boost::filesystem::path path) @@ -410,7 +413,7 @@ private: shared_ptr<Job> (new SendKDMEmailJob (_film, d->screens (), d->cpl (), d->from (), d->until (), d->formulation ())) ); } - } 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 ()); @@ -423,7 +426,7 @@ private: void content_scale_to_fit_width () { - VideoContentList vc = _film_editor->selected_video_content (); + VideoContentList vc = _film_editor->content_panel()->selected_video (); for (VideoContentList::iterator i = vc.begin(); i != vc.end(); ++i) { (*i)->scale_and_crop_to_fit_width (); } @@ -431,7 +434,7 @@ private: void content_scale_to_fit_height () { - VideoContentList vc = _film_editor->selected_video_content (); + VideoContentList vc = _film_editor->content_panel()->selected_video (); for (VideoContentList::iterator i = vc.begin(); i != vc.end(); ++i) { (*i)->scale_and_crop_to_fit_height (); } @@ -542,7 +545,7 @@ private: } bool const dcp_creation = (i != jobs.end ()) && !(*i)->finished (); bool const have_cpl = _film && !_film->cpls().empty (); - bool const have_selected_video_content = !_film_editor->selected_video_content().empty(); + bool const have_selected_video_content = !_film_editor->content_panel()->selected_video().empty(); for (map<wxMenuItem*, int>::iterator j = menu_items.begin(); j != menu_items.end(); ++j) { @@ -707,6 +710,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 5cb05e11d..8c33b7d83 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 26de1c71f..304f4f697 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 758060a08..6257d60af 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" " --formulation modified-transitional-1, dci-any or dci-specific [default modified-transitional-1]\n" " -z, --zip ZIP each cinema's KDMs into its own file\n" @@ -111,7 +111,7 @@ int main (int argc, char* argv[]) bool cinemas = false; string duration_string; bool verbose = false; - libdcp::KDM::Formulation formulation = libdcp::KDM::MODIFIED_TRANSITIONAL_1; + dcp::Formulation formulation = dcp::MODIFIED_TRANSITIONAL_1; program_name = argv[0]; @@ -171,13 +171,13 @@ int main (int argc, char* argv[]) break; case 'C': if (string (optarg) == "modified-transitional-1") { - formulation = libdcp::KDM::MODIFIED_TRANSITIONAL_1; + formulation = dcp::MODIFIED_TRANSITIONAL_1; } else if (string (optarg) == "dci-any") { - formulation = libdcp::KDM::DCI_ANY; + formulation = dcp::DCI_ANY; } else if (string (optarg) == "dci-specific") { - formulation = libdcp::KDM::DCI_SPECIFIC; + formulation = dcp::DCI_SPECIFIC; } else { - error ("unrecognised KDM formulation " + formulation); + error ("unrecognised KDM formulation " + string (optarg)); } } } @@ -248,8 +248,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(), formulation); + dcp::Certificate certificate (dcp::file_to_string (certificate_file)); + dcp::EncryptedKDM kdm = film->make_kdm (certificate, cpl, valid_from.get(), valid_to.get(), formulation); kdm.as_xml (output); if (verbose) { cout << "Generated KDM " << output << " for certificate.\n"; @@ -273,12 +273,18 @@ int main (int argc, char* argv[]) try { if (zip) { - write_kdm_zip_files (film, (*i)->screens(), cpl, valid_from.get(), valid_to.get(), formulation, output); + write_kdm_zip_files ( + film, (*i)->screens(), cpl, dcp::LocalTime (valid_from.get()), dcp::LocalTime (valid_to.get()), formulation, output + ); + if (verbose) { cout << "Wrote ZIP files to " << output << "\n"; } } else { - write_kdm_files (film, (*i)->screens(), cpl, valid_from.get(), valid_to.get(), formulation, output); + write_kdm_files ( + film, (*i)->screens(), cpl, dcp::LocalTime (valid_from.get()), dcp::LocalTime (valid_to.get()), formulation, output + ); + if (verbose) { cout << "Wrote KDM files to " << output << "\n"; } diff --git a/src/tools/dcpomatic_server_cli.cc b/src/tools/dcpomatic_server_cli.cc index f35797954..b816460a3 100644 --- a/src/tools/dcpomatic_server_cli.cc +++ b/src/tools/dcpomatic_server_cli.cc @@ -32,7 +32,7 @@ #include <boost/thread/mutex.hpp> #include <boost/thread/condition.hpp> #include "lib/config.h" -#include "lib/dcp_video_frame.h" +#include "lib/dcp_video.h" #include "lib/exceptions.h" #include "lib/util.h" #include "lib/config.h" diff --git a/src/tools/server_test.cc b/src/tools/server_test.cc index a5d31fc08..9223efb3e 100644 --- a/src/tools/server_test.cc +++ b/src/tools/server_test.cc @@ -27,14 +27,15 @@ #include "lib/util.h" #include "lib/scaler.h" #include "lib/server.h" -#include "lib/dcp_video_frame.h" +#include "lib/dcp_video.h" #include "lib/decoder.h" #include "lib/exceptions.h" #include "lib/scaler.h" #include "lib/log.h" #include "lib/video_decoder.h" #include "lib/player.h" -#include "lib/player_video_frame.h" +#include "lib/player_video.h" +#include "lib/encoded_data.h" using std::cout; using std::cerr; @@ -45,18 +46,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) +process_video (shared_ptr<PlayerVideo> 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<DCPVideo> local (new DCPVideo (pvf, frame_count, film->video_frame_rate(), 250000000, RESOLUTION_2K, true, log_)); + shared_ptr<DCPVideo> remote (new DCPVideo (pvf, frame_count, film->video_frame_rate(), 250000000, RESOLUTION_2K, true, 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 +145,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/tools/wscript b/src/tools/wscript index c4ea1530f..34d8be059 100644 --- a/src/tools/wscript +++ b/src/tools/wscript @@ -13,9 +13,9 @@ def build(bld): obj = bld(features = 'cxx cxxprogram') obj.uselib = 'BOOST_THREAD BOOST_DATETIME OPENJPEG DCP CXML AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC WXWIDGETS QUICKMAIL' obj.includes = ['..'] - obj.use = ['libdcpomatic'] + obj.use = ['libdcpomatic2'] obj.source = '%s.cc' % t - obj.target = t + obj.target = t.replace('dcpomatic', 'dcpomatic2') if t == 'server_test': obj.install_path = None @@ -26,13 +26,13 @@ def build(bld): if bld.env.BUILD_STATIC: obj.uselib += ' GTK' obj.includes = ['..'] - obj.use = ['libdcpomatic', 'libdcpomatic-wx'] + obj.use = ['libdcpomatic2', 'libdcpomatic2-wx'] obj.source = '%s.cc' % t if bld.env.TARGET_WINDOWS: obj.source += ' ../../platform/windows/dcpomatic.rc' - obj.target = t + obj.target = t.replace('dcpomatic', 'dcpomatic2') - i18n.po_to_mo(os.path.join('src', 'tools'), 'dcpomatic', bld) + i18n.po_to_mo(os.path.join('src', 'tools'), 'dcpomatic2', bld) def pot(bld): i18n.pot(os.path.join('src', 'tools'), 'dcpomatic.cc dcpomatic_batch.cc', 'dcpomatic') diff --git a/src/wx/about_dialog.cc b/src/wx/about_dialog.cc index 58f299723..15b667f9b 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" @@ -220,6 +224,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 917775181..82604763c 100644 --- a/src/wx/audio_panel.cc +++ b/src/wx/audio_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,24 +20,27 @@ #include <boost/lexical_cast.hpp> #include <wx/spinctrl.h> #include "lib/config.h" -#include "lib/sound_processor.h" #include "lib/ffmpeg_content.h" +#include "lib/ffmpeg_audio_stream.h" +#include "lib/audio_processor.h" +#include "lib/cinema_sound_processor.h" #include "audio_dialog.h" #include "audio_panel.h" #include "audio_mapping_view.h" #include "wx_util.h" #include "gain_calculator_dialog.h" -#include "film_editor.h" +#include "content_panel.h" using std::vector; using std::cout; using std::string; +using std::list; using boost::dynamic_pointer_cast; using boost::lexical_cast; using boost::shared_ptr; -AudioPanel::AudioPanel (FilmEditor* e) - : FilmEditorPanel (e, _("Audio")) +AudioPanel::AudioPanel (ContentPanel* p) + : ContentSubPanel (p, _("Audio")) , _audio_dialog (0) { wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); @@ -80,8 +83,13 @@ AudioPanel::AudioPanel (FilmEditor* e) add_label_to_grid_bag_sizer (grid, this, _("Stream"), true, wxGBPosition (r, 0)); _stream = new wxChoice (this, wxID_ANY); - grid->Add (_stream, wxGBPosition (r, 1)); - _description = add_label_to_grid_bag_sizer (grid, this, "", false, wxGBPosition (r, 3)); + grid->Add (_stream, wxGBPosition (r, 1), wxGBSpan (1, 3), wxEXPAND); + ++r; + + add_label_to_grid_bag_sizer (grid, this, _("Process with"), true, wxGBPosition (r, 0)); + _processor = new wxChoice (this, wxID_ANY); + setup_processors (); + grid->Add (_processor, wxGBPosition (r, 1), wxGBSpan (1, 3), wxEXPAND); ++r; _mapping = new AudioMappingView (this); @@ -92,9 +100,10 @@ AudioPanel::AudioPanel (FilmEditor* e) _gain->wrapped()->SetIncrement (0.5); _delay->wrapped()->SetRange (-1000, 1000); - _stream->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&AudioPanel::stream_changed, this)); - _show->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&AudioPanel::show_clicked, this)); - _gain_calculate_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&AudioPanel::gain_calculate_button_clicked, this)); + _stream->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&AudioPanel::stream_changed, this)); + _show->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&AudioPanel::show_clicked, this)); + _gain_calculate_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&AudioPanel::gain_calculate_button_clicked, this)); + _processor->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&AudioPanel::processor_changed, this)); _mapping->Changed.connect (boost::bind (&AudioPanel::mapping_changed, this, _1)); } @@ -105,7 +114,7 @@ AudioPanel::film_changed (Film::Property property) { switch (property) { case Film::AUDIO_CHANNELS: - _mapping->set_channels (_editor->film()->audio_channels ()); + _mapping->set_channels (_parent->film()->audio_channels ()); _sizer->Layout (); break; default: @@ -116,7 +125,7 @@ AudioPanel::film_changed (Film::Property property) void AudioPanel::film_content_changed (int property) { - AudioContentList ac = _editor->selected_audio_content (); + AudioContentList ac = _parent->selected_audio (); shared_ptr<AudioContent> acs; shared_ptr<FFmpegContent> fcs; if (ac.size() == 1) { @@ -128,7 +137,6 @@ AudioPanel::film_content_changed (int property) _mapping->set (acs ? acs->audio_mapping () : AudioMapping ()); _sizer->Layout (); } else if (property == FFmpegContentProperty::AUDIO_STREAM) { - setup_stream_description (); _mapping->set (acs ? acs->audio_mapping () : AudioMapping ()); _sizer->Layout (); } else if (property == FFmpegContentProperty::AUDIO_STREAMS) { @@ -141,9 +149,14 @@ AudioPanel::film_content_changed (int property) if (fcs->audio_stream()) { checked_set (_stream, fcs->audio_stream()->identifier ()); - setup_stream_description (); } } + } else if (property == AudioContentProperty::AUDIO_PROCESSOR) { + if (acs) { + checked_set (_processor, acs->audio_processor() ? acs->audio_processor()->id() : N_("none")); + } else { + checked_set (_processor, N_("none")); + } } } @@ -159,7 +172,7 @@ AudioPanel::gain_calculate_button_clicked () } _gain->wrapped()->SetValue ( - Config::instance()->sound_processor()->db_for_fader_change ( + Config::instance()->cinema_sound_processor()->db_for_fader_change ( d->wanted_fader (), d->actual_fader () ) @@ -181,7 +194,7 @@ AudioPanel::show_clicked () _audio_dialog = 0; } - AudioContentList ac = _editor->selected_audio_content (); + AudioContentList ac = _parent->selected_audio (); if (ac.size() != 1) { return; } @@ -194,7 +207,7 @@ AudioPanel::show_clicked () void AudioPanel::stream_changed () { - FFmpegContentList fc = _editor->selected_ffmpeg_content (); + FFmpegContentList fc = _parent->selected_ffmpeg (); if (fc.size() != 1) { return; } @@ -215,39 +228,27 @@ AudioPanel::stream_changed () if (i != a.end ()) { fcs->set_audio_stream (*i); } - - setup_stream_description (); } void -AudioPanel::setup_stream_description () +AudioPanel::processor_changed () { - FFmpegContentList fc = _editor->selected_ffmpeg_content (); - if (fc.size() != 1) { - _description->SetLabel (""); - return; + string const s = string_client_data (_processor->GetClientObject (_processor->GetSelection ())); + AudioProcessor const * p = 0; + if (s != wx_to_std (N_("none"))) { + p = AudioProcessor::from_id (s); } - - shared_ptr<FFmpegContent> fcs = fc.front (); - - if (!fcs->audio_stream ()) { - _description->SetLabel (wxT ("")); - } else { - wxString s; - if (fcs->audio_channels() == 1) { - s << _("1 channel"); - } else { - s << fcs->audio_channels() << wxT (" ") << _("channels"); - } - s << wxT (", ") << fcs->content_audio_frame_rate() << _("Hz"); - _description->SetLabel (s); + + AudioContentList c = _parent->selected_audio (); + for (AudioContentList::const_iterator i = c.begin(); i != c.end(); ++i) { + (*i)->set_audio_processor (p); } } void AudioPanel::mapping_changed (AudioMapping m) { - AudioContentList c = _editor->selected_audio_content (); + AudioContentList c = _parent->selected_audio (); if (c.size() == 1) { c.front()->set_audio_mapping (m); } @@ -256,7 +257,7 @@ AudioPanel::mapping_changed (AudioMapping m) void AudioPanel::content_selection_changed () { - AudioContentList sel = _editor->selected_audio_content (); + AudioContentList sel = _parent->selected_audio (); if (_audio_dialog && sel.size() == 1) { _audio_dialog->set_content (sel.front ()); @@ -265,11 +266,37 @@ AudioPanel::content_selection_changed () _gain->set_content (sel); _delay->set_content (sel); + _gain_calculate_button->Enable (sel.size() == 1); _show->Enable (sel.size() == 1); _stream->Enable (sel.size() == 1); + _processor->Enable (!sel.empty()); _mapping->Enable (sel.size() == 1); + setup_processors (); + film_content_changed (AudioContentProperty::AUDIO_MAPPING); + film_content_changed (AudioContentProperty::AUDIO_PROCESSOR); film_content_changed (FFmpegContentProperty::AUDIO_STREAM); film_content_changed (FFmpegContentProperty::AUDIO_STREAMS); } + +void +AudioPanel::setup_processors () +{ + AudioContentList sel = _parent->selected_audio (); + + _processor->Clear (); + list<AudioProcessor const *> ap = AudioProcessor::all (); + _processor->Append (_("None"), new wxStringClientData (N_("none"))); + for (list<AudioProcessor const *>::const_iterator i = ap.begin(); i != ap.end(); ++i) { + + AudioContentList::const_iterator j = sel.begin(); + while (j != sel.end() && (*i)->in_channels().includes ((*j)->audio_channels ())) { + ++j; + } + + if (j == sel.end ()) { + _processor->Append (std_to_wx ((*i)->name ()), new wxStringClientData (std_to_wx ((*i)->id ()))); + } + } +} diff --git a/src/wx/audio_panel.h b/src/wx/audio_panel.h index 2ba5a9ffc..d5821d26a 100644 --- a/src/wx/audio_panel.h +++ b/src/wx/audio_panel.h @@ -18,7 +18,7 @@ */ #include "lib/audio_mapping.h" -#include "film_editor_panel.h" +#include "content_sub_panel.h" #include "content_widget.h" class wxSpinCtrlDouble; @@ -28,10 +28,10 @@ class wxStaticText; class AudioMappingView; class AudioDialog; -class AudioPanel : public FilmEditorPanel +class AudioPanel : public ContentSubPanel { public: - AudioPanel (FilmEditor *); + AudioPanel (ContentPanel *); void film_changed (Film::Property); void film_content_changed (int); @@ -42,14 +42,15 @@ private: void show_clicked (); void stream_changed (); void mapping_changed (AudioMapping); - void setup_stream_description (); + void processor_changed (); + void setup_processors (); ContentSpinCtrlDouble<AudioContent>* _gain; wxButton* _gain_calculate_button; wxButton* _show; ContentSpinCtrl<AudioContent>* _delay; wxChoice* _stream; - wxStaticText* _description; + wxChoice* _processor; AudioMappingView* _mapping; AudioDialog* _audio_dialog; }; diff --git a/src/wx/config_dialog.cc b/src/wx/config_dialog.cc index 816602355..8d8f44b4e 100644 --- a/src/wx/config_dialog.cc +++ b/src/wx/config_dialog.cc @@ -28,13 +28,19 @@ #include <wx/preferences.h> #include <wx/filepicker.h> #include <wx/spinctrl.h> -#include <libdcp/colour_matrix.h> +#include <dcp/colour_matrix.h> +#include <dcp/exceptions.h> +#include <dcp/signer.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 "lib/util.h" +#include "lib/cross.h" +#include "lib/exceptions.h" #include "config_dialog.h" #include "wx_util.h" #include "editable_list.h" @@ -43,6 +49,7 @@ #include "isdcf_metadata_dialog.h" #include "preset_colour_conversion_dialog.h" #include "server_dialog.h" +#include "make_signer_chain_dialog.h" using std::vector; using std::string; @@ -112,7 +119,6 @@ public: _num_local_encoding_threads = new wxSpinCtrl (panel); table->Add (_num_local_encoding_threads, 1); - _check_for_updates = new wxCheckBox (panel, wxID_ANY, _("Check for updates on startup")); table->Add (_check_for_updates, 1, wxEXPAND | wxALL); table->AddSpacer (0); @@ -530,6 +536,339 @@ private: } }; +class KeysPage : public wxPreferencesPage, public Page +{ +public: + KeysPage (wxSize panel_size, int border) + : Page (panel_size, border) + {} + + wxString GetName () const + { + return _("Keys"); + } + +#ifdef DCPOMATIC_OSX + wxBitmap GetLargeIcon () const + { + return wxBitmap ("keys", wxBITMAP_TYPE_PNG_RESOURCE); + } +#endif + + wxWindow* CreateWindow (wxWindow* parent) + { + _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size); + wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL); + _panel->SetSizer (overall_sizer); + + wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Certificate chain for signing DCPs and KDMs:")); + overall_sizer->Add (m, 0, wxALL, _border); + + wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL); + overall_sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, _border); + + _certificates = new wxListCtrl (_panel, wxID_ANY, wxDefaultPosition, wxSize (400, 200), wxLC_REPORT | wxLC_SINGLE_SEL); + + { + wxListItem ip; + ip.SetId (0); + ip.SetText (_("Type")); + ip.SetWidth (100); + _certificates->InsertColumn (0, ip); + } + + { + wxListItem ip; + ip.SetId (1); + ip.SetText (_("Thumbprint")); + ip.SetWidth (300); + + wxFont font = ip.GetFont (); + font.SetFamily (wxFONTFAMILY_TELETYPE); + ip.SetFont (font); + + _certificates->InsertColumn (1, ip); + } + + certificates_sizer->Add (_certificates, 1, wxEXPAND); + + { + wxSizer* s = new wxBoxSizer (wxVERTICAL); + _add_certificate = new wxButton (_panel, wxID_ANY, _("Add...")); + s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP); + _remove_certificate = new wxButton (_panel, wxID_ANY, _("Remove")); + s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP); + certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP); + } + + wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); + table->AddGrowableCol (1, 1); + overall_sizer->Add (table, 1, wxALL | wxEXPAND, _border); + + _remake_certificates = new wxButton (_panel, wxID_ANY, _("Re-make certificates...")); + table->Add (_remake_certificates, 0); + table->AddSpacer (0); + + add_label_to_sizer (table, _panel, _("Private key for leaf certificate"), true); + { + wxSizer* s = new wxBoxSizer (wxHORIZONTAL); + _signer_private_key = new wxStaticText (_panel, wxID_ANY, wxT ("")); + wxFont font = _signer_private_key->GetFont (); + font.SetFamily (wxFONTFAMILY_TELETYPE); + _signer_private_key->SetFont (font); + s->Add (_signer_private_key, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP); + _load_signer_private_key = new wxButton (_panel, wxID_ANY, _("Load...")); + s->Add (_load_signer_private_key, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP); + table->Add (s, 0); + } + + add_label_to_sizer (table, _panel, _("Certificate for decrypting DCPs"), true); + { + wxSizer* s = new wxBoxSizer (wxHORIZONTAL); + _decryption_certificate = new wxStaticText (_panel, wxID_ANY, wxT ("")); + wxFont font = _decryption_certificate->GetFont (); + font.SetFamily (wxFONTFAMILY_TELETYPE); + _decryption_certificate->SetFont (font); + s->Add (_decryption_certificate, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP); + _load_decryption_certificate = new wxButton (_panel, wxID_ANY, _("Load...")); + s->Add (_load_decryption_certificate, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP); + table->Add (s, 0); + } + + add_label_to_sizer (table, _panel, _("Private key for decrypting DCPs"), true); + { + wxSizer* s = new wxBoxSizer (wxHORIZONTAL); + _decryption_private_key = new wxStaticText (_panel, wxID_ANY, wxT ("")); + wxFont font = _decryption_private_key->GetFont (); + font.SetFamily (wxFONTFAMILY_TELETYPE); + _decryption_private_key->SetFont (font); + s->Add (_decryption_private_key, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP); + _load_decryption_private_key = new wxButton (_panel, wxID_ANY, _("Load...")); + s->Add (_load_decryption_private_key, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP); + table->Add (s, 0); + } + + _export_decryption_certificate = new wxButton (_panel, wxID_ANY, _("Export DCP decryption certificate...")); + table->Add (_export_decryption_certificate); + table->AddSpacer (0); + + _add_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::add_certificate, this)); + _remove_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::remove_certificate, this)); + _certificates->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, boost::bind (&KeysPage::update_sensitivity, this)); + _certificates->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&KeysPage::update_sensitivity, this)); + _remake_certificates->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::remake_certificates, this)); + _load_signer_private_key->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_signer_private_key, this)); + _load_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_decryption_certificate, this)); + _load_decryption_private_key->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_decryption_private_key, this)); + _export_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::export_decryption_certificate, this)); + + _signer.reset (new dcp::Signer (*Config::instance()->signer().get ())); + + update_certificate_list (); + update_signer_private_key (); + update_decryption_certificate (); + update_decryption_private_key (); + update_sensitivity (); + + return _panel; + } + +private: + void add_certificate () + { + wxFileDialog* d = new wxFileDialog (_panel, _("Select Certificate File")); + + if (d->ShowModal() == wxID_OK) { + try { + dcp::Certificate c (dcp::file_to_string (wx_to_std (d->GetPath ()))); + _signer->certificates().add (c); + Config::instance()->set_signer (_signer); + update_certificate_list (); + } catch (dcp::MiscError& e) { + error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ())); + } + } + + d->Destroy (); + + update_sensitivity (); + } + + void remove_certificate () + { + int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (i == -1) { + return; + } + + _certificates->DeleteItem (i); + _signer->certificates().remove (i); + Config::instance()->set_signer (_signer); + + update_sensitivity (); + } + + void update_certificate_list () + { + _certificates->DeleteAllItems (); + dcp::CertificateChain::List certs = _signer->certificates().root_to_leaf (); + size_t n = 0; + for (dcp::CertificateChain::List::const_iterator i = certs.begin(); i != certs.end(); ++i) { + wxListItem item; + item.SetId (n); + _certificates->InsertItem (item); + _certificates->SetItem (n, 1, std_to_wx (i->thumbprint ())); + + if (n == 0) { + _certificates->SetItem (n, 0, _("Root")); + } else if (n == (certs.size() - 1)) { + _certificates->SetItem (n, 0, _("Leaf")); + } else { + _certificates->SetItem (n, 0, _("Intermediate")); + } + + ++n; + } + } + + void remake_certificates () + { + MakeSignerChainDialog* d = new MakeSignerChainDialog (_panel); + if (d->ShowModal () == wxID_OK) { + _signer.reset ( + new dcp::Signer ( + openssl_path (), + d->organisation (), + d->organisational_unit (), + d->root_common_name (), + d->intermediate_common_name (), + d->leaf_common_name () + ) + ); + + Config::instance()->set_signer (_signer); + update_certificate_list (); + update_signer_private_key (); + } + + d->Destroy (); + } + + void update_sensitivity () + { + _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1); + } + + void update_signer_private_key () + { + _signer_private_key->SetLabel (std_to_wx (dcp::private_key_fingerprint (_signer->key ()))); + } + + void load_signer_private_key () + { + wxFileDialog* d = new wxFileDialog (_panel, _("Select Key File")); + + if (d->ShowModal() == wxID_OK) { + try { + boost::filesystem::path p (wx_to_std (d->GetPath ())); + if (boost::filesystem::file_size (p) > 1024) { + error_dialog (_panel, wxString::Format (_("Could not read key file (%s)"), std_to_wx (p.string ()))); + return; + } + + _signer->set_key (dcp::file_to_string (p)); + Config::instance()->set_signer (_signer); + update_signer_private_key (); + } catch (dcp::MiscError& e) { + error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ())); + } + } + + d->Destroy (); + + update_sensitivity (); + + } + + void load_decryption_certificate () + { + wxFileDialog* d = new wxFileDialog (_panel, _("Select Certificate File")); + + if (d->ShowModal() == wxID_OK) { + try { + dcp::Certificate c (dcp::file_to_string (wx_to_std (d->GetPath ()))); + Config::instance()->set_decryption_certificate (c); + update_decryption_certificate (); + } catch (dcp::MiscError& e) { + error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ())); + } + } + + d->Destroy (); + } + + void update_decryption_certificate () + { + _decryption_certificate->SetLabel (std_to_wx (Config::instance()->decryption_certificate().thumbprint ())); + } + + void load_decryption_private_key () + { + wxFileDialog* d = new wxFileDialog (_panel, _("Select Key File")); + + if (d->ShowModal() == wxID_OK) { + try { + boost::filesystem::path p (wx_to_std (d->GetPath ())); + Config::instance()->set_decryption_private_key (dcp::file_to_string (p)); + update_decryption_private_key (); + } catch (dcp::MiscError& e) { + error_dialog (_panel, wxString::Format (_("Could not read key file (%s)"), e.what ())); + } + } + + d->Destroy (); + } + + void update_decryption_private_key () + { + _decryption_private_key->SetLabel (std_to_wx (dcp::private_key_fingerprint (Config::instance()->decryption_private_key()))); + } + + void export_decryption_certificate () + { + wxFileDialog* d = new wxFileDialog ( + _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"), + wxFD_SAVE | wxFD_OVERWRITE_PROMPT + ); + + if (d->ShowModal () == wxID_OK) { + FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w"); + if (!f) { + throw OpenFileError (wx_to_std (d->GetPath ())); + } + + string const s = Config::instance()->decryption_certificate().certificate (true); + fwrite (s.c_str(), 1, s.length(), f); + fclose (f); + } + d->Destroy (); + } + + wxPanel* _panel; + wxListCtrl* _certificates; + wxButton* _add_certificate; + wxButton* _remove_certificate; + wxButton* _remake_certificates; + wxStaticText* _signer_private_key; + wxButton* _load_signer_private_key; + wxStaticText* _decryption_certificate; + wxButton* _load_decryption_certificate; + wxStaticText* _decryption_private_key; + wxButton* _load_decryption_private_key; + wxButton* _export_decryption_certificate; + shared_ptr<dcp::Signer> _signer; +}; + class TMSPage : public wxPreferencesPage, public Page { public: @@ -770,6 +1109,9 @@ private: wxButton* _reset_kdm_email; }; +/** @class AdvancedPage + * @brief Advanced page of the preferences dialog. + */ class AdvancedPage : public wxStockPreferencesPage, public Page { public: @@ -821,6 +1163,12 @@ public: table->Add (t, 0, wxALL, 6); } +#ifdef DCPOMATIC_WINDOWS + _win32_console = new wxCheckBox (panel, wxID_ANY, _("Open console window")); + table->Add (_win32_console, 1, wxEXPAND | wxALL); + table->AddSpacer (0); +#endif + Config* config = Config::instance (); _maximum_j2k_bandwidth->SetRange (1, 500); @@ -836,6 +1184,10 @@ public: _log_error->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this)); _log_timing->SetValue (config->log_types() & Log::TYPE_TIMING); _log_timing->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this)); +#ifdef DCPOMATIC_WINDOWS + _win32_console->SetValue (config->win32_console()); + _win32_console->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::win32_console_changed, this)); +#endif return panel; } @@ -869,6 +1221,13 @@ private: } Config::instance()->set_log_types (types); } + +#ifdef DCPOMATIC_WINDOWS + void win32_console_changed () + { + Config::instance()->set_win32_console (_win32_console->GetValue ()); + } +#endif wxSpinCtrl* _maximum_j2k_bandwidth; wxCheckBox* _allow_any_dcp_frame_rate; @@ -876,6 +1235,9 @@ private: wxCheckBox* _log_warning; wxCheckBox* _log_error; wxCheckBox* _log_timing; +#ifdef DCPOMATIC_WINDOWS + wxCheckBox* _win32_console; +#endif }; wxPreferencesEditor* @@ -899,6 +1261,7 @@ create_config_dialog () e->AddPage (new DefaultsPage (ps, border)); e->AddPage (new EncodingServersPage (ps, border)); e->AddPage (new ColourConversionsPage (ps, border)); + e->AddPage (new KeysPage (ps, border)); e->AddPage (new TMSPage (ps, border)); e->AddPage (new KDMEmailPage (ps, border)); e->AddPage (new AdvancedPage (ps, border)); diff --git a/src/wx/content_menu.cc b/src/wx/content_menu.cc index b91c82ab1..3e3c462b2 100644 --- a/src/wx/content_menu.cc +++ b/src/wx/content_menu.cc @@ -26,6 +26,7 @@ #include "lib/examine_content_job.h" #include "lib/job_manager.h" #include "lib/exceptions.h" +#include "lib/dcp_content.h" #include "content_menu.h" #include "repeat_dialog.h" #include "wx_util.h" @@ -40,6 +41,8 @@ enum { ID_repeat = 1, ID_join, ID_find_missing, + ID_re_examine, + ID_kdm, ID_remove }; @@ -50,12 +53,16 @@ ContentMenu::ContentMenu (wxWindow* p) _repeat = _menu->Append (ID_repeat, _("Repeat...")); _join = _menu->Append (ID_join, _("Join")); _find_missing = _menu->Append (ID_find_missing, _("Find missing...")); + _re_examine = _menu->Append (ID_re_examine, _("Re-examine...")); + _kdm = _menu->Append (ID_kdm, _("Add KDM...")); _menu->AppendSeparator (); _remove = _menu->Append (ID_remove, _("Remove")); _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::repeat, this), ID_repeat); _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::join, this), ID_join); _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::find_missing, this), ID_find_missing); + _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::re_examine, this), ID_re_examine); + _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::kdm, this), ID_kdm); _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::remove, this), ID_remove); } @@ -81,6 +88,15 @@ ContentMenu::popup (weak_ptr<Film> f, ContentList c, wxPoint p) _join->Enable (n > 1); _find_missing->Enable (_content.size() == 1 && !_content.front()->paths_valid ()); + _re_examine->Enable (!_content.empty ()); + + if (_content.size() == 1) { + shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (_content.front ()); + _kdm->Enable (dcp && dcp->encrypted ()); + } else { + _kdm->Enable (false); + } + _remove->Enable (!_content.empty ()); _parent->PopupMenu (_menu, p); } @@ -207,6 +223,19 @@ ContentMenu::find_missing () } void +ContentMenu::re_examine () +{ + shared_ptr<Film> film = _film.lock (); + if (!film) { + return; + } + + for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) { + film->examine_content (*i); + } +} + +void ContentMenu::maybe_found_missing (weak_ptr<Job> j, weak_ptr<Content> oc, weak_ptr<Content> nc) { shared_ptr<Job> job = j.lock (); @@ -226,3 +255,22 @@ ContentMenu::maybe_found_missing (weak_ptr<Job> j, weak_ptr<Content> oc, weak_pt old_content->set_path (new_content->path (0)); } + +void +ContentMenu::kdm () +{ + assert (!_content.empty ()); + shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (_content.front ()); + assert (dcp); + + wxFileDialog* d = new wxFileDialog (_parent, _("Select KDM")); + + if (d->ShowModal() == wxID_OK) { + dcp->add_kdm (dcp::EncryptedKDM (dcp::file_to_string (wx_to_std (d->GetPath ())))); + shared_ptr<Film> film = _film.lock (); + assert (film); + film->examine_content (dcp); + } + + d->Destroy (); +} diff --git a/src/wx/content_menu.h b/src/wx/content_menu.h index a9f9093c6..77cf29a30 100644 --- a/src/wx/content_menu.h +++ b/src/wx/content_menu.h @@ -30,15 +30,17 @@ class Film; class ContentMenu { public: - ContentMenu (wxWindow *); + ContentMenu (wxWindow* p); ~ContentMenu (); - + void popup (boost::weak_ptr<Film>, ContentList, wxPoint); private: void repeat (); void join (); void find_missing (); + void re_examine (); + void kdm (); void remove (); void maybe_found_missing (boost::weak_ptr<Job>, boost::weak_ptr<Content>, boost::weak_ptr<Content>); @@ -50,6 +52,8 @@ private: wxMenuItem* _repeat; wxMenuItem* _join; wxMenuItem* _find_missing; + wxMenuItem* _re_examine; + wxMenuItem* _kdm; wxMenuItem* _remove; }; diff --git a/src/wx/content_panel.cc b/src/wx/content_panel.cc new file mode 100644 index 000000000..b4b9f13da --- /dev/null +++ b/src/wx/content_panel.cc @@ -0,0 +1,473 @@ +/* + Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <wx/wx.h> +#include <wx/notebook.h> +#include <wx/listctrl.h> +#include "lib/audio_content.h" +#include "lib/subtitle_content.h" +#include "lib/video_content.h" +#include "lib/ffmpeg_content.h" +#include "lib/content_factory.h" +#include "lib/image_content.h" +#include "lib/dcp_content.h" +#include "lib/playlist.h" +#include "content_panel.h" +#include "wx_util.h" +#include "video_panel.h" +#include "audio_panel.h" +#include "subtitle_panel.h" +#include "timing_panel.h" +#include "timeline_dialog.h" + +using std::list; +using std::string; +using std::cout; +using boost::shared_ptr; +using boost::weak_ptr; +using boost::dynamic_pointer_cast; + +ContentPanel::ContentPanel (wxNotebook* n, boost::shared_ptr<Film> f) + : _timeline_dialog (0) + , _film (f) + , _generally_sensitive (true) +{ + _panel = new wxPanel (n); + _sizer = new wxBoxSizer (wxVERTICAL); + _panel->SetSizer (_sizer); + + _menu = new ContentMenu (_panel); + + { + wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); + + _content = new wxListCtrl (_panel, wxID_ANY, wxDefaultPosition, wxSize (320, 160), wxLC_REPORT | wxLC_NO_HEADER); + s->Add (_content, 1, wxEXPAND | wxTOP | wxBOTTOM, 6); + + _content->InsertColumn (0, wxT("")); + _content->SetColumnWidth (0, 512); + + wxBoxSizer* b = new wxBoxSizer (wxVERTICAL); + _add_file = new wxButton (_panel, wxID_ANY, _("Add file(s)...")); + b->Add (_add_file, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP); + _add_folder = new wxButton (_panel, wxID_ANY, _("Add folder...")); + b->Add (_add_folder, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP); + _remove = new wxButton (_panel, wxID_ANY, _("Remove")); + b->Add (_remove, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP); + _earlier = new wxButton (_panel, wxID_ANY, _("Up")); + b->Add (_earlier, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP); + _later = new wxButton (_panel, wxID_ANY, _("Down")); + b->Add (_later, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP); + _timeline = new wxButton (_panel, wxID_ANY, _("Timeline...")); + b->Add (_timeline, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP); + + s->Add (b, 0, wxALL, 4); + + _sizer->Add (s, 0, wxEXPAND | wxALL, 6); + } + + _sequence_video = new wxCheckBox (_panel, wxID_ANY, _("Keep video in sequence")); + _sizer->Add (_sequence_video); + + _notebook = new wxNotebook (_panel, wxID_ANY); + _sizer->Add (_notebook, 1, wxEXPAND | wxTOP, 6); + + _video_panel = new VideoPanel (this); + _panels.push_back (_video_panel); + _audio_panel = new AudioPanel (this); + _panels.push_back (_audio_panel); + _subtitle_panel = new SubtitlePanel (this); + _panels.push_back (_subtitle_panel); + _timing_panel = new TimingPanel (this); + _panels.push_back (_timing_panel); + + _content->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, boost::bind (&ContentPanel::selection_changed, this)); + _content->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&ContentPanel::selection_changed, this)); + _content->Bind (wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK, boost::bind (&ContentPanel::right_click, this, _1)); + _content->Bind (wxEVT_DROP_FILES, boost::bind (&ContentPanel::files_dropped, this, _1)); + _add_file->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentPanel::add_file_clicked, this)); + _add_folder->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentPanel::add_folder_clicked, this)); + _remove->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentPanel::remove_clicked, this)); + _earlier->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentPanel::earlier_clicked, this)); + _later->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentPanel::later_clicked, this)); + _timeline->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentPanel::timeline_clicked, this)); + _sequence_video->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&ContentPanel::sequence_video_changed, this)); +} + +ContentList +ContentPanel::selected () +{ + ContentList sel; + long int s = -1; + while (true) { + s = _content->GetNextItem (s, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (s == -1) { + break; + } + + if (s < int (_film->content().size ())) { + sel.push_back (_film->content()[s]); + } + } + + return sel; +} + +VideoContentList +ContentPanel::selected_video () +{ + ContentList c = selected (); + VideoContentList vc; + + for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { + shared_ptr<VideoContent> t = dynamic_pointer_cast<VideoContent> (*i); + if (t) { + vc.push_back (t); + } + } + + return vc; +} + +AudioContentList +ContentPanel::selected_audio () +{ + ContentList c = selected (); + AudioContentList ac; + + for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { + shared_ptr<AudioContent> t = dynamic_pointer_cast<AudioContent> (*i); + if (t) { + ac.push_back (t); + } + } + + return ac; +} + +SubtitleContentList +ContentPanel::selected_subtitle () +{ + ContentList c = selected (); + SubtitleContentList sc; + + for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { + shared_ptr<SubtitleContent> t = dynamic_pointer_cast<SubtitleContent> (*i); + if (t) { + sc.push_back (t); + } + } + + return sc; +} + +FFmpegContentList +ContentPanel::selected_ffmpeg () +{ + ContentList c = selected (); + FFmpegContentList sc; + + for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { + shared_ptr<FFmpegContent> t = dynamic_pointer_cast<FFmpegContent> (*i); + if (t) { + sc.push_back (t); + } + } + + return sc; +} + +void +ContentPanel::sequence_video_changed () +{ + if (!_film) { + return; + } + + _film->set_sequence_video (_sequence_video->GetValue ()); +} + +void +ContentPanel::film_changed (Film::Property p) +{ + switch (p) { + case Film::CONTENT: + setup (); + break; + case Film::SEQUENCE_VIDEO: + checked_set (_sequence_video, _film->sequence_video ()); + break; + default: + break; + } + + for (list<ContentSubPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) { + (*i)->film_changed (p); + } +} + +void +ContentPanel::selection_changed () +{ + setup_sensitivity (); + + for (list<ContentSubPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) { + (*i)->content_selection_changed (); + } +} + +void +ContentPanel::add_file_clicked () +{ + /* The wxFD_CHANGE_DIR here prevents a `could not set working directory' error 123 on Windows when using + non-Latin filenames or paths. + */ + wxFileDialog* d = new wxFileDialog (_panel, _("Choose a file or files"), wxT (""), wxT (""), wxT ("*.*"), wxFD_MULTIPLE | wxFD_CHANGE_DIR); + int const r = d->ShowModal (); + + if (r != wxID_OK) { + d->Destroy (); + return; + } + + wxArrayString paths; + d->GetPaths (paths); + + /* XXX: check for lots of files here and do something */ + + for (unsigned int i = 0; i < paths.GetCount(); ++i) { + _film->examine_and_add_content (content_factory (_film, wx_to_std (paths[i]))); + } + + d->Destroy (); +} + +void +ContentPanel::add_folder_clicked () +{ + wxDirDialog* d = new wxDirDialog (_panel, _("Choose a folder"), wxT (""), wxDD_DIR_MUST_EXIST); + int const r = d->ShowModal (); + d->Destroy (); + + if (r != wxID_OK) { + return; + } + + shared_ptr<Content> content; + + try { + content.reset (new ImageContent (_film, boost::filesystem::path (wx_to_std (d->GetPath ())))); + } catch (...) { + try { + content.reset (new DCPContent (_film, boost::filesystem::path (wx_to_std (d->GetPath ())))); + } catch (...) { + error_dialog (_panel, _("Could not find any images nor a DCP in that folder")); + return; + } + } + + if (content) { + _film->examine_and_add_content (content); + } +} + +void +ContentPanel::remove_clicked () +{ + ContentList c = selected (); + if (c.size() == 1) { + _film->remove_content (c.front ()); + } + + selection_changed (); +} + +void +ContentPanel::timeline_clicked () +{ + if (_timeline_dialog) { + _timeline_dialog->Destroy (); + _timeline_dialog = 0; + } + + _timeline_dialog = new TimelineDialog (this, _film); + _timeline_dialog->Show (); +} + +void +ContentPanel::right_click (wxListEvent& ev) +{ + _menu->popup (_film, selected (), ev.GetPoint ()); +} + +/** Set up broad sensitivity based on the type of content that is selected */ +void +ContentPanel::setup_sensitivity () +{ + _add_file->Enable (_generally_sensitive); + _add_folder->Enable (_generally_sensitive); + + ContentList selection = selected (); + VideoContentList video_selection = selected_video (); + AudioContentList audio_selection = selected_audio (); + + _remove->Enable (selection.size() == 1 && _generally_sensitive); + _earlier->Enable (selection.size() == 1 && _generally_sensitive); + _later->Enable (selection.size() == 1 && _generally_sensitive); + _timeline->Enable (!_film->content().empty() && _generally_sensitive); + + _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<SubtitleContent> (selection.front()) && _generally_sensitive); + _timing_panel->Enable (selection.size() == 1 && _generally_sensitive); +} + +void +ContentPanel::set_film (shared_ptr<Film> f) +{ + _film = f; + + film_changed (Film::CONTENT); + selection_changed (); +} + +void +ContentPanel::set_general_sensitivity (bool s) +{ + _generally_sensitive = s; + + _content->Enable (s); + _add_file->Enable (s); + _add_folder->Enable (s); + _remove->Enable (s); + _earlier->Enable (s); + _later->Enable (s); + _timeline->Enable (s); + _sequence_video->Enable (s); + + /* Set the panels in the content notebook */ + for (list<ContentSubPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) { + (*i)->Enable (s); + } +} + +void +ContentPanel::earlier_clicked () +{ + ContentList sel = selected (); + if (sel.size() == 1) { + _film->move_content_earlier (sel.front ()); + selection_changed (); + } +} + +void +ContentPanel::later_clicked () +{ + ContentList sel = selected (); + if (sel.size() == 1) { + _film->move_content_later (sel.front ()); + selection_changed (); + } +} + +void +ContentPanel::set_selection (weak_ptr<Content> wc) +{ + ContentList content = _film->content (); + for (size_t i = 0; i < content.size(); ++i) { + if (content[i] == wc.lock ()) { + _content->SetItemState (i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + } else { + _content->SetItemState (i, 0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); + } + } +} + +void +ContentPanel::film_content_changed (int property) +{ + if (property == ContentProperty::PATH || property == ContentProperty::POSITION || property == DCPContentProperty::CAN_BE_PLAYED) { + setup (); + } + + for (list<ContentSubPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) { + (*i)->film_content_changed (property); + } +} + +void +ContentPanel::setup () +{ + string selected_summary; + int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (s != -1) { + selected_summary = wx_to_std (_content->GetItemText (s)); + } + + _content->DeleteAllItems (); + + ContentList content = _film->content (); + sort (content.begin(), content.end(), ContentSorter ()); + + for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { + int const t = _content->GetItemCount (); + bool const valid = (*i)->paths_valid (); + shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (*i); + bool const needs_kdm = dcp && !dcp->can_be_played (); + + string s = (*i)->summary (); + + if (!valid) { + s = _("MISSING: ") + s; + } + + if (needs_kdm) { + s = _("NEEDS KDM: ") + s; + } + + _content->InsertItem (t, std_to_wx (s)); + + if ((*i)->summary() == selected_summary) { + _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + } + + if (!valid || needs_kdm) { + _content->SetItemTextColour (t, *wxRED); + } + } + + if (selected_summary.empty () && !content.empty ()) { + /* Select the item of content if none was selected before */ + _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + } +} + +void +ContentPanel::files_dropped (wxDropFilesEvent& event) +{ + if (!_film) { + return; + } + + wxString* paths = event.GetFiles (); + for (int i = 0; i < event.GetNumberOfFiles(); i++) { + _film->examine_and_add_content (content_factory (_film, wx_to_std (paths[i]))); + } +} diff --git a/src/wx/content_panel.h b/src/wx/content_panel.h new file mode 100644 index 000000000..ab198411d --- /dev/null +++ b/src/wx/content_panel.h @@ -0,0 +1,103 @@ +/* + Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <list> +#include <boost/shared_ptr.hpp> +#include "lib/types.h" +#include "lib/film.h" +#include "content_menu.h" + +class wxNotebook; +class wxPanel; +class wxSizer; +class wxListCtrl; +class wxListEvent; +class TimelineDialog; +class FilmEditor; +class ContentSubPanel; +class Film; + +class ContentPanel +{ +public: + ContentPanel (wxNotebook *, boost::shared_ptr<Film>); + + boost::shared_ptr<Film> film () const { + return _film; + } + + void set_film (boost::shared_ptr<Film> f); + void set_general_sensitivity (bool s); + void set_selection (boost::weak_ptr<Content>); + + void film_changed (Film::Property p); + void film_content_changed (int p); + + wxPanel* panel () const { + return _panel; + } + + wxNotebook* notebook () const { + return _notebook; + } + + ContentList selected (); + VideoContentList selected_video (); + AudioContentList selected_audio (); + SubtitleContentList selected_subtitle (); + FFmpegContentList selected_ffmpeg (); + + void add_file_clicked (); + +private: + void sequence_video_changed (); + void selection_changed (); + void add_folder_clicked (); + void remove_clicked (); + void earlier_clicked (); + void later_clicked (); + void right_click (wxListEvent &); + void files_dropped (wxDropFilesEvent &); + void timeline_clicked (); + + void setup (); + void setup_sensitivity (); + + wxPanel* _panel; + wxSizer* _sizer; + wxNotebook* _notebook; + wxListCtrl* _content; + wxButton* _add_file; + wxButton* _add_folder; + wxButton* _remove; + wxButton* _earlier; + wxButton* _later; + wxButton* _timeline; + wxCheckBox* _sequence_video; + ContentSubPanel* _video_panel; + ContentSubPanel* _audio_panel; + ContentSubPanel* _subtitle_panel; + ContentSubPanel* _timing_panel; + std::list<ContentSubPanel *> _panels; + ContentMenu* _menu; + TimelineDialog* _timeline_dialog; + + boost::shared_ptr<Film> _film; + bool _generally_sensitive; +}; diff --git a/src/wx/film_editor_panel.cc b/src/wx/content_sub_panel.cc index a637df1fe..7ea17c15c 100644 --- a/src/wx/film_editor_panel.cc +++ b/src/wx/content_sub_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 @@ -18,17 +18,17 @@ */ #include <wx/notebook.h> -#include "film_editor_panel.h" -#include "film_editor.h" +#include "content_sub_panel.h" +#include "content_panel.h" using boost::shared_ptr; -FilmEditorPanel::FilmEditorPanel (FilmEditor* e, wxString name) - : wxPanel (e->content_notebook (), wxID_ANY) - , _editor (e) +ContentSubPanel::ContentSubPanel (ContentPanel* p, wxString name) + : wxPanel (p->notebook(), wxID_ANY) + , _parent (p) , _sizer (new wxBoxSizer (wxVERTICAL)) { - e->content_notebook()->AddPage (this, name, false); + p->notebook()->AddPage (this, name, false); SetSizer (_sizer); } diff --git a/src/wx/film_editor_panel.h b/src/wx/content_sub_panel.h index e0514ba99..5a1b739ef 100644 --- a/src/wx/film_editor_panel.h +++ b/src/wx/content_sub_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,20 +17,20 @@ */ -#ifndef DCPOMATIC_FILM_EDITOR_PANEL_H -#define DCPOMATIC_FILM_EDITOR_PANEL_H +#ifndef DCPOMATIC_CONTENT_SUB_PANEL_H +#define DCPOMATIC_CONTENT_SUB_PANEL_H #include <boost/shared_ptr.hpp> #include <wx/wx.h> #include "lib/film.h" -class FilmEditor; +class ContentPanel; class Content; -class FilmEditorPanel : public wxPanel +class ContentSubPanel : public wxPanel { public: - FilmEditorPanel (FilmEditor *, wxString); + ContentSubPanel (ContentPanel *, wxString); virtual void film_changed (Film::Property) {} /** Called when a given property of one of the selected Contents changes */ @@ -39,7 +39,7 @@ public: virtual void content_selection_changed () = 0; protected: - FilmEditor* _editor; + ContentPanel* _parent; wxSizer* _sizer; }; diff --git a/src/wx/content_widget.h b/src/wx/content_widget.h index ca9485006..b4d06286e 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,16 +17,22 @@ */ +/** @file src/wx/content_widget.h + * @brief ContentWidget class. + */ + #ifndef DCPOMATIC_MULTIPLE_WIDGET_H #define DCPOMATIC_MULTIPLE_WIDGET_H #include <vector> #include <wx/wx.h> #include <wx/gbsizer.h> +#include <wx/spinctrl.h> #include <boost/function.hpp> #include "wx_util.h" -/** A widget which represents some Content state and which can be used +/** @class ContentWidget + * @brief A widget which represents some Content state and which can be used * when multiple pieces of content are selected. * * @param S Type containing the content being represented (e.g. VideoContent) @@ -97,11 +103,12 @@ public: } /** Add this widget to a wxGridBagSizer */ - void add (wxGridBagSizer* sizer, wxGBPosition position) + void add (wxGridBagSizer* sizer, wxGBPosition position, wxGBSpan span = wxDefaultSpan) { _sizer = sizer; _position = position; - _sizer->Add (_wrapped, _position); + _span = span; + _sizer->Add (_wrapped, _position, _span); } /** Update the view from the model */ @@ -145,7 +152,7 @@ private: _sizer->Detach (_button); _button->Hide (); - _sizer->Add (_wrapped, _position); + _sizer->Add (_wrapped, _position, _span); _wrapped->Show (); _sizer->Layout (); } @@ -159,7 +166,7 @@ private: _wrapped->Hide (); _sizer->Detach (_wrapped); _button->Show (); - _sizer->Add (_button, _position); + _sizer->Add (_button, _position, _span); _sizer->Layout (); } @@ -181,6 +188,7 @@ private: T* _wrapped; wxGridBagSizer* _sizer; wxGBPosition _position; + wxGBSpan _span; wxButton* _button; List _content; int _property; diff --git a/src/wx/dcp_panel.cc b/src/wx/dcp_panel.cc new file mode 100644 index 000000000..f042e5eb1 --- /dev/null +++ b/src/wx/dcp_panel.cc @@ -0,0 +1,633 @@ +/* + Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "dcp_panel.h" +#include "wx_util.h" +#include "isdcf_metadata_dialog.h" +#include "lib/ratio.h" +#include "lib/scaler.h" +#include "lib/config.h" +#include "lib/dcp_content_type.h" +#include "lib/util.h" +#include "lib/film.h" +#include "lib/ffmpeg_content.h" +#include <wx/wx.h> +#include <wx/notebook.h> +#include <wx/gbsizer.h> +#include <wx/spinctrl.h> +#include <boost/lexical_cast.hpp> + +using std::cout; +using std::list; +using std::string; +using std::vector; +using boost::lexical_cast; +using boost::shared_ptr; + +DCPPanel::DCPPanel (wxNotebook* n, boost::shared_ptr<Film> f) + : _film (f) + , _generally_sensitive (true) +{ + _panel = new wxPanel (n); + _sizer = new wxBoxSizer (wxVERTICAL); + _panel->SetSizer (_sizer); + + wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); + _sizer->Add (grid, 0, wxEXPAND | wxALL, 8); + + int r = 0; + + add_label_to_grid_bag_sizer (grid, _panel, _("Name"), true, wxGBPosition (r, 0)); + _name = new wxTextCtrl (_panel, wxID_ANY); + grid->Add (_name, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND | wxLEFT | wxRIGHT); + ++r; + + int flags = wxALIGN_CENTER_VERTICAL; +#ifdef __WXOSX__ + flags |= wxALIGN_RIGHT; +#endif + + _use_isdcf_name = new wxCheckBox (_panel, wxID_ANY, _("Use ISDCF name")); + grid->Add (_use_isdcf_name, wxGBPosition (r, 0), wxDefaultSpan, flags); + _edit_isdcf_button = new wxButton (_panel, wxID_ANY, _("Details...")); + grid->Add (_edit_isdcf_button, wxGBPosition (r, 1)); + ++r; + + add_label_to_grid_bag_sizer (grid, _panel, _("DCP Name"), true, wxGBPosition (r, 0)); + _dcp_name = new wxStaticText (_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END); + grid->Add (_dcp_name, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxEXPAND); + ++r; + + add_label_to_grid_bag_sizer (grid, _panel, _("Content Type"), true, wxGBPosition (r, 0)); + _dcp_content_type = new wxChoice (_panel, wxID_ANY); + grid->Add (_dcp_content_type, wxGBPosition (r, 1)); + ++r; + + _notebook = new wxNotebook (_panel, wxID_ANY); + _sizer->Add (_notebook, 1, wxEXPAND | wxTOP, 6); + + _notebook->AddPage (make_video_panel (), _("Video"), false); + _notebook->AddPage (make_audio_panel (), _("Audio"), false); + + _signed = new wxCheckBox (_panel, wxID_ANY, _("Signed")); + grid->Add (_signed, wxGBPosition (r, 0), wxGBSpan (1, 2)); + ++r; + + _encrypted = new wxCheckBox (_panel, wxID_ANY, _("Encrypted")); + grid->Add (_encrypted, wxGBPosition (r, 0), wxGBSpan (1, 2)); + ++r; + + add_label_to_grid_bag_sizer (grid, _panel, _("Standard"), true, wxGBPosition (r, 0)); + _standard = new wxChoice (_panel, wxID_ANY); + grid->Add (_standard, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); + ++r; + + _name->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&DCPPanel::name_changed, this)); + _use_isdcf_name->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&DCPPanel::use_isdcf_name_toggled, this)); + _edit_isdcf_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&DCPPanel::edit_isdcf_button_clicked, this)); + _dcp_content_type->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DCPPanel::dcp_content_type_changed, this)); + _signed->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&DCPPanel::signed_toggled, this)); + _encrypted->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&DCPPanel::encrypted_toggled, this)); + _standard->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DCPPanel::standard_changed, this)); + + vector<DCPContentType const *> const ct = DCPContentType::all (); + for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) { + _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ())); + } + + _standard->Append (_("SMPTE")); + _standard->Append (_("Interop")); + + Config::instance()->Changed.connect (boost::bind (&DCPPanel::config_changed, this)); +} + +void +DCPPanel::name_changed () +{ + if (!_film) { + return; + } + + _film->set_name (string (_name->GetValue().mb_str())); +} + +void +DCPPanel::j2k_bandwidth_changed () +{ + if (!_film) { + return; + } + + _film->set_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000); +} + +void +DCPPanel::signed_toggled () +{ + if (!_film) { + return; + } + + _film->set_signed (_signed->GetValue ()); +} + +void +DCPPanel::burn_subtitles_toggled () +{ + if (!_film) { + return; + } + + _film->set_burn_subtitles (_burn_subtitles->GetValue ()); +} + +void +DCPPanel::encrypted_toggled () +{ + if (!_film) { + return; + } + + _film->set_encrypted (_encrypted->GetValue ()); +} + +/** Called when the frame rate choice widget has been changed */ +void +DCPPanel::frame_rate_choice_changed () +{ + if (!_film) { + return; + } + + _film->set_video_frame_rate ( + boost::lexical_cast<int> ( + wx_to_std (_frame_rate_choice->GetString (_frame_rate_choice->GetSelection ())) + ) + ); +} + +/** Called when the frame rate spin widget has been changed */ +void +DCPPanel::frame_rate_spin_changed () +{ + if (!_film) { + return; + } + + _film->set_video_frame_rate (_frame_rate_spin->GetValue ()); +} + +void +DCPPanel::audio_channels_changed () +{ + if (!_film) { + return; + } + + _film->set_audio_channels (_audio_channels->GetValue ()); +} + +void +DCPPanel::resolution_changed () +{ + if (!_film) { + return; + } + + _film->set_resolution (_resolution->GetSelection() == 0 ? RESOLUTION_2K : RESOLUTION_4K); +} + +void +DCPPanel::standard_changed () +{ + if (!_film) { + return; + } + + _film->set_interop (_standard->GetSelection() == 1); +} + +void +DCPPanel::film_changed (int p) +{ + switch (p) { + case Film::NONE: + break; + case Film::CONTAINER: + setup_container (); + break; + case Film::NAME: + checked_set (_name, _film->name()); + setup_dcp_name (); + break; + case Film::DCP_CONTENT_TYPE: + checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ())); + setup_dcp_name (); + break; + case Film::SCALER: + checked_set (_scaler, Scaler::as_index (_film->scaler ())); + break; + case Film::BURN_SUBTITLES: + checked_set (_burn_subtitles, _film->burn_subtitles ()); + break; + case Film::SIGNED: + checked_set (_signed, _film->is_signed ()); + break; + case Film::ENCRYPTED: + checked_set (_encrypted, _film->encrypted ()); + if (_film->encrypted ()) { + _film->set_signed (true); + _signed->Enable (false); + } else { + _signed->Enable (_generally_sensitive); + } + break; + case Film::RESOLUTION: + checked_set (_resolution, _film->resolution() == RESOLUTION_2K ? 0 : 1); + setup_dcp_name (); + break; + case Film::J2K_BANDWIDTH: + checked_set (_j2k_bandwidth, _film->j2k_bandwidth() / 1000000); + break; + case Film::USE_ISDCF_NAME: + { + checked_set (_use_isdcf_name, _film->use_isdcf_name ()); + setup_dcp_name (); + bool const i = _film->use_isdcf_name (); + if (!i) { + _film->set_name (_film->isdcf_name (true)); + } + _edit_isdcf_button->Enable (i); + break; + } + case Film::ISDCF_METADATA: + setup_dcp_name (); + break; + case Film::VIDEO_FRAME_RATE: + { + bool done = false; + for (unsigned int i = 0; i < _frame_rate_choice->GetCount(); ++i) { + if (wx_to_std (_frame_rate_choice->GetString(i)) == boost::lexical_cast<string> (_film->video_frame_rate())) { + checked_set (_frame_rate_choice, i); + done = true; + break; + } + } + + if (!done) { + checked_set (_frame_rate_choice, -1); + } + + _frame_rate_spin->SetValue (_film->video_frame_rate ()); + + _best_frame_rate->Enable (_film->best_video_frame_rate () != _film->video_frame_rate ()); + break; + } + case Film::AUDIO_CHANNELS: + checked_set (_audio_channels, _film->audio_channels ()); + setup_dcp_name (); + break; + case Film::THREE_D: + checked_set (_three_d, _film->three_d ()); + setup_dcp_name (); + break; + case Film::INTEROP: + checked_set (_standard, _film->interop() ? 1 : 0); + break; + default: + break; + } +} + +void +DCPPanel::film_content_changed (int property) +{ + if (property == FFmpegContentProperty::AUDIO_STREAM || + property == SubtitleContentProperty::USE_SUBTITLES || + property == VideoContentProperty::VIDEO_SCALE) { + setup_dcp_name (); + } +} + + +void +DCPPanel::setup_container () +{ + int n = 0; + vector<Ratio const *> ratios = Ratio::all (); + vector<Ratio const *>::iterator i = ratios.begin (); + while (i != ratios.end() && *i != _film->container ()) { + ++i; + ++n; + } + + if (i == ratios.end()) { + checked_set (_container, -1); + } else { + checked_set (_container, n); + } + + setup_dcp_name (); +} + +/** Called when the container widget has been changed */ +void +DCPPanel::container_changed () +{ + if (!_film) { + return; + } + + int const n = _container->GetSelection (); + if (n >= 0) { + vector<Ratio const *> ratios = Ratio::all (); + assert (n < int (ratios.size())); + _film->set_container (ratios[n]); + } +} + +/** Called when the DCP content type widget has been changed */ +void +DCPPanel::dcp_content_type_changed () +{ + if (!_film) { + return; + } + + int const n = _dcp_content_type->GetSelection (); + if (n != wxNOT_FOUND) { + _film->set_dcp_content_type (DCPContentType::from_index (n)); + } +} + +void +DCPPanel::set_film (shared_ptr<Film> film) +{ + _film = film; + + film_changed (Film::NAME); + film_changed (Film::USE_ISDCF_NAME); + film_changed (Film::CONTENT); + film_changed (Film::DCP_CONTENT_TYPE); + film_changed (Film::CONTAINER); + film_changed (Film::RESOLUTION); + film_changed (Film::SCALER); + film_changed (Film::SIGNED); + film_changed (Film::BURN_SUBTITLES); + film_changed (Film::ENCRYPTED); + film_changed (Film::J2K_BANDWIDTH); + film_changed (Film::ISDCF_METADATA); + film_changed (Film::VIDEO_FRAME_RATE); + film_changed (Film::AUDIO_CHANNELS); + film_changed (Film::SEQUENCE_VIDEO); + film_changed (Film::THREE_D); + film_changed (Film::INTEROP); +} + +void +DCPPanel::set_general_sensitivity (bool s) +{ + _name->Enable (s); + _use_isdcf_name->Enable (s); + _edit_isdcf_button->Enable (s); + _dcp_content_type->Enable (s); + + bool si = s; + if (_film && _film->encrypted ()) { + si = false; + } + _burn_subtitles->Enable (s); + _signed->Enable (si); + + _encrypted->Enable (s); + _frame_rate_choice->Enable (s); + _frame_rate_spin->Enable (s); + _audio_channels->Enable (s); + _j2k_bandwidth->Enable (s); + _container->Enable (s); + _best_frame_rate->Enable (s && _film && _film->best_video_frame_rate () != _film->video_frame_rate ()); + _resolution->Enable (s); + _scaler->Enable (s); + _three_d->Enable (s); + _standard->Enable (s); +} + +/** Called when the scaler widget has been changed */ +void +DCPPanel::scaler_changed () +{ + if (!_film) { + return; + } + + int const n = _scaler->GetSelection (); + if (n >= 0) { + _film->set_scaler (Scaler::from_index (n)); + } +} + +void +DCPPanel::use_isdcf_name_toggled () +{ + if (!_film) { + return; + } + + _film->set_use_isdcf_name (_use_isdcf_name->GetValue ()); +} + +void +DCPPanel::edit_isdcf_button_clicked () +{ + if (!_film) { + return; + } + + ISDCFMetadataDialog* d = new ISDCFMetadataDialog (_panel, _film->isdcf_metadata ()); + d->ShowModal (); + _film->set_isdcf_metadata (d->isdcf_metadata ()); + d->Destroy (); +} + +void +DCPPanel::setup_dcp_name () +{ + string s = _film->dcp_name (true); + if (s.length() > 28) { + _dcp_name->SetLabel (std_to_wx (s.substr (0, 28)) + N_("...")); + _dcp_name->SetToolTip (std_to_wx (s)); + } else { + _dcp_name->SetLabel (std_to_wx (s)); + } +} + +void +DCPPanel::best_frame_rate_clicked () +{ + if (!_film) { + return; + } + + _film->set_video_frame_rate (_film->best_video_frame_rate ()); +} + +void +DCPPanel::three_d_changed () +{ + if (!_film) { + return; + } + + _film->set_three_d (_three_d->GetValue ()); +} + +void +DCPPanel::config_changed () +{ + _j2k_bandwidth->SetRange (1, Config::instance()->maximum_j2k_bandwidth() / 1000000); + setup_frame_rate_widget (); +} + +void +DCPPanel::setup_frame_rate_widget () +{ + if (Config::instance()->allow_any_dcp_frame_rate ()) { + _frame_rate_choice->Hide (); + _frame_rate_spin->Show (); + } else { + _frame_rate_choice->Show (); + _frame_rate_spin->Hide (); + } + + _frame_rate_sizer->Layout (); +} + +wxPanel * +DCPPanel::make_video_panel () +{ + wxPanel* panel = new wxPanel (_notebook); + wxSizer* sizer = new wxBoxSizer (wxVERTICAL); + wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); + sizer->Add (grid, 0, wxALL, 8); + panel->SetSizer (sizer); + + int r = 0; + + add_label_to_grid_bag_sizer (grid, panel, _("Container"), true, wxGBPosition (r, 0)); + _container = new wxChoice (panel, wxID_ANY); + grid->Add (_container, wxGBPosition (r, 1)); + ++r; + + { + add_label_to_grid_bag_sizer (grid, panel, _("Frame Rate"), true, wxGBPosition (r, 0)); + _frame_rate_sizer = new wxBoxSizer (wxHORIZONTAL); + _frame_rate_choice = new wxChoice (panel, wxID_ANY); + _frame_rate_sizer->Add (_frame_rate_choice, 1, wxALIGN_CENTER_VERTICAL); + _frame_rate_spin = new wxSpinCtrl (panel, wxID_ANY); + _frame_rate_sizer->Add (_frame_rate_spin, 1, wxALIGN_CENTER_VERTICAL); + setup_frame_rate_widget (); + _best_frame_rate = new wxButton (panel, wxID_ANY, _("Use best")); + _frame_rate_sizer->Add (_best_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND); + grid->Add (_frame_rate_sizer, wxGBPosition (r, 1)); + } + ++r; + + _burn_subtitles = new wxCheckBox (panel, wxID_ANY, _("Burn subtitles into image")); + grid->Add (_burn_subtitles, wxGBPosition (r, 0), wxGBSpan (1, 2)); + ++r; + + _three_d = new wxCheckBox (panel, wxID_ANY, _("3D")); + grid->Add (_three_d, wxGBPosition (r, 0), wxGBSpan (1, 2)); + ++r; + + add_label_to_grid_bag_sizer (grid, panel, _("Resolution"), true, wxGBPosition (r, 0)); + _resolution = new wxChoice (panel, wxID_ANY); + grid->Add (_resolution, wxGBPosition (r, 1)); + ++r; + + { + add_label_to_grid_bag_sizer (grid, panel, _("JPEG2000 bandwidth"), true, wxGBPosition (r, 0)); + wxSizer* s = new wxBoxSizer (wxHORIZONTAL); + _j2k_bandwidth = new wxSpinCtrl (panel, wxID_ANY); + s->Add (_j2k_bandwidth, 1); + add_label_to_sizer (s, panel, _("Mbit/s"), false); + grid->Add (s, wxGBPosition (r, 1)); + } + ++r; + + add_label_to_grid_bag_sizer (grid, panel, _("Scaler"), true, wxGBPosition (r, 0)); + _scaler = new wxChoice (panel, wxID_ANY); + grid->Add (_scaler, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); + ++r; + + _container->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DCPPanel::container_changed, this)); + _scaler->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DCPPanel::scaler_changed, this)); + _frame_rate_choice->Bind(wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DCPPanel::frame_rate_choice_changed, this)); + _frame_rate_spin->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DCPPanel::frame_rate_spin_changed, this)); + _best_frame_rate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&DCPPanel::best_frame_rate_clicked, this)); + _burn_subtitles->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&DCPPanel::burn_subtitles_toggled, this)); + _j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DCPPanel::j2k_bandwidth_changed, this)); + _resolution->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DCPPanel::resolution_changed, this)); + _three_d->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&DCPPanel::three_d_changed, this)); + + vector<Scaler const *> const sc = Scaler::all (); + for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) { + _scaler->Append (std_to_wx ((*i)->name())); + } + + vector<Ratio const *> const ratio = Ratio::all (); + for (vector<Ratio const *>::const_iterator i = ratio.begin(); i != ratio.end(); ++i) { + _container->Append (std_to_wx ((*i)->nickname ())); + } + + list<int> const dfr = Config::instance()->allowed_dcp_frame_rates (); + for (list<int>::const_iterator i = dfr.begin(); i != dfr.end(); ++i) { + _frame_rate_choice->Append (std_to_wx (boost::lexical_cast<string> (*i))); + } + + _j2k_bandwidth->SetRange (1, Config::instance()->maximum_j2k_bandwidth() / 1000000); + _frame_rate_spin->SetRange (1, 480); + + _resolution->Append (_("2K")); + _resolution->Append (_("4K")); + + return panel; +} + +wxPanel * +DCPPanel::make_audio_panel () +{ + wxPanel* panel = new wxPanel (_notebook); + wxSizer* sizer = new wxBoxSizer (wxVERTICAL); + wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); + sizer->Add (grid, 0, wxALL, 8); + panel->SetSizer (sizer); + + int r = 0; + add_label_to_grid_bag_sizer (grid, panel, _("Channels"), true, wxGBPosition (r, 0)); + _audio_channels = new wxSpinCtrl (panel, wxID_ANY); + grid->Add (_audio_channels, wxGBPosition (r, 1)); + ++r; + + _audio_channels->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DCPPanel::audio_channels_changed, this)); + + _audio_channels->SetRange (0, MAX_DCP_AUDIO_CHANNELS); + + return panel; +} diff --git a/src/wx/dcp_panel.h b/src/wx/dcp_panel.h new file mode 100644 index 000000000..88a9c4c51 --- /dev/null +++ b/src/wx/dcp_panel.h @@ -0,0 +1,106 @@ +/* + Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <boost/shared_ptr.hpp> + +class wxNotebook; +class wxPanel; +class wxBoxSizer; +class wxTextCtrl; +class wxStaticText; +class wxCheckBox; +class wxChoice; +class wxButton; +class wxSpinCtrl; +class wxSizer; + +class Film; + +class DCPPanel +{ +public: + DCPPanel (wxNotebook *, boost::shared_ptr<Film>); + + void set_film (boost::shared_ptr<Film>); + void set_general_sensitivity (bool); + + void film_changed (int); + void film_content_changed (int); + + wxPanel* panel () const { + return _panel; + } + +private: + void name_changed (); + void use_isdcf_name_toggled (); + void edit_isdcf_button_clicked (); + void container_changed (); + void dcp_content_type_changed (); + void scaler_changed (); + void j2k_bandwidth_changed (); + void frame_rate_choice_changed (); + void frame_rate_spin_changed (); + void best_frame_rate_clicked (); + void content_timeline_clicked (); + void audio_channels_changed (); + void resolution_changed (); + void three_d_changed (); + void standard_changed (); + void signed_toggled (); + void burn_subtitles_toggled (); + void encrypted_toggled (); + + void setup_frame_rate_widget (); + void setup_container (); + void setup_dcp_name (); + + wxPanel* make_general_panel (); + wxPanel* make_video_panel (); + wxPanel* make_audio_panel (); + + void config_changed (); + + wxPanel* _panel; + wxNotebook* _notebook; + wxBoxSizer* _sizer; + + wxTextCtrl* _name; + wxStaticText* _dcp_name; + wxCheckBox* _use_isdcf_name; + wxChoice* _container; + wxButton* _edit_isdcf_button; + wxChoice* _scaler; + wxSpinCtrl* _j2k_bandwidth; + wxChoice* _dcp_content_type; + wxChoice* _frame_rate_choice; + wxSpinCtrl* _frame_rate_spin; + wxSizer* _frame_rate_sizer; + wxSpinCtrl* _audio_channels; + wxButton* _best_frame_rate; + wxCheckBox* _three_d; + wxChoice* _resolution; + wxChoice* _standard; + wxCheckBox* _signed; + wxCheckBox* _burn_subtitles; + wxCheckBox* _encrypted; + + boost::shared_ptr<Film> _film; + bool _generally_sensitive; +}; diff --git a/src/wx/editable_list.h b/src/wx/editable_list.h index 5772f6391..481a14741 100644 --- a/src/wx/editable_list.h +++ b/src/wx/editable_list.h @@ -43,7 +43,7 @@ public: wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); table->AddGrowableCol (0, 1); - s->Add (table, 1, wxALL | wxEXPAND, 8); + s->Add (table, 1, wxEXPAND); _list = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (columns.size() * 200, height), wxLC_REPORT | wxLC_SINGLE_SEL); diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc index e73b27267..7f9461d94 100644 --- a/src/wx/film_editor.cc +++ b/src/wx/film_editor.cc @@ -40,21 +40,22 @@ #include "lib/ffmpeg_content.h" #include "lib/sndfile_content.h" #include "lib/dcp_content_type.h" -#include "lib/sound_processor.h" #include "lib/scaler.h" #include "lib/playlist.h" #include "lib/content.h" #include "lib/content_factory.h" +#include "lib/dcp_content.h" #include "lib/safe_stringstream.h" #include "timecode.h" #include "wx_util.h" #include "film_editor.h" -#include "isdcf_metadata_dialog.h" #include "timeline_dialog.h" #include "timing_panel.h" #include "subtitle_panel.h" #include "audio_panel.h" #include "video_panel.h" +#include "content_panel.h" +#include "dcp_panel.h" using std::string; using std::cout; @@ -72,343 +73,26 @@ using boost::lexical_cast; /** @param f Film to edit */ FilmEditor::FilmEditor (wxWindow* parent) : wxPanel (parent) - , _menu (this) - , _generally_sensitive (true) - , _timeline_dialog (0) { wxBoxSizer* s = new wxBoxSizer (wxVERTICAL); _main_notebook = new wxNotebook (this, wxID_ANY); s->Add (_main_notebook, 1); - make_content_panel (); - _main_notebook->AddPage (_content_panel, _("Content"), true); - make_dcp_panel (); - _main_notebook->AddPage (_dcp_panel, _("DCP"), false); + _content_panel = new ContentPanel (_main_notebook, _film); + _main_notebook->AddPage (_content_panel->panel (), _("Content"), true); + _dcp_panel = new DCPPanel (_main_notebook, _film); + _main_notebook->AddPage (_dcp_panel->panel (), _("DCP"), false); - connect_to_widgets (); - JobManager::instance()->ActiveJobsChanged.connect ( bind (&FilmEditor::active_jobs_changed, this, _1) ); - Config::instance()->Changed.connect (boost::bind (&FilmEditor::config_changed, this)); - set_film (shared_ptr<Film> ()); - SetSizerAndFit (s); -} - -void -FilmEditor::make_dcp_panel () -{ - _dcp_panel = new wxPanel (_main_notebook); - _dcp_sizer = new wxBoxSizer (wxVERTICAL); - _dcp_panel->SetSizer (_dcp_sizer); - - wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); - _dcp_sizer->Add (grid, 0, wxEXPAND | wxALL, 8); - - int r = 0; - - add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Name"), true, wxGBPosition (r, 0)); - _name = new wxTextCtrl (_dcp_panel, wxID_ANY); - grid->Add (_name, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND | wxLEFT | wxRIGHT); - ++r; - - int flags = wxALIGN_CENTER_VERTICAL; -#ifdef __WXOSX__ - flags |= wxALIGN_RIGHT; -#endif - - _use_isdcf_name = new wxCheckBox (_dcp_panel, wxID_ANY, _("Use ISDCF name")); - grid->Add (_use_isdcf_name, wxGBPosition (r, 0), wxDefaultSpan, flags); - _edit_isdcf_button = new wxButton (_dcp_panel, wxID_ANY, _("Details...")); - grid->Add (_edit_isdcf_button, wxGBPosition (r, 1), wxDefaultSpan); - ++r; - - add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Name"), true, wxGBPosition (r, 0)); - _dcp_name = new wxStaticText (_dcp_panel, wxID_ANY, wxT ("")); - grid->Add (_dcp_name, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); - ++r; - - add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Container"), true, wxGBPosition (r, 0)); - _container = new wxChoice (_dcp_panel, wxID_ANY); - grid->Add (_container, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND); - ++r; - - add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Content Type"), true, wxGBPosition (r, 0)); - _dcp_content_type = new wxChoice (_dcp_panel, wxID_ANY); - grid->Add (_dcp_content_type, wxGBPosition (r, 1)); - ++r; - - { - add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Frame Rate"), true, wxGBPosition (r, 0)); - _frame_rate_sizer = new wxBoxSizer (wxHORIZONTAL); - _frame_rate_choice = new wxChoice (_dcp_panel, wxID_ANY); - _frame_rate_sizer->Add (_frame_rate_choice, 1, wxALIGN_CENTER_VERTICAL); - _frame_rate_spin = new wxSpinCtrl (_dcp_panel, wxID_ANY); - _frame_rate_sizer->Add (_frame_rate_spin, 1, wxALIGN_CENTER_VERTICAL); - setup_frame_rate_widget (); - _best_frame_rate = new wxButton (_dcp_panel, wxID_ANY, _("Use best")); - _frame_rate_sizer->Add (_best_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND); - grid->Add (_frame_rate_sizer, wxGBPosition (r, 1)); - } - ++r; - - _signed = new wxCheckBox (_dcp_panel, wxID_ANY, _("Signed")); - grid->Add (_signed, wxGBPosition (r, 0), wxGBSpan (1, 2)); - ++r; - _encrypted = new wxCheckBox (_dcp_panel, wxID_ANY, _("Encrypted")); - grid->Add (_encrypted, wxGBPosition (r, 0), wxGBSpan (1, 2)); - ++r; - - add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Audio channels"), true, wxGBPosition (r, 0)); - _audio_channels = new wxSpinCtrl (_dcp_panel, wxID_ANY); - grid->Add (_audio_channels, wxGBPosition (r, 1)); - ++r; - - _three_d = new wxCheckBox (_dcp_panel, wxID_ANY, _("3D")); - grid->Add (_three_d, wxGBPosition (r, 0), wxGBSpan (1, 2)); - ++r; - - add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Resolution"), true, wxGBPosition (r, 0)); - _resolution = new wxChoice (_dcp_panel, wxID_ANY); - grid->Add (_resolution, wxGBPosition (r, 1)); - ++r; - - { - add_label_to_grid_bag_sizer (grid, _dcp_panel, _("JPEG2000 bandwidth"), true, wxGBPosition (r, 0)); - wxSizer* s = new wxBoxSizer (wxHORIZONTAL); - _j2k_bandwidth = new wxSpinCtrl (_dcp_panel, wxID_ANY); - s->Add (_j2k_bandwidth, 1); - add_label_to_sizer (s, _dcp_panel, _("Mbit/s"), false); - grid->Add (s, wxGBPosition (r, 1)); - } - ++r; - - add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Standard"), true, wxGBPosition (r, 0)); - _standard = new wxChoice (_dcp_panel, wxID_ANY); - grid->Add (_standard, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); - ++r; - - add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Scaler"), true, wxGBPosition (r, 0)); - _scaler = new wxChoice (_dcp_panel, wxID_ANY); - grid->Add (_scaler, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); - ++r; - - vector<Scaler const *> const sc = Scaler::all (); - for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) { - _scaler->Append (std_to_wx ((*i)->name())); - } - - vector<Ratio const *> const ratio = Ratio::all (); - for (vector<Ratio const *>::const_iterator i = ratio.begin(); i != ratio.end(); ++i) { - _container->Append (std_to_wx ((*i)->nickname ())); - } - - vector<DCPContentType const *> const ct = DCPContentType::all (); - for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) { - _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ())); - } - - list<int> const dfr = Config::instance()->allowed_dcp_frame_rates (); - for (list<int>::const_iterator i = dfr.begin(); i != dfr.end(); ++i) { - _frame_rate_choice->Append (std_to_wx (boost::lexical_cast<string> (*i))); - } - - _audio_channels->SetRange (0, MAX_DCP_AUDIO_CHANNELS); - _j2k_bandwidth->SetRange (1, Config::instance()->maximum_j2k_bandwidth() / 1000000); - _frame_rate_spin->SetRange (1, 480); - - _resolution->Append (_("2K")); - _resolution->Append (_("4K")); - - _standard->Append (_("SMPTE")); - _standard->Append (_("Interop")); -} - -void -FilmEditor::connect_to_widgets () -{ - _name->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&FilmEditor::name_changed, this)); - _use_isdcf_name->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&FilmEditor::use_isdcf_name_toggled, this)); - _edit_isdcf_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&FilmEditor::edit_isdcf_button_clicked, this)); - _container->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&FilmEditor::container_changed, this)); - _content->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, boost::bind (&FilmEditor::content_selection_changed, this)); - _content->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&FilmEditor::content_selection_changed, this)); - _content->Bind (wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK, boost::bind (&FilmEditor::content_right_click, this, _1)); - _content->Bind (wxEVT_DROP_FILES, boost::bind (&FilmEditor::content_files_dropped, this, _1)); - _content_add_file->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&FilmEditor::content_add_file_clicked, this)); - _content_add_folder->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&FilmEditor::content_add_folder_clicked, this)); - _content_remove->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&FilmEditor::content_remove_clicked, this)); - _content_earlier->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&FilmEditor::content_earlier_clicked, this)); - _content_later->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&FilmEditor::content_later_clicked, this)); - _content_timeline->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&FilmEditor::content_timeline_clicked, this)); - _scaler->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&FilmEditor::scaler_changed, this)); - _dcp_content_type->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&FilmEditor::dcp_content_type_changed, this)); - _frame_rate_choice->Bind(wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&FilmEditor::frame_rate_choice_changed, this)); - _frame_rate_spin->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&FilmEditor::frame_rate_spin_changed, this)); - _best_frame_rate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&FilmEditor::best_frame_rate_clicked, this)); - _signed->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&FilmEditor::signed_toggled, this)); - _encrypted->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&FilmEditor::encrypted_toggled, this)); - _audio_channels->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&FilmEditor::audio_channels_changed, this)); - _j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&FilmEditor::j2k_bandwidth_changed, this)); - _resolution->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&FilmEditor::resolution_changed, this)); - _sequence_video->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&FilmEditor::sequence_video_changed, this)); - _three_d->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&FilmEditor::three_d_changed, this)); - _standard->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&FilmEditor::standard_changed, this)); -} - -void -FilmEditor::make_content_panel () -{ - _content_panel = new wxPanel (_main_notebook); - _content_sizer = new wxBoxSizer (wxVERTICAL); - _content_panel->SetSizer (_content_sizer); - - { - wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - - _content = new wxListCtrl (_content_panel, wxID_ANY, wxDefaultPosition, wxSize (320, 160), wxLC_REPORT | wxLC_NO_HEADER); - s->Add (_content, 1, wxEXPAND | wxTOP | wxBOTTOM, 6); - - _content->InsertColumn (0, wxT("")); - _content->SetColumnWidth (0, 512); - - wxBoxSizer* b = new wxBoxSizer (wxVERTICAL); - _content_add_file = new wxButton (_content_panel, wxID_ANY, _("Add file(s)...")); - b->Add (_content_add_file, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP); - _content_add_folder = new wxButton (_content_panel, wxID_ANY, _("Add folder...")); - b->Add (_content_add_folder, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP); - _content_remove = new wxButton (_content_panel, wxID_ANY, _("Remove")); - b->Add (_content_remove, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP); - _content_earlier = new wxButton (_content_panel, wxID_ANY, _("Up")); - b->Add (_content_earlier, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP); - _content_later = new wxButton (_content_panel, wxID_ANY, _("Down")); - b->Add (_content_later, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP); - _content_timeline = new wxButton (_content_panel, wxID_ANY, _("Timeline...")); - b->Add (_content_timeline, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP); - - s->Add (b, 0, wxALL, 4); - - _content_sizer->Add (s, 0, wxEXPAND | wxALL, 6); - } - - _sequence_video = new wxCheckBox (_content_panel, wxID_ANY, _("Keep video in sequence")); - _content_sizer->Add (_sequence_video); - - _content_notebook = new wxNotebook (_content_panel, wxID_ANY); - _content_sizer->Add (_content_notebook, 1, wxEXPAND | wxTOP, 6); - - _video_panel = new VideoPanel (this); - _panels.push_back (_video_panel); - _audio_panel = new AudioPanel (this); - _panels.push_back (_audio_panel); - _subtitle_panel = new SubtitlePanel (this); - _panels.push_back (_subtitle_panel); - _timing_panel = new TimingPanel (this); - _panels.push_back (_timing_panel); - - _content->DragAcceptFiles (true); -} - -/** Called when the name widget has been changed */ -void -FilmEditor::name_changed () -{ - if (!_film) { - return; - } - - _film->set_name (string (_name->GetValue().mb_str())); -} - -void -FilmEditor::j2k_bandwidth_changed () -{ - if (!_film) { - return; - } - - _film->set_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000); -} - -void -FilmEditor::signed_toggled () -{ - if (!_film) { - return; - } - - _film->set_signed (_signed->GetValue ()); -} - -void -FilmEditor::encrypted_toggled () -{ - if (!_film) { - return; - } - - _film->set_encrypted (_encrypted->GetValue ()); -} - -/** Called when the frame rate choice widget has been changed */ -void -FilmEditor::frame_rate_choice_changed () -{ - if (!_film) { - return; - } - - _film->set_video_frame_rate ( - boost::lexical_cast<int> ( - wx_to_std (_frame_rate_choice->GetString (_frame_rate_choice->GetSelection ())) - ) - ); -} - -/** Called when the frame rate spin widget has been changed */ -void -FilmEditor::frame_rate_spin_changed () -{ - if (!_film) { - return; - } - - _film->set_video_frame_rate (_frame_rate_spin->GetValue ()); -} - -void -FilmEditor::audio_channels_changed () -{ - if (!_film) { - return; - } - - _film->set_audio_channels (_audio_channels->GetValue ()); -} - -void -FilmEditor::resolution_changed () -{ - if (!_film) { - return; - } - - _film->set_resolution (_resolution->GetSelection() == 0 ? RESOLUTION_2K : RESOLUTION_4K); + SetSizerAndFit (s); } -void -FilmEditor::standard_changed () -{ - if (!_film) { - return; - } - - _film->set_interop (_standard->GetSelection() == 1); -} /** Called when the metadata stored in the Film object has changed; * so that we can update the GUI. @@ -423,97 +107,8 @@ FilmEditor::film_changed (Film::Property p) return; } - SafeStringStream s; - - for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) { - (*i)->film_changed (p); - } - - switch (p) { - case Film::NONE: - break; - case Film::CONTENT: - setup_content (); - break; - case Film::CONTAINER: - setup_container (); - break; - case Film::NAME: - checked_set (_name, _film->name()); - setup_dcp_name (); - break; - case Film::WITH_SUBTITLES: - setup_dcp_name (); - break; - case Film::DCP_CONTENT_TYPE: - checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ())); - setup_dcp_name (); - break; - case Film::SCALER: - checked_set (_scaler, Scaler::as_index (_film->scaler ())); - break; - case Film::SIGNED: - checked_set (_signed, _film->is_signed ()); - break; - case Film::ENCRYPTED: - checked_set (_encrypted, _film->encrypted ()); - if (_film->encrypted ()) { - _film->set_signed (true); - _signed->Enable (false); - } else { - _signed->Enable (_generally_sensitive); - } - break; - case Film::RESOLUTION: - checked_set (_resolution, _film->resolution() == RESOLUTION_2K ? 0 : 1); - setup_dcp_name (); - break; - case Film::J2K_BANDWIDTH: - checked_set (_j2k_bandwidth, _film->j2k_bandwidth() / 1000000); - break; - case Film::USE_ISDCF_NAME: - checked_set (_use_isdcf_name, _film->use_isdcf_name ()); - setup_dcp_name (); - use_isdcf_name_changed (); - break; - case Film::ISDCF_METADATA: - setup_dcp_name (); - break; - case Film::VIDEO_FRAME_RATE: - { - bool done = false; - for (unsigned int i = 0; i < _frame_rate_choice->GetCount(); ++i) { - if (wx_to_std (_frame_rate_choice->GetString(i)) == boost::lexical_cast<string> (_film->video_frame_rate())) { - checked_set (_frame_rate_choice, i); - done = true; - break; - } - } - - if (!done) { - checked_set (_frame_rate_choice, -1); - } - - _frame_rate_spin->SetValue (_film->video_frame_rate ()); - - _best_frame_rate->Enable (_film->best_video_frame_rate () != _film->video_frame_rate ()); - break; - } - case Film::AUDIO_CHANNELS: - checked_set (_audio_channels, _film->audio_channels ()); - setup_dcp_name (); - break; - case Film::SEQUENCE_VIDEO: - checked_set (_sequence_video, _film->sequence_video ()); - break; - case Film::THREE_D: - checked_set (_three_d, _film->three_d ()); - setup_dcp_name (); - break; - case Film::INTEROP: - checked_set (_standard, _film->interop() ? 1 : 0); - break; - } + _content_panel->film_changed (p); + _dcp_panel->film_changed (p); } void @@ -528,69 +123,8 @@ FilmEditor::film_content_changed (int property) return; } - for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) { - (*i)->film_content_changed (property); - } - - if (property == FFmpegContentProperty::AUDIO_STREAM) { - setup_dcp_name (); - } else if (property == ContentProperty::PATH) { - setup_content (); - } else if (property == ContentProperty::POSITION) { - setup_content (); - } else if (property == VideoContentProperty::VIDEO_SCALE) { - setup_dcp_name (); - } -} - -void -FilmEditor::setup_container () -{ - int n = 0; - vector<Ratio const *> ratios = Ratio::all (); - vector<Ratio const *>::iterator i = ratios.begin (); - while (i != ratios.end() && *i != _film->container ()) { - ++i; - ++n; - } - - if (i == ratios.end()) { - checked_set (_container, -1); - } else { - checked_set (_container, n); - } - - setup_dcp_name (); -} - -/** Called when the container widget has been changed */ -void -FilmEditor::container_changed () -{ - if (!_film) { - return; - } - - int const n = _container->GetSelection (); - if (n >= 0) { - vector<Ratio const *> ratios = Ratio::all (); - assert (n < int (ratios.size())); - _film->set_container (ratios[n]); - } -} - -/** Called when the DCP content type widget has been changed */ -void -FilmEditor::dcp_content_type_changed () -{ - if (!_film) { - return; - } - - int const n = _dcp_content_type->GetSelection (); - if (n != wxNOT_FOUND) { - _film->set_dcp_content_type (DCPContentType::from_index (n)); - } + _content_panel->film_content_changed (property); + _dcp_panel->film_content_changed (property); } /** Sets the Film that we are editing */ @@ -605,6 +139,9 @@ FilmEditor::set_film (shared_ptr<Film> f) _film = f; + _content_panel->set_film (_film); + _dcp_panel->set_film (_film); + if (_film) { _film->Changed.connect (bind (&FilmEditor::film_changed, this, _1)); _film->ContentChanged.connect (bind (&FilmEditor::film_content_changed, this, _2)); @@ -616,121 +153,16 @@ FilmEditor::set_film (shared_ptr<Film> f) FileChanged (""); } - film_changed (Film::NAME); - film_changed (Film::USE_ISDCF_NAME); - film_changed (Film::CONTENT); - film_changed (Film::DCP_CONTENT_TYPE); - film_changed (Film::CONTAINER); - film_changed (Film::RESOLUTION); - film_changed (Film::SCALER); - film_changed (Film::WITH_SUBTITLES); - film_changed (Film::SIGNED); - film_changed (Film::ENCRYPTED); - film_changed (Film::J2K_BANDWIDTH); - film_changed (Film::ISDCF_METADATA); - film_changed (Film::VIDEO_FRAME_RATE); - film_changed (Film::AUDIO_CHANNELS); - film_changed (Film::SEQUENCE_VIDEO); - film_changed (Film::THREE_D); - film_changed (Film::INTEROP); - if (!_film->content().empty ()) { - set_selection (_film->content().front ()); + _content_panel->set_selection (_film->content().front ()); } - - content_selection_changed (); } void FilmEditor::set_general_sensitivity (bool s) { - _generally_sensitive = s; - - /* Stuff in the Content / DCP tabs */ - _name->Enable (s); - _use_isdcf_name->Enable (s); - _edit_isdcf_button->Enable (s); - _content->Enable (s); - _content_add_file->Enable (s); - _content_add_folder->Enable (s); - _content_remove->Enable (s); - _content_earlier->Enable (s); - _content_later->Enable (s); - _content_timeline->Enable (s); - _dcp_content_type->Enable (s); - - bool si = s; - if (_film && _film->encrypted ()) { - si = false; - } - _signed->Enable (si); - - _encrypted->Enable (s); - _frame_rate_choice->Enable (s); - _frame_rate_spin->Enable (s); - _audio_channels->Enable (s); - _j2k_bandwidth->Enable (s); - _container->Enable (s); - _best_frame_rate->Enable (s && _film && _film->best_video_frame_rate () != _film->video_frame_rate ()); - _sequence_video->Enable (s); - _resolution->Enable (s); - _scaler->Enable (s); - _three_d->Enable (s); - _standard->Enable (s); - - /* Set the panels in the content notebook */ - for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) { - (*i)->Enable (s); - } -} - -/** Called when the scaler widget has been changed */ -void -FilmEditor::scaler_changed () -{ - if (!_film) { - return; - } - - int const n = _scaler->GetSelection (); - if (n >= 0) { - _film->set_scaler (Scaler::from_index (n)); - } -} - -void -FilmEditor::use_isdcf_name_toggled () -{ - if (!_film) { - return; - } - - _film->set_use_isdcf_name (_use_isdcf_name->GetValue ()); -} - -void -FilmEditor::use_isdcf_name_changed () -{ - bool const i = _film->use_isdcf_name (); - - if (!i) { - _film->set_name (_film->isdcf_name (true)); - } - - _edit_isdcf_button->Enable (i); -} - -void -FilmEditor::edit_isdcf_button_clicked () -{ - if (!_film) { - return; - } - - ISDCFMetadataDialog* d = new ISDCFMetadataDialog (this, _film->isdcf_metadata ()); - d->ShowModal (); - _film->set_isdcf_metadata (d->isdcf_metadata ()); - d->Destroy (); + _content_panel->set_general_sensitivity (s); + _dcp_panel->set_general_sensitivity (s); } void @@ -738,356 +170,3 @@ FilmEditor::active_jobs_changed (bool a) { set_general_sensitivity (!a); } - -void -FilmEditor::setup_dcp_name () -{ - string s = _film->dcp_name (true); - if (s.length() > 28) { - _dcp_name->SetLabel (std_to_wx (s.substr (0, 28)) + N_("...")); - _dcp_name->SetToolTip (std_to_wx (s)); - } else { - _dcp_name->SetLabel (std_to_wx (s)); - } -} - -void -FilmEditor::best_frame_rate_clicked () -{ - if (!_film) { - return; - } - - _film->set_video_frame_rate (_film->best_video_frame_rate ()); -} - -void -FilmEditor::setup_content () -{ - string selected_summary; - int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - if (s != -1) { - selected_summary = wx_to_std (_content->GetItemText (s)); - } - - _content->DeleteAllItems (); - - ContentList content = _film->content (); - sort (content.begin(), content.end(), ContentSorter ()); - - for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { - int const t = _content->GetItemCount (); - bool const valid = (*i)->paths_valid (); - - string s = (*i)->summary (); - if (!valid) { - s = _("MISSING: ") + s; - } - - _content->InsertItem (t, std_to_wx (s)); - - if ((*i)->summary() == selected_summary) { - _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); - } - - if (!valid) { - _content->SetItemTextColour (t, *wxRED); - } - } - - if (selected_summary.empty () && !content.empty ()) { - /* Select the item of content if none was selected before */ - _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); - } -} - -void -FilmEditor::content_add_file_clicked () -{ - /* The wxFD_CHANGE_DIR here prevents a `could not set working directory' error 123 on Windows when using - non-Latin filenames or paths. - */ - wxFileDialog* d = new wxFileDialog (this, _("Choose a file or files"), wxT (""), wxT (""), wxT ("*.*"), wxFD_MULTIPLE | wxFD_CHANGE_DIR); - int const r = d->ShowModal (); - - if (r != wxID_OK) { - d->Destroy (); - return; - } - - wxArrayString paths; - d->GetPaths (paths); - - /* XXX: check for lots of files here and do something */ - - for (unsigned int i = 0; i < paths.GetCount(); ++i) { - _film->examine_and_add_content (content_factory (_film, wx_to_std (paths[i]))); - } - - d->Destroy (); -} - -void -FilmEditor::content_add_folder_clicked () -{ - wxDirDialog* d = new wxDirDialog (this, _("Choose a folder"), wxT (""), wxDD_DIR_MUST_EXIST); - int const r = d->ShowModal (); - d->Destroy (); - - if (r != wxID_OK) { - return; - } - - shared_ptr<ImageContent> ic; - - try { - ic.reset (new ImageContent (_film, boost::filesystem::path (wx_to_std (d->GetPath ())))); - } catch (FileError& e) { - error_dialog (this, std_to_wx (e.what ())); - return; - } - - _film->examine_and_add_content (ic); -} - -void -FilmEditor::content_remove_clicked () -{ - ContentList c = selected_content (); - for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { - _film->remove_content (*i); - } - - content_selection_changed (); -} - -void -FilmEditor::content_selection_changed () -{ - setup_content_sensitivity (); - - for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) { - (*i)->content_selection_changed (); - } -} - -/** Set up broad sensitivity based on the type of content that is selected */ -void -FilmEditor::setup_content_sensitivity () -{ - _content_add_file->Enable (_generally_sensitive); - _content_add_folder->Enable (_generally_sensitive); - - ContentList selection = selected_content (); - VideoContentList video_selection = selected_video_content (); - AudioContentList audio_selection = selected_audio_content (); - - _content_remove->Enable (!selection.empty() && _generally_sensitive); - _content_earlier->Enable (selection.size() == 1 && _generally_sensitive); - _content_later->Enable (selection.size() == 1 && _generally_sensitive); - _content_timeline->Enable (!_film->content().empty() && _generally_sensitive); - - _video_panel->Enable (!video_selection.empty() && _generally_sensitive); - _audio_panel->Enable (!audio_selection.empty() && _generally_sensitive); - _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<FFmpegContent> (selection.front()) && _generally_sensitive); - _timing_panel->Enable (!selection.empty() && _generally_sensitive); -} - -ContentList -FilmEditor::selected_content () -{ - ContentList sel; - - if (!_film) { - return sel; - } - - /* The list was populated using a sorted content list, so we must sort it here too - so that we can look up by index and get the right thing. - */ - ContentList content = _film->content (); - sort (content.begin(), content.end(), ContentSorter ()); - - long int s = -1; - while (true) { - s = _content->GetNextItem (s, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - if (s == -1) { - break; - } - - if (s < int (_film->content().size ())) { - sel.push_back (content[s]); - } - } - - return sel; -} - -VideoContentList -FilmEditor::selected_video_content () -{ - ContentList c = selected_content (); - VideoContentList vc; - - for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { - shared_ptr<VideoContent> t = dynamic_pointer_cast<VideoContent> (*i); - if (t) { - vc.push_back (t); - } - } - - return vc; -} - -AudioContentList -FilmEditor::selected_audio_content () -{ - ContentList c = selected_content (); - AudioContentList ac; - - for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { - shared_ptr<AudioContent> t = dynamic_pointer_cast<AudioContent> (*i); - if (t) { - ac.push_back (t); - } - } - - return ac; -} - -SubtitleContentList -FilmEditor::selected_subtitle_content () -{ - ContentList c = selected_content (); - SubtitleContentList sc; - - for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { - shared_ptr<SubtitleContent> t = dynamic_pointer_cast<SubtitleContent> (*i); - if (t) { - sc.push_back (t); - } - } - - return sc; -} - -FFmpegContentList -FilmEditor::selected_ffmpeg_content () -{ - ContentList c = selected_content (); - FFmpegContentList sc; - - for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { - shared_ptr<FFmpegContent> t = dynamic_pointer_cast<FFmpegContent> (*i); - if (t) { - sc.push_back (t); - } - } - - return sc; -} - -void -FilmEditor::content_timeline_clicked () -{ - if (_timeline_dialog) { - _timeline_dialog->Destroy (); - _timeline_dialog = 0; - } - - _timeline_dialog = new TimelineDialog (this, _film); - _timeline_dialog->Show (); -} - -void -FilmEditor::set_selection (weak_ptr<Content> wc) -{ - ContentList content = _film->content (); - for (size_t i = 0; i < content.size(); ++i) { - if (content[i] == wc.lock ()) { - _content->SetItemState (i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); - } else { - _content->SetItemState (i, 0, wxLIST_STATE_SELECTED); - } - } -} - -void -FilmEditor::sequence_video_changed () -{ - if (!_film) { - return; - } - - _film->set_sequence_video (_sequence_video->GetValue ()); -} - -void -FilmEditor::content_right_click (wxListEvent& ev) -{ - _menu.popup (_film, selected_content (), ev.GetPoint ()); -} - -void -FilmEditor::three_d_changed () -{ - if (!_film) { - return; - } - - _film->set_three_d (_three_d->GetValue ()); -} - -void -FilmEditor::content_earlier_clicked () -{ - ContentList sel = selected_content (); - if (sel.size() == 1) { - _film->move_content_earlier (sel.front ()); - content_selection_changed (); - } -} - -void -FilmEditor::content_later_clicked () -{ - ContentList sel = selected_content (); - if (sel.size() == 1) { - _film->move_content_later (sel.front ()); - content_selection_changed (); - } -} - -void -FilmEditor::config_changed () -{ - _j2k_bandwidth->SetRange (1, Config::instance()->maximum_j2k_bandwidth() / 1000000); - setup_frame_rate_widget (); -} - -void -FilmEditor::setup_frame_rate_widget () -{ - if (Config::instance()->allow_any_dcp_frame_rate ()) { - _frame_rate_choice->Hide (); - _frame_rate_spin->Show (); - } else { - _frame_rate_choice->Show (); - _frame_rate_spin->Hide (); - } - - _frame_rate_sizer->Layout (); -} - -void -FilmEditor::content_files_dropped (wxDropFilesEvent& event) -{ - if (!_film) { - return; - } - - wxString* paths = event.GetFiles (); - for (int i = 0; i < event.GetNumberOfFiles(); i++) { - _film->examine_and_add_content (content_factory (_film, wx_to_std (paths[i]))); - } -} diff --git a/src/wx/film_editor.h b/src/wx/film_editor.h index ba9ff6fa0..25749fffa 100644 --- a/src/wx/film_editor.h +++ b/src/wx/film_editor.h @@ -22,23 +22,15 @@ */ #include <wx/wx.h> -#include <wx/spinctrl.h> -#include <wx/filepicker.h> -#include <wx/collpane.h> #include <boost/signals2.hpp> #include "lib/film.h" -#include "content_menu.h" +class wxSpinCtrl; class wxNotebook; -class wxListCtrl; -class wxListEvent; -class wxGridBagSizer; class Film; -class TimelineDialog; class Ratio; -class Timecode; -class FilmEditorPanel; -class SubtitleContent; +class ContentPanel; +class DCPPanel; /** @class FilmEditor * @brief A wx widget to edit a film's metadata, and perform various functions. @@ -49,121 +41,30 @@ public: FilmEditor (wxWindow *); void set_film (boost::shared_ptr<Film>); - void set_selection (boost::weak_ptr<Content>); boost::signals2::signal<void (boost::filesystem::path)> FileChanged; /* Stuff for panels */ - - wxNotebook* content_notebook () const { - return _content_notebook; - } + ContentPanel* content_panel () const { + return _content_panel; + } + boost::shared_ptr<Film> film () const { return _film; } - ContentList selected_content (); - VideoContentList selected_video_content (); - AudioContentList selected_audio_content (); - SubtitleContentList selected_subtitle_content (); - FFmpegContentList selected_ffmpeg_content (); - - void content_add_file_clicked (); - -private: - void make_dcp_panel (); - void make_content_panel (); - void connect_to_widgets (); - - /* Handle changes to the view */ - void name_changed (); - void use_isdcf_name_toggled (); - void edit_isdcf_button_clicked (); - void content_selection_changed (); - void content_add_folder_clicked (); - void content_remove_clicked (); - void content_earlier_clicked (); - void content_later_clicked (); - void content_files_dropped (wxDropFilesEvent& event); - void container_changed (); - void dcp_content_type_changed (); - void scaler_changed (); - void j2k_bandwidth_changed (); - void frame_rate_choice_changed (); - void frame_rate_spin_changed (); - void best_frame_rate_clicked (); - void content_timeline_clicked (); - void audio_channels_changed (); - void resolution_changed (); - void sequence_video_changed (); - void content_right_click (wxListEvent &); - void three_d_changed (); - void standard_changed (); - void signed_toggled (); - void encrypted_toggled (); - /* Handle changes to the model */ void film_changed (Film::Property); void film_content_changed (int); - void use_isdcf_name_changed (); void set_general_sensitivity (bool); - void setup_dcp_name (); - void setup_content (); - void setup_container (); - void setup_content_sensitivity (); - void setup_frame_rate_widget (); - void active_jobs_changed (bool); - void config_changed (); - - FilmEditorPanel* _video_panel; - FilmEditorPanel* _audio_panel; - FilmEditorPanel* _subtitle_panel; - FilmEditorPanel* _timing_panel; - std::list<FilmEditorPanel *> _panels; wxNotebook* _main_notebook; - wxNotebook* _content_notebook; - wxPanel* _dcp_panel; - wxSizer* _dcp_sizer; - wxPanel* _content_panel; - wxSizer* _content_sizer; + ContentPanel* _content_panel; + DCPPanel* _dcp_panel; /** The film we are editing */ boost::shared_ptr<Film> _film; - wxTextCtrl* _name; - wxStaticText* _dcp_name; - wxCheckBox* _use_isdcf_name; - wxChoice* _container; - wxListCtrl* _content; - wxButton* _content_add_file; - wxButton* _content_add_folder; - wxButton* _content_remove; - wxButton* _content_earlier; - wxButton* _content_later; - wxButton* _content_timeline; - wxCheckBox* _sequence_video; - wxButton* _edit_isdcf_button; - wxChoice* _scaler; - wxSpinCtrl* _j2k_bandwidth; - wxChoice* _dcp_content_type; - wxChoice* _frame_rate_choice; - wxSpinCtrl* _frame_rate_spin; - wxSizer* _frame_rate_sizer; - wxSpinCtrl* _audio_channels; - wxButton* _best_frame_rate; - wxCheckBox* _three_d; - wxChoice* _resolution; - wxChoice* _standard; - wxCheckBox* _signed; - wxCheckBox* _encrypted; - - ContentMenu _menu; - - std::vector<Ratio const *> _ratios; - - bool _generally_sensitive; - TimelineDialog* _timeline_dialog; }; diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index 595fd4720..7ecba1903 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -24,6 +24,7 @@ #include <iostream> #include <iomanip> #include <wx/tglbtn.h> +#include <dcp/exceptions.h> #include "lib/film.h" #include "lib/ratio.h" #include "lib/util.h" @@ -34,9 +35,10 @@ #include "lib/examine_content_job.h" #include "lib/filter.h" #include "lib/player.h" -#include "lib/player_video_frame.h" +#include "lib/player_video.h" #include "lib/video_content.h" #include "lib/video_decoder.h" +#include "lib/timer.h" #include "film_viewer.h" #include "wx_util.h" @@ -51,18 +53,19 @@ 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 (wxWindow* p) : wxPanel (p) , _panel (new wxPanel (this)) + , _outline_content (new wxCheckBox (this, wxID_ANY, _("Outline content"))) , _slider (new wxSlider (this, wxID_ANY, 0, 0, 4096)) , _back_button (new wxButton (this, wxID_ANY, wxT("<"))) , _forward_button (new wxButton (this, wxID_ANY, wxT(">"))) , _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) + , _last_get_accurate (true) { #ifndef __WXOSX__ _panel->SetDoubleBuffered (true); @@ -75,6 +78,8 @@ FilmViewer::FilmViewer (wxWindow* p) _v_sizer->Add (_panel, 1, wxEXPAND); + _v_sizer->Add (_outline_content, 0, wxALL, DCPOMATIC_SIZER_GAP); + wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL); wxBoxSizer* time_sizer = new wxBoxSizer (wxVERTICAL); @@ -95,6 +100,7 @@ FilmViewer::FilmViewer (wxWindow* p) _panel->Bind (wxEVT_PAINT, boost::bind (&FilmViewer::paint_panel, this)); _panel->Bind (wxEVT_SIZE, boost::bind (&FilmViewer::panel_sized, this, _1)); + _outline_content->Bind(wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&FilmViewer::refresh_panel, this)); _slider->Bind (wxEVT_SCROLL_THUMBTRACK, boost::bind (&FilmViewer::slider_moved, this)); _slider->Bind (wxEVT_SCROLL_PAGEUP, boost::bind (&FilmViewer::slider_moved, this)); _slider->Bind (wxEVT_SCROLL_PAGEDOWN, boost::bind (&FilmViewer::slider_moved, this)); @@ -108,6 +114,8 @@ FilmViewer::FilmViewer (wxWindow* p) JobManager::instance()->ActiveJobsChanged.connect ( bind (&FilmViewer::active_jobs_changed, this, _1) ); + + setup_sensitivity (); } void @@ -122,7 +130,7 @@ FilmViewer::set_film (shared_ptr<Film> f) _frame.reset (); _slider->SetValue (0); - set_position_text (0); + set_position_text (); if (!_film) { return; @@ -136,47 +144,73 @@ FilmViewer::set_film (shared_ptr<Film> f) return; } - _player->disable_audio (); - _player->Video.connect (boost::bind (&FilmViewer::process_video, this, _1, _3)); - _player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1)); + _film_connection = _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1)); + + _player->set_approximate_size (); + _player_connection = _player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1)); calculate_sizes (); - fetch_next_frame (); + get (_position, _last_get_accurate); + + setup_sensitivity (); +} + +void +FilmViewer::refresh_panel () +{ + _panel->Refresh (); + _panel->Update (); } 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<PlayerVideo> > pvf = _player->get_video (p, accurate); + if (!pvf.empty ()) { + try { + _frame = pvf.front()->image (true); + _frame = _frame->scale (_frame->size(), Scaler::from_id ("fastbilinear"), PIX_FMT_RGB24, false); + _position = pvf.front()->time (); + _inter_position = pvf.front()->inter_position (); + _inter_size = pvf.front()->inter_size (); + } catch (dcp::DCPReadError& e) { + /* This can happen on the following sequence of events: + * - load encrypted DCP + * - add KDM + * - DCP is examined again, which sets its "playable" flag to 1 + * - as a side effect of the exam, the viewer is updated using the old pieces + * - the DCPDecoder in the old piece gives us an encrypted frame + * - then, the pieces are re-made (but too late). + * + * I hope there's a better way to handle this ... + */ + _frame.reset (); + _position = p; + } + } else { + _frame.reset (); + _position = p; } - - _panel->Refresh (); - _panel->Update (); + + set_position_text (); + refresh_panel (); + + _last_get_accurate = accurate; } 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); } @@ -212,22 +246,29 @@ FilmViewer::paint_panel () dc.SetPen (p); dc.SetBrush (b); dc.DrawRectangle (0, _out_size.height, _panel_size.width, _panel_size.height - _out_size.height); - } -} + } + if (_outline_content->GetValue ()) { + wxPen p (wxColour (255, 0, 0), 2); + dc.SetPen (p); + dc.SetBrush (*wxTRANSPARENT_BRUSH); + dc.DrawRectangle (_inter_position.x, _inter_position.y, _inter_size.width, _inter_size.height); + } +} 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 @@ -235,8 +276,9 @@ 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, _last_get_accurate); } void @@ -254,17 +296,24 @@ FilmViewer::calculate_sizes () if (panel_ratio < film_ratio) { /* panel is less widscreen than the film; clamp width */ _out_size.width = _panel_size.width; - _out_size.height = _out_size.width / film_ratio; + _out_size.height = rint (_out_size.width / film_ratio); } else { /* panel is more widescreen than the film; clamp height */ _out_size.height = _panel_size.height; - _out_size.width = _out_size.height * film_ratio; + _out_size.width = rint (_out_size.height * film_ratio); } /* Catch silly values */ _out_size.width = max (64, _out_size.width); _out_size.height = max (64, _out_size.height); + /* The player will round its image size down to the next lowest 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 +338,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 +348,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 +361,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 +384,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 +406,27 @@ FilmViewer::player_changed (bool frequent) } calculate_sizes (); - fetch_current_frame_again (); + get (_position, _last_get_accurate); +} + +void +FilmViewer::setup_sensitivity () +{ + bool const c = _film && !_film->content().empty (); + + _slider->Enable (c); + _back_button->Enable (c); + _forward_button->Enable (c); + _play_button->Enable (c); + _outline_content->Enable (c); + _frame_number->Enable (c); + _timecode->Enable (c); +} + +void +FilmViewer::film_changed (Film::Property p) +{ + if (p == Film::CONTENT) { + setup_sensitivity (); + } } diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h index 337b68446..e502c6f45 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 @@ -28,23 +28,10 @@ class wxToggleButton; class FFmpegPlayer; class Image; class RGBPlusAlphaImage; -class PlayerVideoFrame; +class PlayerVideo; /** @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,22 +46,24 @@ 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); + void refresh_panel (); + void setup_sensitivity (); + void film_changed (Film::Property); boost::shared_ptr<Film> _film; boost::shared_ptr<Player> _player; wxSizer* _v_sizer; wxPanel* _panel; + wxCheckBox* _outline_content; wxSlider* _slider; wxButton* _back_button; wxButton* _forward_button; @@ -84,10 +73,20 @@ private: wxTimer _timer; boost::shared_ptr<const Image> _frame; - bool _got_frame; + DCPTime _position; + Position<int> _inter_position; + dcp::Size _inter_size; /** 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; + /** true if the last call to ::get() was specified to be accurate; + * this is used so that when re-fetching the current frame we + * can get the same one that we got last time. + */ + bool _last_get_accurate; + + boost::signals2::scoped_connection _film_connection; + boost::signals2::scoped_connection _player_connection; }; diff --git a/src/wx/kdm_dialog.cc b/src/wx/kdm_dialog.cc index ebecd234c..4334fd446 100644 --- a/src/wx/kdm_dialog.cc +++ b/src/wx/kdm_dialog.cc @@ -161,10 +161,10 @@ KDMDialog::KDMDialog (wxWindow* parent, boost::shared_ptr<const Film> film) add_label_to_sizer (table, this, _("KDM type"), true); _type = new wxChoice (this, wxID_ANY); - _type->Append ("Modified Transitional 1", ((void *) libdcp::KDM::MODIFIED_TRANSITIONAL_1)); + _type->Append ("Modified Transitional 1", ((void *) dcp::MODIFIED_TRANSITIONAL_1)); if (!film->interop ()) { - _type->Append ("DCI Any", ((void *) libdcp::KDM::DCI_ANY)); - _type->Append ("DCI Specific", ((void *) libdcp::KDM::DCI_SPECIFIC)); + _type->Append ("DCI Any", ((void *) dcp::DCI_ANY)); + _type->Append ("DCI Specific", ((void *) dcp::DCI_SPECIFIC)); } table->Add (_type, 1, wxEXPAND); _type->SetSelection (0); @@ -490,10 +490,10 @@ KDMDialog::write_to () const return _write_to->GetValue (); } -libdcp::KDM::Formulation +dcp::Formulation KDMDialog::formulation () const { - return (libdcp::KDM::Formulation) reinterpret_cast<intptr_t> (_type->GetClientData (_type->GetSelection())); + return (dcp::Formulation) reinterpret_cast<intptr_t> (_type->GetClientData (_type->GetSelection())); } void diff --git a/src/wx/kdm_dialog.h b/src/wx/kdm_dialog.h index 13e9196ea..0fc95db84 100644 --- a/src/wx/kdm_dialog.h +++ b/src/wx/kdm_dialog.h @@ -48,7 +48,7 @@ public: boost::filesystem::path cpl () const; boost::filesystem::path directory () const; bool write_to () const; - libdcp::KDM::Formulation formulation () const; + dcp::Formulation formulation () const; private: void add_cinema (boost::shared_ptr<Cinema>); diff --git a/src/wx/make_signer_chain_dialog.cc b/src/wx/make_signer_chain_dialog.cc new file mode 100644 index 000000000..8736f2456 --- /dev/null +++ b/src/wx/make_signer_chain_dialog.cc @@ -0,0 +1,35 @@ +/* + 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 "make_signer_chain_dialog.h" + +MakeSignerChainDialog::MakeSignerChainDialog (wxWindow* parent) + : TableDialog (parent, _("Make certificate chain"), 2, true) +{ + add (_("Organisation"), true); + add (_organisation = new wxTextCtrl (this, wxID_ANY)); + add (_("Organisational unit"), true); + add (_organisational_unit = new wxTextCtrl (this, wxID_ANY)); + add (_("Root common name"), true); + add (_root_common_name = new wxTextCtrl (this, wxID_ANY)); + add (_("Intermediate common name"), true); + add (_intermediate_common_name = new wxTextCtrl (this, wxID_ANY)); + add (_("Leaf common name"), true); + add (_leaf_common_name = new wxTextCtrl (this, wxID_ANY)); +} diff --git a/src/wx/make_signer_chain_dialog.h b/src/wx/make_signer_chain_dialog.h new file mode 100644 index 000000000..fc6391a94 --- /dev/null +++ b/src/wx/make_signer_chain_dialog.h @@ -0,0 +1,56 @@ +/* + 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 "table_dialog.h" +#include "wx_util.h" + +class MakeSignerChainDialog : public TableDialog +{ +public: + MakeSignerChainDialog (wxWindow* parent); + + std::string organisation () const { + return wx_to_std (_organisation->GetValue ()); + } + + std::string organisational_unit () const { + return wx_to_std (_organisational_unit->GetValue ()); + } + + std::string root_common_name () const { + return wx_to_std (_root_common_name->GetValue ()); + } + + std::string intermediate_common_name () const { + return wx_to_std (_intermediate_common_name->GetValue ()); + } + + std::string leaf_common_name () const { + return wx_to_std (_leaf_common_name->GetValue ()); + } + + +private: + wxTextCtrl* _organisation; + wxTextCtrl* _organisational_unit; + wxTextCtrl* _root_common_name; + wxTextCtrl* _intermediate_common_name; + wxTextCtrl* _leaf_common_name; +}; + diff --git a/src/wx/properties_dialog.cc b/src/wx/properties_dialog.cc index 53ca23755..27fc75b1b 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; SafeStringStream 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 c69912716..503745683 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" @@ -29,9 +29,9 @@ using std::string; using std::cout; -using boost::shared_ptr; +using boost::optional; -ScreenDialog::ScreenDialog (wxWindow* parent, string title, string name, shared_ptr<libdcp::Certificate> certificate) +ScreenDialog::ScreenDialog (wxWindow* parent, string title, string name, optional<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> +optional<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 = dcp::Certificate (dcp::file_to_string (file)); _certificate_text->SetValue (_certificate->certificate ()); - } catch (libdcp::MiscError& e) { + } catch (dcp::MiscError& e) { error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what())); } } diff --git a/src/wx/screen_dialog.h b/src/wx/screen_dialog.h index 3601a8f6c..3e110d230 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::optional<dcp::Certificate> c = boost::optional<dcp::Certificate> ()); std::string name () const; - boost::shared_ptr<libdcp::Certificate> certificate () const; + boost::optional<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::optional<dcp::Certificate> _certificate; }; diff --git a/src/wx/subtitle_panel.cc b/src/wx/subtitle_panel.cc index 7953682fc..21d6f8e5b 100644 --- a/src/wx/subtitle_panel.cc +++ b/src/wx/subtitle_panel.cc @@ -20,9 +20,16 @@ #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 "lib/dcp_subtitle_content.h" +#include "lib/subrip_decoder.h" +#include "lib/dcp_subtitle_decoder.h" #include "subtitle_panel.h" #include "film_editor.h" #include "wx_util.h" +#include "subtitle_view.h" +#include "content_panel.h" using std::vector; using std::string; @@ -30,16 +37,17 @@ using boost::shared_ptr; using boost::lexical_cast; using boost::dynamic_pointer_cast; -SubtitlePanel::SubtitlePanel (FilmEditor* e) - : FilmEditorPanel (e, _("Subtitles")) +SubtitlePanel::SubtitlePanel (ContentPanel* p) + : ContentSubPanel (p, _("Subtitles")) + , _view (0) { wxFlexGridSizer* grid = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); _sizer->Add (grid, 0, wxALL, 8); - _with_subtitles = new wxCheckBox (this, wxID_ANY, _("With Subtitles")); - grid->Add (_with_subtitles, 1); + _use = new wxCheckBox (this, wxID_ANY, _("Use subtitles")); + grid->Add (_use); grid->AddSpacer (0); - + { add_label_to_sizer (grid, this, _("X Offset"), true); wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); @@ -79,41 +87,37 @@ SubtitlePanel::SubtitlePanel (FilmEditor* e) add_label_to_sizer (grid, this, _("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); _x_scale->SetRange (10, 1000); _y_scale->SetRange (10, 1000); - _with_subtitles->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&SubtitlePanel::with_subtitles_toggled, this)); - _x_offset->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::x_offset_changed, this)); - _y_offset->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::y_offset_changed, this)); - _x_scale->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::x_scale_changed, this)); - _y_scale->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::y_scale_changed, this)); - _stream->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&SubtitlePanel::stream_changed, this)); + _use->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&SubtitlePanel::use_toggled, this)); + _x_offset->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::x_offset_changed, this)); + _y_offset->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::y_offset_changed, this)); + _x_scale->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::x_scale_changed, this)); + _y_scale->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::y_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 SubtitlePanel::film_changed (Film::Property property) { - switch (property) { - case Film::CONTENT: - setup_sensitivity (); - break; - case Film::WITH_SUBTITLES: - checked_set (_with_subtitles, _editor->film()->with_subtitles ()); + if (property == Film::CONTENT) { setup_sensitivity (); - break; - default: - break; } } void SubtitlePanel::film_content_changed (int property) { - FFmpegContentList fc = _editor->selected_ffmpeg_content (); - SubtitleContentList sc = _editor->selected_subtitle_content (); + FFmpegContentList fc = _parent->selected_ffmpeg (); + SubtitleContentList sc = _parent->selected_subtitle (); shared_ptr<FFmpegContent> fcs; if (fc.size() == 1) { @@ -124,7 +128,7 @@ SubtitlePanel::film_content_changed (int property) if (sc.size() == 1) { scs = sc.front (); } - + if (property == FFmpegContentProperty::SUBTITLE_STREAMS) { _stream->Clear (); if (fcs) { @@ -140,6 +144,9 @@ SubtitlePanel::film_content_changed (int property) } } setup_sensitivity (); + } else if (property == SubtitleContentProperty::USE_SUBTITLES) { + checked_set (_use, scs ? scs->use_subtitles() : false); + setup_sensitivity (); } else if (property == SubtitleContentProperty::SUBTITLE_X_OFFSET) { checked_set (_x_offset, scs ? (scs->subtitle_x_offset() * 100) : 0); } else if (property == SubtitleContentProperty::SUBTITLE_Y_OFFSET) { @@ -152,37 +159,53 @@ SubtitlePanel::film_content_changed (int property) } void -SubtitlePanel::with_subtitles_toggled () +SubtitlePanel::use_toggled () { - if (!_editor->film()) { - return; + SubtitleContentList c = _parent->selected_subtitle (); + for (SubtitleContentList::iterator i = c.begin(); i != c.end(); ++i) { + (*i)->set_use_subtitles (_use->GetValue()); } - - _editor->film()->set_with_subtitles (_with_subtitles->GetValue ()); } void SubtitlePanel::setup_sensitivity () { - bool h = false; - bool j = false; - if (_editor->film()) { - h = _editor->film()->has_subtitles (); - j = _editor->film()->with_subtitles (); + int any_subs = 0; + int ffmpeg_subs = 0; + int subrip_or_dcp_subs = 0; + SubtitleContentList c = _parent->selected_subtitle (); + for (SubtitleContentList::const_iterator i = c.begin(); i != c.end(); ++i) { + shared_ptr<const FFmpegContent> fc = boost::dynamic_pointer_cast<const FFmpegContent> (*i); + shared_ptr<const SubRipContent> sc = boost::dynamic_pointer_cast<const SubRipContent> (*i); + shared_ptr<const DCPSubtitleContent> dsc = boost::dynamic_pointer_cast<const DCPSubtitleContent> (*i); + if (fc) { + if (fc->has_subtitles ()) { + ++ffmpeg_subs; + ++any_subs; + } + } else if (sc || dsc) { + ++subrip_or_dcp_subs; + ++any_subs; + } else { + ++any_subs; + } } + + _use->Enable (any_subs > 0); + bool const use = _use->GetValue (); - _with_subtitles->Enable (h); - _x_offset->Enable (j); - _y_offset->Enable (j); - _x_scale->Enable (j); - _y_scale->Enable (j); - _stream->Enable (j); + _x_offset->Enable (any_subs > 0 && use); + _y_offset->Enable (any_subs > 0 && use); + _x_scale->Enable (any_subs > 0 && use); + _y_scale->Enable (any_subs > 0 && use); + _stream->Enable (ffmpeg_subs == 1); + _view_button->Enable (subrip_or_dcp_subs == 1); } void SubtitlePanel::stream_changed () { - FFmpegContentList fc = _editor->selected_ffmpeg_content (); + FFmpegContentList fc = _parent->selected_ffmpeg (); if (fc.size() != 1) { return; } @@ -204,25 +227,25 @@ SubtitlePanel::stream_changed () void SubtitlePanel::x_offset_changed () { - SubtitleContentList c = _editor->selected_subtitle_content (); - if (c.size() == 1) { - c.front()->set_subtitle_x_offset (_x_offset->GetValue() / 100.0); + SubtitleContentList c = _parent->selected_subtitle (); + for (SubtitleContentList::iterator i = c.begin(); i != c.end(); ++i) { + (*i)->set_subtitle_x_offset (_x_offset->GetValue() / 100.0); } } void SubtitlePanel::y_offset_changed () { - SubtitleContentList c = _editor->selected_subtitle_content (); - if (c.size() == 1) { - c.front()->set_subtitle_y_offset (_y_offset->GetValue() / 100.0); + SubtitleContentList c = _parent->selected_subtitle (); + for (SubtitleContentList::iterator i = c.begin(); i != c.end(); ++i) { + (*i)->set_subtitle_y_offset (_y_offset->GetValue() / 100.0); } } void SubtitlePanel::x_scale_changed () { - SubtitleContentList c = _editor->selected_subtitle_content (); + SubtitleContentList c = _parent->selected_subtitle (); if (c.size() == 1) { c.front()->set_subtitle_x_scale (_x_scale->GetValue() / 100.0); } @@ -231,9 +254,9 @@ SubtitlePanel::x_scale_changed () void SubtitlePanel::y_scale_changed () { - SubtitleContentList c = _editor->selected_subtitle_content (); - if (c.size() == 1) { - c.front()->set_subtitle_y_scale (_y_scale->GetValue() / 100.0); + SubtitleContentList c = _parent->selected_subtitle (); + for (SubtitleContentList::iterator i = c.begin(); i != c.end(); ++i) { + (*i)->set_subtitle_y_scale (_y_scale->GetValue() / 100.0); } } @@ -241,8 +264,38 @@ void SubtitlePanel::content_selection_changed () { film_content_changed (FFmpegContentProperty::SUBTITLE_STREAMS); + film_content_changed (SubtitleContentProperty::USE_SUBTITLES); film_content_changed (SubtitleContentProperty::SUBTITLE_X_OFFSET); film_content_changed (SubtitleContentProperty::SUBTITLE_Y_OFFSET); film_content_changed (SubtitleContentProperty::SUBTITLE_X_SCALE); film_content_changed (SubtitleContentProperty::SUBTITLE_Y_SCALE); } + +void +SubtitlePanel::view_clicked () +{ + if (_view) { + _view->Destroy (); + _view = 0; + } + + SubtitleContentList c = _parent->selected_subtitle (); + assert (c.size() == 1); + + shared_ptr<SubtitleDecoder> decoder; + + shared_ptr<SubRipContent> sr = dynamic_pointer_cast<SubRipContent> (c.front ()); + if (sr) { + decoder.reset (new SubRipDecoder (sr)); + } + + shared_ptr<DCPSubtitleContent> dc = dynamic_pointer_cast<DCPSubtitleContent> (c.front ()); + if (dc) { + decoder.reset (new DCPSubtitleDecoder (dc)); + } + + if (decoder) { + _view = new SubtitleView (this, _parent->film(), decoder, c.front()->position ()); + _view->Show (); + } +} diff --git a/src/wx/subtitle_panel.h b/src/wx/subtitle_panel.h index 7f5d9239d..bcff995a0 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 @@ -17,34 +17,38 @@ */ -#include "film_editor_panel.h" +#include "content_sub_panel.h" class wxCheckBox; class wxSpinCtrl; +class SubtitleView; -class SubtitlePanel : public FilmEditorPanel +class SubtitlePanel : public ContentSubPanel { public: - SubtitlePanel (FilmEditor *); + SubtitlePanel (ContentPanel *); void film_changed (Film::Property); void film_content_changed (int); void content_selection_changed (); private: - void with_subtitles_toggled (); + void use_toggled (); void x_offset_changed (); void y_offset_changed (); void x_scale_changed (); void y_scale_changed (); void stream_changed (); + void view_clicked (); void setup_sensitivity (); - wxCheckBox* _with_subtitles; + wxCheckBox* _use; wxSpinCtrl* _x_offset; wxSpinCtrl* _y_offset; wxSpinCtrl* _x_scale; wxSpinCtrl* _y_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..dc41db2fa --- /dev/null +++ b/src/wx/subtitle_view.cc @@ -0,0 +1,86 @@ +/* + 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 "lib/film.h" +#include "lib/subrip_content.h" +#include "subtitle_view.h" +#include "wx_util.h" + +using std::list; +using boost::shared_ptr; +using boost::dynamic_pointer_cast; + +SubtitleView::SubtitleView (wxWindow* parent, shared_ptr<Film> film, shared_ptr<SubtitleDecoder> decoder, DCPTime position) + : 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); + if (buttons) { + sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder()); + } + + list<ContentTextSubtitle> subs = decoder->get_text_subtitles (ContentTimePeriod (ContentTime(), ContentTime::max ()), true); + FrameRateChange const frc = film->active_frame_rate_change (position); + int n = 0; + for (list<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); + ContentTimePeriod const p = i->period (); + _list->SetItem (n, 0, std_to_wx (p.from.timecode (frc.source))); + _list->SetItem (n, 1, std_to_wx (p.to.timecode (frc.source))); + _list->SetItem (n, 2, std_to_wx (j->text ())); + ++n; + } + } + + SetSizerAndFit (sizer); +} + diff --git a/src/wx/subtitle_view.h b/src/wx/subtitle_view.h new file mode 100644 index 000000000..338742afc --- /dev/null +++ b/src/wx/subtitle_view.h @@ -0,0 +1,33 @@ +/* + Copyright (C) 2014 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <boost/shared_ptr.hpp> +#include <wx/wx.h> +#include <wx/listctrl.h> + +class SubtitleDecoder; + +class SubtitleView : public wxDialog +{ +public: + SubtitleView (wxWindow *, boost::shared_ptr<Film>, boost::shared_ptr<SubtitleDecoder>, DCPTime position); + +private: + wxListCtrl* _list; +}; diff --git a/src/wx/timecode.cc b/src/wx/timecode.cc index 166446d8c..bd0a182c2 100644 --- a/src/wx/timecode.cc +++ b/src/wx/timecode.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,16 @@ */ -#include <boost/lexical_cast.hpp> #include "lib/util.h" #include "timecode.h" #include "wx_util.h" +#include <boost/lexical_cast.hpp> using std::string; using std::cout; using boost::lexical_cast; -Timecode::Timecode (wxWindow* parent) +TimecodeBase::TimecodeBase (wxWindow* parent) : wxPanel (parent) { wxClientDC dc (parent); @@ -69,11 +69,11 @@ Timecode::Timecode (wxWindow* parent) _fixed = add_label_to_sizer (_sizer, this, wxT ("42"), false); - _hours->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&Timecode::changed, this)); - _minutes->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&Timecode::changed, this)); - _seconds->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&Timecode::changed, this)); - _frames->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&Timecode::changed, this)); - _set_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&Timecode::set_clicked, this)); + _hours->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TimecodeBase::changed, this)); + _minutes->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TimecodeBase::changed, this)); + _seconds->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TimecodeBase::changed, this)); + _frames->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TimecodeBase::changed, this)); + _set_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&TimecodeBase::set_clicked, this)); _set_button->Enable (false); @@ -83,46 +83,7 @@ Timecode::Timecode (wxWindow* parent) } void -Timecode::set (Time 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); - - int const h = f / (3600 * fps); - f -= h * 3600 * fps; - int const m = f / (60 * fps); - f -= m * 60 * fps; - int const s = f / fps; - f -= s * fps; - - checked_set (_hours, lexical_cast<string> (h)); - checked_set (_minutes, lexical_cast<string> (m)); - checked_set (_seconds, lexical_cast<string> (s)); - checked_set (_frames, lexical_cast<string> (f)); - - _fixed->SetLabel (wxString::Format ("%02d:%02d:%02d.%02" wxLongLongFmtSpec "d", h, m, s, f)); -} - -Time -Timecode::get (int fps) const -{ - Time t = 0; - string const h = wx_to_std (_hours->GetValue ()); - t += lexical_cast<int> (h.empty() ? "0" : h) * 3600 * TIME_HZ; - string const m = wx_to_std (_minutes->GetValue()); - t += lexical_cast<int> (m.empty() ? "0" : m) * 60 * TIME_HZ; - string const s = wx_to_std (_seconds->GetValue()); - t += lexical_cast<int> (s.empty() ? "0" : s) * TIME_HZ; - string const f = wx_to_std (_frames->GetValue()); - t += lexical_cast<int> (f.empty() ? "0" : f) * TIME_HZ / fps; - - return t; -} - -void -Timecode::clear () +TimecodeBase::clear () { checked_set (_hours, ""); checked_set (_minutes, ""); @@ -132,20 +93,20 @@ Timecode::clear () } void -Timecode::changed () +TimecodeBase::changed () { _set_button->Enable (true); } void -Timecode::set_clicked () +TimecodeBase::set_clicked () { Changed (); _set_button->Enable (false); } void -Timecode::set_editable (bool e) +TimecodeBase::set_editable (bool e) { _editable->Show (e); _fixed->Show (!e); diff --git a/src/wx/timecode.h b/src/wx/timecode.h index d0e8176f2..7a34b80fe 100644 --- a/src/wx/timecode.h +++ b/src/wx/timecode.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,24 +17,27 @@ */ -#include <boost/signals2.hpp> -#include <wx/wx.h> +#ifndef DCPOMATIC_WX_TIMECODE_H +#define DCPOMATIC_WX_TIMECODE_H + +#include "wx_util.h" #include "lib/types.h" +#include <wx/wx.h> +#include <boost/signals2.hpp> +#include <boost/lexical_cast.hpp> -class Timecode : public wxPanel +class TimecodeBase : public wxPanel { public: - Timecode (wxWindow *); + TimecodeBase (wxWindow *); - void set (Time, int); - Time get (int) const; void clear (); void set_editable (bool); boost::signals2::signal<void ()> Changed; -private: +protected: void changed (); void set_clicked (); @@ -48,3 +51,46 @@ private: wxStaticText* _fixed; }; +template <class T> +class Timecode : public TimecodeBase +{ +public: + Timecode (wxWindow* parent) + : TimecodeBase (parent) + { + + } + + void set (T t, int fps) + { + int h; + int m; + int s; + int f; + t.split (fps, h, m, s, f); + + checked_set (_hours, boost::lexical_cast<std::string> (h)); + checked_set (_minutes, boost::lexical_cast<std::string> (m)); + checked_set (_seconds, boost::lexical_cast<std::string> (s)); + checked_set (_frames, boost::lexical_cast<std::string> (f)); + + _fixed->SetLabel (std_to_wx (t.timecode (fps))); + } + + T get (int fps) const + { + T t; + std::string const h = wx_to_std (_hours->GetValue ()); + t += T::from_seconds (boost::lexical_cast<int> (h.empty() ? "0" : h) * 3600); + std::string const m = wx_to_std (_minutes->GetValue()); + t += T::from_seconds (boost::lexical_cast<int> (m.empty() ? "0" : m) * 60); + std::string const s = wx_to_std (_seconds->GetValue()); + t += T::from_seconds (boost::lexical_cast<int> (s.empty() ? "0" : s)); + std::string const f = wx_to_std (_frames->GetValue()); + t += T::from_seconds (boost::lexical_cast<double> (f.empty() ? "0" : f) / fps); + + return t; + } +}; + +#endif diff --git a/src/wx/timeline.cc b/src/wx/timeline.cc index eba12c47f..13baef6b7 100644 --- a/src/wx/timeline.cc +++ b/src/wx/timeline.cc @@ -22,8 +22,10 @@ #include <boost/weak_ptr.hpp> #include "lib/film.h" #include "lib/playlist.h" +#include "lib/image_content.h" #include "film_editor.h" #include "timeline.h" +#include "content_panel.h" #include "wx_util.h" using std::list; @@ -35,7 +37,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 +68,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 +80,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 +107,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 ); } @@ -132,7 +138,8 @@ public: } virtual wxString type () const = 0; - virtual wxColour colour () const = 0; + virtual wxColour background_colour () const = 0; + virtual wxColour foreground_colour () const = 0; private: @@ -146,18 +153,16 @@ 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); + wxColour selected (background_colour().Red() / 2, background_colour().Green() / 2, background_colour().Blue() / 2); - gc->SetPen (*wxBLACK_PEN); - - gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 4, wxPENSTYLE_SOLID)); + gc->SetPen (*wxThePenList->FindOrCreatePen (foreground_colour(), 4, wxPENSTYLE_SOLID)); if (_selected) { gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (selected, wxBRUSHSTYLE_SOLID)); } else { - gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (colour(), wxBRUSHSTYLE_SOLID)); + gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (background_colour(), wxBRUSHSTYLE_SOLID)); } wxGraphicsPath path = gc->CreatePath (); @@ -169,14 +174,15 @@ private: gc->StrokePath (path); gc->FillPath (path); - wxString name = wxString::Format (wxT ("%s [%s]"), std_to_wx (cont->path_summary()).data(), type().data()); + wxString name = wxString::Format (wxT ("%s [%s]"), std_to_wx (cont->summary()).data(), type().data()); wxDouble name_width; wxDouble name_height; wxDouble name_descent; 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->SetFont (gc->CreateFont (*wxNORMAL_FONT, foreground_colour ())); gc->DrawText (name, time_x (position) + 12, y_pos (_track.get() + 1) - name_height - 4); gc->ResetClip (); } @@ -195,7 +201,7 @@ private: } if (!frequent) { - _timeline.setup_pixels_per_time_unit (); + _timeline.setup_pixels_per_second (); _timeline.Refresh (); } } @@ -207,6 +213,9 @@ private: boost::signals2::scoped_connection _content_connection; }; +/** @class AudioContentView + * @brief Timeline view for AudioContent. + */ class AudioContentView : public ContentView { public: @@ -220,12 +229,20 @@ private: return _("audio"); } - wxColour colour () const + wxColour background_colour () const { return wxColour (149, 121, 232, 255); } + + wxColour foreground_colour () const + { + return wxColour (0, 0, 0, 255); + } }; +/** @class AudioContentView + * @brief Timeline view for VideoContent. + */ class VideoContentView : public ContentView { public: @@ -237,17 +254,62 @@ private: wxString type () const { - if (dynamic_pointer_cast<FFmpegContent> (content ())) { - return _("video"); - } else { + if (dynamic_pointer_cast<ImageContent> (content ()) && content()->number_of_paths() == 1) { return _("still"); + } else { + return _("video"); } } - wxColour colour () const + wxColour background_colour () const { return wxColour (242, 92, 120, 255); } + + wxColour foreground_colour () const + { + return wxColour (0, 0, 0, 255); + } +}; + +/** @class AudioContentView + * @brief Timeline view for SubtitleContent. + */ +class SubtitleContentView : public ContentView +{ +public: + SubtitleContentView (Timeline& tl, shared_ptr<SubtitleContent> c) + : ContentView (tl, c) + , _subtitle_content (c) + {} + +private: + wxString type () const + { + return _("subtitles"); + } + + wxColour background_colour () const + { + shared_ptr<SubtitleContent> sc = _subtitle_content.lock (); + if (!sc || !sc->use_subtitles ()) { + return wxColour (210, 210, 210, 128); + } + + return wxColour (163, 255, 154, 255); + } + + wxColour foreground_colour () const + { + shared_ptr<SubtitleContent> sc = _subtitle_content.lock (); + if (!sc || !sc->use_subtitles ()) { + return wxColour (180, 180, 180, 128); + } + + return wxColour (0, 0, 0, 255); + } + + boost::weak_ptr<SubtitleContent> _subtitle_content; }; class TimeAxisView : public View @@ -273,26 +335,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) { @@ -304,14 +366,17 @@ private: path.AddLineToPoint (_timeline.width(), _y); gc->StrokePath (path); - Time t = 0; - while ((t * pptu) < _timeline.width()) { + gc->SetFont (gc->CreateFont (*wxNORMAL_FONT)); + + /* 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; @@ -325,12 +390,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); } } @@ -339,9 +404,9 @@ private: }; -Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film) +Timeline::Timeline (wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film) : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE) - , _film_editor (ed) + , _content_panel (cp) , _film (film) , _time_axis_view (new TimeAxisView (*this, 32)) , _tracks (0) @@ -380,8 +445,6 @@ Timeline::paint () return; } - gc->SetFont (gc->CreateFont (*wxNORMAL_FONT)); - for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) { (*i)->paint (gc); } @@ -411,10 +474,15 @@ Timeline::playlist_changed () if (dynamic_pointer_cast<AudioContent> (*i)) { _views.push_back (shared_ptr<View> (new AudioContentView (*this, *i))); } + + shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (*i); + if (sc && sc->has_subtitles ()) { + _views.push_back (shared_ptr<View> (new SubtitleContentView (*this, sc))); + } } assign_tracks (); - setup_pixels_per_time_unit (); + setup_pixels_per_second (); Refresh (); } @@ -425,7 +493,7 @@ Timeline::playlist_content_changed (int property) if (property == ContentProperty::POSITION) { assign_tracks (); - setup_pixels_per_time_unit (); + setup_pixels_per_second (); Refresh (); } } @@ -495,14 +563,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> @@ -545,7 +613,7 @@ Timeline::left_down (wxMouseEvent& ev) } if (view == *i) { - _film_editor->set_selection (cv->content ()); + _content_panel->set_selection (cv->content ()); } } @@ -604,11 +672,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(); @@ -627,13 +695,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) { @@ -644,7 +712,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(); @@ -653,7 +721,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 (); @@ -665,14 +736,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); @@ -697,7 +768,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 fafb09c0e..82d10afde 100644 --- a/src/wx/timeline.h +++ b/src/wx/timeline.h @@ -28,13 +28,13 @@ class Film; class View; class ContentView; -class FilmEditor; +class ContentPanel; class TimeAxisView; class Timeline : public wxPanel { public: - Timeline (wxWindow *, FilmEditor *, boost::shared_ptr<Film>); + Timeline (wxWindow *, ContentPanel *, boost::shared_ptr<Film>); boost::shared_ptr<const Film> film () const; @@ -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; @@ -92,16 +92,16 @@ private: ContentViewList selected_views () const; ContentList selected_content () const; - FilmEditor* _film_editor; + ContentPanel* _content_panel; boost::weak_ptr<Film> _film; 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/timeline_dialog.cc b/src/wx/timeline_dialog.cc index dbf7ae232..8ac90b8de 100644 --- a/src/wx/timeline_dialog.cc +++ b/src/wx/timeline_dialog.cc @@ -23,14 +23,15 @@ #include "film_editor.h" #include "timeline_dialog.h" #include "wx_util.h" +#include "content_panel.h" using std::list; using std::cout; using boost::shared_ptr; -TimelineDialog::TimelineDialog (FilmEditor* ed, shared_ptr<Film> film) - : wxDialog (ed, wxID_ANY, _("Timeline"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE) - , _timeline (this, ed, film) +TimelineDialog::TimelineDialog (ContentPanel* cp, shared_ptr<Film> film) + : wxDialog (cp->panel(), wxID_ANY, _("Timeline"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE) + , _timeline (this, cp, film) { wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL); diff --git a/src/wx/timeline_dialog.h b/src/wx/timeline_dialog.h index 1e5955003..2f5fa5ec7 100644 --- a/src/wx/timeline_dialog.h +++ b/src/wx/timeline_dialog.h @@ -27,7 +27,7 @@ class Playlist; class TimelineDialog : public wxDialog { public: - TimelineDialog (FilmEditor *, boost::shared_ptr<Film>); + TimelineDialog (ContentPanel *, boost::shared_ptr<Film>); private: void snap_toggled (); diff --git a/src/wx/timing_panel.cc b/src/wx/timing_panel.cc index aa4f70a81..0f86a3f3f 100644 --- a/src/wx/timing_panel.cc +++ b/src/wx/timing_panel.cc @@ -17,42 +17,42 @@ */ -#include <libdcp/raw_convert.h> +#include <dcp/raw_convert.h> #include "lib/content.h" #include "lib/image_content.h" #include "timing_panel.h" #include "wx_util.h" #include "timecode.h" -#include "film_editor.h" +#include "content_panel.h" using std::cout; using std::string; using std::set; using boost::shared_ptr; using boost::dynamic_pointer_cast; -using libdcp::raw_convert; +using dcp::raw_convert; -TimingPanel::TimingPanel (FilmEditor* e) +TimingPanel::TimingPanel (ContentPanel* p) /* horrid hack for apparent lack of context support with wxWidgets i18n code */ - : FilmEditorPanel (e, S_("Timing|Timing")) + : ContentSubPanel (p, S_("Timing|Timing")) { wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4); _sizer->Add (grid, 0, wxALL, 8); add_label_to_sizer (grid, this, _("Position"), true); - _position = new Timecode (this); + _position = new Timecode<DCPTime> (this); grid->Add (_position); add_label_to_sizer (grid, this, _("Full length"), true); - _full_length = new Timecode (this); + _full_length = new Timecode<DCPTime> (this); grid->Add (_full_length); add_label_to_sizer (grid, this, _("Trim from start"), true); - _trim_start = new Timecode (this); + _trim_start = new Timecode<DCPTime> (this); grid->Add (_trim_start); add_label_to_sizer (grid, this, _("Trim from end"), true); - _trim_end = new Timecode (this); + _trim_end = new Timecode<DCPTime> (this); grid->Add (_trim_end); add_label_to_sizer (grid, this, _("Play length"), true); - _play_length = new Timecode (this); + _play_length = new Timecode<DCPTime> (this); grid->Add (_play_length); { @@ -78,8 +78,8 @@ TimingPanel::TimingPanel (FilmEditor* e) void TimingPanel::film_content_changed (int property) { - ContentList cl = _editor->selected_content (); - int const film_video_frame_rate = _editor->film()->video_frame_rate (); + ContentList cl = _parent->selected (); + int const film_video_frame_rate = _parent->film()->video_frame_rate (); /* Here we check to see if we have exactly one different value of various properties, and fill the controls with that value if so. @@ -87,7 +87,7 @@ TimingPanel::film_content_changed (int property) if (property == ContentProperty::POSITION) { - set<Time> check; + set<DCPTime> check; for (ContentList::const_iterator i = cl.begin (); i != cl.end(); ++i) { check.insert ((*i)->position ()); } @@ -104,7 +104,7 @@ TimingPanel::film_content_changed (int property) property == VideoContentProperty::VIDEO_FRAME_TYPE ) { - set<Time> check; + set<DCPTime> check; for (ContentList::const_iterator i = cl.begin (); i != cl.end(); ++i) { check.insert ((*i)->full_length ()); } @@ -117,7 +117,7 @@ TimingPanel::film_content_changed (int property) } else if (property == ContentProperty::TRIM_START) { - set<Time> check; + set<DCPTime> check; for (ContentList::const_iterator i = cl.begin (); i != cl.end(); ++i) { check.insert ((*i)->trim_start ()); } @@ -130,7 +130,7 @@ TimingPanel::film_content_changed (int property) } else if (property == ContentProperty::TRIM_END) { - set<Time> check; + set<DCPTime> check; for (ContentList::const_iterator i = cl.begin (); i != cl.end(); ++i) { check.insert ((*i)->trim_end ()); } @@ -138,7 +138,7 @@ TimingPanel::film_content_changed (int property) if (check.size() == 1) { _trim_end->set (cl.front()->trim_end (), film_video_frame_rate); } else { - _trim_end->set (0, 24); + _trim_end->clear (); } } @@ -150,7 +150,7 @@ TimingPanel::film_content_changed (int property) property == VideoContentProperty::VIDEO_FRAME_TYPE ) { - set<Time> check; + set<DCPTime> check; for (ContentList::const_iterator i = cl.begin (); i != cl.end(); ++i) { check.insert ((*i)->length_after_trim ()); } @@ -197,20 +197,21 @@ TimingPanel::film_content_changed (int property) void TimingPanel::position_changed () { - ContentList c = _editor->selected_content (); + ContentList c = _parent->selected (); for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { - (*i)->set_position (_position->get (_editor->film()->video_frame_rate ())); + (*i)->set_position (_position->get (_parent->film()->video_frame_rate ())); } } void TimingPanel::full_length_changed () { - ContentList c = _editor->selected_content (); + ContentList c = _parent->selected (); for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (*i); 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 (_parent->film()->video_frame_rate()), FrameRateChange (1, 1))); } } } @@ -218,9 +219,9 @@ TimingPanel::full_length_changed () void TimingPanel::trim_start_changed () { - ContentList c = _editor->selected_content (); + ContentList c = _parent->selected (); for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { - (*i)->set_trim_start (_trim_start->get (_editor->film()->video_frame_rate ())); + (*i)->set_trim_start (_trim_start->get (_parent->film()->video_frame_rate ())); } } @@ -228,18 +229,18 @@ TimingPanel::trim_start_changed () void TimingPanel::trim_end_changed () { - ContentList c = _editor->selected_content (); + ContentList c = _parent->selected (); for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { - (*i)->set_trim_end (_trim_end->get (_editor->film()->video_frame_rate ())); + (*i)->set_trim_end (_trim_end->get (_parent->film()->video_frame_rate ())); } } void TimingPanel::play_length_changed () { - ContentList c = _editor->selected_content (); + ContentList c = _parent->selected (); for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { - (*i)->set_trim_end ((*i)->full_length() - _play_length->get (_editor->film()->video_frame_rate()) - (*i)->trim_start()); + (*i)->set_trim_end ((*i)->full_length() - _play_length->get (_parent->film()->video_frame_rate()) - (*i)->trim_start()); } } @@ -252,7 +253,7 @@ TimingPanel::video_frame_rate_changed () void TimingPanel::set_video_frame_rate () { - ContentList c = _editor->selected_content (); + ContentList c = _parent->selected (); for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i); if (vc) { @@ -265,7 +266,7 @@ TimingPanel::set_video_frame_rate () void TimingPanel::content_selection_changed () { - bool const e = !_editor->selected_content().empty (); + bool const e = !_parent->selected().empty (); _position->Enable (e); _full_length->Enable (e); diff --git a/src/wx/timing_panel.h b/src/wx/timing_panel.h index d9696a201..00b7f84e7 100644 --- a/src/wx/timing_panel.h +++ b/src/wx/timing_panel.h @@ -17,14 +17,13 @@ */ -#include "film_editor_panel.h" +#include "content_sub_panel.h" +#include "timecode.h" -class Timecode; - -class TimingPanel : public FilmEditorPanel +class TimingPanel : public ContentSubPanel { public: - TimingPanel (FilmEditor *); + TimingPanel (ContentPanel *); void film_content_changed (int); void content_selection_changed (); @@ -38,11 +37,11 @@ private: void video_frame_rate_changed (); void set_video_frame_rate (); - Timecode* _position; - Timecode* _full_length; - Timecode* _trim_start; - Timecode* _trim_end; - Timecode* _play_length; + Timecode<DCPTime>* _position; + Timecode<DCPTime>* _full_length; + Timecode<DCPTime>* _trim_start; + Timecode<DCPTime>* _trim_end; + Timecode<DCPTime>* _play_length; wxTextCtrl* _video_frame_rate; wxButton* _set_video_frame_rate; }; diff --git a/src/wx/video_panel.cc b/src/wx/video_panel.cc index b33a97591..a8510cbba 100644 --- a/src/wx/video_panel.cc +++ b/src/wx/video_panel.cc @@ -28,15 +28,16 @@ #include "filter_dialog.h" #include "video_panel.h" #include "wx_util.h" -#include "film_editor.h" #include "content_colour_conversion_dialog.h" #include "content_widget.h" +#include "content_panel.h" using std::vector; using std::string; using std::pair; using std::cout; using std::list; +using std::set; using boost::shared_ptr; using boost::dynamic_pointer_cast; using boost::bind; @@ -64,8 +65,8 @@ scale_to_index (VideoContentScale scale) assert (false); } -VideoPanel::VideoPanel (FilmEditor* e) - : FilmEditorPanel (e, _("Video")) +VideoPanel::VideoPanel (ContentPanel* p) + : ContentSubPanel (p, _("Video")) { wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); _sizer->Add (grid, 0, wxALL, 8); @@ -82,7 +83,7 @@ VideoPanel::VideoPanel (FilmEditor* e) &caster<int, VideoFrameType>, &caster<VideoFrameType, int> ); - _frame_type->add (grid, wxGBPosition (r, 1)); + _frame_type->add (grid, wxGBPosition (r, 1), wxGBSpan (1, 2)); ++r; add_label_to_grid_bag_sizer (grid, this, _("Left crop"), true, wxGBPosition (r, 0)); @@ -94,9 +95,8 @@ VideoPanel::VideoPanel (FilmEditor* e) boost::mem_fn (&VideoContent::set_left_crop) ); _left_crop->add (grid, wxGBPosition (r, 1)); - ++r; - add_label_to_grid_bag_sizer (grid, this, _("Right crop"), true, wxGBPosition (r, 0)); + add_label_to_grid_bag_sizer (grid, this, _("Right crop"), true, wxGBPosition (r, 2)); _right_crop = new ContentSpinCtrl<VideoContent> ( this, new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)), @@ -104,7 +104,8 @@ VideoPanel::VideoPanel (FilmEditor* e) boost::mem_fn (&VideoContent::right_crop), boost::mem_fn (&VideoContent::set_right_crop) ); - _right_crop->add (grid, wxGBPosition (r, 1)); + _right_crop->add (grid, wxGBPosition (r, 3)); + ++r; add_label_to_grid_bag_sizer (grid, this, _("Top crop"), true, wxGBPosition (r, 0)); @@ -115,10 +116,9 @@ VideoPanel::VideoPanel (FilmEditor* e) boost::mem_fn (&VideoContent::top_crop), boost::mem_fn (&VideoContent::set_top_crop) ); - _top_crop->add (grid, wxGBPosition (r,1 )); - ++r; + _top_crop->add (grid, wxGBPosition (r, 1)); - add_label_to_grid_bag_sizer (grid, this, _("Bottom crop"), true, wxGBPosition (r, 0)); + add_label_to_grid_bag_sizer (grid, this, _("Bottom crop"), true, wxGBPosition (r, 2)); _bottom_crop = new ContentSpinCtrl<VideoContent> ( this, new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)), @@ -126,9 +126,20 @@ VideoPanel::VideoPanel (FilmEditor* e) boost::mem_fn (&VideoContent::bottom_crop), boost::mem_fn (&VideoContent::set_bottom_crop) ); - _bottom_crop->add (grid, wxGBPosition (r, 1)); + _bottom_crop->add (grid, wxGBPosition (r, 3)); + ++r; + add_label_to_grid_bag_sizer (grid, this, _("Fade in"), true, wxGBPosition (r, 0)); + _fade_in = new Timecode<ContentTime> (this); + grid->Add (_fade_in, wxGBPosition (r, 1), wxGBSpan (1, 3)); + ++r; + + add_label_to_grid_bag_sizer (grid, this, _("Fade out"), true, wxGBPosition (r, 0)); + _fade_out = new Timecode<ContentTime> (this); + grid->Add (_fade_out, wxGBPosition (r, 1), wxGBSpan (1, 3)); + ++r; + add_label_to_grid_bag_sizer (grid, this, _("Scale to"), true, wxGBPosition (r, 0)); _scale = new ContentChoice<VideoContent, VideoContentScale> ( this, @@ -139,44 +150,29 @@ VideoPanel::VideoPanel (FilmEditor* e) &index_to_scale, &scale_to_index ); - _scale->add (grid, wxGBPosition (r, 1)); + _scale->add (grid, wxGBPosition (r, 1), wxGBSpan (1, 2)); ++r; - { - add_label_to_grid_bag_sizer (grid, this, _("Filters"), true, wxGBPosition (r, 0)); - wxSizer* s = new wxBoxSizer (wxHORIZONTAL); - - wxClientDC dc (this); - wxSize size = dc.GetTextExtent (wxT ("A quite long name")); - size.SetHeight (-1); - - _filters = new wxStaticText (this, wxID_ANY, _("None"), wxDefaultPosition, size); - s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6); - _filters_button = new wxButton (this, wxID_ANY, _("Edit...")); - s->Add (_filters_button, 0, wxALIGN_CENTER_VERTICAL); - grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); - } + wxClientDC dc (this); + wxSize size = dc.GetTextExtent (wxT ("A quite long name")); + size.SetHeight (-1); + + add_label_to_grid_bag_sizer (grid, this, _("Filters"), true, wxGBPosition (r, 0)); + _filters = new wxStaticText (this, wxID_ANY, _("None"), wxDefaultPosition, size); + grid->Add (_filters, wxGBPosition (r, 1), wxGBSpan (1, 2), wxALIGN_CENTER_VERTICAL); + _filters_button = new wxButton (this, wxID_ANY, _("Edit...")); + grid->Add (_filters_button, wxGBPosition (r, 3), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); ++r; - { - add_label_to_grid_bag_sizer (grid, this, _("Colour conversion"), true, wxGBPosition (r, 0)); - wxSizer* s = new wxBoxSizer (wxHORIZONTAL); - - wxClientDC dc (this); - wxSize size = dc.GetTextExtent (wxT ("A quite long name")); - size.SetHeight (-1); - - _colour_conversion = new wxStaticText (this, wxID_ANY, wxT (""), wxDefaultPosition, size); - - s->Add (_colour_conversion, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6); - _colour_conversion_button = new wxButton (this, wxID_ANY, _("Edit...")); - s->Add (_colour_conversion_button, 0, wxALIGN_CENTER_VERTICAL); - grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); - } + add_label_to_grid_bag_sizer (grid, this, _("Colour conversion"), true, wxGBPosition (r, 0)); + _colour_conversion = new wxStaticText (this, wxID_ANY, wxT (""), wxDefaultPosition, size); + grid->Add (_colour_conversion, wxGBPosition (r, 1), wxGBSpan (1, 2), wxALIGN_CENTER_VERTICAL); + _colour_conversion_button = new wxButton (this, wxID_ANY, _("Edit...")); + grid->Add (_colour_conversion_button, wxGBPosition (r, 3), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); ++r; _description = new wxStaticText (this, wxID_ANY, wxT ("\n \n \n \n \n"), wxDefaultPosition, wxDefaultSize); - grid->Add (_description, wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6); + grid->Add (_description, wxGBPosition (r, 0), wxGBSpan (1, 4), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6); wxFont font = _description->GetFont(); font.SetStyle(wxFONTSTYLE_ITALIC); font.SetPointSize(font.GetPointSize() - 1); @@ -201,6 +197,9 @@ VideoPanel::VideoPanel (FilmEditor* e) _frame_type->wrapped()->Append (_("3D left only")); _frame_type->wrapped()->Append (_("3D right only")); + _fade_in->Changed.connect (boost::bind (&VideoPanel::fade_in_changed, this)); + _fade_out->Changed.connect (boost::bind (&VideoPanel::fade_out_changed, this)); + _filters_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&VideoPanel::edit_filters_clicked, this)); _colour_conversion_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&VideoPanel::edit_colour_conversion_clicked, this)); } @@ -222,7 +221,7 @@ VideoPanel::film_changed (Film::Property property) void VideoPanel::film_content_changed (int property) { - VideoContentList vc = _editor->selected_video_content (); + VideoContentList vc = _parent->selected_video (); shared_ptr<VideoContent> vcs; shared_ptr<FFmpegContent> fcs; if (!vc.empty ()) { @@ -251,6 +250,28 @@ VideoPanel::film_content_changed (int property) _filters->SetLabel (std_to_wx (p)); } } + } else if (property == VideoContentProperty::VIDEO_FADE_IN) { + set<ContentTime> check; + for (VideoContentList::const_iterator i = vc.begin (); i != vc.end(); ++i) { + check.insert ((*i)->fade_in ()); + } + + if (check.size() == 1) { + _fade_in->set (vc.front()->fade_in (), vc.front()->video_frame_rate ()); + } else { + _fade_in->clear (); + } + } else if (property == VideoContentProperty::VIDEO_FADE_OUT) { + set<ContentTime> check; + for (VideoContentList::const_iterator i = vc.begin (); i != vc.end(); ++i) { + check.insert ((*i)->fade_out ()); + } + + if (check.size() == 1) { + _fade_out->set (vc.front()->fade_out (), vc.front()->video_frame_rate ()); + } else { + _fade_out->clear (); + } } } @@ -258,7 +279,7 @@ VideoPanel::film_content_changed (int property) void VideoPanel::edit_filters_clicked () { - FFmpegContentList c = _editor->selected_ffmpeg_content (); + FFmpegContentList c = _parent->selected_ffmpeg (); if (c.size() != 1) { return; } @@ -272,7 +293,7 @@ VideoPanel::edit_filters_clicked () void VideoPanel::setup_description () { - VideoContentList vc = _editor->selected_video_content (); + VideoContentList vc = _parent->selected_video (); if (vc.empty ()) { _description->SetLabel (""); return; @@ -298,8 +319,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 +329,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 = _parent->film()->frame_size (); + dcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size, 1); if (scaled != vcs->video_size_after_crop ()) { d << wxString::Format ( @@ -331,7 +352,7 @@ VideoPanel::setup_description () d << wxString::Format (_("Content frame rate %.4f\n"), vcs->video_frame_rate ()); ++lines; - FrameRateChange frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ()); + FrameRateChange frc (vcs->video_frame_rate(), _parent->film()->video_frame_rate ()); d << std_to_wx (frc.description ()) << "\n"; ++lines; @@ -346,7 +367,7 @@ VideoPanel::setup_description () void VideoPanel::edit_colour_conversion_clicked () { - VideoContentList vc = _editor->selected_video_content (); + VideoContentList vc = _parent->selected_video (); if (vc.size() != 1) { return; } @@ -363,8 +384,8 @@ VideoPanel::edit_colour_conversion_clicked () void VideoPanel::content_selection_changed () { - VideoContentList video_sel = _editor->selected_video_content (); - FFmpegContentList ffmpeg_sel = _editor->selected_ffmpeg_content (); + VideoContentList video_sel = _parent->selected_video (); + FFmpegContentList ffmpeg_sel = _parent->selected_ffmpeg (); bool const single = video_sel.size() == 1; @@ -381,5 +402,25 @@ VideoPanel::content_selection_changed () film_content_changed (VideoContentProperty::VIDEO_CROP); film_content_changed (VideoContentProperty::VIDEO_FRAME_RATE); film_content_changed (VideoContentProperty::COLOUR_CONVERSION); + film_content_changed (VideoContentProperty::VIDEO_FADE_IN); + film_content_changed (VideoContentProperty::VIDEO_FADE_OUT); film_content_changed (FFmpegContentProperty::FILTERS); } + +void +VideoPanel::fade_in_changed () +{ + VideoContentList vc = _parent->selected_video (); + for (VideoContentList::const_iterator i = vc.begin(); i != vc.end(); ++i) { + (*i)->set_fade_in (_fade_in->get (_parent->film()->video_frame_rate ())); + } +} + +void +VideoPanel::fade_out_changed () +{ + VideoContentList vc = _parent->selected_video (); + for (VideoContentList::const_iterator i = vc.begin(); i != vc.end(); ++i) { + (*i)->set_fade_out (_fade_out->get (_parent->film()->video_frame_rate ())); + } +} diff --git a/src/wx/video_panel.h b/src/wx/video_panel.h index 99633491d..aa0c6ed53 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,19 +17,27 @@ */ -#include "lib/film.h" -#include "film_editor_panel.h" +/** @file src/lib/video_panel.h + * @brief VideoPanel class. + */ + +#include "content_sub_panel.h" #include "content_widget.h" +#include "timecode.h" +#include "lib/film.h" class wxChoice; class wxStaticText; class wxSpinCtrl; class wxButton; -class VideoPanel : public FilmEditorPanel +/** @class VideoPanel + * @brief The video tab of the film editor. + */ +class VideoPanel : public ContentSubPanel { public: - VideoPanel (FilmEditor *); + VideoPanel (ContentPanel *); void film_changed (Film::Property); void film_content_changed (int); @@ -38,6 +46,8 @@ public: private: void edit_filters_clicked (); void edit_colour_conversion_clicked (); + void fade_in_changed (); + void fade_out_changed (); void setup_description (); @@ -46,6 +56,8 @@ private: ContentSpinCtrl<VideoContent>* _right_crop; ContentSpinCtrl<VideoContent>* _top_crop; ContentSpinCtrl<VideoContent>* _bottom_crop; + Timecode<ContentTime>* _fade_in; + Timecode<ContentTime>* _fade_out; ContentChoice<VideoContent, VideoContentScale>* _scale; wxStaticText* _description; wxStaticText* _filters; diff --git a/src/wx/wscript b/src/wx/wscript index 8bf2451c2..0f39038a5 100644 --- a/src/wx/wscript +++ b/src/wx/wscript @@ -15,13 +15,15 @@ sources = """ config_dialog.cc content_colour_conversion_dialog.cc content_menu.cc + content_panel.cc + content_sub_panel.cc + dcp_panel.cc isdcf_metadata_dialog.cc dir_picker_ctrl.cc dolby_certificate_dialog.cc doremi_certificate_dialog.cc download_certificate_dialog.cc film_editor.cc - film_editor_panel.cc film_viewer.cc filter_dialog.cc filter_editor.cc @@ -30,6 +32,7 @@ sources = """ job_manager_view.cc job_wrapper.cc kdm_dialog.cc + make_signer_chain_dialog.cc new_film_dialog.cc preset_colour_conversion_dialog.cc properties_dialog.cc @@ -38,6 +41,7 @@ sources = """ server_dialog.cc servers_list_dialog.cc subtitle_panel.cc + subtitle_view.cc table_dialog.cc timecode.cc timeline.cc @@ -83,16 +87,16 @@ def build(bld): else: obj = bld(features = 'cxx cxxshlib') - obj.name = 'libdcpomatic-wx' + obj.name = 'libdcpomatic2-wx' obj.export_includes = ['..'] obj.uselib = 'WXWIDGETS DCP' if bld.env.TARGET_LINUX: obj.uselib += ' GTK' - obj.use = 'libdcpomatic' + obj.use = 'libdcpomatic2' obj.source = sources - obj.target = 'dcpomatic-wx' + obj.target = 'dcpomatic2-wx' - i18n.po_to_mo(os.path.join('src', 'wx'), 'libdcpomatic-wx', bld) + i18n.po_to_mo(os.path.join('src', 'wx'), 'libdcpomatic2-wx', bld) def pot(bld): i18n.pot(os.path.join('src', 'wx'), sources + " editable_list.h", 'libdcpomatic-wx') 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 1e501f54f..003cc222f 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..279944919 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,8 +17,19 @@ */ +/** @file test/audio_analysis_test.cc + * @brief Check audio analysis code. + */ + #include <boost/test/unit_test.hpp> #include "lib/audio_analysis.h" +#include "lib/film.h" +#include "lib/sndfile_content.h" +#include "lib/dcp_content_type.h" +#include "lib/ratio.h" +#include "test.h" + +using boost::shared_ptr; static float random_float () @@ -26,8 +37,7 @@ random_float () return (float (rand ()) / RAND_MAX) * 2 - 1; } -/* Check serialisation of audio analyses */ -BOOST_AUTO_TEST_CASE (audio_analysis_test) +BOOST_AUTO_TEST_CASE (audio_analysis_serialisation_test) { int const channels = 3; int const points = 4096; @@ -44,13 +54,13 @@ BOOST_AUTO_TEST_CASE (audio_analysis_test) } } - a.write ("build/test/audio_analysis_test"); + a.write ("build/test/audio_analysis_serialisation_test"); srand (1); - AudioAnalysis b ("build/test/audio_analysis_test"); + AudioAnalysis b ("build/test/audio_analysis_serialisation_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); @@ -58,3 +68,25 @@ BOOST_AUTO_TEST_CASE (audio_analysis_test) } } } + +void +finished () +{ + +} + +BOOST_AUTO_TEST_CASE (audio_analysis_test) +{ + shared_ptr<Film> film = new_test_film ("audio_analysis_test"); + film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR")); + film->set_container (Ratio::from_id ("185")); + film->set_name ("audio_analysis_test"); + boost::filesystem::path p = private_data / "betty_L.wav"; + + shared_ptr<SndfileContent> c (new SndfileContent (film, p)); + film->examine_and_add_content (c); + wait_for_jobs (); + + c->analyse_audio (boost::bind (&finished)); + wait_for_jobs (); +} 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_filter_test.cc b/test/audio_filter_test.cc new file mode 100644 index 000000000..bcd16fd4e --- /dev/null +++ b/test/audio_filter_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/audio_filter_test.cc + * @brief Basic tests of audio filters. + */ + +#include <boost/test/unit_test.hpp> +#include "lib/audio_filter.h" +#include "lib/audio_buffers.h" + +using boost::shared_ptr; + +static void +audio_filter_impulse_test_one (AudioFilter& f, int block_size, int num_blocks) +{ + int c = 0; + + for (int i = 0; i < num_blocks; ++i) { + + shared_ptr<AudioBuffers> in (new AudioBuffers (1, block_size)); + for (int j = 0; j < block_size; ++j) { + in->data()[0][j] = c + j; + } + + shared_ptr<AudioBuffers> out = f.run (in); + + for (int j = 0; j < out->frames(); ++j) { + BOOST_CHECK_EQUAL (out->data()[0][j], c + j); + } + + c += block_size; + } +} + +/** Create a filter with an impulse as a kernel and check that it + * passes data through unaltered. + */ +BOOST_AUTO_TEST_CASE (audio_filter_impulse_kernel_test) +{ + AudioFilter f (0.02); + f._ir.resize (f._M + 1); + + f._ir[0] = 1; + for (int i = 1; i <= f._M; ++i) { + f._ir[i] = 0; + } + + audio_filter_impulse_test_one (f, 32, 1); + audio_filter_impulse_test_one (f, 256, 1); + audio_filter_impulse_test_one (f, 2048, 1); +} + +/** Create filters and pass them impulses as input and check that + * the filter kernels comes back. + */ +BOOST_AUTO_TEST_CASE (audio_filter_impulse_input_test) +{ + LowPassAudioFilter lpf (0.02, 0.3); + + shared_ptr<AudioBuffers> in (new AudioBuffers (1, 1751)); + in->make_silent (); + in->data(0)[0] = 1; + + shared_ptr<AudioBuffers> out = lpf.run (in); + for (int j = 0; j < out->frames(); ++j) { + if (j <= lpf._M) { + BOOST_CHECK_EQUAL (out->data(0)[j], lpf._ir[j]); + } else { + BOOST_CHECK_EQUAL (out->data(0)[j], 0); + } + } + + HighPassAudioFilter hpf (0.02, 0.3); + + in.reset (new AudioBuffers (1, 9133)); + in->make_silent (); + in->data(0)[0] = 1; + + out = hpf.run (in); + for (int j = 0; j < out->frames(); ++j) { + if (j <= hpf._M) { + BOOST_CHECK_EQUAL (out->data(0)[j], hpf._ir[j]); + } else { + BOOST_CHECK_EQUAL (out->data(0)[j], 0); + } + } +} 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..d10d1ed25 --- /dev/null +++ b/test/burnt_subtitle_test.cc @@ -0,0 +1,69 @@ +/* + 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/dcp_subtitle_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 from a SubRip file */ +BOOST_AUTO_TEST_CASE (burnt_subtitle_test_subrip) +{ + shared_ptr<Film> film = new_test_film ("burnt_subtitle_test_subrip"); + film->set_container (Ratio::from_id ("185")); + film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR")); + film->set_name ("frobozz"); + film->set_burn_subtitles (true); + shared_ptr<SubRipContent> content (new SubRipContent (film, "test/data/subrip2.srt")); + content->set_use_subtitles (true); + film->examine_and_add_content (content); + wait_for_jobs (); + film->make_dcp (); + wait_for_jobs (); + + check_dcp ("test/data/burnt_subtitle_test_subrip", film->dir (film->dcp_name ())); +} + +/** Build a small DCP with no picture and a single subtitle overlaid onto it from a DCP XML file */ +BOOST_AUTO_TEST_CASE (burnt_subtitle_test_dcp) +{ + shared_ptr<Film> film = new_test_film ("burnt_subtitle_test_dcp"); + film->set_container (Ratio::from_id ("185")); + film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR")); + film->set_name ("frobozz"); + film->set_burn_subtitles (true); + shared_ptr<DCPSubtitleContent> content (new DCPSubtitleContent (film, "test/data/dcp_sub.xml")); + content->set_use_subtitles (true); + film->examine_and_add_content (content); + wait_for_jobs (); + film->make_dcp (); + wait_for_jobs (); + + check_dcp ("test/data/burnt_subtitle_test_dcp", film->dir (film->dcp_name ())); +} diff --git a/test/client_server_test.cc b/test/client_server_test.cc index 07af1255c..0154200ad 100644 --- a/test/client_server_test.cc +++ b/test/client_server_test.cc @@ -17,34 +17,44 @@ */ +/** @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" #include "lib/image.h" #include "lib/cross.h" -#include "lib/dcp_video_frame.h" +#include "lib/dcp_video.h" #include "lib/scaler.h" -#include "lib/player_video_frame.h" -#include "lib/image_proxy.h" +#include "lib/player_video.h" +#include "lib/raw_image_proxy.h" +#include "lib/encoded_data.h" using std::list; using boost::shared_ptr; using boost::thread; +using boost::optional; void -do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription description, shared_ptr<EncodedData> locally_encoded) +do_remote_encode (shared_ptr<DCPVideo> frame, ServerDescription description, shared_ptr<EncodedData> locally_encoded) { shared_ptr<EncodedData> remotely_encoded; BOOST_CHECK_NO_THROW (remotely_encoded = frame->encode_remotely (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 +67,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; @@ -72,12 +82,14 @@ BOOST_AUTO_TEST_CASE (client_server_test_rgb) shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test_rgb.log")); - shared_ptr<PlayerVideoFrame> pvf ( - new PlayerVideoFrame ( + shared_ptr<PlayerVideo> pvf ( + new PlayerVideo ( shared_ptr<ImageProxy> (new RawImageProxy (image, log)), + DCPTime (), Crop (), - libdcp::Size (1998, 1080), - libdcp::Size (1998, 1080), + optional<float> (), + dcp::Size (1998, 1080), + dcp::Size (1998, 1080), Scaler::from_id ("bicubic"), EYES_BOTH, PART_WHOLE, @@ -85,15 +97,16 @@ 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 ( + shared_ptr<DCPVideo> frame ( + new DCPVideo ( pvf, 0, 24, 200000000, RESOLUTION_2K, + true, log ) ); @@ -122,11 +135,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 +151,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; @@ -151,12 +166,14 @@ BOOST_AUTO_TEST_CASE (client_server_test_yuv) shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test_yuv.log")); - shared_ptr<PlayerVideoFrame> pvf ( - new PlayerVideoFrame ( + shared_ptr<PlayerVideo> pvf ( + new PlayerVideo ( shared_ptr<ImageProxy> (new RawImageProxy (image, log)), + DCPTime (), Crop (), - libdcp::Size (1998, 1080), - libdcp::Size (1998, 1080), + optional<float> (), + dcp::Size (1998, 1080), + dcp::Size (1998, 1080), Scaler::from_id ("bicubic"), EYES_BOTH, PART_WHOLE, @@ -164,15 +181,16 @@ 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 ( + shared_ptr<DCPVideo> frame ( + new DCPVideo ( pvf, 0, 24, 200000000, RESOLUTION_2K, + true, log ) ); @@ -201,5 +219,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/dcp_subtitle_test.cc b/test/dcp_subtitle_test.cc new file mode 100644 index 000000000..95fa7d1ec --- /dev/null +++ b/test/dcp_subtitle_test.cc @@ -0,0 +1,46 @@ +/* + 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/dcp_subtitle_test.cc + * @brief Test DCP subtitle content in various ways. + */ + +#include <boost/test/unit_test.hpp> +#include "lib/film.h" +#include "lib/dcp_subtitle_content.h" +#include "lib/ratio.h" +#include "lib/dcp_content_type.h" +#include "test.h" + +using std::cout; +using boost::shared_ptr; + +/** Test load of very simple DCP subtitle file */ +BOOST_AUTO_TEST_CASE (dcp_subtitle_test) +{ + shared_ptr<Film> film = new_test_film ("dcp_subtitle_test"); + film->set_container (Ratio::from_id ("185")); + film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR")); + film->set_name ("frobozz"); + shared_ptr<DCPSubtitleContent> content (new DCPSubtitleContent (film, "test/data/dcp_sub.xml")); + film->examine_and_add_content (content); + wait_for_jobs (); + + BOOST_CHECK_EQUAL (content->full_length(), DCPTime::from_seconds (2)); +} 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 c9f4a2c38..01cff5b06 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 <boost/test/unit_test.hpp> #include <boost/filesystem.hpp> #include <boost/date_time.hpp> @@ -31,13 +35,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); @@ -50,9 +50,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"); @@ -60,5 +60,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/import_dcp_test.cc b/test/import_dcp_test.cc new file mode 100644 index 000000000..80cd9c3df --- /dev/null +++ b/test/import_dcp_test.cc @@ -0,0 +1,74 @@ +/* + 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/test/unit_test.hpp> +#include <dcp/cpl.h> +#include "lib/film.h" +#include "lib/dcp_subtitle_content.h" +#include "lib/ratio.h" +#include "lib/dcp_content_type.h" +#include "lib/dcp_content.h" +#include "lib/ffmpeg_content.h" +#include "lib/config.h" +#include "test.h" + +using boost::shared_ptr; + +/** Make an encrypted DCP, import it and make a new unencrypted DCP */ +BOOST_AUTO_TEST_CASE (import_dcp_test) +{ + shared_ptr<Film> A = new_test_film ("import_dcp_test"); + A->set_container (Ratio::from_id ("185")); + A->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR")); + A->set_name ("frobozz"); + + shared_ptr<FFmpegContent> c (new FFmpegContent (A, "test/data/test.mp4")); + A->examine_and_add_content (c); + A->set_encrypted (true); + wait_for_jobs (); + + A->make_dcp (); + wait_for_jobs (); + + dcp::DCP A_dcp ("build/test/import_dcp_test/" + A->dcp_name()); + A_dcp.read (); + + dcp::EncryptedKDM kdm = A->make_kdm ( + Config::instance()->decryption_certificate(), + A_dcp.cpls().front()->file (), + dcp::LocalTime ("2014-07-21T00:00:00+00:00"), + dcp::LocalTime ("2024-07-21T00:00:00+00:00"), + dcp::MODIFIED_TRANSITIONAL_1 + ); + + shared_ptr<Film> B = new_test_film ("import_dcp_test2"); + B->set_container (Ratio::from_id ("185")); + B->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR")); + B->set_name ("frobozz"); + + shared_ptr<DCPContent> d (new DCPContent (B, "build/test/import_dcp_test/" + A->dcp_name())); + d->add_kdm (kdm); + B->examine_and_add_content (d); + wait_for_jobs (); + + B->make_dcp (); + wait_for_jobs (); + + check_dcp ("build/test/import_dcp_test2/" + B->dcp_name(), "test/data/import_dcp_test2"); +} diff --git a/test/job_test.cc b/test/job_test.cc index 7d2911c4e..97a23b946 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 dd0208b1d..f6c3a4bb2 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); // 2 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..b6f864f82 --- /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 (288000)); + + 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..eab30ceee 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), 1), 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), 1), 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), 1), 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), 1), 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), 1), 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), 1), 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), 1), 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), 1), 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), 1), dcp::Size (2048, 1080)); } diff --git a/test/recover_test.cc b/test/recover_test.cc index 284895e0a..92eae27eb 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_1133fd57e751ce3e82146492466365f9_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..de2108066 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,19 +70,19 @@ 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); - BOOST_CHECK_EQUAL (a.channels, 2); + BOOST_CHECK_EQUAL (a.frame_rate(), 44100); + BOOST_CHECK_EQUAL (a.channels(), 2); BOOST_CHECK_EQUAL (a.name, "hello there world"); - BOOST_CHECK_EQUAL (a.mapping.content_channels(), 2); + 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..e18ee2ea5 --- /dev/null +++ b/test/subrip_test.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. + +*/ + +/** @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 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<ContentTextSubtitle> cts = decoder->get_text_subtitles ( + ContentTimePeriod ( + ContentTime::from_seconds (109), ContentTime::from_seconds (110) + ), false + ); + 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..71cd50ac9 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,16 @@ */ +/** @file test/test.cc + * @brief Overall test stuff and useful methods for tests. + */ + #include <vector> #include <list> +#include <Magick++.h> +#include <sndfile.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 +35,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 +48,8 @@ using std::cerr; using std::list; using boost::shared_ptr; +boost::filesystem::path private_data = boost::filesystem::path ("..") / boost::filesystem::path ("dcpomatic-test-private"); + class TestUISignaller : public UISignaller { public: @@ -53,20 +62,27 @@ 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); + Config::instance()->set_default_j2k_bandwidth (100000000); ServerFinder::instance()->disable (); ui_signaller = new TestUISignaller (); } + + ~TestConfig () + { + JobManager::drop (); + } }; BOOST_GLOBAL_FIXTURE (TestConfig); @@ -95,10 +111,49 @@ new_test_film (string name) } void +check_audio_file (boost::filesystem::path ref, boost::filesystem::path check) +{ + SF_INFO ref_info; + ref_info.format = 0; + SNDFILE* ref_file = sf_open (ref.string().c_str(), SFM_READ, &ref_info); + BOOST_CHECK (ref_file); + + SF_INFO check_info; + check_info.format = 0; + SNDFILE* check_file = sf_open (check.string().c_str(), SFM_READ, &check_info); + BOOST_CHECK (check_file); + + BOOST_CHECK_EQUAL (ref_info.frames, check_info.frames); + BOOST_CHECK_EQUAL (ref_info.samplerate, check_info.samplerate); + BOOST_CHECK_EQUAL (ref_info.channels, check_info.channels); + BOOST_CHECK_EQUAL (ref_info.format, check_info.format); + + /* buffer_size is in frames */ + sf_count_t const buffer_size = 65536 * ref_info.channels; + int32_t* ref_buffer = new int32_t[buffer_size]; + int32_t* check_buffer = new int32_t[buffer_size]; + + sf_count_t N = ref_info.frames; + while (N) { + sf_count_t this_time = min (buffer_size, N); + sf_count_t r = sf_readf_int (ref_file, ref_buffer, this_time); + BOOST_CHECK_EQUAL (r, this_time); + r = sf_readf_int (check_file, check_buffer, this_time); + BOOST_CHECK_EQUAL (r, this_time); + + for (sf_count_t i = 0; i < this_time; ++i) { + BOOST_CHECK (fabs (ref_buffer[i] - check_buffer[i]) <= 65536); + } + + N -= this_time; + } +} + +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)); + BOOST_CHECK_EQUAL (N, boost::filesystem::file_size (check)); FILE* ref_file = fopen_boost (ref, "rb"); BOOST_CHECK (ref_file); FILE* check_file = fopen_boost (check, "rb"); @@ -108,6 +163,9 @@ check_file (boost::filesystem::path ref, boost::filesystem::path check) uint8_t* ref_buffer = new uint8_t[buffer_size]; uint8_t* check_buffer = new uint8_t[buffer_size]; + SafeStringStream error; + error << "File " << check.string() << " differs from reference " << ref.string(); + while (N) { uintmax_t this_time = min (uintmax_t (buffer_size), N); size_t r = fread (ref_buffer, 1, this_time, ref_file); @@ -115,7 +173,11 @@ check_file (boost::filesystem::path ref, boost::filesystem::path check) r = fread (check_buffer, 1, this_time, check_file); BOOST_CHECK_EQUAL (r, this_time); - BOOST_CHECK_EQUAL (memcmp (ref_buffer, check_buffer, this_time), 0); + BOOST_CHECK_MESSAGE (memcmp (ref_buffer, check_buffer, this_time) == 0, error.str ()); + if (memcmp (ref_buffer, check_buffer, this_time)) { + break; + } + N -= this_time; } @@ -127,27 +189,28 @@ 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; + options.reel_hashes_can_differ = true; BOOST_CHECK (ref_dcp.equals (check_dcp, options, boost::bind (note, _1, _2))); } @@ -168,7 +231,7 @@ check_xml (xmlpp::Element* ref, xmlpp::Element* test, list<string> ignore) xmlpp::Element::NodeList::iterator k = ref_children.begin (); xmlpp::Element::NodeList::iterator l = test_children.begin (); - while (k != ref_children.end ()) { + while (k != ref_children.end () && l != test_children.end ()) { /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */ @@ -197,6 +260,9 @@ check_xml (xmlpp::Element* ref, xmlpp::Element* test, list<string> ignore) ++k; ++l; } + + BOOST_CHECK (k == ref_children.end ()); + BOOST_CHECK (l == test_children.end ()); } void @@ -218,10 +284,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 ()) { + ++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)->error_summary () << "\n" - << (*i)->error_details () << "\n"; + cerr << (*i)->name() << ":\n" + << "\tsummary: " << (*i)->error_summary () << "\n" + << "\tdetails: " << (*i)->error_details () << "\n"; } } } @@ -230,3 +305,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..c9e477e02 100644 --- a/test/test.h +++ b/test/test.h @@ -20,10 +20,16 @@ #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_audio_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/upmixer_a_test.cc b/test/upmixer_a_test.cc new file mode 100644 index 000000000..b115db21b --- /dev/null +++ b/test/upmixer_a_test.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 <boost/test/unit_test.hpp> +#include <sndfile.h> +#include "lib/film.h" +#include "lib/ratio.h" +#include "lib/dcp_content_type.h" +#include "lib/sndfile_content.h" +#include "lib/player.h" +#include "lib/audio_buffers.h" +#include "lib/upmixer_a.h" +#include "test.h" + +using boost::shared_ptr; + +BOOST_AUTO_TEST_CASE (upmixer_a_test) +{ + shared_ptr<Film> film = new_test_film ("upmixer_a_test"); + film->set_container (Ratio::from_id ("185")); + film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TLR")); + film->set_name ("frobozz"); + shared_ptr<SndfileContent> content (new SndfileContent (film, "test/data/white.wav")); + content->set_audio_processor (AudioProcessor::from_id ("stereo-5.1-upmix-a")); + film->examine_and_add_content (content); + + wait_for_jobs (); + + SF_INFO info; + info.samplerate = 48000; + info.channels = 1; + info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; + SNDFILE* L = sf_open ("build/test/upmixer_a_test/L.wav", SFM_WRITE, &info); + SNDFILE* R = sf_open ("build/test/upmixer_a_test/R.wav", SFM_WRITE, &info); + SNDFILE* C = sf_open ("build/test/upmixer_a_test/C.wav", SFM_WRITE, &info); + SNDFILE* Lfe = sf_open ("build/test/upmixer_a_test/Lfe.wav", SFM_WRITE, &info); + SNDFILE* Ls = sf_open ("build/test/upmixer_a_test/Ls.wav", SFM_WRITE, &info); + SNDFILE* Rs = sf_open ("build/test/upmixer_a_test/Rs.wav", SFM_WRITE, &info); + + shared_ptr<Player> player = film->make_player (); + for (DCPTime t; t < film->length(); t += DCPTime::from_seconds (1)) { + shared_ptr<AudioBuffers> b = player->get_audio (t, DCPTime::from_seconds (1), true); + sf_write_float (L, b->data(0), b->frames()); + sf_write_float (R, b->data(1), b->frames()); + sf_write_float (C, b->data(2), b->frames()); + sf_write_float (Lfe, b->data(3), b->frames()); + sf_write_float (Ls, b->data(4), b->frames()); + sf_write_float (Rs, b->data(5), b->frames()); + } + + sf_close (L); + sf_close (R); + sf_close (C); + sf_close (Lfe); + sf_close (Ls); + sf_close (Rs); + + check_audio_file ("test/data/upmixer_a_test/L.wav", "build/test/upmixer_a_test/L.wav"); + check_audio_file ("test/data/upmixer_a_test/R.wav", "build/test/upmixer_a_test/R.wav"); + check_audio_file ("test/data/upmixer_a_test/C.wav", "build/test/upmixer_a_test/C.wav"); + check_audio_file ("test/data/upmixer_a_test/Lfe.wav", "build/test/upmixer_a_test/Lfe.wav"); + check_audio_file ("test/data/upmixer_a_test/Ls.wav", "build/test/upmixer_a_test/Ls.wav"); + check_audio_file ("test/data/upmixer_a_test/Rs.wav", "build/test/upmixer_a_test/Rs.wav"); +} diff --git a/test/util_test.cc b/test/util_test.cc index 40a2835f1..f5bf94c01 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); @@ -67,6 +89,12 @@ BOOST_AUTO_TEST_CASE (divide_with_round_test) BOOST_CHECK_EQUAL (divide_with_round (1000, 500), 2); } +BOOST_AUTO_TEST_CASE (timecode_test) +{ + DCPTime t = DCPTime::from_seconds (2 * 60 * 60 + 4 * 60 + 31) + DCPTime::from_frames (19, 24); + BOOST_CHECK_EQUAL (t.timecode (24), "02:04:31:19"); +} + BOOST_AUTO_TEST_CASE (seconds_to_approximate_hms_test) { BOOST_CHECK_EQUAL (seconds_to_approximate_hms (1), "1 second"); diff --git a/test/wscript b/test/wscript index 09df14673..493836a7a 100644 --- a/test/wscript +++ b/test/wscript @@ -10,42 +10,66 @@ 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.uselib = 'BOOST_TEST BOOST_THREAD DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML MAGICK' + obj.use = 'libdcpomatic2' obj.source = """ 4k_test.cc audio_analysis_test.cc + audio_buffers_test.cc audio_delay_test.cc + audio_decoder_test.cc + audio_filter_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 + dcp_subtitle_test.cc ffmpeg_audio_test.cc ffmpeg_dcp_test.cc + ffmpeg_decoder_seek_test.cc + ffmpeg_decoder_sequential_test.cc ffmpeg_examiner_test.cc - ffmpeg_pts_offset.cc + ffmpeg_pts_offset_test.cc file_group_test.cc film_metadata_test.cc frame_rate_test.cc image_test.cc + import_dcp_test.cc 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 + upmixer_a_test.cc util_test.cc + xml_subtitle_test.cc """ obj.target = 'unit-tests' obj.install_path = '' + + obj = bld(features='cxx cxxprogram') + obj.name = 'long-unit-tests' + obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML' + obj.use = 'libdcpomatic2' + obj.source = """ + test.cc + """ + + obj.target = 'long-unit-tests' + obj.install_path = '' diff --git a/test/xml_subtitle_test.cc b/test/xml_subtitle_test.cc new file mode 100644 index 000000000..561b1021f --- /dev/null +++ b/test/xml_subtitle_test.cc @@ -0,0 +1,50 @@ +/* + 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 creation of XML DCP subtitles. + */ + +#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 (xml_subtitle_test) +{ + shared_ptr<Film> film = new_test_film ("xml_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_burn_subtitles (false); + shared_ptr<SubRipContent> content (new SubRipContent (film, "test/data/subrip2.srt")); + content->set_use_subtitles (true); + film->examine_and_add_content (content); + wait_for_jobs (); + film->make_dcp (); + wait_for_jobs (); + + check_dcp ("test/data/xml_subtitle_test", film->dir (film->dcp_name ())); +} @@ -3,7 +3,7 @@ import os import sys APPNAME = 'dcpomatic' -VERSION = '1.74.2devel' +VERSION = '2.0.14devel' def options(opt): opt.load('compiler_cxx') @@ -57,10 +57,15 @@ def dynamic_openjpeg(conf): conf.check_cfg(package='libopenjpeg', args='--cflags --libs', atleast_version='1.5.0', uselib_store='OPENJPEG', mandatory=True) conf.check_cfg(package='libopenjpeg', args='--cflags --libs', max_version='1.5.2', mandatory=True) +def static_sub(conf): + conf.check_cfg(package='libsub', atleast_version='0.01.0', args='--cflags', uselib_store='SUB', mandatory=True) + conf.env.DEFINES_SUB = [f.replace('\\', '') for f in conf.env.DEFINES_SUB] + conf.env.STLIB_SUB = ['sub'] + def static_dcp(conf, static_boost, static_xmlpp, static_xmlsec, static_ssh): - conf.check_cfg(package='libdcp', atleast_version='0.96', args='--cflags', uselib_store='DCP', mandatory=True) + conf.check_cfg(package='libdcp-1.0', atleast_version='0.96', 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: @@ -84,9 +89,13 @@ 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.97.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_sub(conf): + conf.check_cfg(package='libsub', atleast_version='0.01.0', args='--cflags --libs', uselib_store='SUB', mandatory=True) + conf.env.DEFINES_SUB = [f.replace('\\', '') for f in conf.env.DEFINES_SUB] + def dynamic_ssh(conf): conf.check_cc(fragment=""" #include <libssh/libssh.h>\n @@ -223,7 +232,7 @@ def configure(conf): if conf.env.TARGET_LINUX or conf.env.TARGET_OSX: conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_POSIX') conf.env.append_value('CXXFLAGS', '-DPOSIX_LOCALE_PREFIX="%s/share/locale"' % conf.env['INSTALL_PREFIX']) - conf.env.append_value('CXXFLAGS', '-DPOSIX_ICON_PREFIX="%s/share/dcpomatic"' % conf.env['INSTALL_PREFIX']) + conf.env.append_value('CXXFLAGS', '-DPOSIX_ICON_PREFIX="%s/share/dcpomatic2"' % conf.env['INSTALL_PREFIX']) boost_lib_suffix = '' boost_thread = 'boost_thread' conf.env.append_value('LINKFLAGS', '-pthread') @@ -236,6 +245,10 @@ def configure(conf): if conf.env.TARGET_DEBIAN: # libxml2 seems to be linked against this on Ubuntu but it doesn't mention it in its .pc file conf.check_cfg(package='liblzma', args='--cflags --libs', uselib_store='LZMA', mandatory=True) + + if conf.env.TARGET_CENTOS_6 or conf.env.TARGET_CENTOS_7: + # libavcodec seems to be linked against this on Centos + conf.check_cfg(package='liblzma', args='--cflags --libs', uselib_store='LZMA', mandatory=True) if not conf.env.DISABLE_GUI and conf.env.TARGET_LINUX: conf.check_cfg(package='gtk+-2.0', args='--cflags --libs', uselib_store='GTK', mandatory=True) @@ -258,6 +271,7 @@ def configure(conf): conf.env.STLIB_QUICKMAIL = ['quickmail'] static_ffmpeg(conf) static_openjpeg(conf) + static_sub(conf) static_dcp(conf, False, False, False, False) dynamic_boost(conf, boost_lib_suffix, boost_thread) @@ -272,6 +286,7 @@ def configure(conf): conf.env.LIB_QUICKMAIL = ['ssh2', 'idn'] static_ffmpeg(conf) static_openjpeg(conf) + static_sub(conf) static_dcp(conf, True, True, True, True) static_boost(conf, boost_lib_suffix) @@ -286,6 +301,7 @@ def configure(conf): conf.env.LIB_XMLSEC = ['ltdl'] static_ffmpeg(conf) static_openjpeg(conf) + static_sub(conf) static_dcp(conf, False, True, True, True) dynamic_boost(conf, boost_lib_suffix, boost_thread) @@ -304,6 +320,7 @@ def configure(conf): dynamic_ffmpeg(conf) dynamic_openjpeg(conf) dynamic_dcp(conf) + dynamic_sub(conf) dynamic_ssh(conf) # Not packaging; just a straight build @@ -315,6 +332,7 @@ def configure(conf): dynamic_boost(conf, boost_lib_suffix, boost_thread) dynamic_ffmpeg(conf) dynamic_dcp(conf) + dynamic_sub(conf) dynamic_openjpeg(conf) dynamic_ssh(conf) @@ -323,6 +341,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> @@ -359,10 +379,10 @@ def build(bld): bld.recurse('platform/osx') for r in ['22x22', '32x32', '48x48', '64x64', '128x128']: - bld.install_files('${PREFIX}/share/icons/hicolor/%s/apps' % r, 'icons/%s/dcpomatic.png' % r) + bld.install_files('${PREFIX}/share/icons/hicolor/%s/apps' % r, 'icons/%s/dcpomatic2.png' % r) if not bld.env.TARGET_WINDOWS: - bld.install_files('${PREFIX}/share/dcpomatic', 'icons/taskbar_icon.png') + bld.install_files('${PREFIX}/share/dcpomatic2', 'icons/taskbar_icon.png') bld.add_post_fun(post) @@ -428,4 +448,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') |
