Merge master.
authorCarl Hetherington <cth@carlh.net>
Wed, 16 Jul 2014 10:14:54 +0000 (11:14 +0100)
committerCarl Hetherington <cth@carlh.net>
Wed, 16 Jul 2014 10:14:54 +0000 (11:14 +0100)
275 files changed:
ChangeLog
Doxyfile
cscript
doc/design/Attic/content.tex [new file with mode: 0644]
doc/design/content.tex [deleted file]
doc/design/resampling.tex
doc/mainpage.txt
platform/windows/wscript
src/lib/analyse_audio_job.cc
src/lib/analyse_audio_job.h
src/lib/audio_analysis.h
src/lib/audio_buffers.cc
src/lib/audio_buffers.h
src/lib/audio_content.cc
src/lib/audio_content.h
src/lib/audio_decoder.cc
src/lib/audio_decoder.h
src/lib/audio_examiner.h [new file with mode: 0644]
src/lib/audio_filter.cc [new file with mode: 0644]
src/lib/audio_filter.h [new file with mode: 0644]
src/lib/audio_mapping.cc
src/lib/audio_mapping.h
src/lib/audio_merger.h [deleted file]
src/lib/audio_processor.cc [new file with mode: 0644]
src/lib/audio_processor.h [new file with mode: 0644]
src/lib/channel_count.h [new file with mode: 0644]
src/lib/cinema.cc
src/lib/cinema.h
src/lib/cinema_sound_processor.cc [new file with mode: 0644]
src/lib/cinema_sound_processor.h [new file with mode: 0644]
src/lib/colour_conversion.cc
src/lib/config.cc
src/lib/config.h
src/lib/content.cc
src/lib/content.h
src/lib/content_audio.h [new file with mode: 0644]
src/lib/content_factory.cc
src/lib/content_factory.h
src/lib/content_subtitle.cc [new file with mode: 0644]
src/lib/content_subtitle.h [new file with mode: 0644]
src/lib/content_video.h [new file with mode: 0644]
src/lib/cross.cc
src/lib/cross.h
src/lib/dcp_content.cc [new file with mode: 0644]
src/lib/dcp_content.h [new file with mode: 0644]
src/lib/dcp_content_type.cc
src/lib/dcp_content_type.h
src/lib/dcp_decoder.cc [new file with mode: 0644]
src/lib/dcp_decoder.h [new file with mode: 0644]
src/lib/dcp_examiner.cc [new file with mode: 0644]
src/lib/dcp_examiner.h [new file with mode: 0644]
src/lib/dcp_subtitle_content.cc [new file with mode: 0644]
src/lib/dcp_subtitle_content.h [new file with mode: 0644]
src/lib/dcp_subtitle_decoder.cc [new file with mode: 0644]
src/lib/dcp_subtitle_decoder.h [new file with mode: 0644]
src/lib/dcp_video.cc [new file with mode: 0644]
src/lib/dcp_video.h [new file with mode: 0644]
src/lib/dcp_video_frame.cc [deleted file]
src/lib/dcp_video_frame.h [deleted file]
src/lib/dcpomatic_time.cc [new file with mode: 0644]
src/lib/dcpomatic_time.h [new file with mode: 0644]
src/lib/decoder.cc [deleted file]
src/lib/decoder.h
src/lib/dolby_cp750.cc
src/lib/dolby_cp750.h
src/lib/encoded_data.cc [new file with mode: 0644]
src/lib/encoded_data.h [new file with mode: 0644]
src/lib/encoder.cc
src/lib/encoder.h
src/lib/exceptions.cc
src/lib/exceptions.h
src/lib/ffmpeg.cc
src/lib/ffmpeg.h
src/lib/ffmpeg_audio_stream.cc [new file with mode: 0644]
src/lib/ffmpeg_audio_stream.h [new file with mode: 0644]
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_decoder.h
src/lib/ffmpeg_examiner.cc
src/lib/ffmpeg_examiner.h
src/lib/ffmpeg_stream.cc [new file with mode: 0644]
src/lib/ffmpeg_stream.h [new file with mode: 0644]
src/lib/ffmpeg_subtitle_stream.cc [new file with mode: 0644]
src/lib/ffmpeg_subtitle_stream.h [new file with mode: 0644]
src/lib/file_group.cc
src/lib/file_group.h
src/lib/film.cc
src/lib/film.h
src/lib/filter_graph.cc
src/lib/filter_graph.h
src/lib/image.cc
src/lib/image.h
src/lib/image_content.cc
src/lib/image_content.h
src/lib/image_decoder.cc
src/lib/image_decoder.h
src/lib/image_examiner.cc
src/lib/image_examiner.h
src/lib/image_proxy.cc
src/lib/image_proxy.h
src/lib/image_subtitle.h [new file with mode: 0644]
src/lib/isdcf_metadata.cc
src/lib/isdcf_metadata.h
src/lib/j2k_image_proxy.cc [new file with mode: 0644]
src/lib/j2k_image_proxy.h [new file with mode: 0644]
src/lib/job.cc
src/lib/kdm.cc
src/lib/kdm.h
src/lib/magick_image_proxy.cc [new file with mode: 0644]
src/lib/magick_image_proxy.h [new file with mode: 0644]
src/lib/mid_side_decoder.cc [new file with mode: 0644]
src/lib/mid_side_decoder.h [new file with mode: 0644]
src/lib/piece.cc [deleted file]
src/lib/piece.h
src/lib/player.cc
src/lib/player.h
src/lib/player_subtitles.h [new file with mode: 0644]
src/lib/player_video.cc [new file with mode: 0644]
src/lib/player_video.h [new file with mode: 0644]
src/lib/player_video_frame.cc [deleted file]
src/lib/player_video_frame.h [deleted file]
src/lib/playlist.cc
src/lib/playlist.h
src/lib/position.h
src/lib/position_image.h [new file with mode: 0644]
src/lib/ratio.cc
src/lib/ratio.h
src/lib/raw_image_proxy.cc [new file with mode: 0644]
src/lib/raw_image_proxy.h [new file with mode: 0644]
src/lib/rect.h
src/lib/render_subtitles.cc [new file with mode: 0644]
src/lib/render_subtitles.h [new file with mode: 0644]
src/lib/resampler.cc
src/lib/resampler.h
src/lib/send_kdm_email_job.cc
src/lib/send_kdm_email_job.h
src/lib/server.cc
src/lib/server.h
src/lib/server_finder.cc
src/lib/single_stream_audio_content.cc [new file with mode: 0644]
src/lib/single_stream_audio_content.h [new file with mode: 0644]
src/lib/sndfile_content.cc
src/lib/sndfile_content.h
src/lib/sndfile_decoder.cc
src/lib/sndfile_decoder.h
src/lib/sound_processor.cc [deleted file]
src/lib/sound_processor.h [deleted file]
src/lib/subrip.cc [new file with mode: 0644]
src/lib/subrip.h [new file with mode: 0644]
src/lib/subrip_content.cc [new file with mode: 0644]
src/lib/subrip_content.h [new file with mode: 0644]
src/lib/subrip_decoder.cc [new file with mode: 0644]
src/lib/subrip_decoder.h [new file with mode: 0644]
src/lib/subrip_subtitle.h [new file with mode: 0644]
src/lib/subtitle.cc [deleted file]
src/lib/subtitle.h [deleted file]
src/lib/subtitle_content.cc
src/lib/subtitle_content.h
src/lib/subtitle_decoder.cc
src/lib/subtitle_decoder.h
src/lib/transcode_job.cc
src/lib/transcoder.cc
src/lib/transcoder.h
src/lib/types.cc
src/lib/types.h
src/lib/update.cc
src/lib/update.h
src/lib/upmixer_a.cc [new file with mode: 0644]
src/lib/upmixer_a.h [new file with mode: 0644]
src/lib/util.cc
src/lib/util.h
src/lib/video_content.cc
src/lib/video_content.h
src/lib/video_decoder.cc
src/lib/video_decoder.h
src/lib/video_examiner.h
src/lib/writer.cc
src/lib/writer.h
src/lib/wscript
src/tools/dcpomatic.cc
src/tools/dcpomatic_cli.cc
src/tools/dcpomatic_create.cc
src/tools/dcpomatic_kdm.cc
src/tools/dcpomatic_server_cli.cc
src/tools/server_test.cc
src/wx/about_dialog.cc
src/wx/about_dialog.h
src/wx/audio_mapping_view.cc
src/wx/audio_mapping_view.h
src/wx/audio_panel.cc
src/wx/audio_panel.h
src/wx/config_dialog.cc
src/wx/content_menu.h
src/wx/content_panel.cc [new file with mode: 0644]
src/wx/content_panel.h [new file with mode: 0644]
src/wx/content_sub_panel.cc [new file with mode: 0644]
src/wx/content_sub_panel.h [new file with mode: 0644]
src/wx/content_widget.h
src/wx/film_editor.cc
src/wx/film_editor.h
src/wx/film_editor_panel.cc [deleted file]
src/wx/film_editor_panel.h [deleted file]
src/wx/film_viewer.cc
src/wx/film_viewer.h
src/wx/kdm_dialog.cc
src/wx/kdm_dialog.h
src/wx/properties_dialog.cc
src/wx/screen_dialog.cc
src/wx/screen_dialog.h
src/wx/subtitle_panel.cc
src/wx/subtitle_panel.h
src/wx/subtitle_view.cc [new file with mode: 0644]
src/wx/subtitle_view.h [new file with mode: 0644]
src/wx/timecode.cc
src/wx/timecode.h
src/wx/timeline.cc
src/wx/timeline.h
src/wx/timeline_dialog.cc
src/wx/timeline_dialog.h
src/wx/timing_panel.cc
src/wx/timing_panel.h
src/wx/video_panel.cc
src/wx/video_panel.h
src/wx/wscript
src/wx/wx_ui_signaller.cc
src/wx/wx_ui_signaller.h
src/wx/wx_util.cc
test/4k_test.cc
test/README [new file with mode: 0644]
test/audio_analysis_test.cc
test/audio_buffers_test.cc [new file with mode: 0644]
test/audio_decoder_test.cc [new file with mode: 0644]
test/audio_delay_test.cc
test/audio_filter_test.cc [new file with mode: 0644]
test/audio_mapping_test.cc
test/audio_merger_test.cc [deleted file]
test/black_fill_test.cc
test/burnt_subtitle_test.cc [new file with mode: 0644]
test/client_server_test.cc
test/colour_conversion_test.cc
test/dcp_subtitle_test.cc [new file with mode: 0644]
test/ffmpeg_audio_test.cc
test/ffmpeg_dcp_test.cc
test/ffmpeg_decoder_seek_test.cc [new file with mode: 0644]
test/ffmpeg_decoder_sequential_test.cc [new file with mode: 0644]
test/ffmpeg_examiner_test.cc
test/ffmpeg_pts_offset.cc [deleted file]
test/ffmpeg_pts_offset_test.cc [new file with mode: 0644]
test/file_group_test.cc
test/film_metadata_test.cc
test/frame_rate_test.cc
test/image_test.cc
test/job_test.cc
test/make_black_test.cc
test/pixel_formats_test.cc
test/player_test.cc [new file with mode: 0644]
test/ratio_test.cc
test/recover_test.cc
test/repeat_frame_test.cc [new file with mode: 0644]
test/resampler_test.cc
test/scaling_test.cc
test/seek_zero_test.cc [new file with mode: 0644]
test/silence_padding_test.cc
test/skip_frame_test.cc [new file with mode: 0644]
test/stream_test.cc
test/subrip_test.cc [new file with mode: 0644]
test/test.cc
test/test.h
test/threed_test.cc
test/upmixer_a_test.cc [new file with mode: 0644]
test/util_test.cc
test/wscript
test/xml_subtitle_test.cc [new file with mode: 0644]
wscript

index f96ba701ed400b91ee2f99cff48c5bddcd0ab518..68383d39dff7be7c3031e88633b3da2155a5b74e 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+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.
+
 2014-07-16  Carl Hetherington  <cth@carlh.net>
 
        * Updates to de_DE from Carsten Kurz.
@@ -15,6 +29,7 @@
 2014-07-10  Carl Hetherington  <cth@carlh.net>
 
        * Version 1.72.2 released.
+>>>>>>> origin/master
 
 2014-07-10  Carl Hetherington  <cth@carlh.net>
 
index 4ed65e4f14c5bb940b5eb4bfa894339dcbab1908..a49c8451fc0875cc7e88b48cc5e46e90be0a5092 100644 (file)
--- a/Doxyfile
+++ b/Doxyfile
-# 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
-# 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
diff --git a/cscript b/cscript
index 5ea5a4cfd22e6cccccedb6ef923d7841d6754473..2ac5fea9128a267aa7b6c2d22bc8e58eaec1cf25 100644 (file)
--- a/cscript
+++ b/cscript
@@ -157,7 +157,7 @@ def make_control(debian_version, bits, filename, debug):
 
 def dependencies(target):
     return (('ffmpeg-cdist', '67dc770'),
-            ('libdcp', '009e07f'))
+            ('libdcp', '1.0'))
 
 def build(target, options):
     cmd = './waf configure --prefix=%s' % target.work_dir_cscript()
diff --git a/doc/design/Attic/content.tex b/doc/design/Attic/content.tex
new file mode 100644 (file)
index 0000000..0f5f170
--- /dev/null
@@ -0,0 +1,195 @@
+\documentclass{article}
+\begin{document}
+
+\section{Status quo}
+
+As at 0.78 there is an unfortunate mish-mash of code to handle the
+input `content' the goes into a DCP.
+
+The Film has a `content' file name.  This is guessed to be either a
+movie (for FFmpeg) or a still-image (for ImageMagick) based on its
+extension.  We also have `external audio', which is a set of WAV files
+for libsndfile, and a flag to enable that.
+
+The `content' file is badly named and limiting.  We can't have
+multiple content files, and it's not really the `content' as such (it
+used to be, but increasingly it's only a part of the content, on equal
+footing with `external' audio).
+
+The choice of sources for sound is expressed clumsily by the
+AudioStream class hierarchy.
+
+
+\section{Targets}
+
+We want to be able to implement the following:
+
+\begin{itemize}
+\item Immediately:
+\begin{itemize}
+\item Multiple still images, each with their own duration, made into a `slide-show'
+\item Lack of bugs in adding WAV-file audio to still images.
+\item External subtitle files (either XML or SRT) to be converted to XML subtitles in the DCP.
+\end{itemize}
+
+\item In the future:
+\begin{itemize}
+\item Playlist-style multiple video / audio (perhaps).
+\end{itemize}
+\end{itemize}
+
+
+\section{Content hierarchy}
+
+One idea is to have a hierarchy of Content classes (\texttt{Content},
+\texttt{\{Video/Audio\}Content}, \texttt{FFmpegContent}, \texttt{ImageMagickContent},
+\texttt{SndfileContent}).
+
+Then the Film has a list of these, and decides what to put into the
+DCP based on some rules.  These rules would probably be fixed (for
+now), with the possibility to expand later into some kind of playlist.
+
+
+\section{Immediate questions}
+
+\subsection{What Film attributes are video-content specific, and which are general?}
+
+Questionable attributes:
+
+\begin{itemize}
+\item Trust content header
+\item Crop
+\item Filters
+
+Post-processing (held as part of the filters description) is done in
+the encoder, by which time all knowledge of the source is lost.
+
+\item Scaler
+\item Trim start/end
+
+Messily tied in with the encoding side.  We want to implement this
+using start/end point specifications in the DCP reel, otherwise
+modifying the trim points requires a complete re-encode.
+
+\item Audio gain
+\item Audio delay
+\item With subtitles
+\item Subtitle offset/scale
+\item Colour LUT
+\end{itemize}
+
+Attributes that I think must remain in Film:
+\begin{itemize}
+\item DCP content type
+\item Format
+\item A/B
+\item J2K bandwidth
+\end{itemize}
+
+Part of the consideration here is that per-content attributes need to
+be represented in the GUI differently to how things are represented
+now.
+
+Bear in mind also that, as it stands, the only options for video are:
+
+\begin{enumerate}
+\item An FFmpeg video
+\item A set of stills
+\end{enumerate}
+
+and so the need for multiple scalers, crop and filters is
+questionable.  Also, there is one set of audio (either from WAVs or
+from the FFMpeg file), so per-content audio gain/delay is also
+questionable.  Trust content header is only applicable for FFmpeg
+content, really.  Similarly trim, with-subtitles, subtitle details,
+colour LUT; basically none of it is really important right now.
+
+Hence it may be sensible to keep everything in Film and move it later
+along YAGNI lines.
+
+
+\subsection{Who answers questions like: ``what is the length of video?''?}
+
+If we have FFmpeg video, the question is easy to answer.  For a set of
+stills, it is less easy.  Who knows that we are sticking them all
+together end-to-end, with different durations for each?
+
+If we have one-content-object equalling one file, the content objects
+will presumably know how long their file should be displayed for.
+There would appear to be two options following this:
+
+\begin{enumerate}
+\item There is one \texttt{ImageMagickDecoder} which is fed all the
+  files, and outputs them in order.  The magic knowledge is then
+  within this class, really.
+\item There are multiple \texttt{ImageMagickDecoder} classes, one per
+  \texttt{..Content}, and some controlling (`playlist') class to manage
+  them.  The `playlist' is then itself a
+  \texttt{\{Video/Audio\}Source}, and has the magic knowledge.
+\end{enumerate}
+
+
+\section{Playlist approach}
+
+Let's try the playlist approach.  We define a hierarchy of content classes:
+
+\begin{verbatim}
+
+class Content
+{
+public:
+  boost::filesystem::path file () const;
+};
+
+class VideoContent : virtual public Content
+{
+public:
+  VideoContentFrame video_length () const;
+  float video_frame_rate () const;
+  libdcp::Size size () const;
+
+};
+
+class AudioContent : virtual public Content
+{
+
+};
+
+class FFmpegContent : public VideoContent, public AudioContent
+{
+public:
+  .. stream stuff ..
+};
+
+class ImageMagickContent : public VideoContent
+{
+
+};
+
+class SndfileContent : public AudioContent
+{
+public:
+  .. channel allocation for this file ..
+};
+\end{verbatim}
+
+Then Film has a \texttt{Playlist} which has a
+\texttt{vector<shared\_ptr<Content> >}.  It can answer questions
+about audio/video length, frame rate, audio channels and so on.
+
+\texttt{Playlist} can also be a source of video and audio, so clients can do:
+
+\begin{verbatim}
+shared_ptr<Playlist> p = film->playlist ();
+p->Video.connect (foo);
+p->Audio.connect (foo);
+while (!p->pass ()) {
+  /* carry on */
+}
+\end{verbatim}
+
+Playlist could be created on-demand for all the difference it would
+make.  And perhaps it should, since it will hold Decoders which are
+probably run-once.
+
+\end{document}
diff --git a/doc/design/content.tex b/doc/design/content.tex
deleted file mode 100644 (file)
index 0f5f170..0000000
+++ /dev/null
@@ -1,195 +0,0 @@
-\documentclass{article}
-\begin{document}
-
-\section{Status quo}
-
-As at 0.78 there is an unfortunate mish-mash of code to handle the
-input `content' the goes into a DCP.
-
-The Film has a `content' file name.  This is guessed to be either a
-movie (for FFmpeg) or a still-image (for ImageMagick) based on its
-extension.  We also have `external audio', which is a set of WAV files
-for libsndfile, and a flag to enable that.
-
-The `content' file is badly named and limiting.  We can't have
-multiple content files, and it's not really the `content' as such (it
-used to be, but increasingly it's only a part of the content, on equal
-footing with `external' audio).
-
-The choice of sources for sound is expressed clumsily by the
-AudioStream class hierarchy.
-
-
-\section{Targets}
-
-We want to be able to implement the following:
-
-\begin{itemize}
-\item Immediately:
-\begin{itemize}
-\item Multiple still images, each with their own duration, made into a `slide-show'
-\item Lack of bugs in adding WAV-file audio to still images.
-\item External subtitle files (either XML or SRT) to be converted to XML subtitles in the DCP.
-\end{itemize}
-
-\item In the future:
-\begin{itemize}
-\item Playlist-style multiple video / audio (perhaps).
-\end{itemize}
-\end{itemize}
-
-
-\section{Content hierarchy}
-
-One idea is to have a hierarchy of Content classes (\texttt{Content},
-\texttt{\{Video/Audio\}Content}, \texttt{FFmpegContent}, \texttt{ImageMagickContent},
-\texttt{SndfileContent}).
-
-Then the Film has a list of these, and decides what to put into the
-DCP based on some rules.  These rules would probably be fixed (for
-now), with the possibility to expand later into some kind of playlist.
-
-
-\section{Immediate questions}
-
-\subsection{What Film attributes are video-content specific, and which are general?}
-
-Questionable attributes:
-
-\begin{itemize}
-\item Trust content header
-\item Crop
-\item Filters
-
-Post-processing (held as part of the filters description) is done in
-the encoder, by which time all knowledge of the source is lost.
-
-\item Scaler
-\item Trim start/end
-
-Messily tied in with the encoding side.  We want to implement this
-using start/end point specifications in the DCP reel, otherwise
-modifying the trim points requires a complete re-encode.
-
-\item Audio gain
-\item Audio delay
-\item With subtitles
-\item Subtitle offset/scale
-\item Colour LUT
-\end{itemize}
-
-Attributes that I think must remain in Film:
-\begin{itemize}
-\item DCP content type
-\item Format
-\item A/B
-\item J2K bandwidth
-\end{itemize}
-
-Part of the consideration here is that per-content attributes need to
-be represented in the GUI differently to how things are represented
-now.
-
-Bear in mind also that, as it stands, the only options for video are:
-
-\begin{enumerate}
-\item An FFmpeg video
-\item A set of stills
-\end{enumerate}
-
-and so the need for multiple scalers, crop and filters is
-questionable.  Also, there is one set of audio (either from WAVs or
-from the FFMpeg file), so per-content audio gain/delay is also
-questionable.  Trust content header is only applicable for FFmpeg
-content, really.  Similarly trim, with-subtitles, subtitle details,
-colour LUT; basically none of it is really important right now.
-
-Hence it may be sensible to keep everything in Film and move it later
-along YAGNI lines.
-
-
-\subsection{Who answers questions like: ``what is the length of video?''?}
-
-If we have FFmpeg video, the question is easy to answer.  For a set of
-stills, it is less easy.  Who knows that we are sticking them all
-together end-to-end, with different durations for each?
-
-If we have one-content-object equalling one file, the content objects
-will presumably know how long their file should be displayed for.
-There would appear to be two options following this:
-
-\begin{enumerate}
-\item There is one \texttt{ImageMagickDecoder} which is fed all the
-  files, and outputs them in order.  The magic knowledge is then
-  within this class, really.
-\item There are multiple \texttt{ImageMagickDecoder} classes, one per
-  \texttt{..Content}, and some controlling (`playlist') class to manage
-  them.  The `playlist' is then itself a
-  \texttt{\{Video/Audio\}Source}, and has the magic knowledge.
-\end{enumerate}
-
-
-\section{Playlist approach}
-
-Let's try the playlist approach.  We define a hierarchy of content classes:
-
-\begin{verbatim}
-
-class Content
-{
-public:
-  boost::filesystem::path file () const;
-};
-
-class VideoContent : virtual public Content
-{
-public:
-  VideoContentFrame video_length () const;
-  float video_frame_rate () const;
-  libdcp::Size size () const;
-
-};
-
-class AudioContent : virtual public Content
-{
-
-};
-
-class FFmpegContent : public VideoContent, public AudioContent
-{
-public:
-  .. stream stuff ..
-};
-
-class ImageMagickContent : public VideoContent
-{
-
-};
-
-class SndfileContent : public AudioContent
-{
-public:
-  .. channel allocation for this file ..
-};
-\end{verbatim}
-
-Then Film has a \texttt{Playlist} which has a
-\texttt{vector<shared\_ptr<Content> >}.  It can answer questions
-about audio/video length, frame rate, audio channels and so on.
-
-\texttt{Playlist} can also be a source of video and audio, so clients can do:
-
-\begin{verbatim}
-shared_ptr<Playlist> p = film->playlist ();
-p->Video.connect (foo);
-p->Audio.connect (foo);
-while (!p->pass ()) {
-  /* carry on */
-}
-\end{verbatim}
-
-Playlist could be created on-demand for all the difference it would
-make.  And perhaps it should, since it will hold Decoders which are
-probably run-once.
-
-\end{document}
index 44aeee9b1b4c8abcdea917a7aeeebe66620c7815..cf9cfb1edc220bcdfa6f242b4aeee94427c2a0d5 100644 (file)
@@ -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}
index 649c9c60913506e43cb4c4084ce9dc4209edf453..1e371852b21ba69ee34f0c36e417792fc6a8b9d9 100644 (file)
@@ -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
  *  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/
  */
index 9ae88ba87a15bd65856e22fc8fd623dfc6b0cb1c..19c204f6264c6506095d62a18f93c0110f8e8276 100644 (file)
@@ -103,15 +103,15 @@ File "%static_deps%/bin/libcurl-4.dll"
 File "%static_deps%/bin/ssleay32.dll"
 File "%static_deps%/bin/libzip-2.dll"
 
-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-55.dll"
 File "%cdist_deps%/bin/avfilter-4.dll"
 File "%cdist_deps%/bin/avformat-55.dll"
 File "%cdist_deps%/bin/avutil-52.dll"
 File "%cdist_deps%/bin/avdevice-55.dll"
 File "%cdist_deps%/bin/postproc-52.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-0.dll"
 File "%cdist_deps%/bin/swscale-2.dll"
index bfe0ed61f281816a71e20b66f17c18e429d46ac9..af58e77ac59058097be12614c8c8499656ccff93 100644 (file)
@@ -18,6 +18,7 @@
 */
 
 #include "audio_analysis.h"
+#include "audio_buffers.h"
 #include "analyse_audio_job.h"
 #include "compose.hpp"
 #include "film.h"
@@ -65,19 +66,18 @@ AnalyseAudioJob::run ()
        shared_ptr<Playlist> playlist (new Playlist);
        playlist->add (content);
        shared_ptr<Player> player (new Player (_film, playlist));
-       player->disable_video ();
        
-       player->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1, _2));
-
-       _samples_per_point = max (int64_t (1), _film->time_to_audio_frames (_film->length()) / _num_points);
+       int64_t const len = _film->length().frames (_film->audio_frame_rate());
+       _samples_per_point = max (int64_t (1), len / _num_points);
 
        _current.resize (_film->audio_channels ());
        _analysis.reset (new AudioAnalysis (_film->audio_channels ()));
 
        _done = 0;
-       OutputAudioFrame const len = _film->time_to_audio_frames (_film->length ());
-       while (!player->pass ()) {
-               set_progress (double (_done) / len);
+       DCPTime const block = DCPTime::from_seconds (1.0 / 8);
+       for (DCPTime t; t < _film->length(); t += block) {
+               analyse (player->get_audio (t, block, false));
+               set_progress (t.seconds() / _film->length().seconds());
        }
 
        _analysis->write (content->audio_analysis_path ());
@@ -87,7 +87,7 @@ AnalyseAudioJob::run ()
 }
 
 void
-AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, Time)
+AnalyseAudioJob::analyse (shared_ptr<const AudioBuffers> b)
 {
        for (int i = 0; i < b->frames(); ++i) {
                for (int j = 0; j < b->channels(); ++j) {
index 3e376634cd0d9be375fc825ae60c6285db16588a..4d657951b7123d57d21438c50ee273f9c807d178 100644 (file)
@@ -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
 
 */
 
+/** @file  src/lib/analyse_audio_job.h
+ *  @brief AnalyseAudioJob class.
+ */
+
 #include "job.h"
 #include "audio_analysis.h"
 #include "types.h"
+#include "dcpomatic_time.h"
 
 class AudioBuffers;
 class AudioContent;
 
+/** @class AnalyseAudioJob
+ *  @brief A job to analyse the audio of a piece of AudioContent and make a note of its
+ *  broad peak and RMS levels.
+ *
+ *  After computing the peak and RMS levels over the length of the content, the job
+ *  will write a file to Content::audio_analysis_path.
+ */
 class AnalyseAudioJob : public Job
 {
 public:
@@ -34,10 +46,10 @@ public:
        void run ();
 
 private:
-       void audio (boost::shared_ptr<const AudioBuffers>, Time);
+       void analyse (boost::shared_ptr<const AudioBuffers>);
 
        boost::weak_ptr<AudioContent> _content;
-       OutputAudioFrame _done;
+       int64_t _done;
        int64_t _samples_per_point;
        std::vector<AudioPoint> _current;
 
index 824472dda08c80daf1037a0f5635b5db7eefa556..b91a1cf5123d3c43c3a1b40418e279ff273b4630 100644 (file)
@@ -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
 
 */
 
+/** @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:
index a1c9b81ac099259bbe86e2f9f8dcea1340bbb9ae..56ca7a94b18c5860e3800f03740baa2c09480d59 100644 (file)
@@ -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;
+}
index c9030dbfa9e84af247dd760f276c3ee33eaa5969..8cd67aaa729d52afc42a8ff5b11f3d42327de414 100644 (file)
@@ -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
 
 */
 
-#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:
index 29d159a29a9603f330eb6100ed6c42743a33c630..d02728b00018f5ed6d8bc023698b6fe4a94c7536 100644 (file)
@@ -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 ());
+}
+
index 2c324a3a4966e0dad899c10f24f3f99115317420..540839d695241be94e769bb6e42218b4128e3d50 100644 (file)
@@ -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
 
 */
 
+/** @file  src/lib/audio_content.h
+ *  @brief AudioContent and AudioContentProperty classes.
+ */
+
 #ifndef DCPOMATIC_AUDIO_CONTENT_H
 #define DCPOMATIC_AUDIO_CONTENT_H
 
@@ -27,6 +31,8 @@ namespace cxml {
        class Node;
 }
 
+class AudioProcessor;
+
 class AudioContentProperty
 {
 public:
@@ -36,34 +42,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 +91,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
index c0ef02f65d5ab5518dcb7e53aa145b1ff3f9a598..e4f98c678ad3a150987431f7f587428a07603b10 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -22,6 +22,9 @@
 #include "exceptions.h"
 #include "log.h"
 #include "resampler.h"
+#include "util.h"
+#include "film.h"
+#include "audio_processor.h"
 
 #include "i18n.h"
 
@@ -29,30 +32,190 @@ using std::stringstream;
 using std::list;
 using std::pair;
 using std::cout;
+using std::min;
+using std::max;
 using boost::optional;
 using boost::shared_ptr;
 
-AudioDecoder::AudioDecoder (shared_ptr<const Film> film, shared_ptr<const AudioContent> content)
-       : Decoder (film)
-       , _audio_content (content)
-       , _audio_position (0)
+AudioDecoder::AudioDecoder (shared_ptr<const AudioContent> content)
+       : _audio_content (content)
 {
+       if (content->resampled_audio_frame_rate() != content->audio_frame_rate() && content->audio_channels ()) {
+               _resampler.reset (new Resampler (content->audio_frame_rate(), content->resampled_audio_frame_rate(), content->audio_channels ()));
+       }
 
+       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 (!pass() && (_decoded_audio.frame > frame || (_decoded_audio.frame + _decoded_audio.audio->frames()) < end)) {}
+               decoded_offset = frame - _decoded_audio.frame;
+       } else {
+               while (!pass() && _decoded_audio.audio->frames() < length) {}
+               /* Use decoded_offset of 0, as we don't really care what frames we return */
+       }
+
+       /* The amount of data available in _decoded_audio.audio starting from `frame'.  This could be -ve
+          if pass() returned true before we got enough data.
+       */
+       AudioFrame const available = _decoded_audio.audio->frames() - decoded_offset;
+
+       /* We will return either that, or the requested amount, whichever is smaller */
+       AudioFrame const to_return = max ((AudioFrame) 0, min (available, length));
+
+       /* Copy our data to the output */
+       shared_ptr<AudioBuffers> out (new AudioBuffers (_decoded_audio.audio->channels(), to_return));
+       out->copy_from (_decoded_audio.audio.get(), to_return, decoded_offset, 0);
+
+       AudioFrame const remaining = max ((AudioFrame) 0, available - to_return);
+
+       /* Clean up decoded; first, move the data after what we just returned to the start of the buffer */
+       _decoded_audio.audio->move (decoded_offset + to_return, 0, remaining);
+       /* And set up the number of frames we have left */
+       _decoded_audio.audio->set_frames (remaining);
+       /* Also bump where those frames are in terms of the content */
+       _decoded_audio.frame += decoded_offset + to_return;
+
+       return shared_ptr<ContentAudio> (new ContentAudio (out, frame));
+}
+
+/** Called by subclasses when audio data is ready.
+ *
+ *  Audio timestamping is made hard by many factors, but perhaps the most entertaining is resampling.
+ *  We have to assume that we are feeding continuous data into the resampler, and so we get continuous
+ *  data out.  Hence we do the timestamping here, post-resampler, just by counting samples.
+ *
+ *  The time is passed in here so that after a seek we can set up our _audio_position.  The
+ *  time is ignored once this has been done.
  */
-bool
-AudioDecoder::has_audio () const
+void
+AudioDecoder::audio (shared_ptr<const AudioBuffers> data, ContentTime time)
+{
+       if (_resampler) {
+               data = _resampler->run (data);
+       }
+
+       if (_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()));
+
+       /* 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 ();
+}
+
+/* XXX: called? */
+void
+AudioDecoder::flush ()
+{
+       if (!_resampler) {
+               return;
+       }
+
+       /*
+       shared_ptr<const AudioBuffers> b = _resampler->flush ();
+       if (b) {
+               _pending.push_back (shared_ptr<DecodedAudio> (new DecodedAudio (b, _audio_position.get ())));
+               _audio_position = _audio_position.get() + b->frames ();
+       }
+       */
+}
+
+void
+AudioDecoder::seek (ContentTime t, bool accurate)
 {
-       return _audio_content->audio_channels () > 0;
+       _audio_position.reset ();
+       reset_decoded_audio ();
+       if (accurate) {
+               _seek_reference = t;
+       }
+       if (_processor) {
+               _processor->flush ();
+       }
 }
index ab6c4b8a931e12cfe892c8cb74d86ae7162d1a10..045efc00207758d01cb56fce7996d685f1a1b458 100644 (file)
@@ -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
 #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,37 @@ class AudioBuffers;
 class AudioDecoder : public virtual Decoder
 {
 public:
-       AudioDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const AudioContent>);
-
-       bool has_audio () const;
-
-       /** Emitted when some audio data is ready */
-       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio;
+       AudioDecoder (boost::shared_ptr<const AudioContent>);
+       
+       boost::shared_ptr<const AudioContent> audio_content () const {
+               return _audio_content;
+       }
 
+       /** Try to fetch some audio from a specific place in this content.
+        *  @param frame Frame to start from.
+        *  @param length Frames to get.
+        *  @param accurate true to try hard to return frames from exactly `frame', false if we don't mind nearby frames.
+        *  @return Time-stamped audio data which may or may not be from the location (and of the length) requested.
+        */
+       boost::shared_ptr<ContentAudio> get_audio (AudioFrame time, AudioFrame length, bool accurate);
+       
 protected:
 
-       void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+       void seek (ContentTime time, bool accurate);
+       void audio (boost::shared_ptr<const AudioBuffers>, ContentTime);
+       void flush ();
+       void reset_decoded_audio ();
+
        boost::shared_ptr<const AudioContent> _audio_content;
-       AudioContent::Frame _audio_position;
+       boost::shared_ptr<Resampler> _resampler;
+       boost::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 (file)
index 0000000..d6d4dbe
--- /dev/null
@@ -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 (file)
index 0000000..59b5684
--- /dev/null
@@ -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 (file)
index 0000000..f361c27
--- /dev/null
@@ -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;
+class 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 class audio_filter_impulse_kernel_test;
+       friend class 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);
+};
index e35c1ae9464cf67061426ddb065cfb8c9d162b7a..e86e2e2ac6820499cd551ddaed17ffe8e7f30019 100644 (file)
@@ -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))));
                }
        }
 }
index b0b75ac063372c8f258dc235a6675f44ad2b8e81..a76d83a370f703dceded47e1d5aff6e90cd472e3 100644 (file)
@@ -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
 
 */
 
+/** @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 (file)
index 226601e..0000000
+++ /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 (file)
index 0000000..f350cc2
--- /dev/null
@@ -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 (file)
index 0000000..9b332e7
--- /dev/null
@@ -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 (file)
index 0000000..4247fc0
--- /dev/null
@@ -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
index fca6b6afda36c80c653940eaaaa8819818327b4b..c8cb7b66cc935be866e8049b3a489971fd2712bc 100644 (file)
@@ -24,7 +24,7 @@
 using std::list;
 using boost::shared_ptr;
 
-Cinema::Cinema (shared_ptr<const cxml::Node> node)
+Cinema::Cinema (cxml::ConstNodePtr node)
        : name (node->string_child ("Name"))
        , email (node->string_child ("Email"))
 {
@@ -35,7 +35,7 @@ Cinema::Cinema (shared_ptr<const cxml::Node> node)
    a constructor)
 */
 void
-Cinema::read_screens (shared_ptr<const cxml::Node> node)
+Cinema::read_screens (cxml::ConstNodePtr node)
 {
        list<cxml::NodePtr> s = node->node_children ("Screen");
        for (list<cxml::NodePtr>::iterator i = s.begin(); i != s.end(); ++i) {
@@ -67,10 +67,10 @@ Cinema::remove_screen (shared_ptr<Screen> s)
        _screens.remove (s);
 }
 
-Screen::Screen (shared_ptr<const cxml::Node> node)
+Screen::Screen (cxml::ConstNodePtr node)
 {
        name = node->string_child ("Name");
-       certificate = shared_ptr<libdcp::Certificate> (new libdcp::Certificate (node->string_child ("Certificate")));
+       certificate = shared_ptr<dcp::Certificate> (new dcp::Certificate (node->string_child ("Certificate")));
 }
 
 void
index 40dc15ae02ebaf66decb9f837ce845b5d40dcc99..74357f65d687e0261ded537e94b3b4f95afcfdb2 100644 (file)
@@ -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
 
 */
 
+/** @file  src/lib/cinema.h
+ *  @brief Screen and Cinema classes.
+ */
+
 #include <boost/enable_shared_from_this.hpp>
-#include <libdcp/certificates.h>
+#include <dcp/certificates.h>
+#include <libcxml/cxml.h>
 
 class Cinema;
 
-namespace cxml {
-       class Node;
-}
-
+/** @class Screen
+ *  @brief A representation of a Screen for KDM generation.
+ *
+ *  This is the name of the screen and the certificate of its
+ *  server.
+ */
 class Screen
 {
 public:
-       Screen (std::string const & n, boost::shared_ptr<libdcp::Certificate> cert)
+       Screen (std::string const & n, boost::shared_ptr<dcp::Certificate> cert)
                : name (n)
                , certificate (cert)
        {}
 
-       Screen (boost::shared_ptr<const cxml::Node>);
+       Screen (cxml::ConstNodePtr);
 
        void as_xml (xmlpp::Element *) const;
        
        boost::shared_ptr<Cinema> cinema;
        std::string name;
-       boost::shared_ptr<libdcp::Certificate> certificate;
+       boost::shared_ptr<dcp::Certificate> certificate;
 };
 
+/** @class Cinema
+ *  @brief A description of a Cinema for KDM generation.
+ *
+ *  This is a cinema name, contact email address and a list of
+ *  Screen objects.
+ */
 class Cinema : public boost::enable_shared_from_this<Cinema>
 {
 public:
@@ -51,9 +64,9 @@ public:
                , email (e)
        {}
 
-       Cinema (boost::shared_ptr<const cxml::Node>);
+       Cinema (cxml::ConstNodePtr);
 
-       void read_screens (boost::shared_ptr<const cxml::Node>);
+       void read_screens (cxml::ConstNodePtr);
 
        void as_xml (xmlpp::Element *) const;
 
diff --git a/src/lib/cinema_sound_processor.cc b/src/lib/cinema_sound_processor.cc
new file mode 100644 (file)
index 0000000..6a79051
--- /dev/null
@@ -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.
+
+*/
+
+/** @file src/sound_processor.cc
+ *  @brief CinemaSoundProcessor class.
+ */
+
+#include <iostream>
+#include <cassert>
+#include "cinema_sound_processor.h"
+#include "dolby_cp750.h"
+
+using namespace std;
+
+vector<CinemaSoundProcessor const *> CinemaSoundProcessor::_cinema_sound_processors;
+
+/** @param i Our id.
+ *  @param n User-visible name.
+ */
+CinemaSoundProcessor::CinemaSoundProcessor (string i, string n)
+       : _id (i)
+       , _name (n)
+{
+
+}
+
+/** @return All available sound processors */
+vector<CinemaSoundProcessor const *>
+CinemaSoundProcessor::all ()
+{
+       return _cinema_sound_processors;
+}
+
+/** Set up the static _sound_processors vector; must be called before from_*
+ *  methods are used.
+ */
+void
+CinemaSoundProcessor::setup_cinema_sound_processors ()
+{
+       _cinema_sound_processors.push_back (new DolbyCP750);
+}
+
+/** @param id One of our ids.
+ *  @return Corresponding sound processor, or 0.
+ */
+CinemaSoundProcessor const *
+CinemaSoundProcessor::from_id (string id)
+{
+       vector<CinemaSoundProcessor const *>::iterator i = _cinema_sound_processors.begin ();
+       while (i != _cinema_sound_processors.end() && (*i)->id() != id) {
+               ++i;
+       }
+
+       if (i == _cinema_sound_processors.end ()) {
+               return 0;
+       }
+
+       return *i;
+}
+
+/** @param s A sound processor from our static list.
+ *  @return Index of the sound processor with the list, or -1.
+ */
+int
+CinemaSoundProcessor::as_index (CinemaSoundProcessor const * s)
+{
+       vector<CinemaSoundProcessor*>::size_type i = 0;
+       while (i < _cinema_sound_processors.size() && _cinema_sound_processors[i] != s) {
+               ++i;
+       }
+
+       if (i == _cinema_sound_processors.size ()) {
+               return -1;
+       }
+
+       return i;
+}
+
+/** @param i An index returned from as_index().
+ *  @return Corresponding sound processor.
+ */
+CinemaSoundProcessor const *
+CinemaSoundProcessor::from_index (int i)
+{
+       assert (i <= int(_cinema_sound_processors.size ()));
+       return _cinema_sound_processors[i];
+}
diff --git a/src/lib/cinema_sound_processor.h b/src/lib/cinema_sound_processor.h
new file mode 100644 (file)
index 0000000..f735b12
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+    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.
+
+*/
+
+/** @file src/cinema_sound_processor.h
+ *  @brief CinemaSoundProcessor class
+ */
+
+#ifndef DCPOMATIC_CINEMA_SOUND_PROCESSOR_H
+#define DCPOMATIC_CINEMA_SOUND_PROCESSOR_H
+
+#include <string>
+#include <vector>
+#include <boost/utility.hpp>
+
+/** @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 CinemaSoundProcessor : public boost::noncopyable
+{
+public:
+       CinemaSoundProcessor (std::string i, std::string n);
+
+       virtual float db_for_fader_change (float from, float to) const = 0;
+
+       /** @return id for our use */
+       std::string id () const {
+               return _id;
+       }
+
+       /** @return user-visible name for this sound processor */
+       std::string name () const {
+               return _name;
+       }
+       
+       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 */
+       std::string _id;
+       /** user-visible name for this sound processor */
+       std::string _name;
+
+       /** sll available cinema sound processors */
+       static std::vector<CinemaSoundProcessor const *> _cinema_sound_processors;
+};
+
+#endif
index f8675900ef2d59019b6e79ad4f862a5ef145b00f..aacefaa05502c9202178b2637dfa7dd5dbd50df6 100644 (file)
@@ -18,8 +18,8 @@
 */
 
 #include <libxml++/libxml++.h>
-#include <libdcp/colour_matrix.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/colour_matrix.h>
+#include <dcp/raw_convert.h>
 #include <libcxml/cxml.h>
 #include "config.h"
 #include "colour_conversion.h"
@@ -35,7 +35,7 @@ using std::cout;
 using std::vector;
 using boost::shared_ptr;
 using boost::optional;
-using libdcp::raw_convert;
+using dcp::raw_convert;
 
 ColourConversion::ColourConversion ()
        : input_gamma (2.4)
@@ -45,7 +45,7 @@ ColourConversion::ColourConversion ()
 {
        for (int i = 0; i < 3; ++i) {
                for (int j = 0; j < 3; ++j) {
-                       matrix (i, j) = libdcp::colour_matrix::srgb_to_xyz[i][j];
+                       matrix (i, j) = dcp::colour_matrix::srgb_to_xyz[i][j];
                }
        }
 }
index 8e6dffee73f51d08a42098bd47adecd539922529..d4976676d574b5f34b93eb0b3a2d84a09f5ba88c 100644 (file)
@@ -23,8 +23,8 @@
 #include <glib.h>
 #include <boost/filesystem.hpp>
 #include <boost/algorithm/string.hpp>
-#include <libdcp/colour_matrix.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/colour_matrix.h>
+#include <dcp/raw_convert.h>
 #include <libcxml/cxml.h>
 #include "config.h"
 #include "server.h"
@@ -32,7 +32,7 @@
 #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"
@@ -50,7 +50,7 @@ using boost::shared_ptr;
 using boost::optional;
 using boost::algorithm::is_any_of;
 using boost::algorithm::split;
-using libdcp::raw_convert;
+using dcp::raw_convert;
 
 Config* Config::_instance = 0;
 
@@ -60,7 +60,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_container (Ratio::from_id ("185"))
@@ -79,9 +79,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 ();
 }
@@ -90,7 +90,6 @@ void
 Config::read ()
 {
        if (!boost::filesystem::exists (file (false))) {
-               read_old_metadata ();
                return;
        }
 
@@ -128,7 +127,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");
@@ -170,7 +173,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");
@@ -199,70 +202,6 @@ Config::read ()
        _log_types = f.optional_number_child<int> ("LogTypes").get_value_or (Log::TYPE_GENERAL | Log::TYPE_WARNING | Log::TYPE_ERROR);
 }
 
-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;
-               }
-
-               if (line[0] == '#') {
-                       continue;
-               }
-
-               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_metadata.issuer = v;
-               } else if (k == "dcp_metadata_creator") {
-                       _dcp_metadata.creator = v;
-               } else if (k == "dcp_metadata_issue_date") {
-                       _dcp_metadata.issue_date = v;
-               }
-
-               _default_isdcf_metadata.read_old_metadata (k, v);
-       }
-}
-
 /** @return Filename to write configuration to */
 boost::filesystem::path
 Config::file (bool old) const
@@ -334,8 +273,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());
index ebcf7e83dd98fa524f839c341e2e4873094c4eac..f26e2532a1006cbe9a2e5be5f64aadeec5b90712 100644 (file)
 #include <boost/shared_ptr.hpp>
 #include <boost/signals2.hpp>
 #include <boost/filesystem.hpp>
-#include <libdcp/metadata.h>
+#include <dcp/metadata.h>
 #include "isdcf_metadata.h"
 #include "colour_conversion.h"
-#include "server.h"
 
 class ServerDescription;
 class Scaler;
 class Filter;
-class SoundProcessor;
+class CinemaSoundProcessor;
 class DCPContentType;
 class Ratio;
 class Cinema;
@@ -104,9 +103,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 {
@@ -141,7 +140,7 @@ public:
                return _default_dcp_content_type;
        }
 
-       libdcp::XMLMetadata dcp_metadata () const {
+       dcp::XMLMetadata dcp_metadata () const {
                return _dcp_metadata;
        }
 
@@ -288,7 +287,7 @@ public:
                changed ();
        }
 
-       void set_dcp_metadata (libdcp::XMLMetadata m) {
+       void set_dcp_metadata (dcp::XMLMetadata m) {
                _dcp_metadata = m;
                changed ();
        }
@@ -372,7 +371,6 @@ private:
        Config ();
        boost::filesystem::path file (bool) const;
        void read ();
-       void read_old_metadata ();
        void write () const;
 
        /** number of threads to use for J2K encoding on the local machine */
@@ -395,8 +393,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;
@@ -406,7 +404,7 @@ private:
        int _default_still_length;
        Ratio const * _default_container;
        DCPContentType const * _default_dcp_content_type;
-       libdcp::XMLMetadata _dcp_metadata;
+       dcp::XMLMetadata _dcp_metadata;
        int _default_j2k_bandwidth;
        int _default_audio_delay;
        std::vector<PresetColourConversion> _colour_conversions;
index 7966219ff60a4bf186a3af6ce58a6b52aa0a97e5..bbbe9b6ce4bdd4dd5ff6b8dcf231c8d08126e253 100644 (file)
@@ -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
 
 */
 
+/** @file  src/lib/content.cc
+ *  @brief Content class.
+ */
+
 #include <boost/thread/mutex.hpp>
 #include <libxml++/libxml++.h>
 #include <libcxml/cxml.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
 #include "content.h"
 #include "util.h"
 #include "content_factory.h"
@@ -38,7 +42,7 @@ using std::cout;
 using std::vector;
 using std::max;
 using boost::shared_ptr;
-using libdcp::raw_convert;
+using dcp::raw_convert;
 
 int const ContentProperty::PATH = 400;
 int const ContentProperty::POSITION = 401;
@@ -56,7 +60,7 @@ Content::Content (shared_ptr<const Film> f)
 
 }
 
-Content::Content (shared_ptr<const Film> f, Time p)
+Content::Content (shared_ptr<const Film> f, DCPTime p)
        : _film (f)
        , _position (p)
        , _trim_start (0)
@@ -76,7 +80,7 @@ Content::Content (shared_ptr<const Film> f, boost::filesystem::path p)
        _paths.push_back (p);
 }
 
-Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+Content::Content (shared_ptr<const Film> f, cxml::ConstNodePtr node)
        : _film (f)
        , _change_signals_frequent (false)
 {
@@ -85,9 +89,9 @@ Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
                _paths.push_back ((*i)->content ());
        }
        _digest = node->string_child ("Digest");
-       _position = node->number_child<Time> ("Position");
-       _trim_start = node->number_child<Time> ("TrimStart");
-       _trim_end = node->number_child<Time> ("TrimEnd");
+       _position = DCPTime (node->number_child<double> ("Position"));
+       _trim_start = DCPTime (node->number_child<double> ("TrimStart"));
+       _trim_end = DCPTime (node->number_child<double> ("TrimEnd"));
 }
 
 Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
@@ -98,11 +102,11 @@ Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
        , _change_signals_frequent (false)
 {
        for (size_t i = 0; i < c.size(); ++i) {
-               if (i > 0 && c[i]->trim_start ()) {
+               if (i > 0 && c[i]->trim_start() > DCPTime()) {
                        throw JoinError (_("Only the first piece of content to be joined can have a start trim."));
                }
 
-               if (i < (c.size() - 1) && c[i]->trim_end ()) {
+               if (i < (c.size() - 1) && c[i]->trim_end () > DCPTime()) {
                        throw JoinError (_("Only the last piece of content to be joined can have an end trim."));
                }
 
@@ -121,9 +125,9 @@ Content::as_xml (xmlpp::Node* node) const
                node->add_child("Path")->add_child_text (i->string ());
        }
        node->add_child("Digest")->add_child_text (_digest);
-       node->add_child("Position")->add_child_text (raw_convert<string> (_position));
-       node->add_child("TrimStart")->add_child_text (raw_convert<string> (_trim_start));
-       node->add_child("TrimEnd")->add_child_text (raw_convert<string> (_trim_end));
+       node->add_child("Position")->add_child_text (raw_convert<string> (_position.get ()));
+       node->add_child("TrimStart")->add_child_text (raw_convert<string> (_trim_start.get ()));
+       node->add_child("TrimEnd")->add_child_text (raw_convert<string> (_trim_end.get ()));
 }
 
 void
@@ -148,7 +152,7 @@ Content::signal_changed (int p)
 }
 
 void
-Content::set_position (Time p)
+Content::set_position (DCPTime p)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -163,7 +167,7 @@ Content::set_position (Time p)
 }
 
 void
-Content::set_trim_start (Time t)
+Content::set_trim_start (DCPTime t)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -174,7 +178,7 @@ Content::set_trim_start (Time t)
 }
 
 void
-Content::set_trim_end (Time t)
+Content::set_trim_end (DCPTime t)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -206,22 +210,13 @@ Content::clone () const
 string
 Content::technical_summary () const
 {
-       return String::compose ("%1 %2 %3", path_summary(), digest(), position());
+       return String::compose ("%1 %2 %3", path_summary(), digest(), position().seconds());
 }
 
-Time
+DCPTime
 Content::length_after_trim () const
 {
-       return max (int64_t (0), full_length() - trim_start() - trim_end());
-}
-
-/** @param t A time relative to the start of this content (not the position).
- *  @return true if this time is trimmed by our trim settings.
- */
-bool
-Content::trimmed (Time t) const
-{
-       return (t < trim_start() || t > (full_length() - trim_end ()));
+       return max (DCPTime (), full_length() - trim_start() - trim_end());
 }
 
 /** @return string which includes everything about how this content affects
@@ -233,9 +228,9 @@ Content::identifier () const
        stringstream s;
        
        s << Content::digest()
-         << "_" << position()
-         << "_" << trim_start()
-         << "_" << trim_end();
+         << "_" << position().get()
+         << "_" << trim_start().get()
+         << "_" << trim_end().get();
 
        return s.str ();
 }
index 596a0a905c95217d4daaf8ba116b8a6f518c6555..f7e97feac9a3489d1083f96278a4eea3e491d367 100644 (file)
@@ -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
 
 */
 
+/** @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 (file)
index 0000000..535c0b4
--- /dev/null
@@ -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;
+};
index 98b1dd859536643c83c524e3a22a4df45b039c8a..16340adb43b0e74f8f14d4b2bc039e258d8e58a9 100644 (file)
@@ -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
 
 */
 
+/** @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));
        }
index 2eeebbc9f8924c9ebb45c04df47bfdbefa9de22c..fae7648eabfa8910baa7157a695cca1035dba2cc 100644 (file)
@@ -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
 
 */
 
+/** @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 (file)
index 0000000..93e0677
--- /dev/null
@@ -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 (file)
index 0000000..8868618
--- /dev/null
@@ -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 (file)
index 0000000..a7f7359
--- /dev/null
@@ -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
index e30cf64598f57e717745cc22291022ebbb0eccac..0224e461d5fb8193fe82841ecdb34051711b2f6a 100644 (file)
@@ -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
index 1c77545033fdb0c7e2ee17db2a297b9e8100659f..c206fa55dbf3064cf17bcf68e719a45bb1765e2d 100644 (file)
@@ -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
 
 */
 
+/** @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 (file)
index 0000000..0eef075
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+    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_content.h"
+#include "dcp_examiner.h"
+#include "job.h"
+#include "film.h"
+#include "compose.hpp"
+
+#include "i18n.h"
+
+using std::string;
+using boost::shared_ptr;
+
+DCPContent::DCPContent (shared_ptr<const Film> f, boost::filesystem::path p)
+       : Content (f)
+       , VideoContent (f)
+       , SingleStreamAudioContent (f)
+       , SubtitleContent (f)
+       , _has_subtitles (false)
+       , _directory (p)
+{
+       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");
+}
+
+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)
+{
+       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 ();
+}
+
+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("Directory")->add_child_text (_directory.string ());
+}
+
+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 ();
+}
+
+bool
+DCPContent::has_subtitles () const
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       return _has_subtitles;
+}
diff --git a/src/lib/dcp_content.h b/src/lib/dcp_content.h
new file mode 100644 (file)
index 0000000..60b7142
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+    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/dcp_content.h
+ *  @brief DCPContent class.
+ */
+
+#include <libcxml/cxml.h>
+#include "video_content.h"
+#include "single_stream_audio_content.h"
+#include "subtitle_content.h"
+
+/** @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::filesystem::path directory () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _directory;
+       }
+
+private:
+       void read_directory (boost::filesystem::path);
+       
+       std::string _name;
+       bool _has_subtitles;
+       boost::filesystem::path _directory;
+};
index f24ed95ea24de85c74dd4d6b646fe744cc526f36..e5466e1398936c1e4917efe11e95fa1cf5b12472 100644 (file)
@@ -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 *
index 88f3c4a857e18d8e1574846906118ec1921c344f..ebfe09518a5577189e745d05d0a37bfd6d1ce04c 100644 (file)
@@ -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.)
 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 (file)
index 0000000..d0642d8
--- /dev/null
@@ -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/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"
+
+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 ();
+       assert (dcp.cpls().size() == 1);
+       _reels = dcp.cpls().front()->reels ();
+       _reel = _reels.begin ();
+}
+
+bool
+DCPDecoder::pass ()
+{
+       if (_reel == _reels.end ()) {
+               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 (file)
index 0000000..d81b20b
--- /dev/null
@@ -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 (file)
index 0000000..625276e
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+    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/sound_mxf.h>
+#include "dcp_examiner.h"
+#include "dcp_content.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::list;
+using boost::shared_ptr;
+
+DCPExaminer::DCPExaminer (shared_ptr<const DCPContent> content)
+{
+       dcp::DCP dcp (content->directory ());
+       dcp.read ();
+
+       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;
+               }
+       }
+}
diff --git a/src/lib/dcp_examiner.h b/src/lib/dcp_examiner.h
new file mode 100644 (file)
index 0000000..5b51074
--- /dev/null
@@ -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.
+
+*/
+
+#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;
+       }
+
+       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);
+       }
+
+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;
+};
diff --git a/src/lib/dcp_subtitle_content.cc b/src/lib/dcp_subtitle_content.cc
new file mode 100644 (file)
index 0000000..83b0d20
--- /dev/null
@@ -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 (file)
index 0000000..5794b59
--- /dev/null
@@ -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 (file)
index 0000000..20a9f32
--- /dev/null
@@ -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 (file)
index 0000000..0705624
--- /dev/null
@@ -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.cc b/src/lib/dcp_video.cc
new file mode 100644 (file)
index 0000000..9b1c8c3
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+    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.
+
+*/
+
+/** @file  src/dcp_video_frame.cc
+ *  @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.
+ */
+
+#include <stdint.h>
+#include <cstring>
+#include <cstdlib>
+#include <stdexcept>
+#include <cstdio>
+#include <iomanip>
+#include <sstream>
+#include <iostream>
+#include <fstream>
+#include <unistd.h>
+#include <errno.h>
+#include <boost/array.hpp>
+#include <boost/asio.hpp>
+#include <boost/filesystem.hpp>
+#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.h"
+#include "config.h"
+#include "exceptions.h"
+#include "server.h"
+#include "util.h"
+#include "scaler.h"
+#include "image.h"
+#include "log.h"
+#include "cross.h"
+#include "player_video.h"
+#include "encoded_data.h"
+
+#define LOG_GENERAL(...) _log->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using dcp::Size;
+using dcp::raw_convert;
+
+#define DCI_COEFFICENT (48.0 / 52.37)
+
+/** Construct a DCP video frame.
+ *  @param frame Input frame.
+ *  @param index Index of the frame within the DCP.
+ *  @param bw J2K bandwidth to use (see Config::j2k_bandwidth ())
+ *  @param l Log to write to.
+ */
+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)
+{
+       
+}
+
+DCPVideo::DCPVideo (shared_ptr<const PlayerVideo> frame, shared_ptr<const cxml::Node> node, shared_ptr<Log> log)
+       : _frame (frame)
+       , _log (log)
+{
+       _index = node->number_child<int> ("Index");
+       _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>
+DCPVideo::encode_locally ()
+{
+       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];
+       for (int i = 0; i < 3; ++i) {
+               for (int j = 0; j < 3; ++j) {
+                       matrix[i][j] = _frame->colour_conversion().matrix (i, j);
+               }
+       }
+
+       shared_ptr<dcp::XYZFrame> xyz = dcp::rgb_to_xyz (
+               _frame->image (_burn_subtitles),
+               in_lut,
+               dcp::GammaLUT::cache.get (16, 1 / _frame->colour_conversion().output_gamma, false),
+               matrix
+               );
+
+       /* Set the max image and component sizes based on frame_rate */
+       int max_cs_len = ((float) _j2k_bandwidth) / 8 / _frames_per_second;
+       if (_frame->eyes() == EYES_LEFT || _frame->eyes() == EYES_RIGHT) {
+               /* In 3D we have only half the normal bandwidth per eye */
+               max_cs_len /= 2;
+       }
+       int const max_comp_size = max_cs_len / 1.25;
+
+       /* get a J2K compressor handle */
+       opj_cinfo_t* cinfo = opj_create_compress (CODEC_J2K);
+       if (cinfo == 0) {
+               throw EncodeError (N_("could not create JPEG2000 encoder"));
+       }
+
+       /* Set encoding parameters to default values */
+       opj_cparameters_t parameters;
+       opj_set_default_encoder_parameters (&parameters);
+
+       /* Set default cinema parameters */
+       parameters.tile_size_on = false;
+       parameters.cp_tdx = 1;
+       parameters.cp_tdy = 1;
+       
+       /* Tile part */
+       parameters.tp_flag = 'C';
+       parameters.tp_on = 1;
+       
+       /* Tile and Image shall be at (0,0) */
+       parameters.cp_tx0 = 0;
+       parameters.cp_ty0 = 0;
+       parameters.image_offset_x0 = 0;
+       parameters.image_offset_y0 = 0;
+
+       /* Codeblock size = 32x32 */
+       parameters.cblockw_init = 32;
+       parameters.cblockh_init = 32;
+       parameters.csty |= 0x01;
+       
+       /* The progression order shall be CPRL */
+       parameters.prog_order = CPRL;
+       
+       /* No ROI */
+       parameters.roi_compno = -1;
+       
+       parameters.subsampling_dx = 1;
+       parameters.subsampling_dy = 1;
+       
+       /* 9-7 transform */
+       parameters.irreversible = 1;
+       
+       parameters.tcp_rates[0] = 0;
+       parameters.tcp_numlayers++;
+       parameters.cp_disto_alloc = 1;
+       parameters.cp_rsiz = _resolution == RESOLUTION_2K ? CINEMA2K : CINEMA4K;
+       if (_resolution == RESOLUTION_4K) {
+               parameters.numpocs = 2;
+               parameters.POC[0].tile = 1;
+               parameters.POC[0].resno0 = 0; 
+               parameters.POC[0].compno0 = 0;
+               parameters.POC[0].layno1 = 1;
+               parameters.POC[0].resno1 = parameters.numresolution - 1;
+               parameters.POC[0].compno1 = 3;
+               parameters.POC[0].prg1 = CPRL;
+               parameters.POC[1].tile = 1;
+               parameters.POC[1].resno0 = parameters.numresolution - 1; 
+               parameters.POC[1].compno0 = 0;
+               parameters.POC[1].layno1 = 1;
+               parameters.POC[1].resno1 = parameters.numresolution;
+               parameters.POC[1].compno1 = 3;
+               parameters.POC[1].prg1 = CPRL;
+       }
+       
+       parameters.cp_comment = strdup (N_("DCP-o-matic"));
+       parameters.cp_cinema = _resolution == RESOLUTION_2K ? CINEMA2K_24 : CINEMA4K_24;
+
+       /* 3 components, so use MCT */
+       parameters.tcp_mct = 1;
+       
+       /* set max image */
+       parameters.max_comp_size = max_comp_size;
+       parameters.tcp_rates[0] = ((float) (3 * xyz->size().width * xyz->size().height * 12)) / (max_cs_len * 8);
+
+       /* Set event manager to null (openjpeg 1.3 bug) */
+       cinfo->event_mgr = 0;
+
+       /* Setup the encoder parameters using the current image and user parameters */
+       opj_setup_encoder (cinfo, &parameters, xyz->opj_image ());
+
+       opj_cio_t* cio = opj_cio_open ((opj_common_ptr) cinfo, 0, 0);
+       if (cio == 0) {
+               opj_destroy_compress (cinfo);
+               throw EncodeError (N_("could not open JPEG2000 stream"));
+       }
+
+       int const r = opj_encode (cinfo, cio, xyz->opj_image(), 0);
+       if (r == 0) {
+               opj_cio_close (cio);
+               opj_destroy_compress (cinfo);
+               throw EncodeError (N_("JPEG2000 encoding failed"));
+       }
+
+       switch (_frame->eyes()) {
+       case EYES_BOTH:
+               LOG_GENERAL (N_("Finished locally-encoded frame %1 for mono"), _index);
+               break;
+       case EYES_LEFT:
+               LOG_GENERAL (N_("Finished locally-encoded frame %1 for L"), _index);
+               break;
+       case EYES_RIGHT:
+               LOG_GENERAL (N_("Finished locally-encoded frame %1 for R"), _index);
+               break;
+       default:
+               break;
+       }
+
+       shared_ptr<EncodedData> enc (new LocallyEncodedData (cio->buffer, cio_tell (cio)));
+
+       opj_cio_close (cio);
+       free (parameters.cp_comment);
+       opj_destroy_compress (cinfo);
+
+       return enc;
+}
+
+/** Send this frame to a remote server for J2K encoding, then read the result.
+ *  @param serv Server to send to.
+ *  @return Encoded data.
+ */
+shared_ptr<EncodedData>
+DCPVideo::encode_remotely (ServerDescription serv)
+{
+       boost::asio::io_service io_service;
+       boost::asio::ip::tcp::resolver resolver (io_service);
+       boost::asio::ip::tcp::resolver::query query (serv.host_name(), raw_convert<string> (Config::instance()->server_port_base ()));
+       boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve (query);
+
+       shared_ptr<Socket> socket (new Socket);
+
+       socket->connect (*endpoint_iterator);
+
+       /* Collect all XML metadata */
+       xmlpp::Document doc;
+       xmlpp::Element* root = doc.create_root_node ("EncodingRequest");
+       root->add_child("Version")->add_child_text (raw_convert<string> (SERVER_LINK_VERSION));
+       add_metadata (root);
+
+       LOG_GENERAL (N_("Sending frame %1 to remote"), _index);
+       
+       /* Send XML metadata */
+       stringstream xml;
+       doc.write_to_stream (xml, "UTF-8");
+       socket->write (xml.str().length() + 1);
+       socket->write ((uint8_t *) xml.str().c_str(), xml.str().length() + 1);
+
+       /* Send binary data */
+       _frame->send_binary (socket, _burn_subtitles);
+
+       /* Read the response (JPEG2000-encoded data); this blocks until the data
+          is ready and sent back.
+       */
+       shared_ptr<EncodedData> e (new RemotelyEncodedData (socket->read_uint32 ()));
+       socket->read (e->data(), e->size());
+
+       LOG_GENERAL (N_("Finished remotely-encoded frame %1"), _index);
+       
+       return e;
+}
+
+void
+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)));
+       el->add_child("BurnSubtitles")->add_child_text (_burn_subtitles ? "1" : "0");
+       _frame->add_metadata (el, _burn_subtitles);
+}
+
+Eyes
+DCPVideo::eyes () const
+{
+       return _frame->eyes ();
+}
+
diff --git a/src/lib/dcp_video.h b/src/lib/dcp_video.h
new file mode 100644 (file)
index 0000000..e8e9026
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+    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;
+       
+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/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc
deleted file mode 100644 (file)
index 09b9096..0000000
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
-    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.
-
-*/
-
-/** @file  src/dcp_video_frame.cc
- *  @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.
- */
-
-#include <stdint.h>
-#include <cstring>
-#include <cstdlib>
-#include <stdexcept>
-#include <cstdio>
-#include <iomanip>
-#include <sstream>
-#include <iostream>
-#include <fstream>
-#include <unistd.h>
-#include <errno.h>
-#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 <libcxml/cxml.h>
-#include "film.h"
-#include "dcp_video_frame.h"
-#include "config.h"
-#include "exceptions.h"
-#include "server.h"
-#include "util.h"
-#include "scaler.h"
-#include "image.h"
-#include "log.h"
-#include "cross.h"
-#include "player_video_frame.h"
-
-#define LOG_GENERAL(...) _log->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
-
-#include "i18n.h"
-
-using std::string;
-using std::stringstream;
-using std::cout;
-using boost::shared_ptr;
-using libdcp::Size;
-using libdcp::raw_convert;
-
-#define DCI_COEFFICENT (48.0 / 52.37)
-
-/** Construct a DCP video frame.
- *  @param frame Input frame.
- *  @param index Index of the frame within the DCP.
- *  @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
-       )
-       : _frame (frame)
-       , _index (index)
-       , _frames_per_second (dcp_fps)
-       , _j2k_bandwidth (bw)
-       , _resolution (r)
-       , _log (l)
-{
-       
-}
-
-DCPVideoFrame::DCPVideoFrame (shared_ptr<const PlayerVideoFrame> frame, shared_ptr<const cxml::Node> node, shared_ptr<Log> log)
-       : _frame (frame)
-       , _log (log)
-{
-       _index = node->number_child<int> ("Index");
-       _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));
-}
-
-/** J2K-encode this frame on the local host.
- *  @return Encoded data.
- */
-shared_ptr<EncodedData>
-DCPVideoFrame::encode_locally ()
-{
-       shared_ptr<libdcp::LUT> in_lut;
-       if (_frame->colour_conversion().input_gamma_linearised) {
-               in_lut = libdcp::SRGBLinearisedGammaLUT::cache.get (12, _frame->colour_conversion().input_gamma);
-       } else {
-               in_lut = libdcp::GammaLUT::cache.get (12, _frame->colour_conversion().input_gamma);
-       }
-
-       /* XXX: libdcp should probably use boost */
-       
-       double matrix[3][3];
-       for (int i = 0; i < 3; ++i) {
-               for (int j = 0; j < 3; ++j) {
-                       matrix[i][j] = _frame->colour_conversion().matrix (i, j);
-               }
-       }
-
-       shared_ptr<libdcp::XYZFrame> xyz = libdcp::rgb_to_xyz (
-               _frame->image(),
-               in_lut,
-               libdcp::GammaLUT::cache.get (16, 1 / _frame->colour_conversion().output_gamma),
-               matrix
-               );
-
-       /* Set the max image and component sizes based on frame_rate */
-       int max_cs_len = ((float) _j2k_bandwidth) / 8 / _frames_per_second;
-       if (_frame->eyes() == EYES_LEFT || _frame->eyes() == EYES_RIGHT) {
-               /* In 3D we have only half the normal bandwidth per eye */
-               max_cs_len /= 2;
-       }
-       int const max_comp_size = max_cs_len / 1.25;
-
-       /* get a J2K compressor handle */
-       opj_cinfo_t* cinfo = opj_create_compress (CODEC_J2K);
-       if (cinfo == 0) {
-               throw EncodeError (N_("could not create JPEG2000 encoder"));
-       }
-
-       /* Set encoding parameters to default values */
-       opj_cparameters_t parameters;
-       opj_set_default_encoder_parameters (&parameters);
-
-       /* Set default cinema parameters */
-       parameters.tile_size_on = false;
-       parameters.cp_tdx = 1;
-       parameters.cp_tdy = 1;
-       
-       /* Tile part */
-       parameters.tp_flag = 'C';
-       parameters.tp_on = 1;
-       
-       /* Tile and Image shall be at (0,0) */
-       parameters.cp_tx0 = 0;
-       parameters.cp_ty0 = 0;
-       parameters.image_offset_x0 = 0;
-       parameters.image_offset_y0 = 0;
-
-       /* Codeblock size = 32x32 */
-       parameters.cblockw_init = 32;
-       parameters.cblockh_init = 32;
-       parameters.csty |= 0x01;
-       
-       /* The progression order shall be CPRL */
-       parameters.prog_order = CPRL;
-       
-       /* No ROI */
-       parameters.roi_compno = -1;
-       
-       parameters.subsampling_dx = 1;
-       parameters.subsampling_dy = 1;
-       
-       /* 9-7 transform */
-       parameters.irreversible = 1;
-       
-       parameters.tcp_rates[0] = 0;
-       parameters.tcp_numlayers++;
-       parameters.cp_disto_alloc = 1;
-       parameters.cp_rsiz = _resolution == RESOLUTION_2K ? CINEMA2K : CINEMA4K;
-       if (_resolution == RESOLUTION_4K) {
-               parameters.numpocs = 2;
-               parameters.POC[0].tile = 1;
-               parameters.POC[0].resno0 = 0; 
-               parameters.POC[0].compno0 = 0;
-               parameters.POC[0].layno1 = 1;
-               parameters.POC[0].resno1 = parameters.numresolution - 1;
-               parameters.POC[0].compno1 = 3;
-               parameters.POC[0].prg1 = CPRL;
-               parameters.POC[1].tile = 1;
-               parameters.POC[1].resno0 = parameters.numresolution - 1; 
-               parameters.POC[1].compno0 = 0;
-               parameters.POC[1].layno1 = 1;
-               parameters.POC[1].resno1 = parameters.numresolution;
-               parameters.POC[1].compno1 = 3;
-               parameters.POC[1].prg1 = CPRL;
-       }
-       
-       parameters.cp_comment = strdup (N_("DCP-o-matic"));
-       parameters.cp_cinema = _resolution == RESOLUTION_2K ? CINEMA2K_24 : CINEMA4K_24;
-
-       /* 3 components, so use MCT */
-       parameters.tcp_mct = 1;
-       
-       /* set max image */
-       parameters.max_comp_size = max_comp_size;
-       parameters.tcp_rates[0] = ((float) (3 * xyz->size().width * xyz->size().height * 12)) / (max_cs_len * 8);
-
-       /* Set event manager to null (openjpeg 1.3 bug) */
-       cinfo->event_mgr = 0;
-
-       /* Setup the encoder parameters using the current image and user parameters */
-       opj_setup_encoder (cinfo, &parameters, xyz->opj_image ());
-
-       opj_cio_t* cio = opj_cio_open ((opj_common_ptr) cinfo, 0, 0);
-       if (cio == 0) {
-               opj_destroy_compress (cinfo);
-               throw EncodeError (N_("could not open JPEG2000 stream"));
-       }
-
-       int const r = opj_encode (cinfo, cio, xyz->opj_image(), 0);
-       if (r == 0) {
-               opj_cio_close (cio);
-               opj_destroy_compress (cinfo);
-               throw EncodeError (N_("JPEG2000 encoding failed"));
-       }
-
-       switch (_frame->eyes()) {
-       case EYES_BOTH:
-               LOG_GENERAL (N_("Finished locally-encoded frame %1 for mono"), _index);
-               break;
-       case EYES_LEFT:
-               LOG_GENERAL (N_("Finished locally-encoded frame %1 for L"), _index);
-               break;
-       case EYES_RIGHT:
-               LOG_GENERAL (N_("Finished locally-encoded frame %1 for R"), _index);
-               break;
-       default:
-               break;
-       }
-
-       shared_ptr<EncodedData> enc (new LocallyEncodedData (cio->buffer, cio_tell (cio)));
-
-       opj_cio_close (cio);
-       free (parameters.cp_comment);
-       opj_destroy_compress (cinfo);
-
-       return enc;
-}
-
-/** Send this frame to a remote server for J2K encoding, then read the result.
- *  @param serv Server to send to.
- *  @return Encoded data.
- */
-shared_ptr<EncodedData>
-DCPVideoFrame::encode_remotely (ServerDescription serv)
-{
-       boost::asio::io_service io_service;
-       boost::asio::ip::tcp::resolver resolver (io_service);
-       boost::asio::ip::tcp::resolver::query query (serv.host_name(), raw_convert<string> (Config::instance()->server_port_base ()));
-       boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve (query);
-
-       shared_ptr<Socket> socket (new Socket);
-
-       socket->connect (*endpoint_iterator);
-
-       /* Collect all XML metadata */
-       xmlpp::Document doc;
-       xmlpp::Element* root = doc.create_root_node ("EncodingRequest");
-       root->add_child("Version")->add_child_text (raw_convert<string> (SERVER_LINK_VERSION));
-       add_metadata (root);
-
-       LOG_GENERAL (N_("Sending frame %1 to remote"), _index);
-       
-       /* Send XML metadata */
-       stringstream xml;
-       doc.write_to_stream (xml, "UTF-8");
-       socket->write (xml.str().length() + 1);
-       socket->write ((uint8_t *) xml.str().c_str(), xml.str().length() + 1);
-
-       /* Send binary data */
-       _frame->send_binary (socket);
-
-       /* Read the response (JPEG2000-encoded data); this blocks until the data
-          is ready and sent back.
-       */
-       shared_ptr<EncodedData> e (new RemotelyEncodedData (socket->read_uint32 ()));
-       socket->read (e->data(), e->size());
-
-       LOG_GENERAL (N_("Finished remotely-encoded frame %1"), _index);
-       
-       return e;
-}
-
-void
-DCPVideoFrame::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);
-}
-
-Eyes
-DCPVideoFrame::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.
- */
-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, 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)
-{
-
-}
diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h
deleted file mode 100644 (file)
index e4006d9..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
-    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 <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.
- */
-
-class Film;
-class ServerDescription;
-class Scaler;
-class Image;
-class Log;
-class Subtitle;
-class PlayerVideoFrame;
-
-/** @class EncodedData
- *  @brief Container for J2K-encoded data.
- */
-class EncodedData : public boost::noncopyable
-{
-public:
-       /** @param s Size of data, in bytes */
-       EncodedData (int s);
-
-       EncodedData (boost::filesystem::path);
-
-       virtual ~EncodedData ();
-
-       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;
-
-       /** @return data */
-       uint8_t* data () const {
-               return _data;
-       }
-
-       /** @return data size, in bytes */
-       int size () const {
-               return _size;
-       }
-
-protected:
-       uint8_t* _data; ///< data
-       int _size;      ///< data size in bytes
-};
-
-/** @class LocallyEncodedData
- *  @brief EncodedData that was encoded locally; this class
- *  just keeps a pointer to the data, but does no memory
- *  management.
- */
-class LocallyEncodedData : public EncodedData
-{
-public:
-       /** @param d Data (which will be copied by this class)
-        *  @param s Size of data, in bytes.
-        */
-       LocallyEncodedData (uint8_t* d, int s);
-};
-
-/** @class RemotelyEncodedData
- *  @brief EncodedData that is being read from a remote server;
- *  this class allocates and manages memory for the data.
- */
-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/dcpomatic_time.cc b/src/lib/dcpomatic_time.cc
new file mode 100644 (file)
index 0000000..812c756
--- /dev/null
@@ -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 (file)
index 0000000..a0bbad0
--- /dev/null
@@ -0,0 +1,298 @@
+/*
+    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"
+
+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);
+
+               std::ostringstream 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 class 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.cc b/src/lib/decoder.cc
deleted file mode 100644 (file)
index 3f4cda6..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file  src/decoder.cc
- *  @brief Parent class for decoders of content.
- */
-
-#include "film.h"
-#include "decoder.h"
-
-#include "i18n.h"
-
-using boost::shared_ptr;
-
-/** @param f Film.
- *  @param o Decode options.
- */
-Decoder::Decoder (shared_ptr<const Film> f)
-       : _film (f)
-{
-
-}
index d67592ed812544c644b8766bcb1b1be1c03e84de..583a92636443ba7cd1bd650eb3cf61144017394c 100644 (file)
@@ -18,7 +18,7 @@
 */
 
 /** @file  src/decoder.h
- *  @brief Parent class for decoders of content.
+ *  @brief Decoder class.
  */
 
 #ifndef DCPOMATIC_DECODER_H
 #include <boost/shared_ptr.hpp>
 #include <boost/weak_ptr.hpp>
 #include <boost/utility.hpp>
+#include "types.h"
+#include "dcpomatic_time.h"
 
-class 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
index aeb469d293a6e6385b82160e61596b35198b50a1..317d129d99f0abcba3af6c78d4bf904baf05010c 100644 (file)
@@ -24,7 +24,7 @@
 using namespace std;
 
 DolbyCP750::DolbyCP750 ()
-       : SoundProcessor ("dolby_cp750", _("Dolby CP650 and CP750"))
+       : CinemaSoundProcessor ("dolby_cp750", _("Dolby CP650 and CP750"))
 {
 
 }
index b6c0e7df29915bec52085167bda2aa5cbab3cc4c..c545844fe584bca13cc425bc26a48452c4438387 100644 (file)
@@ -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 (file)
index 0000000..61d2644
--- /dev/null
@@ -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/encoded_data.h b/src/lib/encoded_data.h
new file mode 100644 (file)
index 0000000..232ed6e
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+    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/noncopyable.hpp>
+#include <boost/filesystem.hpp>
+#include <dcp/picture_mxf_writer.h>
+#include "types.h"
+
+class Socket;
+class Film;
+
+/** @class EncodedData
+ *  @brief Container for J2K-encoded data.
+ */
+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);
+
+       virtual ~EncodedData ();
+
+       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, dcp::FrameInfo) const;
+
+       /** @return data */
+       uint8_t* data () const {
+               return _data;
+       }
+
+       /** @return data size, in bytes */
+       int size () const {
+               return _size;
+       }
+
+protected:
+       uint8_t* _data; ///< data
+       int _size;      ///< data size in bytes
+};
+
+/** @class LocallyEncodedData
+ *  @brief EncodedData that was encoded locally; this class
+ *  just keeps a pointer to the data, but does no memory
+ *  management.
+ */
+class LocallyEncodedData : public EncodedData
+{
+public:
+       /** @param d Data (which will be copied by this class)
+        *  @param s Size of data, in bytes.
+        */
+       LocallyEncodedData (uint8_t* d, int s);
+};
+
+/** @class RemotelyEncodedData
+ *  @brief EncodedData that is being read from a remote server;
+ *  this class allocates and manages memory for the data.
+ */
+class RemotelyEncodedData : public EncodedData
+{
+public:
+       RemotelyEncodedData (int s);
+};
index 02a2710290f5c8ef6656fd3a49497dc65dbc45bc..e8ab5452bd404ec8a5c10cdb49ee761a8ad29f20 100644 (file)
 #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"
 
@@ -59,15 +59,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 ()
@@ -89,18 +88,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);
 
@@ -127,7 +125,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 ());
@@ -136,9 +134,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,
@@ -183,7 +178,7 @@ Encoder::frame_done ()
 }
 
 void
-Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf, bool same)
+Encoder::enqueue (shared_ptr<PlayerVideo> pvf)
 {
        _waker.nudge ();
        
@@ -211,19 +206,21 @@ Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf, bool same)
 
        if (_writer->can_fake_write (_video_frames_out)) {
                _writer->fake_write (_video_frames_out, pvf->eyes ());
-               _have_a_real_frame[pvf->eyes()] = false;
-               frame_done ();
-       } else if (same && _have_a_real_frame[pvf->eyes()]) {
-               /* Use the last frame that we encoded. */
-               _writer->repeat (_video_frames_out, pvf->eyes());
                frame_done ();
+       } else if (pvf->has_j2k ()) {
+               _writer->write (pvf->j2k(), _video_frames_out, pvf->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 (
+                                                 pvf,
+                                                 _video_frames_out,
+                                                 _film->video_frame_rate(),
+                                                 _film->j2k_bandwidth(),
+                                                 _film->resolution(),
+                                                 _film->burn_subtitles(),
+                                                 _film->log()
                                                  )
                                          ));
 
@@ -231,7 +228,6 @@ 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) {
@@ -239,12 +235,6 @@ Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf, bool same)
        }
 }
 
-void
-Encoder::process_audio (shared_ptr<const AudioBuffers> data)
-{
-       _writer->write (data);
-}
-
 void
 Encoder::terminate_threads ()
 {
@@ -288,7 +278,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 ();
                
index 8d5aa2c405a0c54843b49a30ae24d1bf42ea15aa..51df0176b575267a50ec75e5596059655811ec9e 100644 (file)
@@ -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 */
index 8144f41b999690f526db309a4102547f4e69a05c..f34cebbfe04a69f94fa2e07df3bb74aab5d41a51 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -56,8 +56,14 @@ MissingSettingError::MissingSettingError (string s)
 
 }
 
-PixelFormatError::PixelFormatError (std::string o, AVPixelFormat f)
+PixelFormatError::PixelFormatError (string o, AVPixelFormat f)
        : StringError (String::compose (_("Cannot handle pixel format %1 during %2"), f, o))
 {
 
 }
+
+SubRipError::SubRipError (string saw, string expecting, boost::filesystem::path f)
+       : FileError (String::compose (_("Error in SubRip file: saw %1 while expecting %2"), saw.empty() ? "[nothing]" : saw, expecting), f)
+{
+       
+}
index 3423a5754e340e3909b6b59ef617b5785d1a2809..a8969d7024c6aa60c0bd3460d66ca9034b7d3456 100644 (file)
@@ -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
 
 */
 
-#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,38 @@ 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 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 {
index 7ecc811be879a5af330402829377c7697a7204c2..f5af239b0a0eeba9445d85a98fbe97c767ae9658 100644 (file)
@@ -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"
 
@@ -34,7 +36,7 @@ using std::string;
 using std::cout;
 using std::stringstream;
 using boost::shared_ptr;
-using libdcp::raw_convert;
+using dcp::raw_convert;
 
 boost::mutex FFmpeg::_mutex;
 
@@ -48,8 +50,7 @@ FFmpeg::FFmpeg (boost::shared_ptr<const FFmpegContent> c)
        , _video_stream (-1)
 {
        setup_general ();
-       setup_video ();
-       setup_audio ();
+       setup_decoders ();
 }
 
 FFmpeg::~FFmpeg ()
@@ -57,14 +58,10 @@ FFmpeg::~FFmpeg ()
        boost::mutex::scoped_lock lm (_mutex);
 
        for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
-               AVCodecContext* context = _format_context->streams[i]->codec;
-               if (context->codec_type == AVMEDIA_TYPE_VIDEO || context->codec_type == AVMEDIA_TYPE_AUDIO) {
-                       avcodec_close (context);
-               }
+               avcodec_close (_format_context->streams[i]->codec);
        }
 
        av_frame_free (&_frame);
-       
        avformat_close_input (&_format_context);
 }
 
@@ -144,46 +141,24 @@ FFmpeg::setup_general ()
 }
 
 void
-FFmpeg::setup_video ()
-{
-       boost::mutex::scoped_lock lm (_mutex);
-
-       assert (_video_stream >= 0);
-       AVCodecContext* context = _format_context->streams[_video_stream]->codec;
-       AVCodec* codec = avcodec_find_decoder (context->codec_id);
-
-       if (codec == 0) {
-               throw DecodeError (_("could not find video decoder"));
-       }
-
-       if (avcodec_open2 (context, codec, 0) < 0) {
-               throw DecodeError (N_("could not open video decoder"));
-       }
-}
-
-void
-FFmpeg::setup_audio ()
+FFmpeg::setup_decoders ()
 {
        boost::mutex::scoped_lock lm (_mutex);
 
        for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
                AVCodecContext* context = _format_context->streams[i]->codec;
-               if (context->codec_type != AVMEDIA_TYPE_AUDIO) {
-                       continue;
-               }
                
                AVCodec* codec = avcodec_find_decoder (context->codec_id);
-               if (codec == 0) {
-                       throw DecodeError (_("could not find audio decoder"));
-               }
-               
-               if (avcodec_open2 (context, codec, 0) < 0) {
-                       throw DecodeError (N_("could not open audio decoder"));
+               if (codec) {
+                       if (avcodec_open2 (context, codec, 0) < 0) {
+                               throw DecodeError (N_("could not open decoder"));
+                       }
                }
+
+               /* We are silently ignoring any failures to find suitable decoders here */
        }
 }
 
-
 AVCodecContext *
 FFmpeg::video_codec_context () const
 {
@@ -193,9 +168,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)
 {
index 760918437d403dd0624984e31a07fb0bdddff583..8aaa54f84afab6973938e83eb138a60d6a2fa9c3 100644 (file)
@@ -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 (file)
index 0000000..255952b
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
+#include <dcp/raw_convert.h>
+#include "ffmpeg_audio_stream.h"
+
+using std::string;
+using dcp::raw_convert;
+
+FFmpegAudioStream::FFmpegAudioStream (cxml::ConstNodePtr node, int version)
+       : FFmpegStream (node)
+       , mapping (node->node_child ("Mapping"), version)
+{
+       frame_rate = node->number_child<int> ("FrameRate");
+       channels = node->number_child<int64_t> ("Channels");
+       first_audio = node->optional_number_child<double> ("FirstAudio");
+}
+
+void
+FFmpegAudioStream::as_xml (xmlpp::Node* root) const
+{
+       FFmpegStream::as_xml (root);
+       root->add_child("FrameRate")->add_child_text (raw_convert<string> (frame_rate));
+       root->add_child("Channels")->add_child_text (raw_convert<string> (channels));
+       if (first_audio) {
+               root->add_child("FirstAudio")->add_child_text (raw_convert<string> (first_audio.get().get()));
+       }
+       mapping.as_xml (root->add_child("Mapping"));
+}
diff --git a/src/lib/ffmpeg_audio_stream.h b/src/lib/ffmpeg_audio_stream.h
new file mode 100644 (file)
index 0000000..5e455d1
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "ffmpeg_stream.h"
+#include "audio_mapping.h"
+#include "dcpomatic_time.h"
+
+class ffmpeg_pts_offset_test;
+
+class FFmpegAudioStream : public FFmpegStream
+{
+public:
+       FFmpegAudioStream (std::string n, int i, int f, int c)
+               : FFmpegStream (n, i)
+               , frame_rate (f)
+               , channels (c)
+               , mapping (c)
+       {
+               mapping.make_default ();
+       }
+
+       FFmpegAudioStream (cxml::ConstNodePtr, int);
+
+       void as_xml (xmlpp::Node *) const;
+
+       int frame_rate;
+       int channels;
+       AudioMapping mapping;
+       boost::optional<ContentTime> first_audio;
+
+private:
+       friend class ffmpeg_pts_offset_test;
+
+       /* Constructor for tests */
+       FFmpegAudioStream ()
+               : FFmpegStream ("", 0)
+               , frame_rate (0)
+               , channels (0)
+               , mapping (1)
+       {}
+};
index 4d886a6ddf224ceabc38d15369d9da1bb4261806..e14d1bd6bf784454639d1e73408c5bf5a7208a68 100644 (file)
@@ -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 "";
        }
        
        stringstream s;
        
-       s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine(), video_frame_rate()) << "\n";
+       s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine().frames (video_frame_rate()), video_frame_rate()) << "\n";
        s << VideoContent::information ();
 
        return s.str ();
@@ -271,19 +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
@@ -299,7 +288,7 @@ FFmpegContent::audio_channels () const
 }
 
 int
-FFmpegContent::content_audio_frame_rate () const
+FFmpegContent::audio_frame_rate () const
 {
        boost::mutex::scoped_lock lm (_mutex);
 
@@ -322,94 +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
@@ -439,7 +346,7 @@ void
 FFmpegContent::set_audio_mapping (AudioMapping m)
 {
        audio_stream()->mapping = m;
-       signal_changed (AudioContentProperty::AUDIO_MAPPING);
+       AudioContent::set_audio_mapping (m);
 }
 
 string
@@ -482,3 +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 ();
+}
index c15e5c10ec0edc66d61db8238739d2d259f67ec1..67839794b97945496f4158f7379349794273fa66 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
 #define DCPOMATIC_FFMPEG_CONTENT_H
 
 #include <boost/enable_shared_from_this.hpp>
+#include <boost/lexical_cast.hpp>
 #include "video_content.h"
 #include "audio_content.h"
 #include "subtitle_content.h"
@@ -30,89 +31,10 @@ struct AVFormatContext;
 struct AVStream;
 
 class Filter;
+class FFmpegSubtitleStream;
+class FFmpegAudioStream;
 class ffmpeg_pts_offset_test;
 
-class FFmpegStream
-{
-public:
-       FFmpegStream (std::string n, int i)
-               : name (n)
-               , _id (i)
-       {}
-                               
-       FFmpegStream (boost::shared_ptr<const cxml::Node>);
-
-       void as_xml (xmlpp::Node *) const;
-
-       /** @param c An AVFormatContext.
-        *  @param index A stream index within the AVFormatContext.
-        *  @return true if this FFmpegStream uses the given stream index.
-        */
-       bool uses_index (AVFormatContext const * c, int index) const;
-       AVStream* stream (AVFormatContext const * c) const;
-
-       std::string technical_summary () const {
-               return "id " + boost::lexical_cast<std::string> (_id);
-       }
-
-       std::string identifier () const {
-               return boost::lexical_cast<std::string> (_id);
-       }
-
-       std::string name;
-
-       friend bool operator== (FFmpegStream const & a, FFmpegStream const & b);
-       friend bool operator!= (FFmpegStream const & a, FFmpegStream const & b);
-       
-private:
-       int _id;
-};
-
-class FFmpegAudioStream : public FFmpegStream
-{
-public:
-       FFmpegAudioStream (std::string n, int i, int f, int c)
-               : FFmpegStream (n, i)
-               , frame_rate (f)
-               , channels (c)
-               , mapping (c)
-       {
-               mapping.make_default ();
-       }
-
-       FFmpegAudioStream (boost::shared_ptr<const cxml::Node>, int);
-
-       void as_xml (xmlpp::Node *) const;
-
-       int frame_rate;
-       int channels;
-       AudioMapping mapping;
-       boost::optional<double> first_audio;
-
-private:
-       friend class ffmpeg_pts_offset_test;
-
-       /* Constructor for tests */
-       FFmpegAudioStream ()
-               : FFmpegStream ("", 0)
-               , frame_rate (0)
-               , channels (0)
-               , mapping (1)
-       {}
-};
-
-class FFmpegSubtitleStream : public FFmpegStream
-{
-public:
-       FFmpegSubtitleStream (std::string n, int i)
-               : FFmpegStream (n, i)
-       {}
-       
-       FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>);
-
-       void as_xml (xmlpp::Node *) const;
-};
-
 class FFmpegContentProperty : public VideoContentProperty
 {
 public:
@@ -127,7 +49,7 @@ class FFmpegContent : public VideoContent, public AudioContent, public SubtitleC
 {
 public:
        FFmpegContent (boost::shared_ptr<const Film>, boost::filesystem::path);
-       FFmpegContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int version, std::list<std::string> &);
+       FFmpegContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int version, std::list<std::string> &);
        FFmpegContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
 
        boost::shared_ptr<FFmpegContent> shared_from_this () {
@@ -139,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,11 +106,13 @@ public:
        void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>);
        void set_audio_stream (boost::shared_ptr<FFmpegAudioStream>);
 
-       boost::optional<double> first_video () const {
+       boost::optional<ContentTime> first_video () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _first_video;
        }
 
+       std::list<ContentTimePeriod> subtitles_during (ContentTimePeriod, bool starting) const;
+
 private:
        friend class ffmpeg_pts_offset_test;
        
@@ -193,7 +120,7 @@ private:
        boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream;
        std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
        boost::shared_ptr<FFmpegAudioStream> _audio_stream;
-       boost::optional<double> _first_video;
+       boost::optional<ContentTime> _first_video;
        /** Video filters that should be used when generating DCPs */
        std::vector<Filter const *> _filters;
 };
index f2d434e3bab1415ce38b170b7ae762a82062fc6f..dd47d306a6e54c0611e0abde6b6ab802cab4e77b 100644 (file)
@@ -32,23 +32,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;
@@ -57,26 +60,19 @@ using std::stringstream;
 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:
 
@@ -86,13 +82,11 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
           Then we remove big initial gaps in PTS and we allow our
           insertion of black frames to work.
 
-          We will do:
-            audio_pts_to_use = audio_pts_from_ffmpeg + pts_offset;
-            video_pts_to_use = video_pts_from_ffmpeg + pts_offset;
+          We will do pts_to_use = pts_from_ffmpeg + pts_offset;
        */
 
-       bool const have_video = video && c->first_video();
-       bool const have_audio = audio && c->audio_stream() && c->audio_stream()->first_audio;
+       bool const have_video = c->first_video();
+       bool const have_audio = c->audio_stream () && c->audio_stream()->first_audio;
 
        /* First, make one of them start at 0 */
 
@@ -106,24 +100,9 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
 
        /* Now adjust both so that the video pts starts on a frame */
        if (have_video && have_audio) {
-               double first_video = c->first_video().get() + _pts_offset;
-               double const old_first_video = first_video;
-               
-               /* Round the first video up to a frame boundary */
-               if (fabs (rint (first_video * c->video_frame_rate()) - first_video * c->video_frame_rate()) > 1e-6) {
-                       first_video = ceil (first_video * c->video_frame_rate()) / c->video_frame_rate ();
-               }
-
-               _pts_offset += first_video - old_first_video;
-       }
-}
-
-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;
        }
 }
 
@@ -137,20 +116,15 @@ FFmpegDecoder::flush ()
        
        /* XXX: should we reset _packet.data and size after each *_decode_* call? */
        
-       if (_decode_video) {
-               while (decode_video_packet ()) {}
-       }
+       while (decode_video_packet ()) {}
        
-       if (_ffmpeg_content->audio_stream() && _decode_audio) {
+       if (_ffmpeg_content->audio_stream()) {
                decode_audio_packet ();
+               AudioDecoder::flush ();
        }
-
-       /* Stop us being asked for any more data */
-       _video_position = _ffmpeg_content->video_length_after_3d_combine ();
-       _audio_position = _ffmpeg_content->audio_length ();
 }
 
-void
+bool
 FFmpegDecoder::pass ()
 {
        int r = av_read_frame (_format_context, &_packet);
@@ -160,29 +134,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.
@@ -315,82 +285,45 @@ 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 (time < ContentTime (0)) {
+               time = ContentTime (0);
        }
 
-       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);
+       ContentTime const u = time - _pts_offset;
+       int64_t s = u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base);
 
-       avcodec_flush_buffers (video_codec_context());
-       if (_subtitle_codec_context) {
-               avcodec_flush_buffers (_subtitle_codec_context);
+       if (_ffmpeg_content->audio_stream ()) {
+               s = min (
+                       s, int64_t (u.seconds() / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base))
+                       );
        }
 
-       /* This !accurate is piling hack upon hack; setting _just_sought to true
-          even with accurate == true defeats our attempt to align the start
-          of the video and audio.  Here we disable that defeat when accurate == true
-          i.e. when we are making a DCP rather than just previewing one.
-          Ewww.  This should be gone in 2.0.
-       */
-       if (!accurate) {
-               _just_sought = true;
-       }
-       
-       _video_position = frame;
-       
-       if (frame == 0 || !accurate) {
-               /* We're already there, or we're as close as we need to be */
-               return;
+       /* Ridiculous empirical hack */
+       s--;
+       if (s < 0) {
+               s = 0;
        }
 
-       while (true) {
-               int r = av_read_frame (_format_context, &_packet);
-               if (r < 0) {
-                       return;
-               }
+       av_seek_frame (_format_context, _video_stream, s, 0);
 
-               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()
-                               );
-
-                       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
@@ -406,42 +339,23 @@ FFmpegDecoder::decode_audio_packet ()
 
                int frame_finished;
                int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
+
                if (decode_result < 0) {
-                       shared_ptr<const Film> film = _film.lock ();
-                       assert (film);
                        LOG_ERROR ("avcodec_decode_audio4 failed (%1)", decode_result);
                        return;
                }
 
                if (frame_finished) {
-                       
-                       if (_audio_position == 0) {
-                               /* Where we are in the source, in seconds */
-                               double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
-                                       * av_frame_get_best_effort_timestamp(_frame) + _pts_offset;
-
-                               if (pts > 0) {
-                                       /* Emit some silence */
-                                       int64_t frames = pts * _ffmpeg_content->content_audio_frame_rate ();
-                                       while (frames > 0) {
-                                               int64_t const this_time = min (frames, (int64_t) _ffmpeg_content->content_audio_frame_rate() / 2);
-                                               
-                                               shared_ptr<AudioBuffers> silence (
-                                                       new AudioBuffers (_ffmpeg_content->audio_channels(), this_time)
-                                                       );
-                                       
-                                               silence->make_silent ();
-                                               audio (silence, _audio_position);
-                                               frames -= this_time;
-                                       }
-                               }
-                       }
+                       ContentTime const ct = ContentTime::from_seconds (
+                               av_frame_get_best_effort_timestamp (_frame) *
+                               av_q2d (_ffmpeg_content->audio_stream()->stream (_format_context)->time_base))
+                               + _pts_offset;
                        
                        int const data_size = av_samples_get_buffer_size (
                                0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
                                );
-                       
-                       audio (deinterleave_audio (_frame->data, data_size), _audio_position);
+
+                       audio (deinterleave_audio (_frame->data, data_size), ct);
                }
                        
                copy_packet.data += decode_result;
@@ -462,17 +376,13 @@ FFmpegDecoder::decode_video_packet ()
        shared_ptr<FilterGraph> graph;
        
        list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
-       while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
+       while (i != _filter_graphs.end() && !(*i)->can_process (dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
                ++i;
        }
 
        if (i == _filter_graphs.end ()) {
-               shared_ptr<const Film> film = _film.lock ();
-               assert (film);
-
-               graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
+               graph.reset (new FilterGraph (_ffmpeg_content, dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
                _filter_graphs.push_back (graph);
-
                LOG_GENERAL (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format);
        } else {
                graph = *i;
@@ -480,56 +390,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");
                }
@@ -537,47 +407,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;
        }
 
@@ -585,31 +421,29 @@ FFmpegDecoder::decode_subtitle_packet ()
           indicate that the previous subtitle should stop.
        */
        if (sub.num_rects <= 0) {
-               subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0);
+               image_subtitle (ContentTimePeriod (), shared_ptr<Image> (), dcpomatic::Rect<double> ());
                return;
        } else if (sub.num_rects > 1) {
                throw DecodeError (_("multi-part subtitles not yet supported"));
        }
                
-       /* Subtitle PTS in seconds (within the source, not taking into account any of the
+       /* Subtitle PTS (within the source, not taking into account any of the
           source that we may have chopped off for the DCP)
        */
-       double const packet_time = (static_cast<double> (sub.pts ) / AV_TIME_BASE) + _pts_offset;
-
-       /* hence start time for this sub */
-       Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
-       Time const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
+       ContentTimePeriod period = subtitle_period (sub) + _pts_offset;
 
        AVSubtitleRect const * rect = sub.rects[0];
 
        if (rect->type != SUBTITLE_BITMAP) {
-               throw DecodeError (_("non-bitmap subtitles not yet supported"));
+               /* XXX */
+               // throw DecodeError (_("non-bitmap subtitles not yet supported"));
+               return;
        }
 
        /* Note RGBA is expressed little-endian, so the first byte in the word is R, second
           G, third B, fourth A.
        */
-       shared_ptr<Image> image (new Image (PIX_FMT_RGBA, libdcp::Size (rect->w, rect->h), true));
+       shared_ptr<Image> image (new Image (PIX_FMT_RGBA, dcp::Size (rect->w, rect->h), true));
 
        /* Start of the first line in the subtitle */
        uint8_t* sub_p = rect->pict.data[0];
@@ -631,20 +465,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);
+}
index d4b4fa1c02984f8690c42bd264434b5266cff52e..b553cc42ea2cc15627f99f5ee9cd35cdfffb6f39 100644 (file)
@@ -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);
-
+       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;
 };
index bc82a9700f8c3b254ea287c43483919299a37de7..62b909b1d65f3708293ca8dcbe91fe4a371d4608 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -23,6 +23,9 @@ extern "C" {
 }
 #include "ffmpeg_examiner.h"
 #include "ffmpeg_content.h"
+#include "ffmpeg_audio_stream.h"
+#include "ffmpeg_subtitle_stream.h"
+#include "util.h"
 
 #include "i18n.h"
 
@@ -61,55 +64,93 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
                }
        }
 
-       /* Run through until we find the first audio (for each stream) and video */
-
+       /* Run through until we find:
+        *   - the first video.
+        *   - the first audio for each stream.
+        *   - the subtitle periods for each stream.
+        *
+        * We have to note subtitle periods as otherwise we have no way of knowing
+        * where we should look for subtitles (video and audio are always present,
+        * so they are ok).
+        */
        while (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 - _format_context->start_time) / AV_TIME_BASE) * video_frame_rate();
-       return max (1, length);
+       ContentTime const length = ContentTime::from_seconds (double (_format_context->duration - _format_context->start_time) / AV_TIME_BASE);
+       return ContentTime (max (ContentTime::Type (1), length.get ()));
 }
 
 string
index 369dac29c992748b3b394b1a42ae1da3a4315235..8c31eb2c4aedadae419e7c5e41b83841f848c93b 100644 (file)
@@ -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 (file)
index 0000000..3fac333
--- /dev/null
@@ -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 (file)
index 0000000..6bbcd0b
--- /dev/null
@@ -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 (file)
index 0000000..3d8fd4e
--- /dev/null
@@ -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 (file)
index 0000000..b16b825
--- /dev/null
@@ -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;
+};
+
index 048f6923316e2cae13edfa9033daaa982f143db3..9c8d43204b1b830726c073de894e7b9f5c6e9e22 100644 (file)
@@ -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
 
 */
 
+/** @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
 {
index 65091c93646ac410c7f83b2c1a6d90dc1ef70e3a..5a65de96f7584e3afdab1ad71fd928f5df3b3005 100644 (file)
@@ -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
 
 */
 
+/** @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:
index 0a77caf502cc4405695a84c6db8aec6fa9f23387..85dc5b168d2012e0d98d78606656fcc221225dd4 100644 (file)
 #include <unistd.h>
 #include <boost/filesystem.hpp>
 #include <boost/algorithm/string.hpp>
-#include <boost/date_time.hpp>
+#include <boost/lexical_cast.hpp>
 #include <libxml++/libxml++.h>
 #include <libcxml/cxml.h>
-#include <libdcp/signer_chain.h>
-#include <libdcp/cpl.h>
-#include <libdcp/signer.h>
-#include <libdcp/util.h>
-#include <libdcp/kdm.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/signer_chain.h>
+#include <dcp/cpl.h>
+#include <dcp/signer.h>
+#include <dcp/util.h>
+#include <dcp/local_time.h>
+#include <dcp/raw_convert.h>
 #include "film.h"
 #include "job.h"
 #include "util.h"
@@ -78,9 +78,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);
@@ -93,8 +94,11 @@ using libdcp::raw_convert;
  * Use <Scale> tag in <VideoContent> rather than <Ratio>.
  * 8 -> 9
  * DCI -> ISDCF
+ *
+ * Bumped to 32 for 2.0 branch; some times are expressed in Times rather
+ * than frames now.
  */
-int const Film::current_state_version = 9;
+int const Film::current_state_version = 32;
 
 /** Construct a Film object in a given directory.
  *
@@ -108,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 ())
@@ -118,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)
 {
@@ -181,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 ();
@@ -226,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
 {
@@ -368,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));
@@ -377,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 ());
@@ -440,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);
@@ -449,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) */
@@ -586,7 +598,7 @@ Film::isdcf_name (bool if_created_now) const
        */
 
        /* The standard says we don't do this for trailers, for some strange reason */
-       if (dcp_content_type() && dcp_content_type()->libdcp_kind() != libdcp::TRAILER) {
+       if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) {
                ContentList cl = content ();
                Ratio const * content_ratio = 0;
                for (ContentList::const_iterator i = cl.begin(); i != cl.end(); ++i) {
@@ -684,7 +696,6 @@ Film::dcp_name (bool if_created_now) const
        return name();
 }
 
-
 void
 Film::set_directory (boost::filesystem::path d)
 {
@@ -734,13 +745,6 @@ Film::set_scaler (Scaler const * s)
        signal_changed (SCALER);
 }
 
-void
-Film::set_with_subtitles (bool w)
-{
-       _with_subtitles = w;
-       signal_changed (WITH_SUBTITLES);
-}
-
 void
 Film::set_j2k_bandwidth (int b)
 {
@@ -783,6 +787,13 @@ Film::set_interop (bool i)
        signal_changed (INTEROP);
 }
 
+void
+Film::set_burn_subtitles (bool b)
+{
+       _burn_subtitles = b;
+       signal_changed (BURN_SUBTITLES);
+}
+
 void
 Film::signal_changed (Property p)
 {
@@ -864,7 +875,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
 {
@@ -878,11 +889,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 (...) {
@@ -981,22 +995,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
@@ -1017,31 +1031,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 */
@@ -1057,58 +1047,52 @@ Film::set_sequence_video (bool s)
 }
 
 /** @return Size of the largest possible image in whatever resolution we are using */
-libdcp::Size
+dcp::Size
 Film::full_frame () const
 {
        switch (_resolution) {
        case RESOLUTION_2K:
-               return libdcp::Size (2048, 1080);
+               return dcp::Size (2048, 1080);
        case RESOLUTION_4K:
-               return libdcp::Size (4096, 2160);
+               return dcp::Size (4096, 2160);
        }
 
        assert (false);
-       return libdcp::Size ();
+       return dcp::Size ();
 }
 
 /** @return Size of the frame */
-libdcp::Size
+dcp::Size
 Film::frame_size () const
 {
        return fit_ratio_within (container()->ratio(), full_frame ());
 }
 
-/** @param from KDM from time in local time.
- *  @param to KDM to time in local time.
- */
-libdcp::KDM
+dcp::EncryptedKDM
 Film::make_kdm (
-       shared_ptr<libdcp::Certificate> target,
+       shared_ptr<dcp::Certificate> target,
        boost::filesystem::path cpl_file,
-       boost::posix_time::ptime from,
-       boost::posix_time::ptime until,
-       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);
-       
-       return libdcp::KDM (cpl_file, signer, target, key (), from, until, "DCP-o-matic", issue_date, formulation);
+       shared_ptr<const dcp::CPL> cpl (new dcp::CPL (cpl_file));
+       return dcp::DecryptedKDM (
+               cpl, from, until, "DCP-o-matic", cpl->content_title_text(), dcp::LocalTime().as_string()
+               ).encrypt (make_signer(), target, 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));
@@ -1123,7 +1107,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
@@ -1141,10 +1125,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 ());
-}
-
index b7d105688d06c2a1bf644544fb43aff9e9fd9c37..6c3f78895791221297b500ea64b1b4ea730777af 100644 (file)
@@ -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"
@@ -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,
+               boost::shared_ptr<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 */
@@ -262,7 +259,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 +269,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;
@@ -315,8 +312,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,7 +330,8 @@ private:
        bool _three_d;
        bool _sequence_video;
        bool _interop;
-       libdcp::Key _key;
+       bool _burn_subtitles;
+       dcp::Key _key;
 
        int _state_version;
 
index c11ee633185aabac54837744b99604fa1f3b359a..f6a6c4529df2376bc08f81a2adaf721d5516459d 100644 (file)
@@ -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);
 }
index 9b403c2bc65734cf03a9b8458180a0b8695a26fc..5b43c5512fb9fa127bf5c364e292a22d9a7e1ce0 100644 (file)
@@ -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;
 };
index f340637dc7ebf2d515983fe6921de162045e9afa..680c063f1a4d30ca9e364bbdbed615e7fafc9376 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -30,6 +30,8 @@ extern "C" {
 #include "image.h"
 #include "exceptions.h"
 #include "scaler.h"
+#include "timer.h"
+#include "rect.h"
 #include "md5_digester.h"
 
 #include "i18n.h"
@@ -38,9 +40,10 @@ using std::string;
 using std::min;
 using std::cout;
 using std::cerr;
+using std::list;
 using std::stringstream;
 using boost::shared_ptr;
-using libdcp::Size;
+using dcp::Size;
 
 int
 Image::line_factor (int n) const
@@ -84,7 +87,7 @@ Image::components () const
 
 /** Crop this image, scale it to `inter_size' and then place it in a black frame of `out_size' */
 shared_ptr<Image>
-Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
+Image::crop_scale_window (Crop crop, dcp::Size inter_size, dcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
 {
        assert (scaler);
        /* Empirical testing suggests that sws_scale() will crash if
@@ -100,13 +103,13 @@ Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_s
        out->make_black ();
 
        /* Size of the image after any crop */
-       libdcp::Size const cropped_size = crop.apply (size ());
+       dcp::Size const cropped_size = crop.apply (size ());
 
        /* Scale context for a scale from cropped_size to inter_size */
        struct SwsContext* scale_context = sws_getContext (
-               cropped_size.width, cropped_size.height, pixel_format(),
-               inter_size.width, inter_size.height, out_format,
-               scaler->ffmpeg_id (), 0, 0, 0
+                       cropped_size.width, cropped_size.height, pixel_format(),
+                       inter_size.width, inter_size.height, out_format,
+                       scaler->ffmpeg_id (), 0, 0, 0
                );
 
        if (!scale_context) {
@@ -140,7 +143,7 @@ Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_s
 }
 
 shared_ptr<Image>
-Image::scale (libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
+Image::scale (dcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
 {
        assert (scaler);
        /* Empirical testing suggests that sws_scale() will crash if
@@ -171,7 +174,7 @@ Image::scale (libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_fo
 shared_ptr<Image>
 Image::crop (Crop crop, bool aligned) const
 {
-       libdcp::Size cropped_size = crop.apply (size ());
+       dcp::Size cropped_size = crop.apply (size ());
        shared_ptr<Image> out (new Image (pixel_format(), cropped_size, aligned));
 
        for (int c = 0; c < components(); ++c) {
@@ -344,11 +347,34 @@ 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;
@@ -367,15 +393,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;
                }
        }
 }
@@ -459,8 +487,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)
 {
@@ -502,7 +530,7 @@ Image::allocate ()
 }
 
 Image::Image (Image const & other)
-       : libdcp::Image (other)
+       : dcp::Image (other)
        ,  _pixel_format (other._pixel_format)
        , _aligned (other._aligned)
 {
@@ -520,7 +548,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)
 {
@@ -539,7 +567,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)
 {
@@ -572,7 +600,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);
 
@@ -615,7 +643,7 @@ Image::stride () const
        return _stride;
 }
 
-libdcp::Size
+dcp::Size
 Image::size () const
 {
        return _size;
@@ -627,6 +655,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
 {
@@ -638,4 +691,3 @@ Image::digest () const
 
        return digester.get ();
 }
-       
index f83bf6998111defbb49fcd49d67dea6703099992..23c85e92b044d1371650805ee6364b49a9063165 100644 (file)
@@ -31,16 +31,17 @@ extern "C" {
 #include <libavcodec/avcodec.h>
 #include <libavfilter/avfilter.h>
 }
-#include <libdcp/image.h>
+#include <dcp/image.h>
 #include "util.h"
 #include "position.h"
+#include "position_image.h"
 
 class Scaler;
 
-class Image : public libdcp::Image
+class Image : public dcp::Image
 {
 public:
-       Image (AVPixelFormat, libdcp::Size, bool);
+       Image (AVPixelFormat, dcp::Size, bool);
        Image (AVFrame *);
        Image (Image const &);
        Image (boost::shared_ptr<const Image>, bool);
@@ -50,19 +51,20 @@ public:
        uint8_t ** data () const;
        int * line_size () const;
        int * stride () const;
-       libdcp::Size size () const;
+       dcp::Size size () const;
        bool aligned () const;
 
        int components () const;
        int line_factor (int) const;
        int lines (int) const;
 
-       boost::shared_ptr<Image> scale (libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
+       boost::shared_ptr<Image> scale (dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
        boost::shared_ptr<Image> crop (Crop c, bool aligned) const;
 
-       boost::shared_ptr<Image> crop_scale_window (Crop c, libdcp::Size, libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
+       boost::shared_ptr<Image> crop_scale_window (Crop c, dcp::Size, dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
        
        void make_black ();
+       void make_transparent ();
        void alpha_blend (boost::shared_ptr<const Image> image, Position<int> pos);
        void copy (boost::shared_ptr<const Image> image, Position<int> pos);
 
@@ -91,4 +93,6 @@ private:
        bool _aligned;
 };
 
+extern PositionImage merge (std::list<PositionImage> images);
+
 #endif
index 6acf0bab924001eaa5b51e1d1349ce134e5e719c..70d777bca11f6e52a466dbf44414b7a1ab048163 100644 (file)
 #include <libcxml/cxml.h>
 #include "image_content.h"
 #include "image_examiner.h"
-#include "config.h"
 #include "compose.hpp"
 #include "film.h"
 #include "job.h"
 #include "frame_rate_change.h"
+#include "exceptions.h"
 
 #include "i18n.h"
 
@@ -55,7 +55,7 @@ ImageContent::ImageContent (shared_ptr<const Film> f, boost::filesystem::path p)
 }
 
 
-ImageContent::ImageContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version)
+ImageContent::ImageContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version)
        : Content (f, node)
        , VideoContent (f, node, version)
 {
@@ -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
 {
        stringstream s;
        s << VideoContent::identifier ();
-       s << "_" << video_length();
+       s << "_" << video_length().get();
        return s.str ();
 }
 
index e56abce4a07a8218ab0ad4a7a1b26280d131adce..a1b1437c811fb391154b222466b7fa22a27b5258 100644 (file)
@@ -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);
 };
index 7a9acd9e4960e611bda76e912e4837738e94dc59..8702c1a33b18692dbc43b89c9e60ab416ef9e50e 100644 (file)
@@ -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"
 
 
 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 ());
 }
index 5b82dd85c161eedd07ccfb0c09cdc90c6dc96708..242f69477826a499d915505cd0b9486808aba68d 100644 (file)
@@ -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;
 };
 
index 4ff324f68d4d76ba29e6437e353e48d1b178be65..004b89e659d47ebd24cb594bc06848d2651585d4 100644 (file)
@@ -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 ();
index 8887f0d3d15a2587f56faff713a62835b3af8a7b..6ae0422cbbd4477f8ac7878e05a2430b251711b0 100644 (file)
@@ -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;
 };
index ba572c72733e9c636a37575ca5cc04d6c609891b..233f477453416ee44dac4c0659442a751e48a701 100644 (file)
 
 */
 
-#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"
@@ -41,127 +43,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;
-       try {
-               magick_image = new Magick::Image (_blob);
-       } catch (...) {
-               throw DecodeError (_("Could not decode image file"));
-       }
-
-       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)
 {
@@ -169,6 +50,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"));
index f6212e54f59bc11a5c679b4551bfc8d8d57cd72f..1a23001cb728ed384e55905ec8ef15899934e2db 100644 (file)
@@ -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.
  *
@@ -59,33 +67,6 @@ 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 (file)
index 0000000..b25943a
--- /dev/null
@@ -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
index dfba50a9a2262ecf02c9e95a026cc9d2abab77a7..7d960b6ac758538d0728b68501a803e0368dcc7d 100644 (file)
 
 #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");
index 0fb7e7baa0e1292468e0573d5d7424e7e7c165b2..e63f290e47de854ed9347433739eafc57564015d 100644 (file)
 
 #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 (file)
index 0000000..6924fad
--- /dev/null
@@ -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 (file)
index 0000000..d7b5c83
--- /dev/null
@@ -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;
+};
index 96aedac65bf4fb8718d8749712db3ae28d9ae627..594c0da34f1b5cdc7a55177e61bee698b5ac21d5 100644 (file)
@@ -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
 
 #include <boost/thread.hpp>
 #include <boost/filesystem.hpp>
-#include <libdcp/exceptions.h>
+#include <dcp/exceptions.h>
 #include "job.h"
 #include "util.h"
 #include "cross.h"
 #include "ui_signaller.h"
 #include "exceptions.h"
+#include "film.h"
+#include "log.h"
 
 #include "i18n.h"
 
@@ -66,8 +68,8 @@ Job::run_wrapper ()
 
                run ();
 
-       } catch (libdcp::FileError& e) {
-               
+       } catch (dcp::FileError& e) {
+
                string m = String::compose (_("An error occurred whilst handling the file %1."), boost::filesystem::path (e.filename()).leaf());
 
                try {
@@ -204,7 +206,7 @@ Job::set_state (State s)
        }       
 }
 
-/** @return Time (in seconds) that this sub-job has been running */
+/** @return DCPTime (in seconds) that this sub-job has been running */
 int
 Job::elapsed_time () const
 {
@@ -279,6 +281,7 @@ Job::error_summary () const
 void
 Job::set_error (string s, string d)
 {
+       _film->log()->log (String::compose ("Error in job: %1 (%2)", s, d), Log::TYPE_ERROR);
        boost::mutex::scoped_lock lm (_state_mutex);
        _error_summary = s;
        _error_details = d;
index 49bfae20ad727c36d62d057582c943c2edc9ac8c..dd2b756c19e79184b25cec9b95bed3e4a6540adc 100644 (file)
@@ -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);
index 8fb4ec494d2e2a52866e8fe68d1cc5a2ff92766a..b80ef454f2ddc2b3092b19bb55783f5f63aa5c01 100644 (file)
@@ -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 (file)
index 0000000..0908ed9
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+    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 boost::shared_ptr;
+
+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;
+       try {
+               magick_image = new Magick::Image (_blob);
+       } catch (...) {
+               throw DecodeError (_("Could not decode image file"));
+       }
+
+       dcp::Size size (magick_image->columns(), magick_image->rows());
+       LOG_TIMING ("[%1] MagickImageProxy decode finished", boost::this_thread::get_id ());
+
+       _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 ());
+}
diff --git a/src/lib/magick_image_proxy.h b/src/lib/magick_image_proxy.h
new file mode 100644 (file)
index 0000000..a263523
--- /dev/null
@@ -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 "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;
+
+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 (file)
index 0000000..be82f67
--- /dev/null
@@ -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 (file)
index 0000000..dac6cb7
--- /dev/null
@@ -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 (file)
index 494fb17..0000000
+++ /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;
-}
-
index 17b87b884664e49ef3ef787f87f5ce5ffbbe3b03..976409381d3a8744c69f5f14e53f67a5ca688e08 100644 (file)
 
 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
index 2d2977606786097f6b827ec07a8048dd7c90da59..c8ac591a7b2919a36a6f77080dad538417c08982 100644 (file)
 */
 
 #include <stdint.h>
+#include <algorithm>
 #include "player.h"
 #include "film.h"
 #include "ffmpeg_decoder.h"
+#include "audio_buffers.h"
 #include "ffmpeg_content.h"
 #include "image_decoder.h"
 #include "image_content.h"
 #include "sndfile_decoder.h"
 #include "sndfile_content.h"
 #include "subtitle_content.h"
+#include "subrip_decoder.h"
+#include "subrip_content.h"
 #include "playlist.h"
 #include "job.h"
 #include "image.h"
-#include "image_proxy.h"
+#include "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"
 
 #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
 
@@ -44,23 +54,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,578 +77,500 @@ 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
+               ) {
+               
+               _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_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_scale) / 2) and
+                *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
+                *
+                * Combining these two translations gives these expressions.
+                */
+
+               all.push_back (
+                       PositionImage (
+                               i->image->scale (
+                                       scaled_size,
+                                       Scaler::from_id ("bicubic"),
+                                       i->image->pixel_format (),
+                                       true
+                                       ),
+                               Position<int> (
+                                       rint (_video_container_size.width * 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 (),
+                       _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 ());
+               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 (),
+                                               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;
-       }
-
-       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);
+       /* 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()));
 
-       } else if (
-               property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
-               property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
-               property == SubtitleContentProperty::SUBTITLE_SCALE
-               ) {
-
-               for (list<Subtitle>::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
-                       i->update (_film, _video_container_size);
-               }
-               
-               Changed (frequent);
-
-       } else if (
-               property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_SCALE ||
-               property == 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;
-       }
+       /* 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 (!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()
-               );
-
-       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_scale ();
+                       i->sub.rectangle.height *= subtitle_content->subtitle_scale ();
+
+                       /* Apply a corrective translation to keep the subtitle centred after that scale */
+                       i->sub.rectangle.x -= i->sub.rectangle.width * (subtitle_content->subtitle_scale() - 1);
+                       i->sub.rectangle.y -= i->sub.rectangle.height * (subtitle_content->subtitle_scale() - 1);
+                       
+                       ps.image.push_back (i->sub);
+               }
+
+               list<ContentTextSubtitle> text = subtitle_decoder->get_text_subtitles (ContentTimePeriod (from, to), starting);
+               for (list<ContentTextSubtitle>::const_iterator i = text.begin(); i != text.end(); ++i) {
+                       copy (i->subs.begin(), i->subs.end(), back_inserter (ps.text));
+               }
+       }
 
-       return true;
+       return ps;
 }
index 6e70ad707cf79052cd24a64cbc0c1344020a2e1c..7fbeebc8c2f4432c85bd101fc3c2bea18ae0fc12 100644 (file)
 #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 class player_overlaps_test;
 
-       void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const ImageProxy>, Eyes, Part, bool, VideoContent::Frame, Time);
-       void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame, bool);
-       void process_subtitle (boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
        void setup_pieces ();
        void playlist_changed ();
        void content_changed (boost::weak_ptr<Content>, int, bool);
-       void do_seek (Time, bool);
        void flush ();
-       void emit_black ();
-       void emit_silence (OutputAudioFrame);
-       boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>, bool);
        void film_changed (Film::Property);
-       void update_subtitle ();
-
+       std::list<PositionImage> 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 (file)
index 0000000..46994ea
--- /dev/null
@@ -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 (file)
index 0000000..a44264c
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+    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,
+       dcp::Size inter_size,
+       dcp::Size out_size,
+       Scaler const * scaler,
+       Eyes eyes,
+       Part part,
+       ColourConversion colour_conversion
+       )
+       : _in (in)
+       , _time (time)
+       , _crop (crop)
+       , _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);
+
+       _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);
+
+       Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
+
+       if (burn_subtitle && _subtitle.image) {
+               out->alpha_blend (_subtitle.image, _subtitle.position);
+       }
+
+       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);
+       _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 ();
+}
diff --git a/src/lib/player_video.h b/src/lib/player_video.h
new file mode 100644 (file)
index 0000000..4fe8712
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+    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 "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 PlayerVideo
+{
+public:
+       PlayerVideo (boost::shared_ptr<const ImageProxy>, DCPTime, Crop, 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 (PositionImage);
+       
+       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;
+
+       bool has_j2k () const;
+       boost::shared_ptr<EncodedData> j2k () const;
+
+       DCPTime time () const {
+               return _time;
+       }
+
+       Eyes eyes () const {
+               return _eyes;
+       }
+
+       ColourConversion colour_conversion () const {
+               return _colour_conversion;
+       }
+
+private:
+       boost::shared_ptr<const ImageProxy> _in;
+       DCPTime _time;
+       Crop _crop;
+       dcp::Size _inter_size;
+       dcp::Size _out_size;
+       Scaler const * _scaler;
+       Eyes _eyes;
+       Part _part;
+       ColourConversion _colour_conversion;
+       PositionImage _subtitle;
+};
diff --git a/src/lib/player_video_frame.cc b/src/lib/player_video_frame.cc
deleted file mode 100644 (file)
index 94760e4..0000000
+++ /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/player_video_frame.h b/src/lib/player_video_frame.h
deleted file mode 100644 (file)
index b085cb6..0000000
+++ /dev/null
@@ -1,67 +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 "types.h"
-#include "position.h"
-#include "colour_conversion.h"
-
-class Image;
-class ImageProxy;
-class Scaler;
-class Socket;
-class Log;
-
-/** 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
-{
-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>);
-
-       void set_subtitle (boost::shared_ptr<const Image>, Position<int>);
-       
-       boost::shared_ptr<Image> image () const;
-
-       void add_metadata (xmlpp::Node* node) const;
-       void send_binary (boost::shared_ptr<Socket> socket) const;
-
-       Eyes eyes () const {
-               return _eyes;
-       }
-
-       ColourConversion colour_conversion () const {
-               return _colour_conversion;
-       }
-
-private:
-       boost::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;
-       boost::shared_ptr<const Image> _subtitle_image;
-       Position<int> _subtitle_position;
-};
index 8b874aae6676a757b7bf4e01b4e1bb13599478ef..16c740943ca89f382f27082ba70a935d987c484b 100644 (file)
@@ -80,20 +80,20 @@ Playlist::maybe_sequence_video ()
        _sequencing_video = true;
        
        ContentList cl = _content;
-       Time next_left = 0;
-       Time next_right = 0;
+       DCPTime next_left;
+       DCPTime next_right;
        for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
                shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i);
                if (!vc) {
                        continue;
                }
-       
+               
                if (vc->video_frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) {
                        vc->set_position (next_right);
-                       next_right = vc->end() + 1;
+                       next_right = vc->end() + DCPTime::delta ();
                } else {
                        vc->set_position (next_left);
-                       next_left = vc->end() + 1;
+                       next_left = vc->end() + DCPTime::delta ();
                }
        }
 
@@ -121,7 +121,7 @@ Playlist::video_identifier () const
 
 /** @param node <Playlist> node */
 void
-Playlist::set_from_xml (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node, int version, list<string>& notes)
+Playlist::set_from_xml (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version, list<string>& notes)
 {
        list<cxml::NodePtr> c = node->node_children ("Content");
        for (list<cxml::NodePtr>::iterator i = c.begin(); i != c.end(); ++i) {
@@ -186,19 +186,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:
@@ -262,12 +249,12 @@ Playlist::best_dcp_frame_rate () const
        return best->dcp;
 }
 
-Time
+DCPTime
 Playlist::length () const
 {
-       Time len = 0;
+       DCPTime len;
        for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
-               len = max (len, (*i)->end() + 1);
+               len = max (len, (*i)->end() + DCPTime::delta ());
        }
 
        return len;
@@ -287,10 +274,10 @@ Playlist::reconnect ()
        }
 }
 
-Time
+DCPTime
 Playlist::video_end () const
 {
-       Time end = 0;
+       DCPTime end;
        for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
                if (dynamic_pointer_cast<const VideoContent> (*i)) {
                        end = max (end, (*i)->end ());
@@ -300,6 +287,23 @@ Playlist::video_end () const
        return end;
 }
 
+FrameRateChange
+Playlist::active_frame_rate_change (DCPTime t, int dcp_video_frame_rate) const
+{
+       for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+               shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i);
+               if (!vc) {
+                       continue;
+               }
+
+               if (vc->position() >= t && t < vc->end()) {
+                       return FrameRateChange (vc->video_frame_rate(), dcp_video_frame_rate);
+               }
+       }
+
+       return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate);
+}
+
 void
 Playlist::set_sequence_video (bool s)
 {
@@ -322,7 +326,7 @@ Playlist::content () const
 void
 Playlist::repeat (ContentList c, int n)
 {
-       pair<Time, Time> range (TIME_MAX, 0);
+       pair<DCPTime, DCPTime> range (DCPTime::max (), DCPTime ());
        for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
                range.first = min (range.first, (*i)->position ());
                range.second = max (range.second, (*i)->position ());
@@ -330,7 +334,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 ();
@@ -364,7 +368,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 ());
@@ -393,20 +397,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);
-}
index 12380696bdf49349debb1c334915c58493a9b187..9e3dbb6dfbf39ccf2f6f6864d1ee2920983ff447 100644 (file)
@@ -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 ();
index f9bd0987cdb5ed3e2688ed63bf0d5cfdbbf1597d..345f7ab4a85bcd8b0cd7565543e4736f26e90968 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -21,7 +21,7 @@
 #define DCPOMATIC_POSITION_H
 
 /** @struct Position
- *  @brief A position.
+ *  @brief A position (x and y coordinates)
  */
 template <class T>
 class Position
@@ -50,4 +50,18 @@ operator+ (Position<T> const & a, Position<T> const & b)
        return Position<T> (a.x + b.x, a.y + b.y);
 }
 
+template<class T>
+Position<T>
+operator- (Position<T> const & a, Position<T> const & b)
+{
+       return Position<T> (a.x - b.x, a.y - b.y);
+}
+
+template<class T>
+bool
+operator== (Position<T> const & a, Position<T> const & b)
+{
+       return a.x == b.x && a.y == b.y;
+}
+
 #endif
diff --git a/src/lib/position_image.h b/src/lib/position_image.h
new file mode 100644 (file)
index 0000000..dbd3c60
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_POSITION_IMAGE_H
+#define DCPOMATIC_POSITION_IMAGE_H
+
+#include "position.h"
+
+class Image;
+
+class PositionImage
+{
+public:
+       PositionImage () {}
+       
+       PositionImage (boost::shared_ptr<Image> i, Position<int> p)
+               : image (i)
+               , position (p)
+       {}
+       
+       boost::shared_ptr<Image> image;
+       Position<int> position;
+};
+
+#endif
index 52577d3bbc5158f7ccdfea8a561caa0f055107c1..fbd7022322c5b0e9e02d8e94785baaa54e23c4c5 100644 (file)
@@ -17,7 +17,7 @@
 
 */
 
-#include <libdcp/types.h>
+#include <dcp/types.h>
 #include "ratio.h"
 #include "util.h"
 
index 8b1a1fc716dc61d9175c6144e30f34ea27bdf39f..22fc7662c9c9cf28d2a625d45256f8cfcaf789a7 100644 (file)
@@ -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 (file)
index 0000000..7e0688d
--- /dev/null
@@ -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 (file)
index 0000000..6707f68
--- /dev/null
@@ -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;
+};
index 6f4709c088ce137b2c589df64cb69304c1b62373..1feb8ad4fed460d19a9f83439e21bdd0abcc1ea0 100644 (file)
@@ -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 (file)
index 0000000..5364b8d
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <cairomm/cairomm.h>
+#include <pangomm.h>
+#include "render_subtitles.h"
+#include "types.h"
+#include "image.h"
+
+using std::list;
+using std::cout;
+using std::string;
+using std::min;
+using std::max;
+using std::pair;
+using boost::shared_ptr;
+using boost::optional;
+
+static int
+calculate_position (dcp::VAlign v_align, double v_position, int target_height, int offset)
+{
+       switch (v_align) {
+       case dcp::TOP:
+               return (v_position / 100) * target_height - offset;
+       case dcp::CENTER:
+               return (0.5 + v_position / 100) * target_height - offset;
+       case dcp::BOTTOM:
+               return (1.0 - v_position / 100) * target_height - offset;
+       }
+
+       return 0;
+}
+
+PositionImage
+render_subtitles (list<dcp::SubtitleString> subtitles, dcp::Size target)
+{
+       if (subtitles.empty ()) {
+               return PositionImage ();
+       }
+
+       /* Estimate height that the subtitle image needs to be */
+       optional<int> top;
+       optional<int> bottom;
+       for (list<dcp::SubtitleString>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
+               int const b = calculate_position (i->v_align(), i->v_position(), target.height, 0);
+               int const t = b - i->size() * target.height / (11 * 72);
+
+               top = min (top.get_value_or (t), t);
+               bottom = max (bottom.get_value_or (b), b);
+       }
+
+       top = top.get() - 32;
+       bottom = bottom.get() + 32;
+
+       shared_ptr<Image> image (new Image (PIX_FMT_RGBA, dcp::Size (target.width, bottom.get() - top.get ()), false));
+       image->make_black ();
+
+       Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create (
+               image->data()[0],
+               Cairo::FORMAT_ARGB32,
+               image->size().width,
+               image->size().height,
+               Cairo::ImageSurface::format_stride_for_width (Cairo::FORMAT_ARGB32, image->size().width)
+               );
+       
+       Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (surface);
+       Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
+
+       layout->set_width (image->size().width * PANGO_SCALE);
+       layout->set_alignment (Pango::ALIGN_CENTER);
+
+       context->set_line_width (1);
+
+       for (list<dcp::SubtitleString>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
+               string f = i->font ();
+               if (f.empty ()) {
+                       f = "Arial";
+               }
+               Pango::FontDescription font (f);
+               font.set_absolute_size (i->size_in_pixels (target.height) * PANGO_SCALE);
+               if (i->italic ()) {
+                       font.set_style (Pango::STYLE_ITALIC);
+               }
+               layout->set_font_description (font);
+               layout->set_text (i->text ());
+
+               /* Compute fade factor */
+               /* XXX */
+               float fade_factor = 1;
+#if 0          
+               dcp::Time now (time * 1000 / (4 * TIME_HZ));
+               dcp::Time end_fade_up = i->in() + i->fade_up_time ();
+               dcp::Time start_fade_down = i->out() - i->fade_down_time ();
+               if (now < end_fade_up) {
+                       fade_factor = (now - i->in()) / i->fade_up_time();
+               } else if (now > start_fade_down) {
+                       fade_factor = 1.0 - ((now - start_fade_down) / i->fade_down_time ());
+               }
+#endif         
+
+               layout->update_from_cairo_context (context);
+               
+               /* Work out position */
+
+               int const x = 0;
+               int const y = calculate_position (i->v_align (), i->v_position (), target.height, (layout->get_baseline() / PANGO_SCALE) + top.get ());
+
+               if (i->effect() == dcp::SHADOW) {
+                       /* Drop-shadow effect */
+                       dcp::Color const ec = i->effect_color ();
+                       context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
+                       context->move_to (x + 4, y + 4);
+                       layout->add_to_cairo_context (context);
+                       context->fill ();
+               }
+
+               /* The actual subtitle */
+               context->move_to (x, y);
+               dcp::Color const c = i->color ();
+               context->set_source_rgba (float(c.r) / 255, float(c.g) / 255, float(c.b) / 255, fade_factor);
+               layout->add_to_cairo_context (context);
+               context->fill ();
+
+               if (i->effect() == dcp::BORDER) {
+                       /* Border effect */
+                       context->move_to (x, y);
+                       dcp::Color ec = i->effect_color ();
+                       context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
+                       layout->add_to_cairo_context (context);
+                       context->stroke ();
+               }
+       }
+
+       return PositionImage (image, Position<int> (0, top.get ()));
+}
+
diff --git a/src/lib/render_subtitles.h b/src/lib/render_subtitles.h
new file mode 100644 (file)
index 0000000..d83dc11
--- /dev/null
@@ -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);
index e7f50c4b7bf7fc2174248b2317d3d50d45813da9..e414436e8b39960c6c5f78ca16188c0781bc0407 100644 (file)
@@ -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>
index 69ec83ba93047f3493cd23005487b7845580944e..4ee11a7f0954958e69a99bf09be75196394838c3 100644 (file)
@@ -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:       
index 1dec2ffb001dc7ce77fdfd4d1c595e8646bb90c9..de03222725074b9a6a9fdfa670a463de0f88f396 100644 (file)
@@ -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)
index 8a8903040c06b573815fe2d3be9b999fa90c47b1..084836715f865fe6a62b52eae1e7ac1536bd202f 100644 (file)
@@ -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;
@@ -44,5 +44,5 @@ private:
        boost::filesystem::path _dcp;
        boost::posix_time::ptime _from;
        boost::posix_time::ptime _to;
-       libdcp::KDM::Formulation _formulation;
+       dcp::Formulation _formulation;
 };
index f1c6d6c44afd0e34d6eb3436a1a493042a6a52c8..5598ef69f8388aae66098669ad9361e369691ab9 100644 (file)
 #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 "i18n.h"
 
@@ -62,16 +63,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.
  */
@@ -91,9 +113,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);
        
@@ -117,10 +139,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 ();
                
@@ -187,39 +213,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);
 
@@ -229,7 +234,7 @@ try
                boost::bind (&Server::broadcast_received, this)
                );
 
-       io_service.run ();
+       _broadcast.io_service.run ();
 }
 catch (...)
 {
@@ -264,3 +269,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 ();
+}
+       
index b925031eb2df667cfff90f64a71eacdf3ecb6d7f..b340bba077953c7320c0fb9f1820e3ca2cf2a556 100644 (file)
@@ -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,11 @@ private:
        boost::shared_ptr<Log> _log;
        bool _verbose;
 
+       boost::asio::io_service _io_service;
+       boost::asio::ip::tcp::acceptor _acceptor;
+
+       int _num_threads;
+
        struct Broadcast {
 
                Broadcast ()
@@ -118,6 +128,7 @@ private:
                boost::asio::ip::udp::socket* socket;
                char buffer[64];
                boost::asio::ip::udp::endpoint send_endpoint;
+               boost::asio::io_service io_service;
                
        } _broadcast;
 };
index 65e0940a0a11aab9469f32247a0a58ba36e11ba3..9b0cd037679f9351c5a9112537dbef283235a4ed 100644 (file)
@@ -18,7 +18,7 @@
 */
 
 #include <libcxml/cxml.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
 #include "server_finder.h"
 #include "exceptions.h"
 #include "util.h"
@@ -33,7 +33,7 @@ using std::vector;
 using std::cout;
 using boost::shared_ptr;
 using boost::scoped_array;
-using libdcp::raw_convert;
+using dcp::raw_convert;
 
 ServerFinder* ServerFinder::_instance = 0;
 
diff --git a/src/lib/single_stream_audio_content.cc b/src/lib/single_stream_audio_content.cc
new file mode 100644 (file)
index 0000000..5215976
--- /dev/null
@@ -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 (file)
index 0000000..9abc25e
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+    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;
+
+       /* AudioContent */
+       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
index 3efba6fd5db219c38e6f93249d28155786020934..a573d43c407f8ac8a825190a1784600ba37ac873 100644 (file)
@@ -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::stringstream;
 using std::cout;
 using boost::shared_ptr;
-using libdcp::raw_convert;
+using dcp::raw_convert;
 
 SndfileContent::SndfileContent (shared_ptr<const Film> f, boost::filesystem::path p)
        : Content (f, p)
-       , 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);
-}
index a32043c5c63660caf3ec6bcf7aba59ac37158f66..75c723518ea61ebf48ed6e06163b2894e71fe53b 100644 (file)
 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
index f66a7c7dc9dd363f649f2c71632a9a5057d78949..602014d5883d2dba1381588bb44a7036debc9c96 100644 (file)
@@ -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;
 }
index 77fa6d17734da4096757dae504a759c9925e0e8b..41d5faf082ec29c75b2c2e1059f528c59a26e0ff 100644 (file)
 #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/sound_processor.cc b/src/lib/sound_processor.cc
deleted file mode 100644 (file)
index 9be6621..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/sound_processor.cc
- *  @brief A class to describe a sound processor.
- */
-
-#include <iostream>
-#include <cassert>
-#include "sound_processor.h"
-#include "dolby_cp750.h"
-
-using namespace std;
-
-vector<SoundProcessor const *> SoundProcessor::_sound_processors;
-
-/** @param i Our id.
- *  @param n User-visible name.
- */
-SoundProcessor::SoundProcessor (string i, string n)
-       : _id (i)
-       , _name (n)
-{
-
-}
-
-/** @return All available sound processors */
-vector<SoundProcessor const *>
-SoundProcessor::all ()
-{
-       return _sound_processors;
-}
-
-/** Set up the static _sound_processors vector; must be called before from_*
- *  methods are used.
- */
-void
-SoundProcessor::setup_sound_processors ()
-{
-       _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)
-{
-       vector<SoundProcessor const *>::iterator i = _sound_processors.begin ();
-       while (i != _sound_processors.end() && (*i)->id() != id) {
-               ++i;
-       }
-
-       if (i == _sound_processors.end ()) {
-               return 0;
-       }
-
-       return *i;
-}
-
-/** @param s A sound processor from our static list.
- *  @return Index of the sound processor with the list, or -1.
- */
-int
-SoundProcessor::as_index (SoundProcessor const * s)
-{
-       vector<SoundProcessor*>::size_type i = 0;
-       while (i < _sound_processors.size() && _sound_processors[i] != s) {
-               ++i;
-       }
-
-       if (i == _sound_processors.size ()) {
-               return -1;
-       }
-
-       return i;
-}
-
-/** @param i An index returned from as_index().
- *  @return Corresponding sound processor.
- */
-SoundProcessor const *
-SoundProcessor::from_index (int i)
-{
-       assert (i <= int(_sound_processors.size ()));
-       return _sound_processors[i];
-}
diff --git a/src/lib/sound_processor.h b/src/lib/sound_processor.h
deleted file mode 100644 (file)
index 8f26522..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/sound_processor.h
- *  @brief A class to describe a sound processor.
- */
-
-#ifndef DCPOMATIC_SOUND_PROCESSOR_H
-#define DCPOMATIC_SOUND_PROCESSOR_H
-
-#include <string>
-#include <vector>
-#include <boost/utility.hpp>
-
-/** @class SoundProcessor
- *  @brief Class to describe a sound processor.
- */
-class SoundProcessor : public boost::noncopyable
-{
-public:
-       SoundProcessor (std::string i, std::string n);
-
-       virtual float db_for_fader_change (float from, float to) const = 0;
-
-       /** @return id for our use */
-       std::string id () const {
-               return _id;
-       }
-
-       /** @return user-visible name for this sound processor */
-       std::string name () const {
-               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 *);
-
-private:
-       /** id for our use */
-       std::string _id;
-       /** user-visible name for this sound processor */
-       std::string _name;
-
-       /** sll available sound processors */
-       static std::vector<SoundProcessor const *> _sound_processors;
-};
-
-#endif
diff --git a/src/lib/subrip.cc b/src/lib/subrip.cc
new file mode 100644 (file)
index 0000000..11ad330
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+#include "subrip.h"
+#include "subrip_content.h"
+#include "subrip_subtitle.h"
+#include "cross.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::list;
+using std::vector;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::algorithm::trim;
+
+SubRip::SubRip (shared_ptr<const SubRipContent> content)
+{
+       FILE* f = fopen_boost (content->path (0), "r");
+       if (!f) {
+               throw OpenFileError (content->path (0));
+       }
+
+       enum {
+               COUNTER,
+               METADATA,
+               CONTENT
+       } state = COUNTER;
+
+       char buffer[256];
+       int next_count = 1;
+
+       boost::optional<SubRipSubtitle> current;
+       list<string> lines;
+       
+       while (!feof (f)) {
+               fgets (buffer, sizeof (buffer), f);
+               if (feof (f)) {
+                       break;
+               }
+               
+               string line (buffer);
+               trim_right_if (line, boost::is_any_of ("\n\r"));
+               
+               switch (state) {
+               case COUNTER:
+               {
+                       if (line.empty ()) {
+                               /* a blank line at the start is ok */
+                               break;
+                       }
+                       
+                       int x = 0;
+                       try {
+                               x = lexical_cast<int> (line);
+                       } catch (...) {
+
+                       }
+
+                       if (x == next_count) {
+                               state = METADATA;
+                               ++next_count;
+                               current = SubRipSubtitle ();
+                       } else {
+                               throw SubRipError (line, _("a subtitle count"), content->path (0));
+                       }
+               }
+               break;
+               case METADATA:
+               {
+                       vector<string> p;
+                       boost::algorithm::split (p, line, boost::algorithm::is_any_of (" "));
+                       if (p.size() != 3 && p.size() != 7) {
+                               throw SubRipError (line, _("a time/position line"), content->path (0));
+                       }
+
+                       current->period = ContentTimePeriod (convert_time (p[0]), convert_time (p[2]));
+
+                       if (p.size() > 3) {
+                               current->x1 = convert_coordinate (p[3]);
+                               current->x2 = convert_coordinate (p[4]);
+                               current->y1 = convert_coordinate (p[5]);
+                               current->y2 = convert_coordinate (p[6]);
+                       }
+                       state = CONTENT;
+                       break;
+               }
+               case CONTENT:
+                       if (line.empty ()) {
+                               state = COUNTER;
+                               current->pieces = convert_content (lines);
+                               _subtitles.push_back (current.get ());
+                               current.reset ();
+                               lines.clear ();
+                       } else {
+                               lines.push_back (line);
+                       }
+                       break;
+               }
+       }
+
+       if (state == CONTENT) {
+               current->pieces = convert_content (lines);
+               _subtitles.push_back (current.get ());
+       }
+
+       fclose (f);
+}
+
+ContentTime
+SubRip::convert_time (string t)
+{
+       ContentTime r;
+
+       vector<string> a;
+       boost::algorithm::split (a, t, boost::is_any_of (":"));
+       assert (a.size() == 3);
+       r += ContentTime::from_seconds (lexical_cast<int> (a[0]) * 60 * 60);
+       r += ContentTime::from_seconds (lexical_cast<int> (a[1]) * 60);
+
+       vector<string> b;
+       boost::algorithm::split (b, a[2], boost::is_any_of (","));
+       r += ContentTime::from_seconds (lexical_cast<int> (b[0]));
+       r += ContentTime::from_seconds (lexical_cast<double> (b[1]) / 1000);
+
+       return r;
+}
+
+int
+SubRip::convert_coordinate (string t)
+{
+       vector<string> a;
+       boost::algorithm::split (a, t, boost::is_any_of (":"));
+       assert (a.size() == 2);
+       return lexical_cast<int> (a[1]);
+}
+
+void
+SubRip::maybe_content (list<SubRipSubtitlePiece>& pieces, SubRipSubtitlePiece& p)
+{
+       if (!p.text.empty ()) {
+               pieces.push_back (p);
+               p.text.clear ();
+       }
+}
+
+list<SubRipSubtitlePiece>
+SubRip::convert_content (list<string> t)
+{
+       list<SubRipSubtitlePiece> pieces;
+       
+       SubRipSubtitlePiece p;
+
+       enum {
+               TEXT,
+               TAG
+       } state = TEXT;
+
+       string tag;
+
+       /* XXX: missing <font> support */
+       /* XXX: nesting of tags e.g. <b>foo<i>bar<b>baz</b>fred</i>jim</b> might
+          not work, I think.
+       */
+
+       for (list<string>::const_iterator i = t.begin(); i != t.end(); ++i) {
+               for (size_t j = 0; j < i->size(); ++j) {
+                       switch (state) {
+                       case TEXT:
+                               if ((*i)[j] == '<' || (*i)[j] == '{') {
+                                       state = TAG;
+                               } else {
+                                       p.text += (*i)[j];
+                               }
+                               break;
+                       case TAG:
+                               if ((*i)[j] == '>' || (*i)[j] == '}') {
+                                       if (tag == "b") {
+                                               maybe_content (pieces, p);
+                                               p.bold = true;
+                                       } else if (tag == "/b") {
+                                               maybe_content (pieces, p);
+                                               p.bold = false;
+                                       } else if (tag == "i") {
+                                               maybe_content (pieces, p);
+                                               p.italic = true;
+                                       } else if (tag == "/i") {
+                                               maybe_content (pieces, p);
+                                               p.italic = false;
+                                       } else if (tag == "u") {
+                                               maybe_content (pieces, p);
+                                               p.underline = true;
+                                       } else if (tag == "/u") {
+                                               maybe_content (pieces, p);
+                                               p.underline = false;
+                                       }
+                                       tag.clear ();
+                                       state = TEXT;
+                               } else {
+                                       tag += (*i)[j];
+                               }
+                               break;
+                       }
+               }
+       }
+
+       maybe_content (pieces, p);
+
+       return pieces;
+}
+
+ContentTime
+SubRip::length () const
+{
+       if (_subtitles.empty ()) {
+               return ContentTime ();
+       }
+
+       return _subtitles.back().period.to;
+}
diff --git a/src/lib/subrip.h b/src/lib/subrip.h
new file mode 100644 (file)
index 0000000..e7d2167
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_H
+#define DCPOMATIC_SUBRIP_H
+
+#include "subrip_subtitle.h"
+
+class SubRipContent;
+class subrip_time_test;
+class subrip_coordinate_test;
+class subrip_content_test;
+class subrip_parse_test;
+
+class SubRip
+{
+public:
+       SubRip (boost::shared_ptr<const SubRipContent>);
+
+       ContentTime length () const;
+
+protected:
+       std::vector<SubRipSubtitle> _subtitles;
+       
+private:
+       friend class subrip_time_test;
+       friend class subrip_coordinate_test;
+       friend class subrip_content_test;
+       friend class subrip_parse_test;
+       
+       static ContentTime convert_time (std::string);
+       static int convert_coordinate (std::string);
+       static std::list<SubRipSubtitlePiece> convert_content (std::list<std::string>);
+       static void maybe_content (std::list<SubRipSubtitlePiece> &, SubRipSubtitlePiece &);
+};
+
+#endif
diff --git a/src/lib/subrip_content.cc b/src/lib/subrip_content.cc
new file mode 100644 (file)
index 0000000..c60b05d
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "subrip_content.h"
+#include "util.h"
+#include "subrip.h"
+#include "film.h"
+#include <dcp/raw_convert.h>
+
+#include "i18n.h"
+
+using std::stringstream;
+using std::string;
+using std::cout;
+using dcp::raw_convert;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, boost::filesystem::path path)
+       : Content (film, path)
+       , SubtitleContent (film, path)
+{
+
+}
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version)
+       : Content (film, node)
+       , SubtitleContent (film, node, version)
+       , _length (node->number_child<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 (file)
index 0000000..d2dcdee
--- /dev/null
@@ -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 (file)
index 0000000..e2bdc34
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <dcp/subtitle_string.h>
+#include "subrip_decoder.h"
+#include "subrip_content.h"
+
+using std::list;
+using std::vector;
+using boost::shared_ptr;
+
+SubRipDecoder::SubRipDecoder (shared_ptr<const SubRipContent> content)
+       : SubtitleDecoder (content)
+       , SubRip (content)
+       , _next (0)
+{
+
+}
+
+void
+SubRipDecoder::seek (ContentTime time, bool accurate)
+{
+       SubtitleDecoder::seek (time, accurate);
+       
+       _next = 0;
+       list<SubRipSubtitlePiece>::const_iterator i = _subtitles[_next].pieces.begin();
+       while (i != _subtitles[_next].pieces.end() && _subtitles[_next].period.from < time) {
+               ++i;
+       }
+       
+}
+
+bool
+SubRipDecoder::pass ()
+{
+       if (_next >= _subtitles.size ()) {
+               return true;
+       }
+       
+       list<dcp::SubtitleString> out;
+       for (list<SubRipSubtitlePiece>::const_iterator i = _subtitles[_next].pieces.begin(); i != _subtitles[_next].pieces.end(); ++i) {
+               out.push_back (
+                       dcp::SubtitleString (
+                               "Arial",
+                               i->italic,
+                               dcp::Color (255, 255, 255),
+                               72,
+                               dcp::Time (rint (_subtitles[_next].period.from.seconds() * 250)),
+                               dcp::Time (rint (_subtitles[_next].period.to.seconds() * 250)),
+                               0.9,
+                               dcp::BOTTOM,
+                               i->text,
+                               dcp::NONE,
+                               dcp::Color (255, 255, 255),
+                               0,
+                               0
+                               )
+                       );
+       }
+
+       text_subtitle (out);
+       ++_next;
+       return false;
+}
+
+list<ContentTimePeriod>
+SubRipDecoder::subtitles_during (ContentTimePeriod p, bool starting) const
+{
+       /* XXX: inefficient */
+
+       list<ContentTimePeriod> d;
+
+       for (vector<SubRipSubtitle>::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
+               if ((starting && p.contains (i->period.from)) || (!starting && p.overlaps (i->period))) {
+                       d.push_back (i->period);
+               }
+       }
+
+       return d;
+}
diff --git a/src/lib/subrip_decoder.h b/src/lib/subrip_decoder.h
new file mode 100644 (file)
index 0000000..ad9d04e
--- /dev/null
@@ -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 (file)
index 0000000..646fc1f
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_SUBTITLE_H
+#define DCPOMATIC_SUBRIP_SUBTITLE_H
+
+#include <boost/optional.hpp>
+#include <dcp/types.h>
+#include "types.h"
+#include "dcpomatic_time.h"
+
+struct SubRipSubtitlePiece
+{
+       SubRipSubtitlePiece ()
+               : bold (false)
+               , italic (false)
+               , underline (false)
+       {}
+       
+       std::string text;
+       bool bold;
+       bool italic;
+       bool underline;
+       dcp::Color color;
+};
+
+struct SubRipSubtitle
+{
+       ContentTimePeriod period;
+       boost::optional<int> x1;
+       boost::optional<int> x2;
+       boost::optional<int> y1;
+       boost::optional<int> y2;
+       std::list<SubRipSubtitlePiece> pieces;
+};
+
+#endif
diff --git a/src/lib/subtitle.cc b/src/lib/subtitle.cc
deleted file mode 100644 (file)
index 0d18861..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
-    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "subtitle.h"
-#include "subtitle_content.h"
-#include "piece.h"
-#include "image.h"
-#include "scaler.h"
-#include "film.h"
-
-using boost::shared_ptr;
-using boost::dynamic_pointer_cast;
-using boost::weak_ptr;
-
-Subtitle::Subtitle (shared_ptr<const Film> film, libdcp::Size video_container_size, weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
-       : _piece (weak_piece)
-       , _in_image (image)
-       , _in_rect (rect)
-       , _in_from (from)
-       , _in_to (to)
-{
-       update (film, video_container_size);
-}
-
-void
-Subtitle::update (shared_ptr<const Film> film, libdcp::Size video_container_size)
-{
-       shared_ptr<Piece> piece = _piece.lock ();
-       if (!piece) {
-               return;
-       }
-
-       if (!_in_image) {
-               _out_image.reset ();
-               return;
-       }
-
-       shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
-       assert (sc);
-
-       dcpomatic::Rect<double> in_rect = _in_rect;
-       libdcp::Size scaled_size;
-
-       in_rect.x += sc->subtitle_x_offset ();
-       in_rect.y += sc->subtitle_y_offset ();
-
-       /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
-       scaled_size.width = in_rect.width * video_container_size.width * sc->subtitle_scale ();
-       scaled_size.height = in_rect.height * video_container_size.height * sc->subtitle_scale ();
-
-       /* Then we need a corrective translation, consisting of two parts:
-        *
-        * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
-        *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
-        *
-        * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
-        *     (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
-        *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
-        *
-        * Combining these two translations gives these expressions.
-        */
-       
-       _out_position.x = rint (video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
-       _out_position.y = rint (video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
-       
-       _out_image = _in_image->scale (
-               scaled_size,
-               Scaler::from_id ("bicubic"),
-               _in_image->pixel_format (),
-               true
-               );
-
-       /* XXX: hack */
-       Time from = _in_from;
-       Time to = _in_to;
-       shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (piece->content);
-       if (vc) {
-               from = rint (from * vc->video_frame_rate() / film->video_frame_rate());
-               to = rint (to * vc->video_frame_rate() / film->video_frame_rate());
-       }
-       
-       _out_from = from + piece->content->position ();
-       _out_to = to + piece->content->position ();
-
-       check_out_to ();
-}
-
-bool
-Subtitle::covers (Time t) const
-{
-       return _out_from <= t && t <= _out_to;
-}
-
-void
-Subtitle::check_out_to ()
-{
-       if (_stop && _out_to > _stop.get ()) {
-               _out_to = _stop.get ();
-       }
-}
diff --git a/src/lib/subtitle.h b/src/lib/subtitle.h
deleted file mode 100644 (file)
index c74f5c1..0000000
+++ /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;
-};
index 0abb7d491ec57ffff13e2d483fc67e282fd473fb..3415ae613f5aee8175e122a9dea0d0517b791cf1 100644 (file)
@@ -18,7 +18,7 @@
 */
 
 #include <libcxml/cxml.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
 #include "subtitle_content.h"
 #include "util.h"
 #include "exceptions.h"
 #include "i18n.h"
 
 using std::string;
+using std::stringstream;
 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_SCALE = 502;
+int const SubtitleContentProperty::USE_SUBTITLES = 503;
+
+SubtitleContent::SubtitleContent (shared_ptr<const Film> f)
+       : Content (f)
+       , _use_subtitles (false)
+       , _subtitle_x_offset (0)
+       , _subtitle_y_offset (0)
+       , _subtitle_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_scale (1)
@@ -44,13 +58,15 @@ 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_scale (1)
 {
        if (version >= 7) {
+               _use_subtitles = node->bool_child ("UseSubtitles");
                _subtitle_x_offset = node->number_child<float> ("SubtitleXOffset");
                _subtitle_y_offset = node->number_child<float> ("SubtitleYOffset");
        } else {
@@ -69,6 +85,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."));
                }
@@ -82,6 +102,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_scale = ref->subtitle_scale ();
@@ -90,11 +111,22 @@ 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("SubtitleScale")->add_child_text (raw_convert<string> (_subtitle_scale));
 }
 
+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)
 {
@@ -124,3 +156,15 @@ SubtitleContent::set_subtitle_scale (double s)
        }
        signal_changed (SubtitleContentProperty::SUBTITLE_SCALE);
 }
+
+string
+SubtitleContent::identifier () const
+{
+       stringstream s;
+       s << Content::identifier()
+         << "_" << raw_convert<string> (subtitle_scale())
+         << "_" << raw_convert<string> (subtitle_x_offset())
+         << "_" << raw_convert<string> (subtitle_y_offset());
+
+       return s.str ();
+}
index 38863768889adb1cbea41dd019afc6fad1517ea0..57f5eb9509264d68957ac8f99ef282932cbb28f9 100644 (file)
@@ -28,21 +28,38 @@ public:
        static int const SUBTITLE_X_OFFSET;
        static int const SUBTITLE_Y_OFFSET;
        static int const SUBTITLE_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_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;
@@ -57,10 +74,11 @@ public:
                boost::mutex::scoped_lock lm (_mutex);
                return _subtitle_scale;
        }
-       
+
 private:
        friend class 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.
         */
index c06f3d718a812e9403266e3ccf91f5c494fa69b8..e1485e221d70807109e3803b0493c9d8524fe974 100644 (file)
@@ -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
 
 #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 ();
 }
index eeeadbd3f65fdda2f9ee46b90c9745f2ce713f8f..142cfa42b766b0813fb1570d530268d886beee2c 100644 (file)
 
 */
 
-#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
index ef15f9f5e9865968ecee6f6b87e0437e672d1e66..4a85fa18aa8935d0a2c0ff5670f504de012b439e 100644 (file)
@@ -37,6 +37,7 @@ using std::string;
 using std::stringstream;
 using std::fixed;
 using std::setprecision;
+using std::cout;
 using boost::shared_ptr;
 
 /** @param s Film to use.
@@ -106,6 +107,7 @@ TranscodeJob::status () const
        return s.str ();
 }
 
+/** @return Approximate remaining time in seconds */
 int
 TranscodeJob::remaining_time () const
 {
@@ -123,6 +125,5 @@ TranscodeJob::remaining_time () const
        }
 
        /* Compute approximate proposed length here, as it's only here that we need it */
-       OutputVideoFrame const left = _film->time_to_video_frames (_film->length ()) - t->video_frames_out();
-       return left / fps;
+       return (_film->length().frames (_film->video_frame_rate ()) - t->video_frames_out()) / fps;
 }
index b11ce8be599aaab0d8c3aa63f210199066fdd279..1012c45442b69320e9b4bd7aac14143167755065 100644 (file)
 #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 ());
+       for (DCPTime t; t < _film->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
index d7736d4e8e56dd3a1cdf7440e6a5872bb7a59db1..ed0a6b1b561bb45a628080819d0866944e8a37f8 100644 (file)
@@ -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;
 };
index 83bbf41e45c19125e779b4be18f6a48e9b2a5cd0..d052b2a9a71dd3c458bc9126785a15af263e8e6c 100644 (file)
 
 #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)
 {
index 4eb3d927e5090c8af08ef0c3cdc1167e9fe88929..9a6a30b861d45344325507186f5c08b2b200ea42 100644 (file)
@@ -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;
 
index af3e46f0e7d14ea1abb8d64c4c4e467143588547..44ecbb232c26b03214f23d3191c54fedd9a9071a 100644 (file)
@@ -22,7 +22,7 @@
 #include <boost/algorithm/string.hpp>
 #include <curl/curl.h>
 #include <libcxml/cxml.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
 #include "update.h"
 #include "version.h"
 #include "ui_signaller.h"
@@ -33,8 +33,9 @@ using std::cout;
 using std::min;
 using std::string;
 using std::stringstream;
-using libdcp::raw_convert;
+using dcp::raw_convert;
 
+/** Singleton instance */
 UpdateChecker* UpdateChecker::_instance = 0;
 
 static size_t
@@ -43,6 +44,9 @@ write_callback_wrapper (void* data, size_t size, size_t nmemb, void* user)
        return reinterpret_cast<UpdateChecker*>(user)->write_callback (data, size, nmemb);
 }
 
+/** Construct an UpdateChecker.  This sets things up and starts a thread to
+ *  do the work.
+ */
 UpdateChecker::UpdateChecker ()
        : _buffer (new char[BUFFER_SIZE])
        , _offset (0)
@@ -74,6 +78,7 @@ UpdateChecker::~UpdateChecker ()
        delete[] _buffer;
 }
 
+/** Start running the update check */
 void
 UpdateChecker::run ()
 {
@@ -86,6 +91,7 @@ void
 UpdateChecker::thread ()
 {
        while (true) {
+               /* Block until there is something to do */
                boost::mutex::scoped_lock lock (_process_mutex);
                while (_to_do == 0) {
                        _condition.wait (lock);
@@ -95,12 +101,16 @@ UpdateChecker::thread ()
                
                try {
                        _offset = 0;
+
+                       /* Perform the request */
                        
                        int r = curl_easy_perform (_curl);
                        if (r != CURLE_OK) {
                                set_state (FAILED);
                                return;
                        }
+
+                       /* Parse the reply */
                        
                        _buffer[_offset] = '\0';
                        stringstream s;
index e96ccec3119903a76af0c26ee1a0a1af41ba49d7..c86adb8736adad074625f8b7a73dd47ae1eb666e 100644 (file)
 
 */
 
+/** @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 (file)
index 0000000..dce08fe
--- /dev/null
@@ -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 (file)
index 0000000..32e3f5f
--- /dev/null
@@ -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;
+};
index 86046bcf8cf8a7ff1a2657b87ec07584bb850d17..d04f195aff6bbfdbb64b030dba3aed40f37bf0e2 100644 (file)
 #endif
 #include <glib.h>
 #include <openjpeg.h>
+#include <pangomm/init.h>
 #include <magick/MagickCore.h>
 #include <magick/version.h>
-#include <libdcp/version.h>
-#include <libdcp/util.h>
-#include <libdcp/signer_chain.h>
-#include <libdcp/signer.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/version.h>
+#include <dcp/util.h>
+#include <dcp/signer_chain.h>
+#include <dcp/signer.h>
+#include <dcp/raw_convert.h>
 extern "C" {
 #include <libavcodec/avcodec.h>
 #include <libavformat/avformat.h>
@@ -63,13 +64,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"
 #ifdef DCPOMATIC_WINDOWS
 #include "stack.hpp"
 #endif
@@ -100,8 +103,8 @@ using std::set_terminate;
 using boost::shared_ptr;
 using boost::thread;
 using boost::optional;
-using libdcp::Size;
-using libdcp::raw_convert;
+using dcp::Size;
+using dcp::raw_convert;
 
 static boost::thread::id ui_thread;
 static boost::filesystem::path backtrace_file;
@@ -237,24 +240,6 @@ ffmpeg_version_to_string (int v)
        return s.str ();
 }
 
-/** Return a user-readable string summarising the versions of our dependencies */
-string
-dependency_version_summary ()
-{
-       stringstream s;
-       s << N_("libopenjpeg ") << opj_version () << N_(", ")
-         << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
-         << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
-         << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
-         << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
-         << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
-         << MagickVersion << N_(", ")
-         << N_("libssh ") << ssh_version (0) << N_(", ")
-         << N_("libdcp ") << libdcp::version << N_(" git ") << libdcp::git_commit;
-
-       return s.str ();
-}
-
 double
 seconds (struct timeval t)
 {
@@ -343,14 +328,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 ();
 }
@@ -725,17 +712,6 @@ ensure_ui_thread ()
        assert (boost::this_thread::get_id() == ui_thread);
 }
 
-/** @param v Content video frame.
- *  @param audio_sample_rate Source audio sample rate.
- *  @param frames_per_second Number of video frames per second.
- *  @return Equivalent number of audio frames for `v'.
- */
-int64_t
-video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
-{
-       return ((int64_t) v * audio_sample_rate / frames_per_second);
-}
-
 string
 audio_channel_name (int c)
 {
@@ -786,7 +762,7 @@ tidy_for_filename (string f)
        return t;
 }
 
-shared_ptr<const libdcp::Signer>
+shared_ptr<const dcp::Signer>
 make_signer ()
 {
        boost::filesystem::path const sd = Config::instance()->signer_chain_directory ();
@@ -806,37 +782,37 @@ make_signer ()
                if (!boost::filesystem::exists (p)) {
                        boost::filesystem::remove_all (sd);
                        boost::filesystem::create_directories (sd);
-                       libdcp::make_signer_chain (sd, openssl_path ());
+                       dcp::make_signer_chain (sd, openssl_path ());
                        break;
                }
 
                ++i;
        }
        
-       libdcp::CertificateChain chain;
+       dcp::CertificateChain chain;
 
        {
                boost::filesystem::path p (sd);
                p /= "ca.self-signed.pem";
-               chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
+               chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
        }
 
        {
                boost::filesystem::path p (sd);
                p /= "intermediate.signed.pem";
-               chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
+               chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
        }
 
        {
                boost::filesystem::path p (sd);
                p /= "leaf.signed.pem";
-               chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
+               chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
        }
 
        boost::filesystem::path signer_key (sd);
        signer_key /= "leaf.key";
 
-       return shared_ptr<const libdcp::Signer> (new libdcp::Signer (chain, signer_key));
+       return shared_ptr<const dcp::Signer> (new dcp::Signer (chain, signer_key));
 }
 
 map<string, string>
@@ -885,14 +861,14 @@ split_get_request (string url)
        return r;
 }
 
-libdcp::Size
-fit_ratio_within (float ratio, libdcp::Size full_frame)
+dcp::Size
+fit_ratio_within (float ratio, dcp::Size full_frame)
 {
        if (ratio < full_frame.ratio ()) {
-               return libdcp::Size (rint (full_frame.height * ratio), full_frame.height);
+               return dcp::Size (rint (full_frame.height * ratio), full_frame.height);
        }
        
-       return libdcp::Size (full_frame.width, rint (full_frame.width / ratio));
+       return dcp::Size (full_frame.width, rint (full_frame.width / ratio));
 }
 
 void *
@@ -923,12 +899,34 @@ divide_with_round (int64_t a, int64_t b)
        }
 }
 
+/** Return a user-readable string summarising the versions of our dependencies */
+string
+dependency_version_summary ()
+{
+       stringstream s;
+       s << N_("libopenjpeg ") << opj_version () << N_(", ")
+         << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
+         << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
+         << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
+         << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
+         << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
+         << MagickVersion << N_(", ")
+         << N_("libssh ") << ssh_version (0) << N_(", ")
+         << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit;
+
+       return s.str ();
+}
+
+/** Construct a ScopedTemporary.  A temporary filename is decided but the file is not opened
+ *  until ::open() is called.
+ */
 ScopedTemporary::ScopedTemporary ()
        : _open (0)
 {
        _file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
 }
 
+/** Close and delete the temporary file */
 ScopedTemporary::~ScopedTemporary ()
 {
        close ();       
@@ -936,12 +934,16 @@ ScopedTemporary::~ScopedTemporary ()
        boost::filesystem::remove (_file, ec);
 }
 
+/** @return temporary filename */
 char const *
 ScopedTemporary::c_str () const
 {
        return _file.string().c_str ();
 }
 
+/** Open the temporary file.
+ *  @return File's FILE pointer.
+ */
 FILE*
 ScopedTemporary::open (char const * params)
 {
@@ -949,6 +951,7 @@ ScopedTemporary::open (char const * params)
        return _open;
 }
 
+/** Close the file */
 void
 ScopedTemporary::close ()
 {
@@ -957,3 +960,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;
+}
index 70bf495c682c42332ea22bf5294497c243ad5276..094d57f40ee21ab741e5b6afdf9d6dfeae92d424 100644 (file)
@@ -31,7 +31,8 @@
 #include <boost/asio.hpp>
 #include <boost/optional.hpp>
 #include <boost/filesystem.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
+#include <dcp/signer.h>
 extern "C" {
 #include <libavcodec/avcodec.h>
 #include <libavfilter/avfilter.h>
@@ -47,11 +48,12 @@ extern "C" {
 
 #define DCPOMATIC_HELLO "Boys, you gotta learn not to talk to nuns that way"
 
-namespace libdcp {
+namespace dcp {
        class Signer;
 }
 
 class Job;
+struct AVSubtitle;
 
 extern std::string seconds_to_hms (int);
 extern std::string seconds_to_approximate_hms (int);
@@ -69,8 +71,8 @@ extern bool valid_image_file (boost::filesystem::path);
 extern boost::filesystem::path mo_path ();
 #endif
 extern std::string tidy_for_filename (std::string);
-extern boost::shared_ptr<const libdcp::Signer> make_signer ();
-extern libdcp::Size fit_ratio_within (float ratio, libdcp::Size);
+extern boost::shared_ptr<const dcp::Signer> make_signer ();
+extern dcp::Size fit_ratio_within (float ratio, dcp::Size);
 extern std::string entities_to_text (std::string e);
 extern std::map<std::string, std::string> split_get_request (std::string url);
 extern int dcp_audio_frame_rate (int);
@@ -83,6 +85,7 @@ extern int get_optional_int (std::multimap<std::string, std::string> const & kv,
 extern std::string get_optional_string (std::multimap<std::string, std::string> const & kv, std::string k);
 extern void* wrapped_av_malloc (size_t);
 extern int64_t divide_with_round (int64_t a, int64_t b);
+extern ContentTimePeriod subtitle_period (AVSubtitle const &);
 
 /** @class Socket
  *  @brief A class to wrap a boost::asio::ip::tcp::socket with some things
@@ -125,16 +128,20 @@ private:
 
 extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
 
+/** @class ScopedTemporary
+ *  @brief A temporary file which is deleted when the ScopedTemporary object goes out of scope.
+ */
 class ScopedTemporary
 {
 public:
        ScopedTemporary ();
        ~ScopedTemporary ();
 
+       /** @return temporary filename */
        boost::filesystem::path file () const {
                return _file;
        }
-       
+
        char const * c_str () const;
        FILE* open (char const *);
        void close ();
index 02cc1f810dd6fc114f10ed738a16b6c5e42c1d58..a2a4e6c6b3b46c452290975d5eccb2952fbaab89 100644 (file)
@@ -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"
 #include "film.h"
 #include "exceptions.h"
 #include "frame_rate_change.h"
+#include "log.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;
@@ -51,14 +54,13 @@ using std::max;
 using boost::shared_ptr;
 using boost::optional;
 using boost::dynamic_pointer_cast;
-using libdcp::raw_convert;
+using dcp::raw_convert;
 
 vector<VideoContentScale> VideoContentScale::_scales;
 
 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 (Ratio::from_id ("185"))
@@ -66,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 (Ratio::from_id ("185"))
@@ -80,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 (Ratio::from_id ("185"))
@@ -88,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");
@@ -152,7 +158,6 @@ VideoContent::VideoContent (shared_ptr<const Film> f, vector<shared_ptr<Content>
        }
 
        _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 ();
@@ -164,11 +169,10 @@ 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"));
@@ -178,25 +182,31 @@ VideoContent::as_xml (xmlpp::Node* node) const
 void
 VideoContent::setup_default_colour_conversion ()
 {
-       _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
+       _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
 }
 
 void
 VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d)
 {
        /* These examiner calls could call other content methods which take a lock on the mutex */
-       libdcp::Size const vs = d->video_size ();
+       dcp::Size const vs = d->video_size ();
        float const vfr = d->video_frame_rate ();
+       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);
 }
 
 
@@ -327,14 +337,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:
@@ -342,9 +355,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);
@@ -362,28 +375,21 @@ VideoContent::set_colour_conversion (ColourConversion c)
 }
 
 /** @return Video size after 3D split and crop */
-libdcp::Size
+dcp::Size
 VideoContent::video_size_after_crop () const
 {
        return crop().apply (video_size_after_3d_split ());
 }
 
 /** @param t A time offset from the start of this piece of content.
- *  @return Corresponding frame index.
+ *  @return Corresponding time with respect to the content.
  */
-VideoContent::Frame
-VideoContent::time_to_content_video_frames (Time t) const
+ContentTime
+VideoContent::dcp_time_to_content_time (DCPTime t) const
 {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
-       
-       FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
-
-       /* Here we are converting from time (in the DCP) to a frame number in the content.
-          Hence we need to use the DCP's frame rate and the double/skip correction, not
-          the source's rate.
-       */
-       return t * film->video_frame_rate() / (frc.factor() * TIME_HZ);
+       return ContentTime (t, FrameRateChange (video_frame_rate(), film->video_frame_rate()));
 }
 
 void
@@ -448,7 +454,7 @@ VideoContentScale::VideoContentScale (bool scale)
 
 }
 
-VideoContentScale::VideoContentScale (shared_ptr<cxml::Node> node)
+VideoContentScale::VideoContentScale (cxml::NodePtr node)
        : _ratio (0)
        , _scale (true)
 {
@@ -501,14 +507,14 @@ VideoContentScale::name () const
 /** @param display_container Size of the container that we are displaying this content in.
  *  @param film_container The size of the film's image.
  */
-libdcp::Size
-VideoContentScale::size (shared_ptr<const VideoContent> c, libdcp::Size display_container, libdcp::Size film_container) const
+dcp::Size
+VideoContentScale::size (shared_ptr<const VideoContent> c, dcp::Size display_container, dcp::Size film_container) const
 {
        if (_ratio) {
                return fit_ratio_within (_ratio->ratio (), display_container);
        }
 
-       libdcp::Size const ac = c->video_size_after_crop ();
+       dcp::Size const ac = c->video_size_after_crop ();
 
        /* Force scale if the film_container is smaller than the content's image */
        if (_scale || film_container.width < ac.width || film_container.height < ac.height) {
@@ -518,7 +524,7 @@ VideoContentScale::size (shared_ptr<const VideoContent> c, libdcp::Size display_
        /* Scale the image so that it will be in the right place in film_container, even if display_container is a
           different size.
        */
-       return libdcp::Size (
+       return dcp::Size (
                c->video_size().width  * float(display_container.width)  / film_container.width,
                c->video_size().height * float(display_container.height) / film_container.height
                );
index 62459222d84cb823d95682b65d2f44481bcc41bf..7d9cb4f8f0298eccb5a307b37612b5ab8e6e95be 100644 (file)
@@ -43,9 +43,9 @@ public:
        VideoContentScale ();
        VideoContentScale (Ratio const *);
        VideoContentScale (bool);
-       VideoContentScale (boost::shared_ptr<cxml::Node>);
+       VideoContentScale (cxml::NodePtr);
 
-       libdcp::Size size (boost::shared_ptr<const VideoContent>, libdcp::Size, libdcp::Size) const;
+       dcp::Size size (boost::shared_ptr<const VideoContent>, dcp::Size, dcp::Size) const;
        std::string id () const;
        std::string name () const;
        void as_xml (xmlpp::Node *) const;
@@ -81,9 +81,9 @@ public:
        typedef int Frame;
 
        VideoContent (boost::shared_ptr<const Film>);
-       VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame);
+       VideoContent (boost::shared_ptr<const Film>, DCPTime, ContentTime);
        VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path);
-       VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
+       VideoContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int);
        VideoContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
 
        void as_xml (xmlpp::Node *) const;
@@ -91,21 +91,21 @@ public:
        virtual std::string information () const;
        virtual std::string identifier () const;
 
-       VideoContent::Frame video_length () const {
+       ContentTime video_length () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _video_length;
        }
 
-       VideoContent::Frame video_length_after_3d_combine () const {
+       ContentTime video_length_after_3d_combine () const {
                boost::mutex::scoped_lock lm (_mutex);
                if (_video_frame_type == VIDEO_FRAME_TYPE_3D_ALTERNATE) {
-                       return _video_length / 2;
+                       return ContentTime (_video_length.get() / 2);
                }
                
                return _video_length;
        }
 
-       libdcp::Size video_size () const {
+       dcp::Size video_size () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _video_size;
        }
@@ -115,11 +115,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);
 
@@ -172,10 +167,10 @@ public:
                return _colour_conversion;
        }
 
-       libdcp::Size video_size_after_3d_split () const;
-       libdcp::Size video_size_after_crop () const;
+       dcp::Size video_size_after_3d_split () const;
+       dcp::Size video_size_after_crop () const;
 
-       VideoContent::Frame time_to_content_video_frames (Time) const;
+       ContentTime dcp_time_to_content_time (DCPTime) const;
 
        void scale_and_crop_to_fit_width ();
        void scale_and_crop_to_fit_height ();
@@ -183,8 +178,7 @@ 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:
@@ -195,7 +189,7 @@ private:
 
        void setup_default_colour_conversion ();
        
-       libdcp::Size _video_size;
+       dcp::Size _video_size;
        VideoFrameType _video_frame_type;
        Crop _crop;
        VideoContentScale _scale;
index 5867ac9257aacc31f8c712039c093f531e636771..5dd078553659b21d3c09bcf8a030ea77e73ad783 100644 (file)
 
 #include "video_decoder.h"
 #include "image.h"
+#include "content_video.h"
 
 #include "i18n.h"
 
 using std::cout;
+using std::list;
 using boost::shared_ptr;
+using boost::optional;
 
-VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c)
-       : Decoder (f)
+VideoDecoder::VideoDecoder (shared_ptr<const VideoContent> c)
+#ifdef DCPOMATIC_DEBUG
+       : test_gaps (0)
        , _video_content (c)
-       , _video_position (0)
+#else
+       : _video_content (c)
+#endif
+       , _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 ();
 }
 
index 42add42aacc547c3be5e2bced87882dfdacecd0b..f5c3cd743ba43bf3df96d4a1706507a33a0e3a95 100644 (file)
@@ -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
 
 */
 
+/** @file  src/lib/video_decoder.h
+ *  @brief VideoDecoder class.
+ */
+
 #ifndef DCPOMATIC_VIDEO_DECODER_H
 #define DCPOMATIC_VIDEO_DECODER_H
 
 #include "decoder.h"
 #include "video_content.h"
 #include "util.h"
+#include "content_video.h"
 
 class VideoContent;
 class ImageProxy;
 
+/** @class VideoDecoder
+ *  @brief Parent for classes which decode video.
+ */
 class VideoDecoder : public virtual Decoder
 {
 public:
-       VideoDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const VideoContent>);
-
-       /** Seek so that the next pass() will yield (approximately) the requested frame.
-        *  Pass accurate = true to try harder to get close to the request.
-        */
-       virtual void seek (VideoContent::Frame frame, bool accurate) = 0;
-
-       /** Emitted when a video frame is ready.
-        *  First parameter is the video image.
-        *  Second parameter is the eye(s) which should see this image.
-        *  Third parameter is the part of this image that should be used.
-        *  Fourth parameter is true if the image is the same as the last one that was emitted for this Eyes value.
-        *  Fourth parameter is the frame within our source.
-        */
-       boost::signals2::signal<void (boost::shared_ptr<const ImageProxy>, Eyes, Part, bool, VideoContent::Frame)> Video;
-       
+       VideoDecoder (boost::shared_ptr<const VideoContent> c);
+
+       std::list<ContentVideo> get_video (VideoFrame frame, bool accurate);
+
+       boost::shared_ptr<const VideoContent> video_content () const {
+               return _video_content;
+       }
+
+#ifdef DCPOMATIC_DEBUG
+       int test_gaps;
+#endif
+
 protected:
 
-       void video (boost::shared_ptr<const ImageProxy>, bool, VideoContent::Frame);
+       void seek (ContentTime time, bool accurate);
+       void video (boost::shared_ptr<const ImageProxy>, VideoFrame frame);
+       std::list<ContentVideo> decoded_video (VideoFrame frame);
+
        boost::shared_ptr<const VideoContent> _video_content;
-       /** This is in frames without taking 3D into account (e.g. if we are doing 3D alternate,
-        *  this would equal 2 on the left-eye second frame (not 1)).
-        */
-       VideoContent::Frame _video_position;
+       std::list<ContentVideo> _decoded_video;
+       bool _same;
 };
 
 #endif
index 039c494b52fe98f630798729207b65b26b4d46f3..87e9a04288a20fb13018391ad4c124a58b757e05 100644 (file)
@@ -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
 
 */
 
-#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;
 };
index 66ddb58f7e901e0c32a14aa3f5414739cc8c1c68..b165545c7b23e5f3eb04b085d50020861f135ffd 100644 (file)
 
 #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 "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 "i18n.h"
 
@@ -56,6 +62,7 @@ using std::cout;
 using std::stringstream;
 using boost::shared_ptr;
 using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
 
 int const Writer::_maximum_frames_in_memory = Config::instance()->num_local_encoding_threads() + 4;
 
@@ -70,7 +77,6 @@ Writer::Writer (shared_ptr<const Film> f, weak_ptr<Job> j)
        , _last_written_eyes (EYES_RIGHT)
        , _full_written (0)
        , _fake_written (0)
-       , _repeat_written (0)
        , _pushed_to_disk (0)
 {
        /* Remove any old DCP */
@@ -88,36 +94,34 @@ Writer::Writer (shared_ptr<const Film> f, weak_ptr<Job> j)
        */
 
        if (_film->three_d ()) {
-               _picture_asset.reset (new libdcp::StereoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
+               _picture_mxf.reset (new dcp::StereoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
        } else {
-               _picture_asset.reset (new libdcp::MonoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
+               _picture_mxf.reset (new dcp::MonoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
        }
 
-       _picture_asset->set_edit_rate (_film->video_frame_rate ());
-       _picture_asset->set_size (_film->frame_size ());
-       _picture_asset->set_interop (_film->interop ());
+       _picture_mxf->set_size (_film->frame_size ());
 
        if (_film->encrypted ()) {
-               _picture_asset->set_key (_film->key ());
+               _picture_mxf->set_key (_film->key ());
        }
        
-       _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
+       _picture_mxf_writer = _picture_mxf->start_write (
+               _film->internal_video_mxf_dir() / _film->internal_video_mxf_filename(),
+               _film->interop() ? dcp::INTEROP : dcp::SMPTE,
+               _first_nonexistant_frame > 0
+               );
 
        if (_film->audio_channels ()) {
-               _sound_asset.reset (new libdcp::SoundAsset (_film->directory (), _film->audio_mxf_filename ()));
-               _sound_asset->set_edit_rate (_film->video_frame_rate ());
-               _sound_asset->set_channels (_film->audio_channels ());
-               _sound_asset->set_sampling_rate (_film->audio_frame_rate ());
-               _sound_asset->set_interop (_film->interop ());
+               _sound_mxf.reset (new dcp::SoundMXF (dcp::Fraction (_film->video_frame_rate(), 1), _film->audio_frame_rate (), _film->audio_channels ()));
 
                if (_film->encrypted ()) {
-                       _sound_asset->set_key (_film->key ());
+                       _sound_mxf->set_key (_film->key ());
                }
-               
-               /* Write the sound asset into the film directory so that we leave the creation
+       
+               /* Write the sound MXF into the film directory so that we leave the creation
                   of the DCP directory until the last minute.
                */
-               _sound_asset_writer = _sound_asset->start_write ();
+               _sound_mxf_writer = _sound_mxf->start_write (_film->directory() / _film->audio_mxf_filename(), _film->interop() ? dcp::INTEROP : dcp::SMPTE);
        }
 
        _thread = new boost::thread (boost::bind (&Writer::thread, this));
@@ -174,7 +178,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;
@@ -199,8 +203,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());
        }
 }
 
@@ -276,7 +280,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;
@@ -284,39 +288,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);
                        }
                }
 
@@ -391,15 +383,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();
@@ -417,14 +405,11 @@ Writer::finish ()
                LOG_WARNING_NC ("Hard-link failed; fell back to copying");
        }
 
-       /* And update the asset */
-
-       _picture_asset->set_directory (_film->dir (_film->dcp_name ()));
-       _picture_asset->set_file_name (_film->video_mxf_filename ());
+       _picture_mxf->set_file (video_to);
 
        /* Move the audio MXF into the DCP */
 
-       if (_sound_asset) {
+       if (_sound_mxf) {
                boost::filesystem::path audio_to;
                audio_to /= _film->dir (_film->dcp_name ());
                audio_to /= _film->audio_mxf_filename ();
@@ -435,80 +420,77 @@ 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);
 
-       cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (
-                                                        _picture_asset,
-                                                        _sound_asset,
-                                                        shared_ptr<libdcp::SubtitleAsset> ()
-                                                        )
-                              ));
+       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);
+       }
+
+       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),
+                                          _subtitle_content->latest_subtitle_out().to_seconds() * _film->video_frame_rate(),
+                                          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 = Config::instance()->dcp_metadata ();
+       dcp::XMLMetadata meta = Config::instance()->dcp_metadata ();
        meta.set_issue_date_now ();
-       dcp.write_xml (_film->interop (), meta, _film->is_signed() ? make_signer () : shared_ptr<const libdcp::Signer> ());
+
+       dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, _film->is_signed() ? make_signer () : shared_ptr<const dcp::Signer> ());
 
        LOG_GENERAL (
-               N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
+               N_("Wrote %1 FULL, %2 FAKE, %3 pushed to disk"), _full_written, _fake_written, _pushed_to_disk
                );
 }
 
-/** Tell the writer that frame `f' should be a repeat of the frame before it */
-void
-Writer::repeat (int f, Eyes e)
-{
-       boost::mutex::scoped_lock lock (_mutex);
-
-       while (_queued_full_in_memory > _maximum_frames_in_memory) {
-               /* 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);
-       }
-
-       /* Now there's something to do: wake anything wait()ing on _empty_condition */
-       _empty_condition.notify_all ();
-}
-
 bool
 Writer::check_existing_picture_mxf_frame (FILE* mxf, int f, Eyes eyes)
 {
@@ -519,7 +501,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);
@@ -604,6 +586,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)
 {
index c0699ad4407158294a3c6d48eccdc8739efdca36..66fe98ec734c914d3de27eeb4b4ccde15e6309b7 100644 (file)
 
 */
 
+/** @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;
 };
index 6d563f255ccb5c100496ec15d7152d43d54e5a00..7d74d462ce90719b7c839525aeed9c19c1c88208 100644 (file)
@@ -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,27 +50,33 @@ sources = """
           image_examiner.cc
           image_proxy.cc
           isdcf_metadata.cc
+          j2k_image_proxy.cc
           job.cc
           job_manager.cc
           kdm.cc
           json_server.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
           ratio.cc
+          raw_image_proxy.cc
+          render_subtitles.cc
           resampler.cc
           scp_dcp_job.cc
           scaler.cc
           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 +85,7 @@ sources = """
           types.cc
           ui_signaller.cc
           update.cc
+          upmixer_a.cc
           util.cc
           video_content.cc
           video_decoder.cc
@@ -83,8 +103,8 @@ def build(bld):
     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
                  """
 
     if bld.env.TARGET_OSX:
index aeb62a44e5f2cfd2b7cf5c92511fd2bec83b69a8..95d0d22a57b08cd57b5a0f70a107a05cae51c1d7 100644 (file)
@@ -30,7 +30,7 @@
 #include <wx/stdpaths.h>
 #include <wx/cmdline.h>
 #include <wx/preferences.h>
-#include <libdcp/exceptions.h>
+#include <dcp/exceptions.h>
 #include "wx/film_viewer.h"
 #include "wx/film_editor.h"
 #include "wx/job_manager_view.h"
@@ -476,7 +476,7 @@ private:
                                        shared_ptr<Job> (new SendKDMEmailJob (film, d->screens (), d->cpl (), d->from (), d->until (), 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 ());
@@ -489,7 +489,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 ();
                }
@@ -497,7 +497,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 ();
                }
@@ -603,7 +603,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) {
                        
@@ -643,6 +643,9 @@ static const wxCmdLineEntryDesc command_line_description[] = {
        { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
 };
 
+/** @class App
+ *  @brief The magic App class for wxWidgets.
+ */
 class App : public wxApp
 {
        bool OnInit ()
index ff948e9fbbae1472089acfc10594c0c914c03f0e..7dd820b9571bc9becf931b176aabcd975e75d996 100644 (file)
@@ -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"
index 26de1c71f8f2485bf273135f110bca61dc9a1bb6..304f4f697ab6e8a031c44dd2a74c3b896f2c448a 100644 (file)
@@ -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));
                        }
                }
 
index 092a1ca1b0725027a5026491ea1efb54b93a7866..2ad07cce7a38bae73c62f2b1824def725e9ada34 100644 (file)
@@ -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,11 +171,11 @@ 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);
                        }
@@ -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);
+               shared_ptr<dcp::Certificate> certificate (new dcp::Certificate (boost::filesystem::path (certificate_file)));
+               dcp::EncryptedKDM kdm = film->make_kdm (certificate, cpl, valid_from.get(), valid_to.get(), 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";
                                }
index e4ac85f4cb6d48f47f19786238ec1a5c221fddc6..c74e258e8ff081680f3b799f354c5ee344ad937c 100644 (file)
@@ -33,7 +33,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"
index a5d31fc086444e70c3bc07b11235df356ea04d3e..9223efb3eb6642592e1fa36af82a0a266bca774f 100644 (file)
 #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";
index c5f2fa3098e48c539c4439a1991562d5c6b3da15..9c7857c1e72c1dc6fddb8d6465446b651bc625fd 100644 (file)
@@ -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
 
 */
 
+/** @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"
@@ -210,6 +214,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)
 {
index a78abb93e267fe1c1c7c60187a4a3edcef1b44af..4901cf9908331d5d46b26cb07e24cf711a03f4dd 100644 (file)
@@ -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
 
 */
 
+/** @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
 };
 
index 6c1508aeebae25b197b4273ace9aec1b7c48395e..8e92400bdb956ffd7b58205dccf5626bf9015be7 100644 (file)
@@ -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
 
 */
 
+/** @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) {
index 98375eb9e3c071b6b83d23fe332e2a7988fb8dcf..7ed6994633c111a7813951ae92c78dcca3023410 100644 (file)
@@ -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
 
 */
 
+/** @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>
index 917775181eeb00d9a7cafb8a02ff533db527e470..e27e10752abb64c4135f4f5ce431f74a2944b0f3 100644 (file)
@@ -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
 #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"
 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);
@@ -81,7 +84,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));
+       _description = add_label_to_grid_bag_sizer (grid, this, "", false, wxGBPosition (r, 2), wxGBSpan (1, 2));
+       ++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));
        ++r;
        
        _mapping = new AudioMappingView (this);
@@ -92,9 +101,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 +115,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 +126,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) {
@@ -144,6 +154,12 @@ AudioPanel::film_content_changed (int property)
                                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 +175,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 +197,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 +210,7 @@ AudioPanel::show_clicked ()
 void
 AudioPanel::stream_changed ()
 {
-       FFmpegContentList fc = _editor->selected_ffmpeg_content ();
+       FFmpegContentList fc = _parent->selected_ffmpeg ();
        if (fc.size() != 1) {
                return;
        }
@@ -222,7 +238,7 @@ AudioPanel::stream_changed ()
 void
 AudioPanel::setup_stream_description ()
 {
-       FFmpegContentList fc = _editor->selected_ffmpeg_content ();
+       FFmpegContentList fc = _parent->selected_ffmpeg ();
        if (fc.size() != 1) {
                _description->SetLabel ("");
                return;
@@ -239,15 +255,30 @@ AudioPanel::setup_stream_description ()
                } else {
                        s << fcs->audio_channels() << wxT (" ") << _("channels");
                }
-               s << wxT (", ") << fcs->content_audio_frame_rate() << _("Hz");
+               s << wxT (", ") << fcs->audio_frame_rate() << _("Hz");
                _description->SetLabel (s);
        }
 }
 
+void
+AudioPanel::processor_changed ()
+{
+       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);
+       }
+               
+       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 +287,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 +296,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 ())));
+               }
+       }
+}
index 2ba5a9ffc051de25e29200d4b28f952cdaff9db8..9a9d326985975bc60f98216f85f92f02d0d31a76 100644 (file)
@@ -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);
@@ -43,6 +43,8 @@ private:
        void stream_changed ();
        void mapping_changed (AudioMapping);
        void setup_stream_description ();
+       void processor_changed ();
+       void setup_processors ();
 
        ContentSpinCtrlDouble<AudioContent>* _gain;
        wxButton* _gain_calculate_button;
@@ -50,6 +52,7 @@ private:
        ContentSpinCtrl<AudioContent>* _delay;
        wxChoice* _stream;
        wxStaticText* _description;
+       wxChoice* _processor;
        AudioMappingView* _mapping;
        AudioDialog* _audio_dialog;
 };
index ff25882539019ad89a351e1b564c5cf218314772..fc77b42a28903f2248ab77bad14bb16ab6c074c8 100644 (file)
 #include <wx/preferences.h>
 #include <wx/filepicker.h>
 #include <wx/spinctrl.h>
-#include <libdcp/colour_matrix.h>
+#include <dcp/colour_matrix.h>
 #include "lib/config.h"
 #include "lib/ratio.h"
 #include "lib/scaler.h"
 #include "lib/filter.h"
 #include "lib/dcp_content_type.h"
 #include "lib/colour_conversion.h"
+#include "lib/log.h"
 #include "config_dialog.h"
 #include "wx_util.h"
 #include "editable_list.h"
@@ -395,14 +396,14 @@ private:
 
        void issuer_changed ()
        {
-               libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
+               dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
                m.issuer = wx_to_std (_issuer->GetValue ());
                Config::instance()->set_dcp_metadata (m);
        }
        
        void creator_changed ()
        {
-               libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
+               dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
                m.creator = wx_to_std (_creator->GetValue ());
                Config::instance()->set_dcp_metadata (m);
        }
index a9f9093c6ab5bb3c94d48244480bc1859495bbf2..f2ad3aa75621ac04a114b46badd931b6bd92a4b1 100644 (file)
@@ -30,9 +30,9 @@ class Film;
 class ContentMenu
 {
 public:
-       ContentMenu (wxWindow *);
+       ContentMenu (wxWindow* p);
        ~ContentMenu ();
-
+       
        void popup (boost::weak_ptr<Film>, ContentList, wxPoint);
 
 private:
diff --git a/src/wx/content_panel.cc b/src/wx/content_panel.cc
new file mode 100644 (file)
index 0000000..13ae2d8
--- /dev/null
@@ -0,0 +1,450 @@
+/*
+    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 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));
+       _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;
+       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) {
+               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 ();
+
+               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);
+       }
+}
+
diff --git a/src/wx/content_panel.h b/src/wx/content_panel.h
new file mode 100644 (file)
index 0000000..5701696
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+    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 ();
+
+private:       
+       void sequence_video_changed ();
+       void selection_changed ();
+       void add_file_clicked ();
+       void add_folder_clicked ();
+       void remove_clicked ();
+       void earlier_clicked ();
+       void later_clicked ();
+       void right_click (wxListEvent &);
+       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/content_sub_panel.cc b/src/wx/content_sub_panel.cc
new file mode 100644 (file)
index 0000000..f0962cc
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+    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/notebook.h>
+#include "content_sub_panel.h"
+#include "film_editor.h"
+
+using boost::shared_ptr;
+
+ContentSubPanel::ContentSubPanel (ContentPanel* p, wxString name)
+       : wxPanel (p->notebook(), wxID_ANY)
+       , _parent (p)
+       , _sizer (new wxBoxSizer (wxVERTICAL))
+{
+       p->notebook()->AddPage (this, name, false);
+       SetSizer (_sizer);
+}
+
diff --git a/src/wx/content_sub_panel.h b/src/wx/content_sub_panel.h
new file mode 100644 (file)
index 0000000..5a1b739
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+    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.
+
+*/
+
+#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 ContentPanel;
+class Content;
+
+class ContentSubPanel : public wxPanel
+{
+public:
+       ContentSubPanel (ContentPanel *, wxString);
+
+       virtual void film_changed (Film::Property) {}
+       /** Called when a given property of one of the selected Contents changes */
+       virtual void film_content_changed (int) = 0;
+       /** Called when the list of selected Contents changes */
+       virtual void content_selection_changed () = 0;
+
+protected:
+       ContentPanel* _parent;
+       wxSizer* _sizer;
+};
+
+#endif
index ca94850065bf4f1e9e75a865a4b463435789bd24..bbe16039998e9d22db60594cd5b3534d856538ef 100644 (file)
@@ -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
 
 */
 
+/** @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)
index 854877ecec86d1e063b982a95dc6fc9222facac6..c80a2a16cd5706510a649ad7ad5db603d83afe99 100644 (file)
 #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 "timecode.h"
 #include "wx_util.h"
 #include "film_editor.h"
@@ -72,17 +72,15 @@ using boost::lexical_cast;
 /** @param f Film to edit */
 FilmEditor::FilmEditor (shared_ptr<Film> f, 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);
+       _content_panel = new ContentPanel (_main_notebook, _film);
+       _main_notebook->AddPage (_content_panel->panel (), _("Content"), true);
        make_dcp_panel ();
        _main_notebook->AddPage (_dcp_panel, _("DCP"), false);
        
@@ -155,6 +153,10 @@ FilmEditor::make_dcp_panel ()
        }
        ++r;
 
+       _burn_subtitles = new wxCheckBox (_dcp_panel, wxID_ANY, _("Burn subtitles into image"));
+       grid->Add (_burn_subtitles, wxGBPosition (r, 0), wxGBSpan (1, 2));
+       ++r;
+
        _signed = new wxCheckBox (_dcp_panel, wxID_ANY, _("Signed"));
        grid->Add (_signed, wxGBPosition (r, 0), wxGBSpan (1, 2));
        ++r;
@@ -235,81 +237,21 @@ FilmEditor::connect_to_widgets ()
        _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_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));
+       _burn_subtitles->Bind   (wxEVT_COMMAND_CHECKBOX_CLICKED,      boost::bind (&FilmEditor::burn_subtitles_toggled, 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);
-}
-
 /** Called when the name widget has been changed */
 void
 FilmEditor::name_changed ()
@@ -341,6 +283,16 @@ FilmEditor::signed_toggled ()
        _film->set_signed (_signed->GetValue ());
 }
 
+void
+FilmEditor::burn_subtitles_toggled ()
+{
+       if (!_film) {
+               return;
+       }
+
+       _film->set_burn_subtitles (_burn_subtitles->GetValue ());
+}
+
 void
 FilmEditor::encrypted_toggled ()
 {
@@ -422,16 +374,11 @@ FilmEditor::film_changed (Film::Property p)
 
        stringstream s;
 
-       for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
-               (*i)->film_changed (p);
-       }
-               
+       _content_panel->film_changed (p);
+
        switch (p) {
        case Film::NONE:
                break;
-       case Film::CONTENT:
-               setup_content ();
-               break;
        case Film::CONTAINER:
                setup_container ();
                break;
@@ -439,9 +386,6 @@ FilmEditor::film_changed (Film::Property p)
                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 ();
@@ -449,6 +393,9 @@ FilmEditor::film_changed (Film::Property p)
        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;
@@ -499,9 +446,6 @@ FilmEditor::film_changed (Film::Property p)
                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 ();
@@ -509,6 +453,8 @@ FilmEditor::film_changed (Film::Property p)
        case Film::INTEROP:
                checked_set (_standard, _film->interop() ? 1 : 0);
                break;
+       default:
+               break;
        }
 }
 
@@ -524,16 +470,10 @@ FilmEditor::film_content_changed (int property)
                return;
        }
 
-       for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
-               (*i)->film_content_changed (property);
-       }
+       _content_panel->film_content_changed (property);
 
-       if (property == FFmpegContentProperty::AUDIO_STREAM) {
+       if (property == FFmpegContentProperty::AUDIO_STREAM || property == SubtitleContentProperty::USE_SUBTITLES) {
                setup_dcp_name ();
-       } else if (property == ContentProperty::PATH) {
-               setup_content ();
-       } else if (property == ContentProperty::POSITION) {
-               setup_content ();
        }
 }
 
@@ -599,6 +539,8 @@ FilmEditor::set_film (shared_ptr<Film> f)
        
        _film = f;
 
+       _content_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));
@@ -617,8 +559,8 @@ FilmEditor::set_film (shared_ptr<Film> f)
        film_changed (Film::CONTAINER);
        film_changed (Film::RESOLUTION);
        film_changed (Film::SCALER);
-       film_changed (Film::WITH_SUBTITLES);
        film_changed (Film::SIGNED);
+       film_changed (Film::BURN_SUBTITLES);
        film_changed (Film::ENCRYPTED);
        film_changed (Film::J2K_BANDWIDTH);
        film_changed (Film::ISDCF_METADATA);
@@ -629,10 +571,8 @@ FilmEditor::set_film (shared_ptr<Film> f)
        film_changed (Film::INTEROP);
 
        if (!_film->content().empty ()) {
-               set_selection (_film->content().front ());
+               _content_panel->set_selection (_film->content().front ());
        }
-
-       content_selection_changed ();
 }
 
 void
@@ -640,23 +580,19 @@ FilmEditor::set_general_sensitivity (bool s)
 {
        _generally_sensitive = s;
 
+       _content_panel->set_general_sensitivity (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;
        }
+       _burn_subtitles->Enable (s);
        _signed->Enable (si);
        
        _encrypted->Enable (s);
@@ -666,16 +602,10 @@ FilmEditor::set_general_sensitivity (bool 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 */
@@ -743,262 +673,6 @@ FilmEditor::best_frame_rate_clicked ()
        _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 ();
-       if (c.size() == 1) {
-               _film->remove_content (c.front ());
-       }
-
-       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.size() == 1 && _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.size() > 0 && _generally_sensitive);
-       _audio_panel->Enable    (audio_selection.size() > 0 && _generally_sensitive);
-       _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<FFmpegContent> (selection.front()) && _generally_sensitive);
-       _timing_panel->Enable   (selection.size() == 1 && _generally_sensitive);
-}
-
-ContentList
-FilmEditor::selected_content ()
-{
-       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
-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 | wxLIST_STATE_FOCUSED);
-               }
-       }
-}
-
-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 ()
 {
@@ -1009,26 +683,6 @@ FilmEditor::three_d_changed ()
        _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 ()
 {
index 96cae3900d66d1b9aea29a4043e285f28dc81f2a..aad2ad9f7faf5c886700382ca696cc52ed5e6015 100644 (file)
@@ -28,6 +28,7 @@
 #include <boost/signals2.hpp>
 #include "lib/film.h"
 #include "content_menu.h"
+#include "content_panel.h"
 
 class wxNotebook;
 class wxListCtrl;
@@ -48,41 +49,27 @@ public:
        FilmEditor (boost::shared_ptr<Film>, 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 ();
-       
 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_file_clicked ();
-       void content_add_folder_clicked ();
-       void content_remove_clicked ();
-       void content_earlier_clicked ();
-       void content_later_clicked ();
        void container_changed ();
        void dcp_content_type_changed ();
        void scaler_changed ();
@@ -93,11 +80,10 @@ private:
        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 burn_subtitles_toggled ();
        void encrypted_toggled ();
 
        /* Handle changes to the model */
@@ -106,26 +92,16 @@ private:
 
        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;
 
        /** The film we are editing */
        boost::shared_ptr<Film> _film;
@@ -133,14 +109,6 @@ private:
        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;
@@ -154,12 +122,10 @@ private:
        wxChoice* _resolution;
        wxChoice* _standard;
        wxCheckBox* _signed;
+       wxCheckBox* _burn_subtitles;
        wxCheckBox* _encrypted;
 
-       ContentMenu _menu;
-
        std::vector<Ratio const *> _ratios;
 
        bool _generally_sensitive;
-       TimelineDialog* _timeline_dialog;
 };
diff --git a/src/wx/film_editor_panel.cc b/src/wx/film_editor_panel.cc
deleted file mode 100644 (file)
index a637df1..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
-    Copyright (C) 2012-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 <wx/notebook.h>
-#include "film_editor_panel.h"
-#include "film_editor.h"
-
-using boost::shared_ptr;
-
-FilmEditorPanel::FilmEditorPanel (FilmEditor* e, wxString name)
-       : wxPanel (e->content_notebook (), wxID_ANY)
-       , _editor (e)
-       , _sizer (new wxBoxSizer (wxVERTICAL))
-{
-       e->content_notebook()->AddPage (this, name, false);
-       SetSizer (_sizer);
-}
-
diff --git a/src/wx/film_editor_panel.h b/src/wx/film_editor_panel.h
deleted file mode 100644 (file)
index e0514ba..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
-    Copyright (C) 2012-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.
-
-*/
-
-#ifndef DCPOMATIC_FILM_EDITOR_PANEL_H
-#define DCPOMATIC_FILM_EDITOR_PANEL_H
-
-#include <boost/shared_ptr.hpp>
-#include <wx/wx.h>
-#include "lib/film.h"
-
-class FilmEditor;
-class Content;
-
-class FilmEditorPanel : public wxPanel
-{
-public:
-       FilmEditorPanel (FilmEditor *, wxString);
-
-       virtual void film_changed (Film::Property) {}
-       /** Called when a given property of one of the selected Contents changes */
-       virtual void film_content_changed (int) = 0;
-       /** Called when the list of selected Contents changes */
-       virtual void content_selection_changed () = 0;
-
-protected:
-       FilmEditor* _editor;
-       wxSizer* _sizer;
-};
-
-#endif
index ed1a2ce4109293b3d932be8fabacb2bc4477878b..74e3b81ed82df4126c5ea1b5bee202b2feb3621b 100644 (file)
 #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,7 +52,7 @@ using std::make_pair;
 using boost::shared_ptr;
 using boost::dynamic_pointer_cast;
 using boost::weak_ptr;
-using libdcp::Size;
+using dcp::Size;
 
 FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
        : wxPanel (p)
@@ -62,7 +63,7 @@ FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
        , _frame_number (new wxStaticText (this, wxID_ANY, wxT("")))
        , _timecode (new wxStaticText (this, wxID_ANY, wxT("")))
        , _play_button (new wxToggleButton (this, wxID_ANY, _("Play")))
-       , _got_frame (false)
+       , _last_get_accurate (true)
 {
 #ifndef __WXOSX__
        _panel->SetDoubleBuffered (true);
@@ -122,7 +123,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 +137,46 @@ FilmViewer::set_film (shared_ptr<Film> f)
                return;
        }
        
-       _player->disable_audio ();
-       _player->Video.connect (boost::bind (&FilmViewer::process_video, this, _1, _3));
+       _player->set_approximate_size ();
        _player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1));
 
        calculate_sizes ();
-       fetch_next_frame ();
+       get (_position, _last_get_accurate);
 }
 
 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 ()) {
+               _frame = pvf.front()->image (true);
+               _frame = _frame->scale (_frame->size(), Scaler::from_id ("fastbilinear"), PIX_FMT_RGB24, false);
+               _position = pvf.front()->time ();
+       } else {
+               _frame.reset ();
+               _position = p;
        }
-       
+
+       set_position_text ();
        _panel->Refresh ();
        _panel->Update ();
+
+       _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);
                }
@@ -219,15 +219,16 @@ FilmViewer::paint_panel ()
 void
 FilmViewer::slider_moved ()
 {
-       if (_film && _player) {
-               Time t = _slider->GetValue() * _film->length() / 4096;
-               /* Ensure that we hit the end of the film at the end of the slider */
-               if (t >= _film->length ()) {
-                       t = _film->length() - _film->video_frames_to_time (1);
-               }
-               _player->seek (t, false);
-               fetch_next_frame ();
+       if (!_film) {
+               return;
        }
+
+       DCPTime t (_slider->GetValue() * _film->length().get() / 4096);
+       /* Ensure that we hit the end of the film at the end of the slider */
+       if (t >= _film->length ()) {
+               t = _film->length() - DCPTime::from_frames (1, _film->video_frame_rate ());
+       }
+       get (t, false);
 }
 
 void
@@ -236,7 +237,7 @@ FilmViewer::panel_sized (wxSizeEvent& ev)
        _panel_size.width = ev.GetSize().GetWidth();
        _panel_size.height = ev.GetSize().GetHeight();
        calculate_sizes ();
-       fetch_current_frame_again ();
+       get (_position, _last_get_accurate);
 }
 
 void
@@ -265,6 +266,13 @@ FilmViewer::calculate_sizes ()
        _out_size.width = max (64, _out_size.width);
        _out_size.height = max (64, _out_size.height);
 
+       /* The player will round its image down to the nearest 4 pixels
+          to speed up its scale, so do similar here to avoid black borders
+          around things.  This is a bit of a hack.
+       */
+       _out_size.width &= ~3;
+       _out_size.height &= ~3;
+
        _player->set_video_container_size (_out_size);
 }
 
@@ -289,20 +297,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 +307,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 +320,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 +343,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 +365,5 @@ FilmViewer::player_changed (bool frequent)
        }
 
        calculate_sizes ();
-       fetch_current_frame_again ();
+       get (_position, _last_get_accurate);
 }
index 1e5b6d34d119f2c3a160ea5cbd2822756453fb19..189b379bf3beeaa68f43a2d343220e307d891ece 100644 (file)
@@ -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,16 +46,14 @@ private:
        void slider_moved ();
        void play_clicked ();
        void timer ();
-       void process_video (boost::shared_ptr<PlayerVideoFrame>, Time);
        void calculate_sizes ();
        void check_play_state ();
-       void fetch_current_frame_again ();
-       void fetch_next_frame ();
        void active_jobs_changed (bool);
        void back_clicked ();
        void forward_clicked ();
        void player_changed (bool);
-       void set_position_text (Time);
+       void set_position_text ();
+       void get (DCPTime, bool);
 
        boost::shared_ptr<Film> _film;
        boost::shared_ptr<Player> _player;
@@ -84,10 +69,15 @@ private:
        wxTimer _timer;
 
        boost::shared_ptr<const Image> _frame;
-       bool _got_frame;
+       DCPTime _position;
 
        /** Size of our output (including padding if we have any) */
-       libdcp::Size _out_size;
+       dcp::Size _out_size;
        /** Size of the panel that we have available */
-       libdcp::Size _panel_size;
+       dcp::Size _panel_size;
+       /** 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;
 };
index a7af7978ca48b65aba8629651917d9cfcbb51892..6ef630eff39f5a29cb39a88ed7cb8952e8698d26 100644 (file)
@@ -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<long int> (_type->GetClientData (_type->GetSelection()));
+       return (dcp::KDM::Formulation) reinterpret_cast<long int> (_type->GetClientData (_type->GetSelection()));
 }
 
 void
index 13e9196ea055180a4ee29e6b664338b2d94b1a33..0fc95db8443dee447023e7170b3ee964c0daef85 100644 (file)
@@ -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>);
index 28247bc33eb3c62b9ef85a7dea83785be7b4fa54..801996efa7bcb611111285a847c41848591f69b7 100644 (file)
@@ -45,8 +45,7 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film)
        add (_("Frames already encoded"), true);
        _encoded = add (new ThreadedStaticText (this, _("counting..."), boost::bind (&PropertiesDialog::frames_already_encoded, this)));
        _encoded->Finished.connect (boost::bind (&PropertiesDialog::layout, this));
-       
-       _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->time_to_video_frames (_film->length()))));
+       _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->length().frames (_film->video_frame_rate ()))));
        double const disk = double (_film->required_disk_space()) / 1073741824.0f;
        stringstream s;
        s << fixed << setprecision (1) << disk << wx_to_std (_("Gb"));
@@ -64,10 +63,11 @@ PropertiesDialog::frames_already_encoded () const
        } catch (boost::thread_interrupted &) {
                return "";
        }
-       
-       if (_film->length()) {
+
+       uint64_t const frames = _film->length().frames (_film->video_frame_rate ());
+       if (frames) {
                /* XXX: encoded_frames() should check which frames have been encoded */
-               u << " (" << (_film->encoded_frames() * 100 / _film->time_to_video_frames (_film->length())) << "%)";
+               u << " (" << (_film->encoded_frames() * 100 / frames) << "%)";
        }
        return u.str ();
 }
index c6991271675f3321e82886478fbad2a879bff1ea..6b58145eb367737c6bf6317f8b4b7d368bf43b8f 100644 (file)
@@ -19,7 +19,7 @@
 
 #include <wx/filepicker.h>
 #include <wx/validate.h>
-#include <libdcp/exceptions.h>
+#include <dcp/exceptions.h>
 #include "lib/compose.hpp"
 #include "lib/util.h"
 #include "screen_dialog.h"
@@ -31,7 +31,7 @@ using std::string;
 using std::cout;
 using boost::shared_ptr;
 
-ScreenDialog::ScreenDialog (wxWindow* parent, string title, string name, shared_ptr<libdcp::Certificate> certificate)
+ScreenDialog::ScreenDialog (wxWindow* parent, string title, string name, shared_ptr<dcp::Certificate> certificate)
        : TableDialog (parent, std_to_wx (title), 2, true)
        , _certificate (certificate)
 {
@@ -79,7 +79,7 @@ ScreenDialog::name () const
        return wx_to_std (_name->GetValue());
 }
 
-shared_ptr<libdcp::Certificate>
+shared_ptr<dcp::Certificate>
 ScreenDialog::certificate () const
 {
        return _certificate;
@@ -89,9 +89,9 @@ void
 ScreenDialog::load_certificate (boost::filesystem::path file)
 {
        try {
-               _certificate.reset (new libdcp::Certificate (file));
+               _certificate.reset (new dcp::Certificate (file));
                _certificate_text->SetValue (_certificate->certificate ());
-       } catch (libdcp::MiscError& e) {
+       } catch (dcp::MiscError& e) {
                error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what()));
        }
 }
@@ -128,7 +128,7 @@ void
 ScreenDialog::setup_sensitivity ()
 {
        wxButton* ok = dynamic_cast<wxButton*> (FindWindowById (wxID_OK, this));
-       ok->Enable (_certificate);
+       ok->Enable (_certificate.get ());
 
        _download_certificate->Enable (
                _manufacturer->GetStringSelection() == _("Doremi") ||
index 3601a8f6c133e57a556ce22db289fa5bd732629a..5c6d964b8297681405d501543c14843f463d5253 100644 (file)
@@ -19,7 +19,7 @@
 
 #include <wx/wx.h>
 #include <boost/shared_ptr.hpp>
-#include <libdcp/certificates.h>
+#include <dcp/certificates.h>
 #include "table_dialog.h"
 
 class Progress;
@@ -27,10 +27,10 @@ class Progress;
 class ScreenDialog : public TableDialog
 {
 public:
-       ScreenDialog (wxWindow *, std::string, std::string name = "", boost::shared_ptr<libdcp::Certificate> c = boost::shared_ptr<libdcp::Certificate> ());
+       ScreenDialog (wxWindow *, std::string, std::string name = "", boost::shared_ptr<dcp::Certificate> c = boost::shared_ptr<dcp::Certificate> ());
 
        std::string name () const;
-       boost::shared_ptr<libdcp::Certificate> certificate () const;
+       boost::shared_ptr<dcp::Certificate> certificate () const;
        
 private:
        void select_certificate ();
@@ -44,5 +44,5 @@ private:
        wxButton* _download_certificate;
        wxTextCtrl* _certificate_text;
 
-       boost::shared_ptr<libdcp::Certificate> _certificate;
+       boost::shared_ptr<dcp::Certificate> _certificate;
 };
index 6470f657d24477ef8e0ec5ef166f7498434defde..9025e5ddc4cb530a39cca106f6ca98ebdf9dff42 100644 (file)
 #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"
 
 using std::vector;
 using std::string;
@@ -30,16 +36,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);
@@ -70,40 +77,36 @@ 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);
        _scale->SetRange (1, 1000);
        _scale->SetValue (100);
 
-       _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));
-       _scale->Bind          (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::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));
+       _scale->Bind       (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::scale_changed, this));
+       _stream->Bind      (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&SubtitlePanel::stream_changed, this));
+       _view_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED,   boost::bind (&SubtitlePanel::view_clicked, this));
 }
 
 void
 SubtitlePanel::film_changed (Film::Property property)
 {
-       switch (property) {
-       case Film::CONTENT:
+       if (property == Film::CONTENT) {
                setup_sensitivity ();
-               break;
-       case Film::WITH_SUBTITLES:
-               checked_set (_with_subtitles, _editor->film()->with_subtitles ());
-               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) {
@@ -114,7 +117,7 @@ SubtitlePanel::film_content_changed (int property)
        if (sc.size() == 1) {
                scs = sc.front ();
        }
-       
+
        if (property == FFmpegContentProperty::SUBTITLE_STREAMS) {
                _stream->Clear ();
                if (fcs) {
@@ -130,6 +133,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) {
@@ -140,36 +146,52 @@ 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);
-       _scale->Enable (j);
-       _stream->Enable (j);
+       _x_offset->Enable (any_subs > 0 && use);
+       _y_offset->Enable (any_subs > 0 && use);
+       _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;
        }
@@ -191,27 +213,27 @@ 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::scale_changed ()
 {
-       SubtitleContentList c = _editor->selected_subtitle_content ();
-       if (c.size() == 1) {
-               c.front()->set_subtitle_scale (_scale->GetValue() / 100.0);
+       SubtitleContentList c = _parent->selected_subtitle ();
+       for (SubtitleContentList::iterator i = c.begin(); i != c.end(); ++i) {
+               (*i)->set_subtitle_scale (_scale->GetValue() / 100.0);
        }
 }
 
@@ -219,7 +241,37 @@ 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_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 ();
+       }
+}
index 20d7c40c2eb1d3eb1add4350a1b1482b3cbfc2a1..9e60db34b23de055ff7208f797622de16ea70fa2 100644 (file)
@@ -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
 
 */
 
-#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 scale_changed ();
        void stream_changed ();
+       void view_clicked ();
 
        void setup_sensitivity ();
        
-       wxCheckBox* _with_subtitles;
+       wxCheckBox* _use;
        wxSpinCtrl* _x_offset;
        wxSpinCtrl* _y_offset;
        wxSpinCtrl* _scale;
        wxChoice* _stream;
+       wxButton* _view_button;
+       SubtitleView* _view;
 };
diff --git a/src/wx/subtitle_view.cc b/src/wx/subtitle_view.cc
new file mode 100644 (file)
index 0000000..dc41db2
--- /dev/null
@@ -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 (file)
index 0000000..338742a
--- /dev/null
@@ -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;
+};
index ee5b5604bef4189e95329a360525be0e0bfbaf7b..86e1997e961ba31ad080b70bfe6fc6fbb7ac2de5 100644 (file)
@@ -83,40 +83,34 @@ Timecode::Timecode (wxWindow* parent)
 }
 
 void
-Timecode::set (Time t, int fps)
+Timecode::set (DCPTime t, int fps)
 {
-       /* Do this calculation with frames so that we can round
-          to a frame boundary at the start rather than the end.
-       */
-       int64_t f = divide_with_round (t * fps, TIME_HZ);
-       
-       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;
+       int h;
+       int m;
+       int s;
+       int f;
+       t.split (fps, h, m, s, f);
 
        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));
+       _fixed->SetLabel (std_to_wx (t.timecode (fps)));
 }
 
-Time
+DCPTime
 Timecode::get (int fps) const
 {
-       Time t = 0;
+       DCPTime t;
        string const h = wx_to_std (_hours->GetValue ());
-       t += lexical_cast<int> (h.empty() ? "0" : h) * 3600 * TIME_HZ;
+       t += DCPTime::from_seconds (lexical_cast<int> (h.empty() ? "0" : h) * 3600);
        string const m = wx_to_std (_minutes->GetValue());
-       t += lexical_cast<int> (m.empty() ? "0" : m) * 60 * TIME_HZ;
+       t += DCPTime::from_seconds (lexical_cast<int> (m.empty() ? "0" : m) * 60);
        string const s = wx_to_std (_seconds->GetValue());
-       t += lexical_cast<int> (s.empty() ? "0" : s) * TIME_HZ;
+       t += DCPTime::from_seconds (lexical_cast<int> (s.empty() ? "0" : s));
        string const f = wx_to_std (_frames->GetValue());
-       t += lexical_cast<int> (f.empty() ? "0" : f) * TIME_HZ / fps;
+       t += DCPTime::from_seconds (lexical_cast<double> (f.empty() ? "0" : f) / fps);
 
        return t;
 }
index 880b44a31be8754ca54634e542d9e739e328b745..b13e8c3c084b9c35514e805f59e752927fe46ae1 100644 (file)
@@ -26,8 +26,8 @@ class Timecode : public wxPanel
 public:
        Timecode (wxWindow *);
 
-       void set (Time, int);
-       Time get (int) const;
+       void set (DCPTime, int);
+       DCPTime get (int) const;
 
        void set_editable (bool);
 
index eba12c47faff1421269b2f42067ec09ce90c8d6a..d728bd47c11349b5dea1ed079e0b6cefb3c397d3 100644 (file)
@@ -22,6 +22,7 @@
 #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 "wx_util.h"
@@ -35,7 +36,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 +67,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 +79,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 +106,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 +137,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 +152,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 +173,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 +200,7 @@ private:
                }
 
                if (!frequent) {
-                       _timeline.setup_pixels_per_time_unit ();
+                       _timeline.setup_pixels_per_second ();
                        _timeline.Refresh ();
                }
        }
@@ -220,10 +225,15 @@ 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 VideoContentView : public ContentView
@@ -237,17 +247,59 @@ 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 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 +325,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 +356,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 +380,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 +394,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 +435,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 +464,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 +483,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 +553,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 +603,7 @@ Timeline::left_down (wxMouseEvent& ev)
                }
                
                if (view == *i) {
-                       _film_editor->set_selection (cv->content ());
+                       _content_panel->set_selection (cv->content ());
                }
        }
 
@@ -604,11 +662,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 +685,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 +702,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 +711,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 +726,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 +758,7 @@ Timeline::film () const
 void
 Timeline::resized ()
 {
-       setup_pixels_per_time_unit ();
+       setup_pixels_per_second ();
 }
 
 void
index fafb09c0e5aa0dc56b9c830131ae899a5c4616f7..82d10afde6cc8a8337c910b8b9506c1461c96585 100644 (file)
 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;
index dbf7ae232be2cd5f72dd8a247e98a80fff968fa7..2f7ade7657fb13bad1ab208867af4996b5142537 100644 (file)
@@ -28,9 +28,9 @@ 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);
 
index 1e595500303b5b964b0ba54157c67d47f260625b..2f5fa5ec75b23a69eee666feeab0a25476005308 100644 (file)
@@ -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 ();
index ef963bbfcce89751a70b5f979e3d83dcccec0aa0..4345e71362e4c2964fea60abbd7ae975e35587e7 100644 (file)
@@ -17,7 +17,7 @@
 
 */
 
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
 #include "lib/content.h"
 #include "lib/image_content.h"
 #include "timing_panel.h"
@@ -29,11 +29,11 @@ using std::cout;
 using std::string;
 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);
@@ -77,19 +77,19 @@ TimingPanel::TimingPanel (FilmEditor* e)
 void
 TimingPanel::film_content_changed (int property)
 {
-       ContentList cl = _editor->selected_content ();
+       ContentList cl = _parent->selected ();
        shared_ptr<Content> content;
        if (cl.size() == 1) {
                content = cl.front ();
        }
 
-       int const film_video_frame_rate = _editor->film()->video_frame_rate ();
+       int const film_video_frame_rate = _parent->film()->video_frame_rate ();
        
        if (property == ContentProperty::POSITION) {
                if (content) {
                        _position->set (content->position (), film_video_frame_rate);
                } else {
-                       _position->set (0, 24);
+                       _position->set (DCPTime () , 24);
                }
        } else if (
                property == ContentProperty::LENGTH ||
@@ -100,24 +100,24 @@ TimingPanel::film_content_changed (int property)
                        _full_length->set (content->full_length (), film_video_frame_rate);
                        _play_length->set (content->length_after_trim (), film_video_frame_rate);
                } else {
-                       _full_length->set (0, 24);
-                       _play_length->set (0, 24);
+                       _full_length->set (DCPTime (), 24);
+                       _play_length->set (DCPTime (), 24);
                }
        } else if (property == ContentProperty::TRIM_START) {
                if (content) {
                        _trim_start->set (content->trim_start (), film_video_frame_rate);
                        _play_length->set (content->length_after_trim (), film_video_frame_rate);
                } else {
-                       _trim_start->set (0, 24);
-                       _play_length->set (0, 24);
+                       _trim_start->set (DCPTime (), 24);
+                       _play_length->set (DCPTime (), 24);
                }
        } else if (property == ContentProperty::TRIM_END) {
                if (content) {
                        _trim_end->set (content->trim_end (), film_video_frame_rate);
                        _play_length->set (content->length_after_trim (), film_video_frame_rate);
                } else {
-                       _trim_end->set (0, 24);
-                       _play_length->set (0, 24);
+                       _trim_end->set (DCPTime (), 24);
+                       _play_length->set (DCPTime (), 24);
                }
        }
 
@@ -145,20 +145,21 @@ TimingPanel::film_content_changed (int property)
 void
 TimingPanel::position_changed ()
 {
-       ContentList c = _editor->selected_content ();
+       ContentList c = _parent->selected ();
        if (c.size() == 1) {
-               c.front()->set_position (_position->get (_editor->film()->video_frame_rate ()));
+               c.front()->set_position (_position->get (_parent->film()->video_frame_rate ()));
        }
 }
 
 void
 TimingPanel::full_length_changed ()
 {
-       ContentList c = _editor->selected_content ();
+       ContentList c = _parent->selected ();
        if (c.size() == 1) {
                shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (c.front ());
                if (ic && ic->still ()) {
-                       ic->set_video_length (rint (_full_length->get (_editor->film()->video_frame_rate()) * ic->video_frame_rate() / TIME_HZ));
+                       /* XXX: No effective FRC here... is this right? */
+                       ic->set_video_length (ContentTime (_full_length->get (_parent->film()->video_frame_rate()), FrameRateChange (1, 1)));
                }
        }
 }
@@ -166,9 +167,9 @@ TimingPanel::full_length_changed ()
 void
 TimingPanel::trim_start_changed ()
 {
-       ContentList c = _editor->selected_content ();
+       ContentList c = _parent->selected ();
        if (c.size() == 1) {
-               c.front()->set_trim_start (_trim_start->get (_editor->film()->video_frame_rate ()));
+               c.front()->set_trim_start (_trim_start->get (_parent->film()->video_frame_rate ()));
        }
 }
 
@@ -176,18 +177,18 @@ TimingPanel::trim_start_changed ()
 void
 TimingPanel::trim_end_changed ()
 {
-       ContentList c = _editor->selected_content ();
+       ContentList c = _parent->selected ();
        if (c.size() == 1) {
-               c.front()->set_trim_end (_trim_end->get (_editor->film()->video_frame_rate ()));
+               c.front()->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 ();
        if (c.size() == 1) {
-               c.front()->set_trim_end (c.front()->full_length() - _play_length->get (_editor->film()->video_frame_rate()) - c.front()->trim_start());
+               c.front()->set_trim_end (c.front()->full_length() - _play_length->get (_parent->film()->video_frame_rate()) - c.front()->trim_start());
        }
 }
 
@@ -200,7 +201,7 @@ TimingPanel::video_frame_rate_changed ()
 void
 TimingPanel::set_video_frame_rate ()
 {
-       ContentList c = _editor->selected_content ();
+       ContentList c = _parent->selected ();
        if (c.size() == 1) {
                shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (c.front ());
                if (vc) {
@@ -213,7 +214,7 @@ TimingPanel::set_video_frame_rate ()
 void
 TimingPanel::content_selection_changed ()
 {
-       ContentList sel = _editor->selected_content ();
+       ContentList sel = _parent->selected ();
        bool const single = sel.size() == 1;
 
        /* Things that are only allowed with single selections */
index d9696a20135671a711fe27a14bc2bd5565df237a..b531db5512ed3974da1ed253878cc45dbe7d86d5 100644 (file)
 
 */
 
-#include "film_editor_panel.h"
+#include "content_sub_panel.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 ();
index 2d874b9594e8997571516d805d3d396df253b366..d8fdf2d02667c7894477a9e5aaa8691d6758c146 100644 (file)
@@ -64,8 +64,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);
@@ -222,7 +222,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 ()) {
@@ -258,7 +258,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 +272,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 +298,8 @@ VideoPanel::setup_description ()
        }
 
        Crop const crop = vcs->crop ();
-       if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != libdcp::Size (0, 0)) {
-               libdcp::Size cropped = vcs->video_size_after_crop ();
+       if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != dcp::Size (0, 0)) {
+               dcp::Size cropped = vcs->video_size_after_crop ();
                d << wxString::Format (
                        _("Cropped to %dx%d (%.2f:1)\n"),
                        cropped.width, cropped.height,
@@ -308,8 +308,8 @@ VideoPanel::setup_description ()
                ++lines;
        }
 
-       libdcp::Size const container_size = _editor->film()->frame_size ();
-       libdcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size);
+       dcp::Size const container_size = _parent->film()->frame_size ();
+       dcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size);
 
        if (scaled != vcs->video_size_after_crop ()) {
                d << wxString::Format (
@@ -331,7 +331,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 +346,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,7 +363,7 @@ VideoPanel::edit_colour_conversion_clicked ()
 void
 VideoPanel::content_selection_changed ()
 {
-       VideoContentList sel = _editor->selected_video_content ();
+       VideoContentList sel = _parent->selected_video ();
        bool const single = sel.size() == 1;
 
        _left_crop->set_content (sel);
index 99633491d835ea9948538ad696459035e5f16876..e17541cd3f8ef3f565549cd7e6564a949e842da0 100644 (file)
@@ -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
 
 */
 
+/** @file  src/lib/video_panel.h
+ *  @brief VideoPanel class.
+ */
+
 #include "lib/film.h"
-#include "film_editor_panel.h"
+#include "content_sub_panel.h"
 #include "content_widget.h"
 
 class wxChoice;
@@ -26,10 +30,13 @@ 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);
index 8bf2451c22cba584271a648c7037a7d2b909a0ca..719496ae702a9d190aaea309a5d76e912ca8443a 100644 (file)
@@ -15,13 +15,14 @@ sources = """
           config_dialog.cc
           content_colour_conversion_dialog.cc
           content_menu.cc
+          content_panel.cc
+          content_sub_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
@@ -38,6 +39,7 @@ sources = """
           server_dialog.cc
           servers_list_dialog.cc
           subtitle_panel.cc
+          subtitle_view.cc
           table_dialog.cc
           timecode.cc
           timeline.cc
index f306319608a0e194d5128b8f490d600179df4bfc..8fc6670d6befe69fd2558ae0e05081444c845acd 100644 (file)
@@ -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
index f7df6fca48a9044f2df8e91f6b4582637cce5687..63f2049cd2ed4ca7ca118a7069002055520fa870 100644 (file)
@@ -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
 
 class wxEvtHandler;
 
+/** @class wxUISignaller
+ *  @brief UISignaller for the wxWidgets event loop
+ */
+
 class wxUISignaller : public UISignaller
 {
 public:
index 218a786b2a8f52dd57237416fb5406867e0726ad..94a08f37289625eaf4c660ab4ba5c81d6b5cab36 100644 (file)
@@ -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);
index e65804aa5e5ff2908eb2434a95c79ebdf1b16769..fa5b33bb921feb348e4a0d6ad83fda54c40411b7 100644 (file)
@@ -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
 
 */
 
+/** @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 (file)
index 0000000..a129b66
--- /dev/null
@@ -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
index 77b2aeaf6f9671a1685bf841d357685145a291a8..13a8dac8436e3fe832e064f11411ea7c051711b5 100644 (file)
@@ -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
 
 */
 
+/** @file  test/audio_analysis_test.cc
+ *  @brief Check serialisation of audio analyses.
+ */
+
 #include <boost/test/unit_test.hpp>
 #include "lib/audio_analysis.h"
 
@@ -26,7 +30,6 @@ random_float ()
        return (float (rand ()) / RAND_MAX) * 2 - 1;
 }
 
-/* Check serialisation of audio analyses */
 BOOST_AUTO_TEST_CASE (audio_analysis_test)
 {
        int const channels = 3;
@@ -50,7 +53,7 @@ BOOST_AUTO_TEST_CASE (audio_analysis_test)
 
        AudioAnalysis b ("build/test/audio_analysis_test");
        for (int i = 0; i < channels; ++i) {
-               BOOST_CHECK (b.points(i) == points);
+               BOOST_CHECK_EQUAL (b.points(i), points);
                for (int j = 0; j < points; ++j) {
                        AudioPoint p = b.get_point (i, j);
                        BOOST_CHECK_CLOSE (p[AudioPoint::PEAK], random_float (), 1);
diff --git a/test/audio_buffers_test.cc b/test/audio_buffers_test.cc
new file mode 100644 (file)
index 0000000..15f9184
--- /dev/null
@@ -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 (file)
index 0000000..a14e2f9
--- /dev/null
@@ -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);
+               }
+       }
+}
index 8ac5f746c8fb46e3621d392f9267297e7e792804..68e14ff3ca353f470d9f09f8c14112f6713b34a4 100644 (file)
@@ -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
 
 */
 
+/** @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 (file)
index 0000000..bcd16fd
--- /dev/null
@@ -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);
+               }
+       }
+}
index bfb53b0871193c681c333497b3343403d5d1e761..fc597b91df10e4ca9dadaf8303a4b7cb844de983 100644 (file)
 
 */
 
+/** @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 (file)
index 31d055a..0000000
+++ /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);
-       }
-}
index 5c594f68cc064f170d3290c83833422522012c52..148ec9738c74329c9c126506488fac020ff41037 100644 (file)
@@ -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 (file)
index 0000000..d10d1ed
--- /dev/null
@@ -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 ()));
+}
index 07af1255c486ea42380f570ba6a643d566aedde0..4e3ecc983a75524d20582b135ed29b73c08e2bff 100644 (file)
 
 */
 
+/** @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;
 
 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 +66,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 +81,13 @@ 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),
+                       dcp::Size (1998, 1080),
+                       dcp::Size (1998, 1080),
                        Scaler::from_id ("bicubic"),
                        EYES_BOTH,
                        PART_WHOLE,
@@ -85,15 +95,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 +133,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 +149,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 +164,13 @@ 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),
+                       dcp::Size (1998, 1080),
+                       dcp::Size (1998, 1080),
                        Scaler::from_id ("bicubic"),
                        EYES_BOTH,
                        PART_WHOLE,
@@ -164,15 +178,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 +216,7 @@ BOOST_AUTO_TEST_CASE (client_server_test_yuv)
        for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
                delete *i;
        }
+
+       delete server;
 }
 
index f850847b837164264ad81bdb28284c43dbdf4c6a..7de169dd3e305ad23d76dbaf12b756ef80ccaebb 100644 (file)
@@ -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
 
 */
 
+/** @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 (file)
index 0000000..95fa7d1
--- /dev/null
@@ -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));
+}
index 2e83d45c9101dbef0c064e3c0e26ef5446bcab85..98efe4dd0769ea62aae9e4550fdd41af43349311 100644 (file)
@@ -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
 
 */
 
+/** @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);
index 4922ec4d4d99f4c6d523ffc9175bf32ece02caee..234bf2c79b10bd8dafc5e74f71d2a36c98c22d3e 100644 (file)
@@ -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
 
 */
 
+/** @file test/ffmpeg_dcp_test.cc
+ *  @brief Test creation of a very simple DCP from some FFmpegContent (data/test.mp4).
+ *
+ *  Also a quick test of Film::have_dcp ().
+ */
+
 #include <boost/test/unit_test.hpp>
 #include <boost/filesystem.hpp>
 #include "lib/film.h"
 
 using boost::shared_ptr;
 
-/** @file test/ffmpeg_dcp_test.cc
- *  @brief Test scaling and black-padding of images from a still-image source.
- */
-
 BOOST_AUTO_TEST_CASE (ffmpeg_dcp_test)
 {
        shared_ptr<Film> film = new_test_film ("ffmpeg_dcp_test");
diff --git a/test/ffmpeg_decoder_seek_test.cc b/test/ffmpeg_decoder_seek_test.cc
new file mode 100644 (file)
index 0000000..968c3bd
--- /dev/null
@@ -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 (file)
index 0000000..9a14c5a
--- /dev/null
@@ -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);
+}
+
index a3b9bb4f6506793186b1aff886f68f67c07d6662..26834aafc8d7512b304ae24488d3d013893c8d3b 100644 (file)
@@ -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
 
 */
 
+/** @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.cc
deleted file mode 100644 (file)
index 6caf0d0..0000000
+++ /dev/null
@@ -1,81 +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 "lib/film.h"
-#include "lib/ffmpeg_decoder.h"
-#include "lib/ffmpeg_content.h"
-#include "test.h"
-
-using boost::shared_ptr;
-
-BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
-{
-       shared_ptr<Film> film = new_test_film ("ffmpeg_pts_offset_test");
-       shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/test.mp4"));
-       content->_audio_stream.reset (new FFmpegAudioStream);
-       content->_video_frame_rate = 24;
-
-       {
-               /* Sound == video so no offset required */
-               content->_first_video = 0;
-               content->_audio_stream->first_audio = 0;
-               FFmpegDecoder decoder (film, content, true, true);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
-       }
-
-       {
-               /* Common offset should be removed */
-               content->_first_video = 600;
-               content->_audio_stream->first_audio = 600;
-               FFmpegDecoder decoder (film, content, true, true);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, -600);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, -600);
-       }
-
-       {
-               /* Video is on a frame boundary */
-               content->_first_video = 1.0 / 24.0;
-               content->_audio_stream->first_audio = 0;
-               FFmpegDecoder decoder (film, content, true, true);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
-       }
-
-       {
-               /* Video is off a frame boundary */
-               double const frame = 1.0 / 24.0;
-               content->_first_video = frame + 0.0215;
-               content->_audio_stream->first_audio = 0;
-               FFmpegDecoder decoder (film, content, true, true);
-               BOOST_CHECK_CLOSE (decoder._pts_offset, (frame - 0.0215), 0.00001);
-               BOOST_CHECK_CLOSE (decoder._pts_offset, (frame - 0.0215), 0.00001);
-       }
-
-       {
-               /* Video is off a frame boundary and both have a common offset */
-               double const frame = 1.0 / 24.0;
-               content->_first_video = frame + 0.0215 + 4.1;
-               content->_audio_stream->first_audio = 4.1;
-               FFmpegDecoder decoder (film, content, true, true);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, (frame - 0.0215) - 4.1);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, (frame - 0.0215) - 4.1);
-       }
-}
diff --git a/test/ffmpeg_pts_offset_test.cc b/test/ffmpeg_pts_offset_test.cc
new file mode 100644 (file)
index 0000000..94e7223
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file  test/ffmpeg_pts_offset_test.cc
+ *  @brief Check the computation of _pts_offset in FFmpegDecoder.
+ */
+
+#include <boost/test/unit_test.hpp>
+#include "lib/film.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/ffmpeg_audio_stream.h"
+#include "test.h"
+
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
+{
+       shared_ptr<Film> film = new_test_film ("ffmpeg_pts_offset_test");
+       shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/test.mp4"));
+       content->_audio_stream.reset (new FFmpegAudioStream);
+       content->_video_frame_rate = 24;
+
+       {
+               /* Sound == video so no offset required */
+               content->_first_video = ContentTime ();
+               content->_audio_stream->first_audio = ContentTime ();
+               FFmpegDecoder decoder (content, film->log());
+               BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime ());
+       }
+
+       {
+               /* Common offset should be removed */
+               content->_first_video = ContentTime::from_seconds (600);
+               content->_audio_stream->first_audio = ContentTime::from_seconds (600);
+               FFmpegDecoder decoder (content, film->log());
+               BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime::from_seconds (-600));
+       }
+
+       {
+               /* Video is on a frame boundary */
+               content->_first_video = ContentTime::from_frames (1, 24);
+               content->_audio_stream->first_audio = ContentTime ();
+               FFmpegDecoder decoder (content, film->log());
+               BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime ());
+       }
+
+       {
+               /* Video is off a frame boundary */
+               double const frame = 1.0 / 24.0;
+               content->_first_video = ContentTime::from_seconds (frame + 0.0215);
+               content->_audio_stream->first_audio = ContentTime ();
+               FFmpegDecoder decoder (content, film->log());
+               BOOST_CHECK_CLOSE (decoder._pts_offset.seconds(), (frame - 0.0215), 0.00001);
+       }
+
+       {
+               /* Video is off a frame boundary and both have a common offset */
+               double const frame = 1.0 / 24.0;
+               content->_first_video = ContentTime::from_seconds (frame + 0.0215 + 4.1);
+               content->_audio_stream->first_audio = ContentTime::from_seconds (4.1);
+               FFmpegDecoder decoder (content, film->log());
+               BOOST_CHECK_CLOSE (decoder._pts_offset.seconds(), (frame - 0.0215) - 4.1, 0.1);
+       }
+}
index 14c01a9763d32d00963bddf94b2e4b83dddd286e..888834511677c8d95f7cffa76a2c921b0dc3fff9 100644 (file)
@@ -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
 
 */
 
+/** @file  test/file_group_test.cc
+ *  @brief Check that FileGroup works.
+ */
+
 #include <stdint.h>
 #include <cstdio>
 #include <boost/test/unit_test.hpp>
index 7440e43381e27dcb82530391f470607c13939332..70f29b998933d355df2b793797ad36d38c9a0205 100644 (file)
@@ -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
 
 */
 
+/** @file  test/film_metadata_test.cc
+ *  @brief Test some basic reading/writing of film metadata.
+ */
+
 #include <sstream>
 #include <boost/test/unit_test.hpp>
 #include <boost/filesystem.hpp>
@@ -33,13 +37,9 @@ using boost::shared_ptr;
 
 BOOST_AUTO_TEST_CASE (film_metadata_test)
 {
-       string const test_film = "build/test/film_metadata_test";
-       
-       if (boost::filesystem::exists (test_film)) {
-               boost::filesystem::remove_all (test_film);
-       }
+       shared_ptr<Film> f = new_test_film ("film_metadata_test");
+       boost::filesystem::path dir = test_film_dir ("film_metadata_test");
 
-       shared_ptr<Film> f (new Film (test_film));
        f->_isdcf_date = boost::gregorian::from_undelimited_string ("20130211");
        BOOST_CHECK (f->container() == 0);
        BOOST_CHECK (f->dcp_content_type() == 0);
@@ -52,9 +52,9 @@ BOOST_AUTO_TEST_CASE (film_metadata_test)
 
        list<string> ignore;
        ignore.push_back ("Key");
-       check_xml ("test/data/metadata.xml.ref", test_film + "/metadata.xml", ignore);
+       check_xml ("test/data/metadata.xml.ref", dir.string() + "/metadata.xml", ignore);
 
-       shared_ptr<Film> g (new Film (test_film));
+       shared_ptr<Film> g (new Film (dir));
        g->read_metadata ();
 
        BOOST_CHECK_EQUAL (g->name(), "fred");
@@ -62,5 +62,5 @@ BOOST_AUTO_TEST_CASE (film_metadata_test)
        BOOST_CHECK_EQUAL (g->container(), Ratio::from_id ("185"));
        
        g->write_metadata ();
-       check_xml ("test/data/metadata.xml.ref", test_film + "/metadata.xml", ignore);
+       check_xml ("test/data/metadata.xml.ref", dir.string() + "/metadata.xml", ignore);
 }
index 7e197dc03bf12472c848579306d0be8d45360ea8..e8ebcea3b959833ccb1009c561c7b99f4394bbf5 100644 (file)
@@ -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
 
 */
 
+/** @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));
 }
 
index 51ad49ebf63479694cd2c4099686a3e630babe7d..ee4819d6b13998135faf7fd2322874077bd147c6 100644 (file)
@@ -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
 
 */
 
+/** @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);
+       }
 }
index a77bc949ce123e7e851ea23d038315039476b1a9..c1b66d4e01f38f36127c49087c79473cbdb20739 100644 (file)
@@ -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
 
 */
 
+/** @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"
index dd0208b1d1dd7379a82ee945c53ff5b573dd54c2..f6c3a4bb2bbc87616ffc3cfbeb84a3ffacc07704 100644 (file)
 
 */
 
+/** @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
index 1b720d9bf5d3b88fe2ecf1f2399c1c9209674bdc..68d225e6efa36dc6b69a5b30f8319b7e6da74eaa 100644 (file)
@@ -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
 
 */
 
+/** @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 (file)
index 0000000..d480602
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file  test/player_test.cc
+ *  @brief Various tests of Player.
+ */
+
+#include <iostream>
+#include <boost/test/unit_test.hpp>
+#include "lib/film.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ratio.h"
+#include "lib/audio_buffers.h"
+#include "lib/player.h"
+#include "test.h"
+
+using std::cout;
+using std::list;
+using boost::shared_ptr;
+
+/** Player::overlaps */
+BOOST_AUTO_TEST_CASE (player_overlaps_test)
+{
+       shared_ptr<Film> film = new_test_film ("player_overlaps_test");
+       film->set_container (Ratio::from_id ("185"));
+       shared_ptr<FFmpegContent> A (new FFmpegContent (film, "test/data/test.mp4"));
+       shared_ptr<FFmpegContent> B (new FFmpegContent (film, "test/data/test.mp4"));
+       shared_ptr<FFmpegContent> C (new FFmpegContent (film, "test/data/test.mp4"));
+
+       film->examine_and_add_content (A);
+       film->examine_and_add_content (B);
+       film->examine_and_add_content (C);
+       wait_for_jobs ();
+
+       BOOST_CHECK_EQUAL (A->full_length(), DCPTime (280000));
+
+       A->set_position (DCPTime::from_seconds (0));
+       B->set_position (DCPTime::from_seconds (10));
+       C->set_position (DCPTime::from_seconds (20));
+
+       shared_ptr<Player> player = film->make_player ();
+
+       list<shared_ptr<Piece> > o = player->overlaps<FFmpegContent> (DCPTime::from_seconds (0), DCPTime::from_seconds (5));
+       BOOST_CHECK_EQUAL (o.size(), 1);
+       BOOST_CHECK_EQUAL (o.front()->content, A);
+
+       o = player->overlaps<FFmpegContent> (DCPTime::from_seconds (5), DCPTime::from_seconds (8));
+       BOOST_CHECK_EQUAL (o.size(), 0);
+
+       o = player->overlaps<FFmpegContent> (DCPTime::from_seconds (8), DCPTime::from_seconds (12));
+       BOOST_CHECK_EQUAL (o.size(), 1);
+       BOOST_CHECK_EQUAL (o.front()->content, B);
+
+       o = player->overlaps<FFmpegContent> (DCPTime::from_seconds (2), DCPTime::from_seconds (12));
+       BOOST_CHECK_EQUAL (o.size(), 2);
+       BOOST_CHECK_EQUAL (o.front()->content, A);
+       BOOST_CHECK_EQUAL (o.back()->content, B);
+
+       o = player->overlaps<FFmpegContent> (DCPTime::from_seconds (8), DCPTime::from_seconds (11));
+       BOOST_CHECK_EQUAL (o.size(), 1);
+       BOOST_CHECK_EQUAL (o.front()->content, B);
+}
+
+/** Check that the Player correctly generates silence when used with a silent FFmpegContent */
+BOOST_AUTO_TEST_CASE (player_silence_padding_test)
+{
+       shared_ptr<Film> film = new_test_film ("player_silence_padding_test");
+       film->set_name ("player_silence_padding_test");
+       shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/test.mp4"));
+       film->set_container (Ratio::from_id ("185"));
+       film->set_audio_channels (6);
+       
+       film->examine_and_add_content (c);
+       wait_for_jobs ();
+
+       shared_ptr<Player> player = film->make_player ();
+       shared_ptr<AudioBuffers> test = player->get_audio (DCPTime (0), DCPTime::from_seconds (1), true);
+       BOOST_CHECK_EQUAL (test->frames(), 48000);
+       BOOST_CHECK_EQUAL (test->channels(), film->audio_channels ());
+
+       for (int i = 0; i < test->frames(); ++i) {
+               for (int c = 0; c < test->channels(); ++c) {
+                       BOOST_CHECK_EQUAL (test->data()[c][i], 0);
+               }
+       }
+}
+
index f3cbb504f0b874b9d5ef97b65d681b2e91ff33f3..84e80861b14cf3f0abb8daae63a781edbf884513 100644 (file)
@@ -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
 
 */
 
+/** @file  test/ratio_test.cc
+ *  @brief Test Ratio and fit_ratio_within().
+ */
+
 #include <iostream>
 #include <boost/test/unit_test.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
 #include "lib/ratio.h"
 #include "lib/util.h"
 
 using std::ostream;
 
-namespace libdcp {
-       
-ostream&
-operator<< (ostream& s, libdcp::Size const & t)
-{
-       s << t.width << "x" << t.height;
-       return s;
-}
-
-}
-
 BOOST_AUTO_TEST_CASE (ratio_test)
 {
        Ratio::setup_ratios ();
 
        Ratio const * r = Ratio::from_id ("119");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1290, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1290, 1080));
 
        r = Ratio::from_id ("133");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1440, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1440, 1080));
 
        r = Ratio::from_id ("137");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1480, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1480, 1080));
 
        r = Ratio::from_id ("138");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1485, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1485, 1080));
 
        r = Ratio::from_id ("166");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1800, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1800, 1080));
 
        r = Ratio::from_id ("178");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1920, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1920, 1080));
 
        r = Ratio::from_id ("185");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1998, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1998, 1080));
 
        r = Ratio::from_id ("239");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (2048, 858));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (2048, 858));
 
        r = Ratio::from_id ("full-frame");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (2048, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (2048, 1080));
 }
 
index 284895e0a75d3bcee73ae157e078fc1ad6b86925..c9a5932418252ab6246467fc8c2749483b43e8d9 100644 (file)
 
 */
 
+/** @file  test/recover_test.cc
+ *  @brief Test recovery of a DCP transcode after a crash.
+ */
+
 #include <boost/test/unit_test.hpp>
-#include <libdcp/stereo_picture_asset.h>
+#include <dcp/stereo_picture_mxf.h>
 #include "lib/film.h"
 #include "lib/dcp_content_type.h"
 #include "lib/image_content.h"
@@ -30,12 +34,13 @@ using std::string;
 using boost::shared_ptr;
 
 static void
-note (libdcp::NoteType, string n)
+note (dcp::NoteType t, string n)
 {
-       cout << n << "\n";
+       if (t == dcp::DCP_ERROR) {
+               cout << n << "\n";
+       }
 }
 
-/** Test recovery of a DCP transcode after a crash */
 BOOST_AUTO_TEST_CASE (recover_test)
 {
        shared_ptr<Film> film = new_test_film ("recover_test");
@@ -52,20 +57,22 @@ BOOST_AUTO_TEST_CASE (recover_test)
        film->make_dcp ();
        wait_for_jobs ();
 
+       boost::filesystem::path const video = "build/test/recover_test/video/185_2K_e8efb95857b62aa6ff94e3d669e75776_24_bicubic_100000000_P_S_3D.mxf";
+
        boost::filesystem::copy_file (
-               "build/test/recover_test/video/185_2K_58a090f8d70a2b410c534120d35e5256_24_bicubic_200000000_P_S_3D.mxf",
+               video,
                "build/test/recover_test/original.mxf"
                );
        
-       boost::filesystem::resize_file ("build/test/recover_test/video/185_2K_58a090f8d70a2b410c534120d35e5256_24_bicubic_200000000_P_S_3D.mxf", 2 * 1024 * 1024);
+       boost::filesystem::resize_file (video, 2 * 1024 * 1024);
 
        film->make_dcp ();
        wait_for_jobs ();
 
-       shared_ptr<libdcp::StereoPictureAsset> A (new libdcp::StereoPictureAsset ("build/test/recover_test", "original.mxf"));
-       shared_ptr<libdcp::StereoPictureAsset> B (new libdcp::StereoPictureAsset ("build/test/recover_test/video", "185_2K_58a090f8d70a2b410c534120d35e5256_24_bicubic_200000000_P_S_3D.mxf"));
+       shared_ptr<dcp::StereoPictureMXF> A (new dcp::StereoPictureMXF ("build/test/recover_test/original.mxf"));
+       shared_ptr<dcp::StereoPictureMXF> B (new dcp::StereoPictureMXF (video));
 
-       libdcp::EqualityOptions eq;
+       dcp::EqualityOptions eq;
        eq.mxf_names_can_differ = true;
        BOOST_CHECK (A->equals (B, eq, boost::bind (&note, _1, _2)));
 }
diff --git a/test/repeat_frame_test.cc b/test/repeat_frame_test.cc
new file mode 100644 (file)
index 0000000..4f66420
--- /dev/null
@@ -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 ()));
+}
+
index 9247159a7065b1a46ecf06d49f74f8cfdbab6695..ffd636ac8ffb6371503d6f1a90743fd945f55c0a 100644 (file)
@@ -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
 
 */
 
+/** @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);
index cdf1653cdf84a7c78b40f1518a3d81516a600063..441af6bf30514ecb2eaaafa58ae7deea3ffdaa16 100644 (file)
@@ -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
 
 */
 
+/** @file test/scaling_test.cc
+ *  @brief Test scaling and black-padding of images from a still-image source.
+ */
+
 #include <boost/test/unit_test.hpp>
 #include "lib/image_content.h"
 #include "lib/ratio.h"
 #include "lib/dcp_content_type.h"
 #include "test.h"
 
-/** @file test/scaling_test.cc
- *  @brief Test scaling and black-padding of images from a still-image source.
- */
-
 using std::string;
 using boost::shared_ptr;
 
@@ -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 (file)
index 0000000..682fa93
--- /dev/null
@@ -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);
+}
index f1136a3f1a91c159fa3a32d3158d8b139822de88..d876a0228d7acafa9cd7ab0bb8319097c127869d 100644 (file)
@@ -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
 
 */
 
+/** @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 (file)
index 0000000..a77d845
--- /dev/null
@@ -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 ()));
+}
+
index fed3ecabeb99283bcad12a4e0f542c5bef664b2c..16ab83baf7747ea933ac1c59d7d115e04e22c591 100644 (file)
@@ -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
 
 */
 
+/** @test  test/stream_test.cc
+ *  @brief Some simple tests of FFmpegAudioStream.
+ */
+
 #include <boost/test/unit_test.hpp>
 #include <libxml++/libxml++.h>
 #include <libcxml/cxml.h>
 #include "lib/ffmpeg_content.h"
+#include "lib/ffmpeg_audio_stream.h"
 #include "lib/film.h"
 
 using std::pair;
@@ -65,7 +70,7 @@ BOOST_AUTO_TEST_CASE (stream_test)
                map->add_child("DCP")->add_child_text ("2");
        }
                
-       FFmpegAudioStream a (shared_ptr<cxml::Node> (new cxml::Node (root)), 5);
+       FFmpegAudioStream a (cxml::NodePtr (new cxml::Node (root)), 5);
 
        BOOST_CHECK_EQUAL (a.identifier(), "4");
        BOOST_CHECK_EQUAL (a.frame_rate, 44100);
@@ -73,11 +78,11 @@ BOOST_AUTO_TEST_CASE (stream_test)
        BOOST_CHECK_EQUAL (a.name, "hello there world");
        BOOST_CHECK_EQUAL (a.mapping.content_channels(), 2);
 
-       BOOST_CHECK_EQUAL (a.mapping.get (0, libdcp::LEFT), 1);
-       BOOST_CHECK_EQUAL (a.mapping.get (0, libdcp::RIGHT), 0);
-       BOOST_CHECK_EQUAL (a.mapping.get (0, libdcp::CENTRE), 1);
-       BOOST_CHECK_EQUAL (a.mapping.get (1, libdcp::LEFT), 0);
-       BOOST_CHECK_EQUAL (a.mapping.get (1, libdcp::RIGHT), 1);
-       BOOST_CHECK_EQUAL (a.mapping.get (1, libdcp::CENTRE), 1);
+       BOOST_CHECK_EQUAL (a.mapping.get (0, dcp::LEFT), 1);
+       BOOST_CHECK_EQUAL (a.mapping.get (0, dcp::RIGHT), 0);
+       BOOST_CHECK_EQUAL (a.mapping.get (0, dcp::CENTRE), 1);
+       BOOST_CHECK_EQUAL (a.mapping.get (1, dcp::LEFT), 0);
+       BOOST_CHECK_EQUAL (a.mapping.get (1, dcp::RIGHT), 1);
+       BOOST_CHECK_EQUAL (a.mapping.get (1, dcp::CENTRE), 1);
 }
 
diff --git a/test/subrip_test.cc b/test/subrip_test.cc
new file mode 100644 (file)
index 0000000..84ad7f2
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file  test/subrip_test.cc
+ *  @brief Various tests of the subrip code.
+ */
+
+#include <boost/test/unit_test.hpp>
+#include <dcp/subtitle_content.h>
+#include "lib/subrip.h"
+#include "lib/subrip_content.h"
+#include "lib/subrip_decoder.h"
+#include "lib/render_subtitles.h"
+#include "test.h"
+
+using std::list;
+using std::vector;
+using std::string;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+
+/** Test SubRip::convert_time */
+BOOST_AUTO_TEST_CASE (subrip_time_test)
+{
+       BOOST_CHECK_EQUAL (SubRip::convert_time ("00:03:10,500"), ContentTime::from_seconds ((3 * 60) + 10 + 0.5));
+       BOOST_CHECK_EQUAL (SubRip::convert_time ("04:19:51,782"), ContentTime::from_seconds ((4 * 3600) + (19 * 60) + 51 + 0.782));
+}
+
+/** Test SubRip::convert_coordinate */
+BOOST_AUTO_TEST_CASE (subrip_coordinate_test)
+{
+       BOOST_CHECK_EQUAL (SubRip::convert_coordinate ("foo:42"), 42);
+       BOOST_CHECK_EQUAL (SubRip::convert_coordinate ("X1:999"), 999);
+}
+
+/** Test SubRip::convert_content */
+BOOST_AUTO_TEST_CASE (subrip_content_test)
+{
+       list<string> c;
+       list<SubRipSubtitlePiece> p;
+       
+       c.push_back ("Hello world");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       c.clear ();
+
+       c.push_back ("<b>Hello world</b>");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().bold, true);
+       c.clear ();
+
+       c.push_back ("<i>Hello world</i>");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().italic, true);
+       c.clear ();
+
+       c.push_back ("<u>Hello world</u>");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().underline, true);
+       c.clear ();
+
+       c.push_back ("{b}Hello world{/b}");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().bold, true);
+       c.clear ();
+
+       c.push_back ("{i}Hello world{/i}");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().italic, true);
+       c.clear ();
+
+       c.push_back ("{u}Hello world{/u}");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().underline, true);
+       c.clear ();
+
+       c.push_back ("<b>This is <i>nesting</i> of subtitles</b>");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 3);
+       list<SubRipSubtitlePiece>::iterator i = p.begin ();     
+       BOOST_CHECK_EQUAL (i->text, "This is ");
+       BOOST_CHECK_EQUAL (i->bold, true);
+       BOOST_CHECK_EQUAL (i->italic, false);
+       ++i;
+       BOOST_CHECK_EQUAL (i->text, "nesting");
+       BOOST_CHECK_EQUAL (i->bold, true);
+       BOOST_CHECK_EQUAL (i->italic, true);
+       ++i;
+       BOOST_CHECK_EQUAL (i->text, " of subtitles");
+       BOOST_CHECK_EQUAL (i->bold, true);
+       BOOST_CHECK_EQUAL (i->italic, false);
+       ++i;
+       c.clear ();
+}
+
+/** Test parsing of full SubRip file content */
+BOOST_AUTO_TEST_CASE (subrip_parse_test)
+{
+       shared_ptr<Film> film = new_test_film ("subrip_parse_test");
+       shared_ptr<SubRipContent> content (new SubRipContent (film, "test/data/subrip.srt"));
+       content->examine (shared_ptr<Job> ());
+       BOOST_CHECK_EQUAL (content->full_length(), DCPTime::from_seconds ((3 * 60) + 56.471));
+
+       SubRip s (content);
+
+       vector<SubRipSubtitle>::const_iterator i = s._subtitles.begin();
+
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->period.from, ContentTime::from_seconds ((1 * 60) + 49.200));
+       BOOST_CHECK_EQUAL (i->period.to, ContentTime::from_seconds ((1 * 60) + 52.351));
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "This is a subtitle, and it goes over two lines.");
+
+       ++i;
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->period.from, ContentTime::from_seconds ((1 * 60) + 52.440));
+       BOOST_CHECK_EQUAL (i->period.to, ContentTime::from_seconds ((1 * 60) + 54.351));
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "We have emboldened this");
+       BOOST_CHECK_EQUAL (i->pieces.front().bold, true);
+
+       ++i;
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->period.from, ContentTime::from_seconds ((1 * 60) + 54.440));
+       BOOST_CHECK_EQUAL (i->period.to, ContentTime::from_seconds ((1 * 60) + 56.590));
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "And italicised this.");
+       BOOST_CHECK_EQUAL (i->pieces.front().italic, true);
+
+       ++i;
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->period.from, ContentTime::from_seconds ((1 * 60) + 56.680));
+       BOOST_CHECK_EQUAL (i->period.to, ContentTime::from_seconds ((1 * 60) + 58.955));
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "Shall I compare thee to a summers' day?");
+
+       ++i;
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->period.from, ContentTime::from_seconds ((2 * 60) + 0.840));
+       BOOST_CHECK_EQUAL (i->period.to, ContentTime::from_seconds ((2 * 60) + 3.400));
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "Is this a dagger I see before me?");
+
+       ++i;
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->period.from, ContentTime::from_seconds ((3 * 60) + 54.560));
+       BOOST_CHECK_EQUAL (i->period.to, ContentTime::from_seconds ((3 * 60) + 56.471));
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "Hello world.");
+
+       ++i;
+       BOOST_CHECK (i == s._subtitles.end ());
+}
+
+/** Test rendering of a SubRip subtitle */
+BOOST_AUTO_TEST_CASE (subrip_render_test)
+{
+       shared_ptr<Film> film = new_test_film ("subrip_render_test");
+       shared_ptr<SubRipContent> content (new SubRipContent (film, "test/data/subrip.srt"));
+       content->examine (shared_ptr<Job> ());
+       BOOST_CHECK_EQUAL (content->full_length(), DCPTime::from_seconds ((3 * 60) + 56.471));
+
+       shared_ptr<SubRipDecoder> decoder (new SubRipDecoder (content));
+       list<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");
+}
+
+/** Test of reading a typical .srt */
+BOOST_AUTO_TEST_CASE (subrip_read_test)
+{
+       shared_ptr<Film> film = new_test_film ("subrip_read_test");
+       boost::filesystem::path p = private_data / "sintel_en.srt";
+       shared_ptr<SubRipContent> s (new SubRipContent (film, p));
+       s->examine (shared_ptr<Job> ());
+}
index 0b87b8062a67adc503bf076ba32624cf7678a2ff..4466b74cb32d77212766b84302df0e4a03456daf 100644 (file)
@@ -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
 
 */
 
+/** @file  test/test.cc
+ *  @brief Overall test stuff and useful methods for tests.
+ */
+
 #include <vector>
 #include <list>
+#include <Magick++.h>
 #include <libxml++/libxml++.h>
-#include <libdcp/dcp.h>
+#include <dcp/dcp.h>
 #include "lib/config.h"
 #include "lib/util.h"
 #include "lib/ui_signaller.h"
@@ -29,6 +34,7 @@
 #include "lib/job.h"
 #include "lib/cross.h"
 #include "lib/server_finder.h"
+#include "lib/image.h"
 #define BOOST_TEST_DYN_LINK
 #define BOOST_TEST_MODULE dcpomatic_test
 #include <boost/test/unit_test.hpp>
@@ -41,6 +47,8 @@ using std::cerr;
 using std::list;
 using boost::shared_ptr;
 
+boost::filesystem::path private_data = boost::filesystem::path ("..") / boost::filesystem::path ("dcpomatic-test-private");
+
 class TestUISignaller : public UISignaller
 {
 public:
@@ -53,20 +61,26 @@ public:
 
 struct TestConfig
 {
-       TestConfig()
+       TestConfig ()
        {
-               dcpomatic_setup();
+               dcpomatic_setup ();
 
                Config::instance()->set_num_local_encoding_threads (1);
                Config::instance()->set_server_port_base (61920);
                Config::instance()->set_default_isdcf_metadata (ISDCFMetadata ());
                Config::instance()->set_default_container (static_cast<Ratio*> (0));
                Config::instance()->set_default_dcp_content_type (static_cast<DCPContentType*> (0));
+               Config::instance()->set_default_audio_delay (0);
 
                ServerFinder::instance()->disable ();
 
                ui_signaller = new TestUISignaller ();
        }
+
+       ~TestConfig ()
+       {
+               JobManager::drop ();
+       }
 };
 
 BOOST_GLOBAL_FIXTURE (TestConfig);
@@ -98,7 +112,7 @@ 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");
@@ -127,26 +141,26 @@ check_file (boost::filesystem::path ref, boost::filesystem::path check)
 }
 
 static void
-note (libdcp::NoteType t, string n)
+note (dcp::NoteType t, string n)
 {
-       if (t == libdcp::ERROR) {
+       if (t == dcp::DCP_ERROR) {
                cerr << n << "\n";
        }
 }
 
 void
-check_dcp (string ref, string check)
+check_dcp (boost::filesystem::path ref, boost::filesystem::path check)
 {
-       libdcp::DCP ref_dcp (ref);
+       dcp::DCP ref_dcp (ref);
        ref_dcp.read ();
-       libdcp::DCP check_dcp (check);
+       dcp::DCP check_dcp (check);
        check_dcp.read ();
 
-       libdcp::EqualityOptions options;
+       dcp::EqualityOptions options;
        options.max_mean_pixel_error = 5;
        options.max_std_dev_pixel_error = 5;
        options.max_audio_sample_error = 255;
-       options.cpl_names_can_differ = true;
+       options.cpl_annotation_texts_can_differ = true;
        options.mxf_names_can_differ = true;
        
        BOOST_CHECK (ref_dcp.equals (check_dcp, options, boost::bind (note, _1, _2)));
@@ -168,7 +182,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 +211,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 +235,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 +256,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 ());
+}
index dd007e8c9a15033e0db8112b4f3856bb5582c3e4..57ea32c5792d6a59e8000b4ffe9f327f0d60e89c 100644 (file)
 #include <boost/filesystem.hpp>
 
 class Film;
+class Image;
+
+extern boost::filesystem::path private_data;
 
 extern void wait_for_jobs ();
 extern boost::shared_ptr<Film> new_test_film (std::string);
-extern void check_dcp (std::string, std::string);
+extern void check_dcp (boost::filesystem::path, boost::filesystem::path);
+extern void check_file (boost::filesystem::path ref, boost::filesystem::path check);
 extern void check_xml (boost::filesystem::path, boost::filesystem::path, std::list<std::string>);
 extern void check_file (boost::filesystem::path, boost::filesystem::path);
 extern boost::filesystem::path test_film_dir (std::string);
+extern void write_image (boost::shared_ptr<const Image> image, boost::filesystem::path file);
index 8da9b46a891fa8daeed4cd406eb784d23b8c7cc6..5ad78103fb98d05c40cdf4e2477c29556cc17757 100644 (file)
@@ -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
 
 */
 
+/** @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 (file)
index 0000000..67e909e
--- /dev/null
@@ -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_file ("test/data/upmixer_a_test/L.wav", "build/test/upmixer_a_test/L.wav");
+       check_file ("test/data/upmixer_a_test/R.wav", "build/test/upmixer_a_test/R.wav");
+       check_file ("test/data/upmixer_a_test/C.wav", "build/test/upmixer_a_test/C.wav");
+       check_file ("test/data/upmixer_a_test/Lfe.wav", "build/test/upmixer_a_test/Lfe.wav");
+       check_file ("test/data/upmixer_a_test/Ls.wav", "build/test/upmixer_a_test/Ls.wav");
+       check_file ("test/data/upmixer_a_test/Rs.wav", "build/test/upmixer_a_test/Rs.wav");
+}
index 750023d9f1a689bb4414706b66c352a4b6cda2d8..39cc0e6bf7df07956b56191e88af6465ee92db71 100644 (file)
@@ -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
 
 */
 
+/** @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);
@@ -66,3 +88,9 @@ 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");
+}
index 09df146737a95c3cff4b1e28e5eef0d655f84218..59e003ac225a5dcc8583c1fbbb281e2248b4ab51 100644 (file)
@@ -10,23 +10,29 @@ def configure(conf):
                               """, msg = 'Checking for boost unit testing library', lib = 'boost_unit_test_framework%s' % boost_test_suffix, uselib_store = 'BOOST_TEST')
 
 def build(bld):
-    obj = bld(features = 'cxx cxxprogram')
+    obj = bld(features='cxx cxxprogram')
     obj.name   = 'unit-tests'
     obj.uselib = 'BOOST_TEST BOOST_THREAD DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
     obj.use    = 'libdcpomatic'
     obj.source = """
                  4k_test.cc
                  audio_analysis_test.cc
+                 audio_buffers_test.cc
                  audio_delay_test.cc
+                 audio_decoder_test.cc
+                 audio_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
@@ -34,18 +40,35 @@ def build(bld):
                  isdcf_name_test.cc
                  job_test.cc
                  make_black_test.cc
+                 player_test.cc
                  pixel_formats_test.cc
-                 play_test.cc
                  ratio_test.cc
+                 repeat_frame_test.cc
                  recover_test.cc
                  resampler_test.cc
                  scaling_test.cc
+                 seek_zero_test.cc
                  silence_padding_test.cc
+                 skip_frame_test.cc
                  stream_test.cc
+                 subrip_test.cc
                  test.cc
                  threed_test.cc
+                 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    = 'libdcpomatic'
+    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 (file)
index 0000000..561b102
--- /dev/null
@@ -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 ()));
+}
diff --git a/wscript b/wscript
index ef72a61e5e2d2677b9596ddcde16c980b40ad722..681654bb8d0acd4fd571866bee3d02fcdedba0e8 100644 (file)
--- a/wscript
+++ b/wscript
@@ -3,7 +3,7 @@ import os
 import sys
 
 APPNAME = 'dcpomatic'
-VERSION = '1.72.2devel'
+VERSION = '2.0.0devel'
 
 def options(opt):
     opt.load('compiler_cxx')
@@ -58,9 +58,13 @@ def dynamic_openjpeg(conf):
     conf.check_cfg(package='libopenjpeg', args='--cflags --libs', max_version='1.5.2', mandatory=True)
 
 def static_dcp(conf, static_boost, static_xmlpp, static_xmlsec, static_ssh):
+<<<<<<< HEAD
+    conf.check_cfg(package='libdcp-1.0', atleast_version='0.95', args='--cflags', uselib_store='DCP', mandatory=True)
+=======
     conf.check_cfg(package='libdcp', atleast_version='0.96', args='--cflags', uselib_store='DCP', mandatory=True)
+>>>>>>> origin/master
     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,7 +88,7 @@ def static_dcp(conf, static_boost, static_xmlpp, static_xmlsec, static_ssh):
         conf.env.LIB_DCP.append('ssh')
 
 def dynamic_dcp(conf):
-    conf.check_cfg(package='libdcp', atleast_version='0.95.0', args='--cflags --libs', uselib_store='DCP', mandatory=True)
+    conf.check_cfg(package='libdcp-1.0', atleast_version='0.92', args='--cflags --libs', uselib_store='DCP', mandatory=True)
     conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
 
 def dynamic_ssh(conf):
@@ -323,6 +327,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>
@@ -428,4 +434,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')