summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/bugs/1c2591d1-0d87-43e7-bf1d-5d16c1c8a16e/values17
-rw-r--r--.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/bugs/53c8f102-7b42-456a-86e2-3e99de0bd883/values17
-rw-r--r--.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/bugs/666e8ba3-f1a3-4245-9d00-9a3a5e048ed9/values17
-rw-r--r--.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/settings0
-rw-r--r--.be/version1
-rw-r--r--.gitignore3
-rw-r--r--ChangeLog114
-rw-r--r--Doxyfile281
-rw-r--r--README2
-rwxr-xr-xbuild-windows31
-rwxr-xr-xbuilds/ubuntu-12.04-6436
-rwxr-xr-xbuilds/windows (renamed from rebuild-windows)0
-rw-r--r--doc/mainpage.txt2
-rw-r--r--doc/manual/dvdomatic.xml179
-rw-r--r--icons/16x16/dvdomatic.pngbin0 -> 211 bytes
-rwxr-xr-xrun/servomatic10
-rwxr-xr-xrun/servomatic_cli10
-rwxr-xr-xrun/servomatic_gui10
-rw-r--r--src/lib/check_hashes_job.cc104
-rw-r--r--src/lib/check_hashes_job.h33
-rw-r--r--src/lib/config.cc4
-rw-r--r--src/lib/config.h26
-rw-r--r--src/lib/dcp_video_frame.cc68
-rw-r--r--src/lib/dcp_video_frame.h10
-rw-r--r--src/lib/decoder.cc78
-rw-r--r--src/lib/decoder.h8
-rw-r--r--src/lib/encoder.cc36
-rw-r--r--src/lib/encoder.h14
-rw-r--r--src/lib/exceptions.h5
-rw-r--r--src/lib/ffmpeg_compatibility.cc109
-rw-r--r--src/lib/ffmpeg_decoder.cc12
-rw-r--r--src/lib/film.cc29
-rw-r--r--src/lib/film.h3
-rw-r--r--src/lib/film_state.cc12
-rw-r--r--src/lib/format.cc1
-rw-r--r--src/lib/image.cc31
-rw-r--r--src/lib/image.h4
-rw-r--r--src/lib/j2k_still_encoder.cc7
-rw-r--r--src/lib/j2k_wav_encoder.cc20
-rw-r--r--src/lib/j2k_wav_encoder.h4
-rw-r--r--src/lib/job.cc17
-rw-r--r--src/lib/job.h17
-rw-r--r--src/lib/job_manager.cc12
-rw-r--r--src/lib/job_manager.h1
-rw-r--r--src/lib/log.cc29
-rw-r--r--src/lib/log.h32
-rw-r--r--src/lib/make_dcp_job.cc7
-rw-r--r--src/lib/server.cc158
-rw-r--r--src/lib/server.h34
-rw-r--r--src/lib/tiff_encoder.cc2
-rw-r--r--src/lib/transcode_job.cc22
-rw-r--r--src/lib/transcode_job.h3
-rw-r--r--src/lib/util.cc290
-rw-r--r--src/lib/util.h78
-rw-r--r--src/lib/wscript9
-rw-r--r--src/tools/dvdomatic.cc12
-rw-r--r--src/tools/servomatic.cc194
-rw-r--r--src/tools/servomatic_cli.cc94
-rw-r--r--src/tools/servomatic_gui.cc151
-rw-r--r--src/tools/servomatictest.cc9
-rw-r--r--src/tools/wscript4
-rw-r--r--src/wscript1
-rw-r--r--src/wx/config_dialog.cc35
-rw-r--r--src/wx/config_dialog.h4
-rw-r--r--src/wx/dcp_range_dialog.cc2
-rw-r--r--src/wx/film_editor.cc15
-rw-r--r--src/wx/film_viewer.cc4
-rw-r--r--src/wx/filter_dialog.cc2
-rw-r--r--src/wx/gain_calculator_dialog.cc10
-rw-r--r--src/wx/job_manager_view.cc14
-rw-r--r--src/wx/job_wrapper.cc6
-rw-r--r--src/wx/properties_dialog.cc89
-rw-r--r--src/wx/properties_dialog.h39
-rw-r--r--src/wx/server_dialog.cc8
-rw-r--r--src/wx/server_dialog.h8
-rw-r--r--src/wx/wscript1
-rw-r--r--src/wx/wx_util.cc56
-rw-r--r--src/wx/wx_util.h25
-rw-r--r--test/test.cc67
-rw-r--r--windows/dvdomatic.rc3
-rw-r--r--windows/dvdomatic_taskbar.icobin0 -> 1150 bytes
-rw-r--r--windows/installer.nsi.in5
-rwxr-xr-xwrapper/makedcp5
-rw-r--r--wscript13
84 files changed, 2207 insertions, 728 deletions
diff --git a/.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/bugs/1c2591d1-0d87-43e7-bf1d-5d16c1c8a16e/values b/.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/bugs/1c2591d1-0d87-43e7-bf1d-5d16c1c8a16e/values
new file mode 100644
index 000000000..365d04d09
--- /dev/null
+++ b/.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/bugs/1c2591d1-0d87-43e7-bf1d-5d16c1c8a16e/values
@@ -0,0 +1,17 @@
+creator: Carl Hetherington <cth@carlh.net>
+
+
+reporter: Carl Hetherington <cth@carlh.net>
+
+
+severity: minor
+
+
+status: open
+
+
+summary: Dependent jobs are started when an earlier one fails
+
+
+time: Sun, 16 Sep 2012 12:17:18 +0000
+
diff --git a/.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/bugs/53c8f102-7b42-456a-86e2-3e99de0bd883/values b/.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/bugs/53c8f102-7b42-456a-86e2-3e99de0bd883/values
new file mode 100644
index 000000000..61b3c91bb
--- /dev/null
+++ b/.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/bugs/53c8f102-7b42-456a-86e2-3e99de0bd883/values
@@ -0,0 +1,17 @@
+creator: Carl Hetherington <cth@carlh.net>
+
+
+reporter: Carl Hetherington <cth@carlh.net>
+
+
+severity: minor
+
+
+status: open
+
+
+summary: Add option to select soundtrack if there is more than one
+
+
+time: Mon, 17 Sep 2012 13:45:16 +0000
+
diff --git a/.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/bugs/666e8ba3-f1a3-4245-9d00-9a3a5e048ed9/values b/.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/bugs/666e8ba3-f1a3-4245-9d00-9a3a5e048ed9/values
new file mode 100644
index 000000000..8861f1d19
--- /dev/null
+++ b/.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/bugs/666e8ba3-f1a3-4245-9d00-9a3a5e048ed9/values
@@ -0,0 +1,17 @@
+creator: Carl Hetherington <cth@carlh.net>
+
+
+reporter: Carl Hetherington <cth@carlh.net>
+
+
+severity: minor
+
+
+status: open
+
+
+summary: Re-setting content file runs thumb scan twice?
+
+
+time: Mon, 24 Sep 2012 09:42:18 +0000
+
diff --git a/.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/settings b/.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/settings
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/settings
diff --git a/.be/version b/.be/version
new file mode 100644
index 000000000..e7aade483
--- /dev/null
+++ b/.be/version
@@ -0,0 +1 @@
+Bugs Everywhere Directory v1.4
diff --git a/.gitignore b/.gitignore
index 757f42ece..0ddc10345 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,5 @@ alignment
sync
doc/manual/html
doc/manual/pdf
-doc/manual/extensions.ent \ No newline at end of file
+doc/manual/extensions.ent
+.be/id-cache
diff --git a/ChangeLog b/ChangeLog
index c09b14fcb..b260b8745 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,117 @@
+2012-09-28 Carl Hetherington <cth@carlh.net>
+
+ * Fix crash bug which seems to have been
+ exposed by recent changes to ffmpeg.
+
+2012-09-27 Carl Hetherington <cth@carlh.net>
+
+ * Version 0.53 released.
+
+2012-09-27 Carl Hetherington <cth@carlh.net>
+
+ * Fix unrecognised capital letters on
+ still-image file extensions.
+
+ * Write hashes of frames to disk and
+ check them before making the final DCP.
+
+2012-09-24 Carl Hetherington <cth@carlh.net>
+
+ * Fix problems with overflow on long films.
+
+2012-09-24 Carl Hetherington <cth@carlh.net>
+
+ * Version 0.52 released.
+
+2012-09-23 Carl Hetherington <cth@carlh.net>
+
+ * Fix alignment of frames per second count.
+
+ * Use hopefully more robust networking
+ code to survive timeouts during reads and
+ writes.
+
+ * Some fixes for bugs when loading Films
+ created on Windows in Linux.
+
+2012-09-22 Carl Hetherington <cth@carlh.net>
+
+ * Fix bug on OK-ing gain calculation
+ dialog without entering any values.
+
+ * Improve spacing in some dialogs.
+
+2012-09-22 Carl Hetherington <cth@carlh.net>
+
+ * Version 0.51 released.
+
+2012-09-22 Carl Hetherington <cth@carlh.net>
+
+ * Improve transcode job progress reporting.
+
+ * Update the slow bits of the properties
+ dialog in a separate thread to improve
+ responsiveness.
+
+ * Fix edit server button on Windows.
+
+2012-09-22 Carl Hetherington <cth@carlh.net>
+
+ * Version 0.50 released.
+
+2012-09-22 Carl Hetherington <cth@carlh.net>
+
+ * Rename servomatic to servomatic_cli and
+ add a very basic system-tray-dwelling GUI server.
+
+ * Tweak formatting of properties dialogue
+ and add a note of how many J2K frames
+ have already been encoded.
+
+ * Correctly set up crop in the viewer
+ on reloading a film.
+
+2012-09-18 Carl Hetherington <cth@carlh.net>
+
+ * Fix non-working removal of encode servers.
+
+ * Add GUI front-end to encode server.
+
+2012-09-17 Carl Hetherington <cth@carlh.net>
+
+ * Include servomatic in the Windows install.
+
+ * Add a simple Properties dialog to give
+ an estimate of disk space required for an
+ encode.
+
+2012-09-17 Carl Hetherington <cth@carlh.net>
+
+ * Version 0.49 released.
+
+2012-09-16 Carl Hetherington <cth@carlh.net>
+
+ * Version 0.48 released.
+
+2012-09-15 Carl Hetherington <cth@carlh.net>
+
+ * Slightly speculative fix for failure to
+ take note of audio gain changes caused by
+ the Calculate dialogue.
+
+2012-09-12 Carl Hetherington <cth@carlh.net>
+
+ * Fix crash when FFmpeg doesn't set up the audio channel
+ layout for some reason.
+
+2012-09-01 Carl Hetherington <cth@carlh.net>
+
+ * Add 1.66-within-flat format.
+
+2012-08-27 Carl Hetherington <cth@carlh.net>
+
+ * Version 0.47 released.
+
2012-08-23 Carl Hetherington <cth@carlh.net>
* Add some more formats.
diff --git a/Doxyfile b/Doxyfile
index 8efb70662..56f7e1d3c 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -1,4 +1,4 @@
-# Doxyfile 1.7.4
+# Doxyfile 1.8.2
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@@ -22,8 +22,9 @@
DOXYFILE_ENCODING = UTF-8
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
-# by quotes) that should identify the project.
+# 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.
PROJECT_NAME = DVD-o-matic
@@ -125,7 +126,9 @@ FULL_PATH_NAMES = YES
# 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.
+# 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.
STRIP_FROM_PATH =
@@ -194,6 +197,13 @@ TAB_SIZE = 8
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.
+
+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
@@ -221,17 +231,34 @@ OPTIMIZE_FOR_FORTRAN = 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.
+# 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.
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
+# 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.
+
+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.
+
+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
@@ -252,12 +279,7 @@ CPP_CLI_SUPPORT = 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 (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.
IDL_PROPERTY_SUPPORT = YES
@@ -283,6 +305,15 @@ SUBGROUPING = YES
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).
+
+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
# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
@@ -305,10 +336,21 @@ TYPEDEF_HIDES_STRUCT = NO
# 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
+# 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.
+
+LOOKUP_CACHE_SIZE = 0
+
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
@@ -325,6 +367,11 @@ EXTRACT_ALL = 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.
+
+EXTRACT_PACKAGE = NO
+
# If the EXTRACT_STATIC tag is set to YES all static members of a file
# will be included in the documentation.
@@ -512,12 +559,6 @@ MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = YES
-# If the sources in your project are distributed over multiple directories
-# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
-# in the documentation. The default is NO.
-
-SHOW_DIRECTORIES = NO
-
# 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.
@@ -543,13 +584,23 @@ 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. The create the layout file
+# 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.
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.
+
+CITE_BIB_FILES =
+
#---------------------------------------------------------------------------
# configuration options related to warning and progress messages
#---------------------------------------------------------------------------
@@ -610,7 +661,8 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
-INPUT = src doc/mainpage.txt
+INPUT = src/lib src/wx src/tools \
+ 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
@@ -636,13 +688,15 @@ FILE_PATTERNS =
RECURSIVE = NO
-# The EXCLUDE tag can be used to specify files and/or directories that should
+# 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.
EXCLUDE =
-# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# 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.
@@ -744,7 +798,7 @@ 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 and C++ comments will always remain visible.
+# fragments. Normal C, C++ and Fortran comments will always remain visible.
STRIP_CODE_COMMENTS = YES
@@ -829,12 +883,13 @@ 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
+# for the proper inclusion of any scripts and style sheets that doxygen
# needs, which is dependent on the configuration options used.
-# It is adviced to generate a default header using "doxygen -w html
+# 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!
+# have to redo this when upgrading to a newer version of doxygen or when
+# changing the value of configuration settings such as GENERATE_TREEVIEW!
HTML_HEADER =
@@ -846,13 +901,23 @@ 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 the tag is left blank doxygen
-# will generate a default style sheet. Note that doxygen will try to copy
-# the style sheet file to the HTML output directory, so don't put your own
-# stylesheet in the HTML output directory as well, or it will be erased!
+# 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.
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.
+
+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
@@ -863,7 +928,7 @@ HTML_STYLESHEET =
HTML_EXTRA_FILES =
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
-# Doxygen will adjust the colors in the stylesheet and background images
+# 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,
@@ -893,20 +958,23 @@ HTML_COLORSTYLE_GAMMA = 80
HTML_TIMESTAMP = YES
-# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
-# files or namespaces will be aligned in HTML using tables. If set to
-# NO a bullet list will be used.
-
-HTML_ALIGN_MEMBERS = 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. For this to work a browser that supports
-# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
-# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+# page has loaded.
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.
+
+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).
@@ -934,9 +1002,9 @@ DOCSET_FEEDNAME = "Doxygen generated docs"
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.
+# 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.
DOCSET_PUBLISHER_ID = org.doxygen.Publisher
@@ -1058,19 +1126,14 @@ GENERATE_ECLIPSEHELP = NO
ECLIPSE_DOC_ID = org.doxygen.Project
-# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
-# top of each HTML page. The value NO (the default) enables the index and
-# the value YES disables it.
+# 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.
DISABLE_INDEX = 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.
-
-ENUM_VALUES_PER_LINE = 4
-
# 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
@@ -1078,13 +1141,17 @@ ENUM_VALUES_PER_LINE = 4
# 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.
GENERATE_TREEVIEW = NO
-# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
-# and Class Hierarchy pages using a tree view instead of an ordered list.
+# 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.
-USE_INLINE_TREES = NO
+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
@@ -1117,7 +1184,7 @@ FORMULA_TRANSPARENT = YES
# (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 also need to install MathJax separately and
+# output. When enabled you may also need to install MathJax separately and
# configure the path to it using the MATHJAX_RELPATH option.
USE_MATHJAX = NO
@@ -1126,13 +1193,19 @@ USE_MATHJAX = NO
# 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.org site, so you can quickly see the result without installing
-# MathJax, but it is strongly recommended to install a local copy of MathJax
-# before deployment.
+# 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.
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.
+
+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
@@ -1246,6 +1319,12 @@ LATEX_HIDE_INDICES = NO
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.
+
+LATEX_BIB_STYLE = plain
+
#---------------------------------------------------------------------------
# configuration options related to the RTF output
#---------------------------------------------------------------------------
@@ -1277,7 +1356,7 @@ COMPACT_RTF = NO
RTF_HYPERLINKS = NO
-# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# 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.
@@ -1468,22 +1547,18 @@ SKIP_FUNCTION_MACROS = YES
# Configuration::additions related to external references
#---------------------------------------------------------------------------
-# The TAGFILES option can be used to specify one or more tagfiles.
-# Optionally an initial location of the external documentation
-# can be added for each tagfile. The format of a tag file without
-# this location is as follows:
+# 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:
#
# 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. If a location is present for each tag, the installdox tool
-# does not have to be run to correct the links.
-# 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. 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.
TAGFILES =
@@ -1551,13 +1626,12 @@ HAVE_DOT = NO
DOT_NUM_THREADS = 0
-# By default doxygen will write a font called Helvetica to the output
-# directory and reference it in 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.
+# 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.
DOT_FONTNAME = Helvetica
@@ -1566,17 +1640,16 @@ DOT_FONTNAME = Helvetica
DOT_FONTSIZE = 10
-# By default doxygen will tell dot to use the output directory to look for the
-# FreeSans.ttf font (which doxygen will put there itself). If you specify a
-# different font using DOT_FONTNAME you can set the path where dot
-# can find it using this tag.
+# 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.
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
-# the CLASS_DIAGRAMS tag to NO.
+# CLASS_DIAGRAMS tag to NO.
CLASS_GRAPH = YES
@@ -1598,6 +1671,15 @@ GROUP_GRAPHS = 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.
+
+UML_LIMIT_NUM_FIELDS = 10
+
# If set to YES, the inheritance and collaboration graphs will show the
# relations between templates and their instances.
@@ -1638,7 +1720,7 @@ CALLER_GRAPH = NO
GRAPHICAL_HIERARCHY = YES
-# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to 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.
@@ -1647,10 +1729,21 @@ 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 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).
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.
+
+INTERACTIVE_SVG = NO
+
# The tag DOT_PATH 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.
diff --git a/README b/README
index d79685f57..fd3983c29 100644
--- a/README
+++ b/README
@@ -28,7 +28,7 @@ You will need these libraries:
FFmpeg version 0.9.x or higher
libtiff
boost thread and filesystem
- libopenjpeg
+ libopenjpeg 1.5.0 or higher
wxWidgets
libsndfile
libssh
diff --git a/build-windows b/build-windows
deleted file mode 100755
index a59cd80cb..000000000
--- a/build-windows
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash
-
-export MINGW_CXX="i686-w64-mingw32-g++"
-export MINGW_WINDRES="i686-w64-mingw32-windres"
-export MINGW_STRIP="i686-w64-mingw32-strip"
-export MINGW_PREFIX="/usr/i686-w64-mingw32"
-export WINDOWS_PREFIX="/home/carl/Environments/windows"
-
-export PKG_CONFIG_LIBDIR=$WINDOWS_PREFIX/lib/pkgconfig
-
-./waf
-if [ "$?" != "0" ]; then
- exit 1
-fi
-
-d=`pwd`
-
-cp build/windows/installer.nsi build/windows/installer2.nsi
-
-$MINGW_STRIP build/src/tools/dvdomatic.exe -o build/src/tools/dvdomatic.exe.tmp
-mv build/src/tools/dvdomatic.exe.tmp build/src/tools/dvdomatic.exe
-$MINGW_STRIP build/src/wx/dvdomatic-wx.dll -o build/src/wx/dvdomatic-wx.dll.tmp
-mv build/src/wx/dvdomatic-wx.dll.tmp build/src/wx/dvdomatic-wx.dll
-$MINGW_STRIP build/src/lib/dvdomatic.dll -o build/src/lib/dvdomatic.dll.tmp
-mv build/src/lib/dvdomatic.dll.tmp build/src/lib/dvdomatic.dll
-
-sed -i "s~%resources%~$d/windows~g" build/windows/installer2.nsi
-sed -i "s~%deps%~$WINDOWS_PREFIX~g" build/windows/installer2.nsi
-sed -i "s~%binaries%~$d/build~g" build/windows/installer2.nsi
-
-makensis build/windows/installer2.nsi
diff --git a/builds/ubuntu-12.04-64 b/builds/ubuntu-12.04-64
new file mode 100755
index 000000000..5f0185087
--- /dev/null
+++ b/builds/ubuntu-12.04-64
@@ -0,0 +1,36 @@
+#!/bin/bash -e
+
+export DEST=dvdomatic-ubuntu-12.04-64
+export ENV=/home/carl/Environments/ubuntu-12.04-64
+
+#./waf clean
+
+export PATH=$ENV/bin:$PATH
+export PKG_CONFIG_LIBDIR=$ENV/lib/pkgconfig:/usr/lib/pkgconfig:/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig
+
+CXXFLAGS="-I$ENV_PREFIX/include" \
+ LINKFLAGS="-L$ENV_PREFIX/lib" \
+ ./waf configure --enable-debug
+
+./waf
+
+mkdir -p $DEST/bin
+mkdir -p $DEST/lib
+cp build/src/tools/makedcp $DEST/bin/makedcp-bin
+cp build/src/lib/libdvdomatic.so $DEST/lib/
+cp build/src/wx/libdvdomatic-wx.so $DEST/lib/
+cp $ENV/lib/libdcp.so $DEST/lib
+cp $ENV/lib/libasdcp-libdcp.so $DEST/lib
+cp $ENV/lib/libkumu-libdcp.so $DEST/lib
+cp $ENV/lib/libavcodec.so* $DEST/lib
+cp $ENV/lib/libavutil.so* $DEST/lib
+cp $ENV/lib/libavformat.so* $DEST/lib
+cp $ENV/lib/libavfilter.so* $DEST/lib
+cp $ENV/lib/libswscale.so* $DEST/lib
+cp $ENV/lib/libswresample.so* $DEST/lib
+cp $ENV/lib/libopenjpeg.so* $DEST/lib
+cp $ENV/lib/libpostproc.so* $DEST/lib
+cp $ENV/lib/libfaac.so* $DEST/lib
+cp wrapper/makedcp $DEST/bin/
+tar -c $DEST | bzip2 -f -9 > $DEST.tar.bz2
+
diff --git a/rebuild-windows b/builds/windows
index 686641a74..686641a74 100755
--- a/rebuild-windows
+++ b/builds/windows
diff --git a/doc/mainpage.txt b/doc/mainpage.txt
index 81c3b4558..e89ca8d26 100644
--- a/doc/mainpage.txt
+++ b/doc/mainpage.txt
@@ -23,7 +23,7 @@
* 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/). libtiff
* (http://www.libtiff.org/) is used for TIFF encoding and decoding, and the GUI is
- * built using GTK (http://www.gtk.org/) and GTKMM (http://www.gtkmm.org). It also uses libmhash (http://mhash.sourceforge.net/)
+ * built using wxWidgets (http://wxwidgets.org/). It also uses libmhash (http://mhash.sourceforge.net/)
* for debugging purposes.
*
* Thanks are due to the authors and communities of all DVD-o-matic's dependencies.
diff --git a/doc/manual/dvdomatic.xml b/doc/manual/dvdomatic.xml
index cd7932eca..3143772c7 100644
--- a/doc/manual/dvdomatic.xml
+++ b/doc/manual/dvdomatic.xml
@@ -117,6 +117,87 @@ dvdomatic
in a shell.
</para>
+<section>
+<title>Obtaining dependencies on Ubuntu 12.04</title>
+
+<para>
+Ubuntu 12.04 packages most of DVD-o-matic's dependencies, but some are missing. This section
+describes how to obtain all of the dependencies.
+</para>
+
+<section>
+<title>Packaged dependencies</title>
+
+<para>
+Most of the dependencies can be obtained from Ubuntu's packages using:
+</para>
+
+<programlisting>
+sudo apt-get install libwxgtk2.8-dev libsndfile1-dev libssl-dev libssh-dev
+sudo apt-get install libmagickcore-dev libboost-filesystem-dev libboost-thread-dev
+</programlisting>
+
+</section>
+
+<section>
+<title>Unpackaged dependencies</title>
+</section>
+
+<section>
+<title>FFmpeg</title>
+
+<para>
+Ubuntu does not package FFmpeg, so you will need to build it from source. The
+following commands should work:
+</para>
+
+<programlisting>
+git clone git://source.ffmpeg.org/ffmpeg.git ffmpeg
+cd ffmpeg
+./configure --enable-shared --enable-postproc --enable-gpl
+make
+sudo make install
+</programlisting>
+
+</section>
+
+<section>
+<title>libdcp</title>
+
+<para>
+You can build libdcp using:
+</para>
+
+<programlisting>
+wget http://carlh.net/software/libdcp-0.20.tar.bz2
+tar xjf libdcp-0.20.tar.bz2
+cd libdcp-0.20
+./waf configure
+./waf build
+sudo ./waf install
+</programlisting>
+
+</section>
+
+<section>
+<title>libopenjpeg</title>
+
+<para>
+Ubuntu does package libopenjpeg, but it is a rather old version. To build
+the current release:
+</para>
+
+<programlisting>
+wget http://code.google.com/p/openjpeg/downloads/detail?name=openjpeg-1.5.1.tar.gz
+tar xzf openjpeg-1.5.1.tar.gz
+cd openjepg-1.5.1.tar.gz
+./configure
+make
+sudo make install
+</programlisting>
+
+</section>
+</section>
</section>
</chapter>
@@ -686,6 +767,104 @@ out.
</para>
</section>
+
+<section xml:id="sec-servers">
+<title>Encoding servers</title>
+
+<para>
+One way to increase the speed of DCP encoding is to use more
+than one machine at the same time. An instance of DVD-o-matic can
+offload some of the time-consuming JPEG2000 encoding to any number of
+other machines on a network. To do this, one &lsquo;master&rsquo;
+machine runs DVD-o-matic, and the &lsquo;server&rsquo; machines run
+a small program called &lsquo;servomatic&rsquo;.
+</para>
+
+<section>
+<title>Running the servers</title>
+
+<para>
+There are two options for the encoding server;
+<code>servomatic_cli</code>, which runs on the command line, and
+<code>servomatic_gui</code>, which has a simple GUI. The command line
+version is well-suited to headless servers, especially on Linux, and
+the GUI version works best on Windows where it will put an icon in the
+system tray.
+</para>
+
+<para>
+To run the command line version, simply enter:
+</para>
+
+<programlisting>
+servomatic_cli
+</programlisting>
+
+<para>
+at a command prompt. If you are running the program on a machine with
+a multi-core processor, you can run multiple parallel encoding threads
+by doing something like:
+</para>
+
+<programlisting>
+servomatic_cli -t 4
+</programlisting>
+
+<para>
+to run 4 threads in parallel.
+</para>
+
+<para>
+To run the GUI version on windows, run the &lsquo;DVD-o-matic encode
+server&rsquo; from the start menu. An icon will appear in the system
+tray; right-click it to open a menu from whence you can quit the
+server or open a window to show its status.
+</para>
+
+</section>
+<section>
+<title>Setting up DVD-o-matic</title>
+
+<para>
+Once your servers are running, you need to tell your master
+DVD-o-matic instance about them. Start DVD-o-matic and open the
+<guilabel>Preferences</guilabel> dialog from the
+<guilabel>Edit</guilabel> menu. At the bottom of this dialog is a
+section where you can add, edit and remove encoding servers. For each
+encoding server you need only specify its IP address and the number of
+threads that it is running, so that DVD-o-matic knows how many
+parallel encode jobs to send to the server.
+</para>
+
+<para>
+Once this is done, any encodes that you start will split the workload
+up between the master machine and the servers.
+</para>
+
+</section>
+<section>
+<title>Some notes about encode servers</title>
+
+<para>
+DVD-o-matic does not mind if servers come and go; if a server
+disappears, DVD-o-matic will stop sending work to it, and will check
+it every minute or so in case it has come back online.
+</para>
+
+<para>
+You will probably find that using a 1Gb/s or faster network will
+provide a significant speed-up compared to a 100Mb/s network.
+</para>
+
+<para>
+Making changes to the server configuration in the master DVD-o-matic
+will have no effect while an encode is running; the changes will only
+be noticed when a new encode is started.
+</para>
+
+</section>
+</section>
+
</chapter>
diff --git a/icons/16x16/dvdomatic.png b/icons/16x16/dvdomatic.png
new file mode 100644
index 000000000..3c5a10f2d
--- /dev/null
+++ b/icons/16x16/dvdomatic.png
Binary files differ
diff --git a/run/servomatic b/run/servomatic
deleted file mode 100755
index 100d0a8bd..000000000
--- a/run/servomatic
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH
-if [ "$1" == "--debug" ]; then
- gdb --args build/src/tools/servomatic
-elif [ "$1" == "--valgrind" ]; then
- valgrind --tool="memcheck" build/src/tools/servomatic
-else
- build/src/tools/servomatic
-fi
diff --git a/run/servomatic_cli b/run/servomatic_cli
new file mode 100755
index 000000000..3dd67d246
--- /dev/null
+++ b/run/servomatic_cli
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH
+if [ "$1" == "--debug" ]; then
+ gdb --args build/src/tools/servomatic_cli
+elif [ "$1" == "--valgrind" ]; then
+ valgrind --tool="memcheck" build/src/tools/servomatic_cli
+else
+ build/src/tools/servomatic_cli
+fi
diff --git a/run/servomatic_gui b/run/servomatic_gui
new file mode 100755
index 000000000..4f1c617cc
--- /dev/null
+++ b/run/servomatic_gui
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH
+if [ "$1" == "--debug" ]; then
+ gdb --args build/src/tools/servomatic_gui
+elif [ "$1" == "--valgrind" ]; then
+ valgrind --tool="memcheck" build/src/tools/servomatic_gui
+else
+ build/src/tools/servomatic_gui
+fi
diff --git a/src/lib/check_hashes_job.cc b/src/lib/check_hashes_job.cc
new file mode 100644
index 000000000..5a927f752
--- /dev/null
+++ b/src/lib/check_hashes_job.cc
@@ -0,0 +1,104 @@
+/*
+ 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.
+
+*/
+
+#include <fstream>
+#include <boost/lexical_cast.hpp>
+#include <boost/filesystem.hpp>
+#include "check_hashes_job.h"
+#include "film_state.h"
+#include "options.h"
+#include "log.h"
+#include "job_manager.h"
+#include "ab_transcode_job.h"
+#include "transcode_job.h"
+
+using namespace std;
+using namespace boost;
+
+CheckHashesJob::CheckHashesJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l)
+ : Job (s, o, l)
+ , _bad (0)
+{
+
+}
+
+string
+CheckHashesJob::name () const
+{
+ stringstream s;
+ s << "Check hashes of " << _fs->name;
+ return s.str ();
+}
+
+void
+CheckHashesJob::run ()
+{
+ _bad = 0;
+
+ for (int i = 0; i < _fs->length; ++i) {
+ string const j2k_file = _opt->frame_out_path (i, false);
+ string const hash_file = j2k_file + ".md5";
+
+ ifstream ref (hash_file.c_str ());
+ string hash;
+ ref >> hash;
+
+ if (hash != md5_digest (j2k_file)) {
+ _log->log ("Frame " + lexical_cast<string> (i) + " has wrong hash; deleting.");
+ filesystem::remove (j2k_file);
+ filesystem::remove (hash_file);
+ ++_bad;
+ }
+
+ set_progress (float (i) / _fs->length);
+ }
+
+ if (_bad) {
+ shared_ptr<Job> tc;
+
+ if (_fs->dcp_ab) {
+ tc.reset (new ABTranscodeJob (_fs, _opt, _log));
+ } else {
+ tc.reset (new TranscodeJob (_fs, _opt, _log));
+ }
+
+ JobManager::instance()->add_after (shared_from_this(), tc);
+ JobManager::instance()->add_after (tc, shared_ptr<Job> (new CheckHashesJob (_fs, _opt, _log)));
+ }
+
+ set_progress (1);
+ set_state (FINISHED_OK);
+}
+
+string
+CheckHashesJob::status () const
+{
+ stringstream s;
+ s << Job::status ();
+ if (overall_progress() > 0) {
+ if (_bad == 0) {
+ s << "; no bad frames found";
+ } else if (_bad == 1) {
+ s << "; 1 bad frame found";
+ } else {
+ s << "; " << _bad << " bad frames found";
+ }
+ }
+ return s.str ();
+}
diff --git a/src/lib/check_hashes_job.h b/src/lib/check_hashes_job.h
new file mode 100644
index 000000000..b59cf031b
--- /dev/null
+++ b/src/lib/check_hashes_job.h
@@ -0,0 +1,33 @@
+/*
+ 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.
+
+*/
+
+#include "job.h"
+
+class CheckHashesJob : public Job
+{
+public:
+ CheckHashesJob (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l);
+
+ std::string name () const;
+ void run ();
+ std::string status () const;
+
+private:
+ int _bad;
+};
diff --git a/src/lib/config.cc b/src/lib/config.cc
index 53674645d..44d110689 100644
--- a/src/lib/config.cc
+++ b/src/lib/config.cc
@@ -76,7 +76,7 @@ Config::Config ()
} else if (k == "reference_filter") {
_reference_filters.push_back (Filter::from_id (v));
} else if (k == "server") {
- _servers.push_back (Server::create_from_metadata (v));
+ _servers.push_back (ServerDescription::create_from_metadata (v));
} else if (k == "screen") {
_screens.push_back (Screen::create_from_metadata (v));
} else if (k == "tms_ip") {
@@ -131,7 +131,7 @@ Config::write () const
f << "reference_filter " << (*i)->id () << "\n";
}
- for (vector<Server*>::const_iterator i = _servers.begin(); i != _servers.end(); ++i) {
+ for (vector<ServerDescription*>::const_iterator i = _servers.begin(); i != _servers.end(); ++i) {
f << "server " << (*i)->as_metadata () << "\n";
}
diff --git a/src/lib/config.h b/src/lib/config.h
index 14b541ee6..840dcdaef 100644
--- a/src/lib/config.h
+++ b/src/lib/config.h
@@ -28,7 +28,7 @@
#include <boost/shared_ptr.hpp>
#include <sigc++/signal.h>
-class Server;
+class ServerDescription;
class Screen;
class Scaler;
class Filter;
@@ -65,7 +65,7 @@ public:
}
/** @return J2K encoding servers to use */
- std::vector<Server*> servers () const {
+ std::vector<ServerDescription*> servers () const {
return _servers;
}
@@ -81,18 +81,22 @@ public:
return _reference_filters;
}
+ /** @return The IP address of a TMS that we can copy DCPs to */
std::string tms_ip () const {
return _tms_ip;
}
+ /** @return The path on a TMS that we should write DCPs to */
std::string tms_path () const {
return _tms_path;
}
+ /** @return User name to log into the TMS with */
std::string tms_user () const {
return _tms_user;
}
+ /** @return Password to log into the TMS with */
std::string tms_password () const {
return _tms_password;
}
@@ -126,7 +130,7 @@ public:
}
/** @param s New list of servers */
- void set_servers (std::vector<Server*> s) {
+ void set_servers (std::vector<ServerDescription*> s) {
_servers = s;
Changed ();
}
@@ -146,21 +150,25 @@ public:
Changed ();
}
+ /** @param i IP address of a TMS that we can copy DCPs to */
void set_tms_ip (std::string i) {
_tms_ip = i;
Changed ();
}
+ /** @param p Path on a TMS that we should write DCPs to */
void set_tms_path (std::string p) {
_tms_path = p;
Changed ();
}
+ /** @param u User name to log into the TMS with */
void set_tms_user (std::string u) {
_tms_user = u;
Changed ();
}
+ /** @param p Password to log into the TMS with */
void set_tms_password (std::string p) {
_tms_password = p;
Changed ();
@@ -188,22 +196,22 @@ private:
int _j2k_bandwidth;
/** J2K encoding servers to use */
- std::vector<Server *> _servers;
-
+ std::vector<ServerDescription *> _servers;
/** Screen definitions */
std::vector<boost::shared_ptr<Screen> > _screens;
-
/** Scaler to use for the "A" part of A/B comparisons */
Scaler const * _reference_scaler;
-
/** Filters to use for the "A" part of A/B comparisons */
std::vector<Filter const *> _reference_filters;
-
+ /** The IP address of a TMS that we can copy DCPs to */
std::string _tms_ip;
+ /** The path on a TMS that we should write DCPs to */
std::string _tms_path;
+ /** User name to log into the TMS with */
std::string _tms_user;
+ /** Password to log into the TMS with */
std::string _tms_password;
-
+ /** Our sound processor */
SoundProcessor const * _sound_processor;
/** Singleton instance, or 0 */
diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc
index 24cdda2e6..da7133c4b 100644
--- a/src/lib/dcp_video_frame.cc
+++ b/src/lib/dcp_video_frame.cc
@@ -36,6 +36,7 @@
#include <iomanip>
#include <sstream>
#include <iostream>
+#include <fstream>
#include <unistd.h>
#include <errno.h>
#include <boost/array.hpp>
@@ -55,10 +56,6 @@
#include "image.h"
#include "log.h"
-#ifdef DEBUG_HASH
-#include <mhash.h>
-#endif
-
using namespace std;
using namespace boost;
@@ -255,12 +252,6 @@ DCPVideoFrame::encode_locally ()
/* Set event manager to null (openjpeg 1.3 bug) */
_cinfo->event_mgr = 0;
-#ifdef DEBUG_HASH
- md5_data ("J2K in X frame " + lexical_cast<string> (_frame), _image->comps[0].data, size * sizeof (int));
- md5_data ("J2K in Y frame " + lexical_cast<string> (_frame), _image->comps[1].data, size * sizeof (int));
- md5_data ("J2K in Z frame " + lexical_cast<string> (_frame), _image->comps[2].data, size * sizeof (int));
-#endif
-
/* Setup the encoder parameters using the current image and user parameters */
opj_setup_encoder (_cinfo, _parameters, _image);
@@ -271,13 +262,9 @@ DCPVideoFrame::encode_locally ()
throw EncodeError ("jpeg2000 encoding failed");
}
-#ifdef DEBUG_HASH
- md5_data ("J2K out frame " + lexical_cast<string> (_frame), _cio->buffer, cio_tell (_cio));
-#endif
-
{
stringstream s;
- s << "Finished locally-encoded frame " << _frame << " length " << cio_tell (_cio);
+ s << "Finished locally-encoded frame " << _frame;
_log->log (s.str ());
}
@@ -289,19 +276,16 @@ DCPVideoFrame::encode_locally ()
* @return Encoded data.
*/
shared_ptr<EncodedData>
-DCPVideoFrame::encode_remotely (Server const * serv)
+DCPVideoFrame::encode_remotely (ServerDescription const * serv)
{
asio::io_service io_service;
asio::ip::tcp::resolver resolver (io_service);
asio::ip::tcp::resolver::query query (serv->host_name(), boost::lexical_cast<string> (Config::instance()->server_port ()));
asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve (query);
- shared_ptr<asio::ip::tcp::socket> socket (new asio::ip::tcp::socket (io_service));
- socket->connect (*endpoint_iterator);
+ Socket socket;
-#ifdef DEBUG_HASH
- _input->hash ("Input for remote encoding (before sending)");
-#endif
+ socket.connect (*endpoint_iterator, 30);
stringstream s;
s << "encode "
@@ -320,29 +304,23 @@ DCPVideoFrame::encode_remotely (Server const * serv)
s << _input->line_size()[i] << " ";
}
- asio::write (*socket, asio::buffer (s.str().c_str(), s.str().length() + 1));
+ socket.write ((uint8_t *) s.str().c_str(), s.str().length() + 1, 30);
for (int i = 0; i < _input->components(); ++i) {
- asio::write (*socket, asio::buffer (_input->data()[i], _input->line_size()[i] * _input->lines(i)));
+ socket.write (_input->data()[i], _input->line_size()[i] * _input->lines(i), 30);
}
- SocketReader reader (socket);
-
char buffer[32];
- reader.read_indefinite ((uint8_t *) buffer, sizeof (buffer));
- reader.consume (strlen (buffer) + 1);
+ socket.read_indefinite ((uint8_t *) buffer, sizeof (buffer), 30);
+ socket.consume (strlen (buffer) + 1);
shared_ptr<EncodedData> e (new RemotelyEncodedData (atoi (buffer)));
/* now read the rest */
- reader.read_definite_and_consume (e->data(), e->size());
-
-#ifdef DEBUG_HASH
- e->hash ("Encoded image (after receiving)");
-#endif
+ socket.read_definite_and_consume (e->data(), e->size(), 30);
{
stringstream s;
- s << "Finished remotely-encoded frame " << _frame << " length " << e->size();
+ s << "Finished remotely-encoded frame " << _frame;
_log->log (s.str ());
}
@@ -367,29 +345,29 @@ EncodedData::write (shared_ptr<const Options> opt, int frame)
fwrite (_data, 1, _size, f);
fclose (f);
+ string const real_j2k = opt->frame_out_path (frame, false);
+
/* Rename the file from foo.j2c.tmp to foo.j2c now that it is complete */
- filesystem::rename (tmp_j2k, opt->frame_out_path (frame, false));
+ filesystem::rename (tmp_j2k, real_j2k);
+
+ /* Write a file containing the hash */
+ string const hash = real_j2k + ".md5";
+ ofstream h (hash.c_str());
+ h << md5_digest (_data, _size) << "\n";
+ h.close ();
}
/** Send this data to a socket.
* @param socket Socket
*/
void
-EncodedData::send (shared_ptr<asio::ip::tcp::socket> socket)
+EncodedData::send (shared_ptr<Socket> socket)
{
stringstream s;
s << _size;
- asio::write (*socket, asio::buffer (s.str().c_str(), s.str().length() + 1));
- asio::write (*socket, asio::buffer (_data, _size));
-}
-
-#ifdef DEBUG_HASH
-void
-EncodedData::hash (string n) const
-{
- md5_data (n, _data, _size);
+ socket->write ((uint8_t *) s.str().c_str(), s.str().length() + 1, 30);
+ socket->write (_data, _size, 30);
}
-#endif
/** @param s Size of data in bytes */
RemotelyEncodedData::RemotelyEncodedData (int s)
diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h
index 464d48515..72f885e45 100644
--- a/src/lib/dcp_video_frame.h
+++ b/src/lib/dcp_video_frame.h
@@ -27,7 +27,7 @@
class FilmState;
class Options;
-class Server;
+class ServerDescription;
class Scaler;
class Image;
class Log;
@@ -48,13 +48,9 @@ public:
virtual ~EncodedData () {}
- void send (boost::shared_ptr<boost::asio::ip::tcp::socket>);
+ void send (boost::shared_ptr<Socket> socket);
void write (boost::shared_ptr<const Options>, int);
-#ifdef DEBUG_HASH
- void hash (std::string) const;
-#endif
-
/** @return data */
uint8_t* data () const {
return _data;
@@ -113,7 +109,7 @@ public:
virtual ~DCPVideoFrame ();
boost::shared_ptr<EncodedData> encode_locally ();
- boost::shared_ptr<EncodedData> encode_remotely (Server const *);
+ boost::shared_ptr<EncodedData> encode_remotely (ServerDescription const *);
int frame () const {
return _frame;
diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc
index faee5bece..973582ca4 100644
--- a/src/lib/decoder.cc
+++ b/src/lib/decoder.cc
@@ -29,6 +29,8 @@ extern "C" {
#if (LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 53 && LIBAVFILTER_VERSION_MINOR <= 77) || LIBAVFILTER_VERSION_MAJOR == 3
#include <libavfilter/avcodec.h>
#include <libavfilter/buffersink.h>
+#elif LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
+#include <libavfilter/vsrc_buffer.h>
#endif
#include <libavformat/avio.h>
}
@@ -67,7 +69,9 @@ Decoder::Decoder (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const
, _video_frame (0)
, _buffer_src_context (0)
, _buffer_sink_context (0)
+#if HAVE_SWRESAMPLE
, _swr_context (0)
+#endif
, _have_setup_video_filters (false)
, _delay_line (0)
, _delay_in_bytes (0)
@@ -83,10 +87,12 @@ Decoder::~Decoder ()
delete _delay_line;
}
+/** Start off a decode processing run */
void
Decoder::process_begin ()
{
if (_fs->audio_sample_rate != dcp_audio_sample_rate (_fs->audio_sample_rate)) {
+#if HAVE_SWRESAMPLE
_swr_context = swr_alloc_set_opts (
0,
audio_channel_layout(),
@@ -99,8 +105,13 @@ Decoder::process_begin ()
);
swr_init (_swr_context);
+#else
+ throw DecodeError ("Cannot resample audio as libswresample is not present");
+#endif
} else {
+#if HAVE_SWRESAMPLE
_swr_context = 0;
+#endif
}
_delay_in_bytes = _fs->audio_delay * _fs->audio_sample_rate * _fs->audio_channels * _fs->bytes_per_sample() / 1000;
@@ -110,9 +121,11 @@ Decoder::process_begin ()
_audio_frames_processed = 0;
}
+/** Finish off a decode processing run */
void
Decoder::process_end ()
{
+#if HAVE_SWRESAMPLE
if (_swr_context) {
int mop = 0;
@@ -139,6 +152,7 @@ Decoder::process_end ()
swr_free (&_swr_context);
}
+#endif
if (_delay_in_bytes < 0) {
uint8_t remainder[-_delay_in_bytes];
@@ -151,20 +165,22 @@ Decoder::process_end ()
in to get it to the right length.
*/
- int const audio_short_by_frames =
- (decoding_frames() * dcp_audio_sample_rate (_fs->audio_sample_rate) / _fs->frames_per_second)
+ int64_t const audio_short_by_frames =
+ ((int64_t) decoding_frames() * dcp_audio_sample_rate (_fs->audio_sample_rate) / _fs->frames_per_second)
- _audio_frames_processed;
- int bytes = audio_short_by_frames * _fs->audio_channels * _fs->bytes_per_sample();
-
- int const silence_size = 64 * 1024;
- uint8_t silence[silence_size];
- memset (silence, 0, silence_size);
-
- while (bytes) {
- int const t = min (bytes, silence_size);
- Audio (silence, t);
- bytes -= t;
+ if (audio_short_by_frames >= 0) {
+ int bytes = audio_short_by_frames * _fs->audio_channels * _fs->bytes_per_sample();
+
+ int const silence_size = 64 * 1024;
+ uint8_t silence[silence_size];
+ memset (silence, 0, silence_size);
+
+ while (bytes) {
+ int const t = min (bytes, silence_size);
+ Audio (silence, t);
+ bytes -= t;
+ }
}
}
@@ -227,10 +243,12 @@ Decoder::process_audio (uint8_t* data, int size)
/* Here's samples per channel */
int const samples = size / _fs->bytes_per_sample();
+#if HAVE_SWRESAMPLE
/* And here's frames (where 1 frame is a collection of samples, 1 for each channel,
so for 5.1 a frame would be 6 samples)
*/
int const frames = samples / _fs->audio_channels;
+#endif
/* Maybe apply gain */
if (_fs->audio_gain != 0) {
@@ -270,6 +288,7 @@ Decoder::process_audio (uint8_t* data, int size)
uint8_t* out_buffer = 0;
/* Maybe sample-rate convert */
+#if HAVE_SWRESAMPLE
if (_swr_context) {
uint8_t const * in[2] = {
@@ -297,6 +316,7 @@ Decoder::process_audio (uint8_t* data, int size)
data = out_buffer;
size = out_frames * _fs->audio_channels * _fs->bytes_per_sample();
}
+#endif
/* Update the number of audio frames we've pushed to the encoder */
_audio_frames_processed += size / (_fs->audio_channels * _fs->bytes_per_sample ());
@@ -339,10 +359,8 @@ Decoder::process_video (AVFrame* frame)
throw DecodeError ("could not push buffer into filter chain.");
}
-#else
+#elif LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
-#if 0
-
AVRational par;
par.num = sample_aspect_ratio_numerator ();
par.den = sample_aspect_ratio_denominator ();
@@ -351,7 +369,7 @@ Decoder::process_video (AVFrame* frame)
throw DecodeError ("could not push buffer into filter chain.");
}
-#endif
+#else
if (av_buffersrc_write_frame (_buffer_src_context, frame) < 0) {
throw DecodeError ("could not push buffer into filter chain.");
@@ -359,13 +377,13 @@ Decoder::process_video (AVFrame* frame)
#endif
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 23 && LIBAVFILTER_VERSION_MINOR <= 61
+#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 15 && LIBAVFILTER_VERSION_MINOR <= 61
while (avfilter_poll_frame (_buffer_sink_context->inputs[0])) {
#else
while (av_buffersink_read (_buffer_sink_context, 0)) {
#endif
-#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 53
+#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 15
int r = avfilter_request_frame (_buffer_sink_context->inputs[0]);
if (r < 0) {
@@ -434,10 +452,7 @@ Decoder::setup_video_filters ()
throw DecodeError ("Could not find buffer src filter");
}
- AVFilter* buffer_sink = avfilter_get_by_name("buffersink");
- if (buffer_sink == 0) {
- throw DecodeError ("Could not create buffer sink filter");
- }
+ AVFilter* buffer_sink = get_sink ();
stringstream a;
a << native_size().width << ":"
@@ -449,12 +464,18 @@ Decoder::setup_video_filters ()
<< sample_aspect_ratio_denominator();
int r;
+
if ((r = avfilter_graph_create_filter (&_buffer_src_context, buffer_src, "in", a.str().c_str(), 0, graph)) < 0) {
throw DecodeError ("could not create buffer source");
}
- enum PixelFormat pixel_formats[] = { pixel_format(), PIX_FMT_NONE };
- if (avfilter_graph_create_filter (&_buffer_sink_context, buffer_sink, "out", 0, pixel_formats, graph) < 0) {
+ AVBufferSinkParams* sink_params = av_buffersink_params_alloc ();
+ PixelFormat* pixel_fmts = new PixelFormat[2];
+ pixel_fmts[0] = pixel_format ();
+ pixel_fmts[1] = PIX_FMT_NONE;
+ sink_params->pixel_fmts = pixel_fmts;
+
+ if (avfilter_graph_create_filter (&_buffer_sink_context, buffer_sink, "out", 0, sink_params, graph) < 0) {
throw DecodeError ("could not create buffer sink.");
}
@@ -471,10 +492,17 @@ Decoder::setup_video_filters ()
inputs->next = 0;
_log->log ("Using filter chain `" + filters + "'");
+
+#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
+ if (avfilter_graph_parse (graph, filters.c_str(), inputs, outputs, 0) < 0) {
+ throw DecodeError ("could not set up filter graph.");
+ }
+#else
if (avfilter_graph_parse (graph, filters.c_str(), &inputs, &outputs, 0) < 0) {
throw DecodeError ("could not set up filter graph.");
}
-
+#endif
+
if (avfilter_graph_config (graph, 0) < 0) {
throw DecodeError ("could not configure filter graph.");
}
diff --git a/src/lib/decoder.h b/src/lib/decoder.h
index 5c69e12d0..14b25c7b0 100644
--- a/src/lib/decoder.h
+++ b/src/lib/decoder.h
@@ -29,9 +29,11 @@
#include <stdint.h>
#include <boost/shared_ptr.hpp>
#include <sigc++/sigc++.h>
+#ifdef HAVE_SWRESAMPLE
extern "C" {
#include <libswresample/swresample.h>
-}
+}
+#endif
#include "util.h"
class Job;
@@ -132,7 +134,9 @@ private:
AVFilterContext* _buffer_src_context;
AVFilterContext* _buffer_sink_context;
+#if HAVE_SWRESAMPLE
SwrContext* _swr_context;
+#endif
bool _have_setup_video_filters;
DelayLine* _delay_line;
@@ -141,7 +145,7 @@ private:
/* Number of audio frames that we have pushed to the encoder
(at the DCP sample rate).
*/
- int _audio_frames_processed;
+ int64_t _audio_frames_processed;
};
#endif
diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc
index c8eb24c80..62ba922da 100644
--- a/src/lib/encoder.cc
+++ b/src/lib/encoder.cc
@@ -36,6 +36,8 @@ Encoder::Encoder (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Lo
: _fs (s)
, _opt (o)
, _log (l)
+ , _just_skipped (false)
+ , _last_frame (0)
{
}
@@ -58,10 +60,32 @@ Encoder::current_frames_per_second () const
return _history_size / (seconds (now) - seconds (_time_history.back ()));
}
+/** @return true if the last frame to be processed was skipped as it already existed */
+bool
+Encoder::skipping () const
+{
+ boost::mutex::scoped_lock (_history_mutex);
+ return _just_skipped;
+}
+
+/** @return Index of last frame to be successfully encoded */
+int
+Encoder::last_frame () const
+{
+ boost::mutex::scoped_lock (_history_mutex);
+ return _last_frame;
+}
+
+/** Should be called when a frame has been encoded successfully.
+ * @param n Frame index.
+ */
void
-Encoder::frame_done ()
+Encoder::frame_done (int n)
{
boost::mutex::scoped_lock lock (_history_mutex);
+ _just_skipped = false;
+ _last_frame = n;
+
struct timeval tv;
gettimeofday (&tv, 0);
_time_history.push_front (tv);
@@ -69,3 +93,13 @@ Encoder::frame_done ()
_time_history.pop_back ();
}
}
+
+/** Called by a subclass when it has just skipped the processing
+ of a frame because it has already been done.
+*/
+void
+Encoder::frame_skipped ()
+{
+ boost::mutex::scoped_lock lock (_history_mutex);
+ _just_skipped = true;
+}
diff --git a/src/lib/encoder.h b/src/lib/encoder.h
index bed2c0988..539b2912c 100644
--- a/src/lib/encoder.h
+++ b/src/lib/encoder.h
@@ -68,9 +68,12 @@ public:
virtual void process_end () = 0;
float current_frames_per_second () const;
+ bool skipping () const;
+ int last_frame () const;
protected:
- void frame_done ();
+ void frame_done (int n);
+ void frame_skipped ();
/** FilmState of the film that we are encoding */
boost::shared_ptr<const FilmState> _fs;
@@ -79,9 +82,18 @@ protected:
/** Log */
Log* _log;
+ /** Mutex for _time_history, _just_skipped and _last_frame */
mutable boost::mutex _history_mutex;
+ /** List of the times of completion of the last _history_size frames;
+ first is the most recently completed.
+ */
std::list<struct timeval> _time_history;
+ /** Number of frames that we should keep history for */
static int const _history_size;
+ /** true if the last frame we processed was skipped (because it was already done) */
+ bool _just_skipped;
+ /** Index of the last frame to be processed */
+ int _last_frame;
};
#endif
diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h
index 6b567805b..8ef09875b 100644
--- a/src/lib/exceptions.h
+++ b/src/lib/exceptions.h
@@ -77,6 +77,9 @@ public:
class FileError : public StringError
{
public:
+ /** @param m Error message.
+ * @param f Name of the file that this exception concerns.
+ */
FileError (std::string m, std::string f)
: StringError (m)
, _file (f)
@@ -84,11 +87,13 @@ public:
virtual ~FileError () throw () {}
+ /** @return name of the file that this exception concerns */
std::string file () const {
return _file;
}
private:
+ /** name of the file that this exception concerns */
std::string _file;
};
diff --git a/src/lib/ffmpeg_compatibility.cc b/src/lib/ffmpeg_compatibility.cc
new file mode 100644
index 000000000..c47cdf5ce
--- /dev/null
+++ b/src/lib/ffmpeg_compatibility.cc
@@ -0,0 +1,109 @@
+/*
+ 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.
+
+*/
+
+extern "C" {
+#include <libavfilter/avfiltergraph.h>
+}
+#include "exceptions.h"
+
+#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
+
+typedef struct {
+ enum PixelFormat pix_fmt;
+} AVSinkContext;
+
+static int
+avsink_init (AVFilterContext* ctx, const char* args, void* opaque)
+{
+ AVSinkContext* priv = (AVSinkContext *) ctx->priv;
+ if (!opaque) {
+ return AVERROR (EINVAL);
+ }
+
+ *priv = *(AVSinkContext *) opaque;
+ return 0;
+}
+
+static void
+null_end_frame (AVFilterLink *)
+{
+
+}
+
+static int
+avsink_query_formats (AVFilterContext* ctx)
+{
+ AVSinkContext* priv = (AVSinkContext *) ctx->priv;
+ enum PixelFormat pix_fmts[] = {
+ priv->pix_fmt,
+ PIX_FMT_NONE
+ };
+
+ avfilter_set_common_formats (ctx, avfilter_make_format_list ((int *) pix_fmts));
+ return 0;
+}
+
+#endif
+
+AVFilter*
+get_sink ()
+{
+#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
+ /* XXX does this leak stuff? */
+ AVFilter* buffer_sink = new AVFilter;
+ buffer_sink->name = av_strdup ("avsink");
+ buffer_sink->priv_size = sizeof (AVSinkContext);
+ buffer_sink->init = avsink_init;
+ buffer_sink->query_formats = avsink_query_formats;
+ buffer_sink->inputs = new AVFilterPad[2];
+ AVFilterPad* i0 = const_cast<AVFilterPad*> (&buffer_sink->inputs[0]);
+ i0->name = "default";
+ i0->type = AVMEDIA_TYPE_VIDEO;
+ i0->min_perms = AV_PERM_READ;
+ i0->rej_perms = 0;
+ i0->start_frame = 0;
+ i0->get_video_buffer = 0;
+ i0->get_audio_buffer = 0;
+ i0->end_frame = null_end_frame;
+ i0->draw_slice = 0;
+ i0->filter_samples = 0;
+ i0->poll_frame = 0;
+ i0->request_frame = 0;
+ i0->config_props = 0;
+ const_cast<AVFilterPad*> (&buffer_sink->inputs[1])->name = 0;
+ buffer_sink->outputs = new AVFilterPad[1];
+ const_cast<AVFilterPad*> (&buffer_sink->outputs[0])->name = 0;
+ return buffer_sink;
+#else
+ AVFilter* buffer_sink = avfilter_get_by_name("buffersink");
+ if (buffer_sink == 0) {
+ throw DecodeError ("Could not create buffer sink filter");
+ }
+
+ return buffer_sink;
+#endif
+}
+
+#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15
+AVFilterInOut *
+avfilter_inout_alloc ()
+{
+ return (AVFilterInOut *) av_malloc (sizeof (AVFilterInOut));
+}
+#endif
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index c12e6728d..3471ffaab 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -142,10 +142,18 @@ FFmpegDecoder::setup_audio ()
if (_audio_codec == 0) {
throw DecodeError ("could not find audio decoder");
}
-
+
if (avcodec_open2 (_audio_codec_context, _audio_codec, 0) < 0) {
throw DecodeError ("could not open audio decoder");
}
+
+ /* This is a hack; sometimes it seems that _audio_codec_context->channel_layout isn't set up,
+ so bodge it here. No idea why we should have to do this.
+ */
+
+ if (_audio_codec_context->channel_layout == 0) {
+ _audio_codec_context->channel_layout = av_get_default_channel_layout (audio_channels ());
+ }
}
bool
@@ -200,7 +208,7 @@ FFmpegDecoder::audio_channels () const
if (_audio_codec_context == 0) {
return 0;
}
-
+
return _audio_codec_context->channels;
}
diff --git a/src/lib/film.cc b/src/lib/film.cc
index f8a3b192d..583a15e19 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -19,6 +19,7 @@
#include <stdexcept>
#include <iostream>
+#include <algorithm>
#include <fstream>
#include <cstdlib>
#include <sstream>
@@ -47,6 +48,7 @@
#include "scaler.h"
#include "decoder_factory.h"
#include "config.h"
+#include "check_hashes_job.h"
using namespace std;
using namespace boost;
@@ -68,7 +70,7 @@ Film::Film (string d, bool must_exist)
filesystem::path p (filesystem::system_complete (d));
filesystem::path result;
- for(filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
+ for (filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
if (*i == "..") {
if (filesystem::is_symlink (result) || result.filename() == "..") {
result /= *i;
@@ -88,7 +90,7 @@ Film::Film (string d, bool must_exist)
read_metadata ();
- _log = new Log (_state.file ("log"));
+ _log = new FileLog (_state.file ("log"));
}
/** Copy constructor */
@@ -122,6 +124,10 @@ Film::read_metadata ()
continue;
}
+ if (line[line.size() - 1] == '\r') {
+ line = line.substr (0, line.size() - 1);
+ }
+
size_t const s = line.find (' ');
if (s == string::npos) {
continue;
@@ -429,7 +435,6 @@ Film::j2k_dir () const
filesystem::path p;
-
/* Start with j2c */
p /= "j2c";
@@ -540,7 +545,8 @@ Film::make_dcp (bool transcode, int freq)
JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (fs, o, log ())));
}
}
-
+
+ JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (fs, o, log ())));
JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (fs, o, log ())));
}
@@ -641,3 +647,18 @@ Film::copy_from_dvd ()
JobManager::instance()->add (j);
}
+int
+Film::encoded_frames () const
+{
+ if (format() == 0) {
+ return 0;
+ }
+
+ int N = 0;
+ for (filesystem::directory_iterator i = filesystem::directory_iterator (j2k_dir ()); i != filesystem::directory_iterator(); ++i) {
+ ++N;
+ this_thread::interruption_point ();
+ }
+
+ return N;
+}
diff --git a/src/lib/film.h b/src/lib/film.h
index 3ff671fbe..cd3b1b8a8 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -29,6 +29,7 @@
#include <vector>
#include <inttypes.h>
#include <boost/thread/mutex.hpp>
+#include <boost/thread.hpp>
#include <sigc++/signal.h>
extern "C" {
#include <libavcodec/avcodec.h>
@@ -229,6 +230,8 @@ public:
return _log;
}
+ int encoded_frames () const;
+
/** Emitted when some metadata property has changed */
mutable sigc::signal1<void, Property> Changed;
diff --git a/src/lib/film_state.cc b/src/lib/film_state.cc
index e0ad20417..e472434ce 100644
--- a/src/lib/film_state.cc
+++ b/src/lib/film_state.cc
@@ -165,11 +165,6 @@ FilmState::read_metadata (string k, string v)
} else if (k == "content_digest") {
content_digest = v;
}
-
- /* Itsy bitsy hack: compute digest here if don't have one (for backwards compatibility) */
- if (content_digest.empty() && !content.empty()) {
- content_digest = md5_digest (content_path ());
- }
}
@@ -256,10 +251,13 @@ ContentType
FilmState::content_type () const
{
#if BOOST_FILESYSTEM_VERSION == 3
- string const ext = filesystem::path(content).extension().string();
+ string ext = filesystem::path(content).extension().string();
#else
- string const ext = filesystem::path(content).extension();
+ string ext = filesystem::path(content).extension();
#endif
+
+ transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
+
if (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png") {
return STILL;
}
diff --git a/src/lib/format.cc b/src/lib/format.cc
index ff3a5b202..e689aa05d 100644
--- a/src/lib/format.cc
+++ b/src/lib/format.cc
@@ -82,6 +82,7 @@ Format::setup_formats ()
_formats.push_back (new Format (133, Size (1998, 1080), "133-in-flat", "4:3 within Flat"));
_formats.push_back (new Format (137, Size (1480, 1080), "137", "Academy"));
_formats.push_back (new Format (166, Size (1793, 1080), "166", "1.66"));
+ _formats.push_back (new Format (166, Size (1998, 1080), "166-in-flat", "1.66 within Flat"));
_formats.push_back (new Format (178, Size (1998, 1080), "178-in-flat", "16:9 within Flat"));
_formats.push_back (new Format (185, Size (1998, 1080), "185", "Flat"));
_formats.push_back (new Format (239, Size (2048, 858), "239", "Scope"));
diff --git a/src/lib/image.cc b/src/lib/image.cc
index f16bb9f77..620e71aa7 100644
--- a/src/lib/image.cc
+++ b/src/lib/image.cc
@@ -39,10 +39,6 @@ extern "C" {
#include "exceptions.h"
#include "scaler.h"
-#ifdef DEBUG_HASH
-#include <mhash.h>
-#endif
-
using namespace std;
using namespace boost;
@@ -85,33 +81,6 @@ Image::components () const
return 0;
}
-#ifdef DEBUG_HASH
-/** Write a MD5 hash of the image's data to stdout.
- * @param n Title to give the output.
- */
-void
-Image::hash (string n) const
-{
- MHASH ht = mhash_init (MHASH_MD5);
- if (ht == MHASH_FAILED) {
- throw EncodeError ("could not create hash thread");
- }
-
- for (int i = 0; i < components(); ++i) {
- mhash (ht, data()[i], line_size()[i] * lines(i));
- }
-
- uint8_t hash[16];
- mhash_deinit (ht, hash);
-
- printf ("%s: ", n.c_str ());
- for (int i = 0; i < int (mhash_get_block_size (MHASH_MD5)); ++i) {
- printf ("%.2x", hash[i]);
- }
- printf ("\n");
-}
-#endif
-
/** Scale this image to a given size and convert it to RGB.
* @param out_size Output image size in pixels.
* @param scaler Scaler to use.
diff --git a/src/lib/image.h b/src/lib/image.h
index 97ab1d5ff..0161d2b01 100644
--- a/src/lib/image.h
+++ b/src/lib/image.h
@@ -68,10 +68,6 @@ public:
boost::shared_ptr<RGBFrameImage> scale_and_convert_to_rgb (Size, int, Scaler const *) const;
boost::shared_ptr<PostProcessImage> post_process (std::string) const;
-#ifdef DEBUG_HASH
- void hash (std::string) const;
-#endif
-
void make_black ();
PixelFormat pixel_format () const {
diff --git a/src/lib/j2k_still_encoder.cc b/src/lib/j2k_still_encoder.cc
index 5243f0668..8f3339a0a 100644
--- a/src/lib/j2k_still_encoder.cc
+++ b/src/lib/j2k_still_encoder.cc
@@ -67,12 +67,15 @@ J2KStillEncoder::process_video (shared_ptr<Image> yuv, int frame)
if (!boost::filesystem::exists (_opt->frame_out_path (i, false))) {
string const link = _opt->frame_out_path (i, false);
#ifdef DVDOMATIC_POSIX
- symlink (real.c_str(), link.c_str());
+ int const r = symlink (real.c_str(), link.c_str());
+ if (r) {
+ throw EncodeError ("could not create symlink");
+ }
#endif
#ifdef DVDOMATIC_WINDOWS
filesystem::copy_file (real, link);
#endif
}
- frame_done ();
+ frame_done (0);
}
}
diff --git a/src/lib/j2k_wav_encoder.cc b/src/lib/j2k_wav_encoder.cc
index 2f29f9021..9ae01c774 100644
--- a/src/lib/j2k_wav_encoder.cc
+++ b/src/lib/j2k_wav_encoder.cc
@@ -27,6 +27,7 @@
#include <iostream>
#include <boost/thread.hpp>
#include <boost/filesystem.hpp>
+#include <boost/lexical_cast.hpp>
#include <sndfile.h>
#include <openjpeg.h>
#include "j2k_wav_encoder.h"
@@ -126,11 +127,13 @@ J2KWAVEncoder::process_video (shared_ptr<Image> yuv, int frame)
));
_worker_condition.notify_all ();
+ } else {
+ frame_skipped ();
}
}
void
-J2KWAVEncoder::encoder_thread (Server* server)
+J2KWAVEncoder::encoder_thread (ServerDescription* server)
{
/* Number of seconds that we currently wait between attempts
to connect to the server; not relevant for localhost
@@ -190,7 +193,7 @@ J2KWAVEncoder::encoder_thread (Server* server)
if (encoded) {
encoded->write (_opt, vf->frame ());
- frame_done ();
+ frame_done (vf->frame ());
} else {
lock.lock ();
_queue.push_front (vf);
@@ -210,12 +213,12 @@ void
J2KWAVEncoder::process_begin ()
{
for (int i = 0; i < Config::instance()->num_local_encoding_threads (); ++i) {
- _worker_threads.push_back (new boost::thread (boost::bind (&J2KWAVEncoder::encoder_thread, this, (Server *) 0)));
+ _worker_threads.push_back (new boost::thread (boost::bind (&J2KWAVEncoder::encoder_thread, this, (ServerDescription *) 0)));
}
- vector<Server*> servers = Config::instance()->servers ();
+ vector<ServerDescription*> servers = Config::instance()->servers ();
- for (vector<Server*>::iterator i = servers.begin(); i != servers.end(); ++i) {
+ for (vector<ServerDescription*>::iterator i = servers.begin(); i != servers.end(); ++i) {
for (int j = 0; j < (*i)->threads (); ++j) {
_worker_threads.push_back (new boost::thread (boost::bind (&J2KWAVEncoder::encoder_thread, this, *i)));
}
@@ -227,8 +230,11 @@ J2KWAVEncoder::process_end ()
{
boost::mutex::scoped_lock lock (_worker_mutex);
+ _log->log ("Clearing queue of " + lexical_cast<string> (_queue.size ()));
+
/* Keep waking workers until the queue is empty */
while (!_queue.empty ()) {
+ _log->log ("Waking with " + lexical_cast<string> (_queue.size ()));
_worker_condition.notify_all ();
_worker_condition.wait (lock);
}
@@ -237,6 +243,8 @@ J2KWAVEncoder::process_end ()
terminate_worker_threads ();
+ _log->log ("Mopping up " + lexical_cast<string> (_queue.size()));
+
/* The following sequence of events can occur in the above code:
1. a remote worker takes the last image off the queue
2. the loop above terminates
@@ -253,7 +261,7 @@ J2KWAVEncoder::process_end ()
try {
shared_ptr<EncodedData> e = (*i)->encode_locally ();
e->write (_opt, (*i)->frame ());
- frame_done ();
+ frame_done ((*i)->frame ());
} catch (std::exception& e) {
stringstream s;
s << "Local encode failed " << e.what() << ".";
diff --git a/src/lib/j2k_wav_encoder.h b/src/lib/j2k_wav_encoder.h
index 656af8321..1c2f50065 100644
--- a/src/lib/j2k_wav_encoder.h
+++ b/src/lib/j2k_wav_encoder.h
@@ -29,7 +29,7 @@
#include <sndfile.h>
#include "encoder.h"
-class Server;
+class ServerDescription;
class DCPVideoFrame;
class Image;
class Log;
@@ -50,7 +50,7 @@ public:
private:
- void encoder_thread (Server *);
+ void encoder_thread (ServerDescription *);
void close_sound_files ();
void terminate_worker_threads ();
diff --git a/src/lib/job.cc b/src/lib/job.cc
index 0feb73d31..22754eb90 100644
--- a/src/lib/job.cc
+++ b/src/lib/job.cc
@@ -223,7 +223,7 @@ Job::set_error (string e)
_error = e;
}
-/** Set that this job's progress will always be unknown */
+/** Say that this job's progress will always be unknown */
void
Job::set_progress_unknown ()
{
@@ -231,16 +231,18 @@ Job::set_progress_unknown ()
_progress_unknown = true;
}
+/** @return Human-readable status of this job */
string
Job::status () const
{
float const p = overall_progress ();
int const t = elapsed_time ();
+ int const r = remaining_time ();
stringstream s;
- if (!finished () && p >= 0 && t > 10) {
- s << rint (p * 100) << "%; about " << seconds_to_approximate_hms (t / p - t) << " remaining";
- } else if (!finished () && t <= 10) {
+ if (!finished () && p >= 0 && t > 10 && r > 0) {
+ s << rint (p * 100) << "%; " << seconds_to_approximate_hms (r) << " remaining";
+ } else if (!finished () && (t <= 10 || r == 0)) {
s << rint (p * 100) << "%";
} else if (finished_ok ()) {
s << "OK (ran for " << seconds_to_hms (t) << ")";
@@ -250,3 +252,10 @@ Job::status () const
return s.str ();
}
+
+/** @return An estimate of the remaining time for this job, in seconds */
+int
+Job::remaining_time () const
+{
+ return elapsed_time() / overall_progress() - elapsed_time();
+}
diff --git a/src/lib/job.h b/src/lib/job.h
index 2a77f78f7..b39130479 100644
--- a/src/lib/job.h
+++ b/src/lib/job.h
@@ -26,6 +26,7 @@
#include <string>
#include <boost/thread/mutex.hpp>
+#include <boost/enable_shared_from_this.hpp>
#include <sigc++/sigc++.h>
class Log;
@@ -35,7 +36,7 @@ class Options;
/** @class Job
* @brief A parent class to represent long-running tasks which are run in their own thread.
*/
-class Job
+class Job : public boost::enable_shared_from_this<Job>
{
public:
Job (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l);
@@ -70,6 +71,9 @@ public:
protected:
+ virtual int remaining_time () const;
+
+ /** Description of a job's state */
enum State {
NEW, ///< the job hasn't been started yet
RUNNING, ///< the job is running
@@ -80,10 +84,11 @@ protected:
void set_state (State);
void set_error (std::string e);
+ /** FilmState for this job */
boost::shared_ptr<const FilmState> _fs;
+ /** options in use for this job */
boost::shared_ptr<const Options> _opt;
-
- /** A log that this job can write to */
+ /** a log that this job can write to */
Log* _log;
private:
@@ -92,11 +97,15 @@ private:
/** mutex for _state and _error */
mutable boost::mutex _state_mutex;
+ /** current state of the job */
State _state;
+ /** message for an error that has occurred (when state == FINISHED_ERROR) */
std::string _error;
+ /** time that this job was started */
time_t _start_time;
-
+
+ /** mutex for _stack and _progress_unknown */
mutable boost::mutex _progress_mutex;
struct Level {
diff --git a/src/lib/job_manager.cc b/src/lib/job_manager.cc
index 93fdbd27a..a166b5924 100644
--- a/src/lib/job_manager.cc
+++ b/src/lib/job_manager.cc
@@ -41,15 +41,23 @@ void
JobManager::add (shared_ptr<Job> j)
{
boost::mutex::scoped_lock lm (_mutex);
-
_jobs.push_back (j);
}
+void
+JobManager::add_after (shared_ptr<Job> after, shared_ptr<Job> j)
+{
+ boost::mutex::scoped_lock lm (_mutex);
+ list<shared_ptr<Job> >::iterator i = find (_jobs.begin(), _jobs.end(), after);
+ assert (i != _jobs.end ());
+ ++i;
+ _jobs.insert (i, j);
+}
+
list<shared_ptr<Job> >
JobManager::get () const
{
boost::mutex::scoped_lock lm (_mutex);
-
return _jobs;
}
diff --git a/src/lib/job_manager.h b/src/lib/job_manager.h
index f2f5e0057..d1d33cfc2 100644
--- a/src/lib/job_manager.h
+++ b/src/lib/job_manager.h
@@ -38,6 +38,7 @@ class JobManager
public:
void add (boost::shared_ptr<Job>);
+ void add_after (boost::shared_ptr<Job> after, boost::shared_ptr<Job> j);
std::list<boost::shared_ptr<Job> > get () const;
bool work_to_do () const;
diff --git a/src/lib/log.cc b/src/lib/log.cc
index accf3694d..7f1eea206 100644
--- a/src/lib/log.cc
+++ b/src/lib/log.cc
@@ -27,10 +27,8 @@
using namespace std;
-/** @param f Filename to write log to */
-Log::Log (string f)
- : _file (f)
- , _level (VERBOSE)
+Log::Log ()
+ : _level (VERBOSE)
{
}
@@ -45,13 +43,13 @@ Log::log (string m, Level l)
return;
}
- ofstream f (_file.c_str(), fstream::app);
-
time_t t;
time (&t);
string a = ctime (&t);
-
- f << a.substr (0, a.length() - 1) << ": " << m << "\n";
+
+ stringstream s;
+ s << a.substr (0, a.length() - 1) << ": " << m;
+ do_log (s.str ());
}
void
@@ -61,3 +59,18 @@ Log::set_level (Level l)
_level = l;
}
+
+/** @param file Filename to write log to */
+FileLog::FileLog (string file)
+ : _file (file)
+{
+
+}
+
+void
+FileLog::do_log (string m)
+{
+ ofstream f (_file.c_str(), fstream::app);
+ f << m << "\n";
+}
+
diff --git a/src/lib/log.h b/src/lib/log.h
index d4de8ebde..2a242e24c 100644
--- a/src/lib/log.h
+++ b/src/lib/log.h
@@ -17,6 +17,9 @@
*/
+#ifndef DVDOMATIC_LOG_H
+#define DVDOMATIC_LOG_H
+
/** @file src/log.h
* @brief A very simple logging class.
*/
@@ -26,15 +29,11 @@
/** @class Log
* @brief A very simple logging class.
- *
- * This class simply accepts log messages and writes them to a file.
- * Its single nod to complexity is that it has a mutex to prevent
- * multi-thread logging from clashing.
*/
class Log
{
public:
- Log (std::string f);
+ Log ();
enum Level {
STANDARD = 0,
@@ -45,11 +44,26 @@ public:
void set_level (Level l);
-private:
- /** mutex to prevent simultaneous writes to the file */
+protected:
+ /** mutex to protect the log */
boost::mutex _mutex;
- /** filename to write to */
- std::string _file;
+
+private:
+ virtual void do_log (std::string m) = 0;
+
/** level above which to ignore log messages */
Level _level;
};
+
+class FileLog : public Log
+{
+public:
+ FileLog (std::string file);
+
+private:
+ void do_log (std::string m);
+ /** filename to write to */
+ std::string _file;
+};
+
+#endif
diff --git a/src/lib/make_dcp_job.cc b/src/lib/make_dcp_job.cc
index 525f76c0e..8d3547cae 100644
--- a/src/lib/make_dcp_job.cc
+++ b/src/lib/make_dcp_job.cc
@@ -87,9 +87,12 @@ MakeDCPJob::run ()
break;
}
- libdcp::DCP dcp (_fs->dir (_fs->name), _fs->name, _fs->dcp_content_type->libdcp_kind (), rint (_fs->frames_per_second), frames);
+ libdcp::DCP dcp (_fs->dir (_fs->name));
dcp.Progress.connect (sigc::mem_fun (*this, &MakeDCPJob::dcp_progress));
+ shared_ptr<libdcp::CPL> cpl (new libdcp::CPL (_fs->dir (_fs->name), _fs->name, _fs->dcp_content_type->libdcp_kind (), frames, rint (_fs->frames_per_second)));
+ dcp.add_cpl (cpl);
+
descend (0.9);
shared_ptr<libdcp::MonoPictureAsset> pa (
new libdcp::MonoPictureAsset (
@@ -124,7 +127,7 @@ MakeDCPJob::run ()
ascend ();
}
- dcp.add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (pa, sa, shared_ptr<libdcp::SubtitleAsset> ())));
+ cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (pa, sa, shared_ptr<libdcp::SubtitleAsset> ())));
dcp.write_xml ();
set_progress (1);
diff --git a/src/lib/server.cc b/src/lib/server.cc
index 8a5b5cfca..f8c4425d9 100644
--- a/src/lib/server.cc
+++ b/src/lib/server.cc
@@ -19,24 +19,30 @@
/** @file src/server.cc
* @brief Class to describe a server to which we can send
- * encoding work.
+ * encoding work, and a class to implement such a server.
*/
#include <string>
#include <vector>
#include <sstream>
+#include <iostream>
#include <boost/algorithm/string.hpp>
#include "server.h"
+#include "util.h"
+#include "scaler.h"
+#include "image.h"
+#include "dcp_video_frame.h"
+#include "config.h"
using namespace std;
using namespace boost;
-/** Create a server from a string of metadata returned from as_metadata().
+/** Create a server description from a string of metadata returned from as_metadata().
* @param v Metadata.
- * @return Server, or 0.
+ * @return ServerDescription, or 0.
*/
-Server *
-Server::create_from_metadata (string v)
+ServerDescription *
+ServerDescription::create_from_metadata (string v)
{
vector<string> b;
split (b, v, is_any_of (" "));
@@ -45,14 +51,152 @@ Server::create_from_metadata (string v)
return 0;
}
- return new Server (b[0], atoi (b[1].c_str ()));
+ return new ServerDescription (b[0], atoi (b[1].c_str ()));
}
/** @return Description of this server as text */
string
-Server::as_metadata () const
+ServerDescription::as_metadata () const
{
stringstream s;
s << _host_name << " " << _threads;
return s.str ();
}
+
+Server::Server (Log* log)
+ : _log (log)
+{
+
+}
+
+int
+Server::process (shared_ptr<Socket> socket)
+{
+ char buffer[128];
+ socket->read_indefinite ((uint8_t *) buffer, sizeof (buffer), 30);
+ socket->consume (strlen (buffer) + 1);
+
+ stringstream s (buffer);
+
+ string command;
+ s >> command;
+ if (command != "encode") {
+ return -1;
+ }
+
+ Size in_size;
+ int pixel_format_int;
+ Size out_size;
+ int padding;
+ string scaler_id;
+ int frame;
+ float frames_per_second;
+ string post_process;
+ int colour_lut_index;
+ int j2k_bandwidth;
+
+ s >> in_size.width >> in_size.height
+ >> pixel_format_int
+ >> out_size.width >> out_size.height
+ >> padding
+ >> scaler_id
+ >> frame
+ >> frames_per_second
+ >> post_process
+ >> colour_lut_index
+ >> j2k_bandwidth;
+
+ PixelFormat pixel_format = (PixelFormat) pixel_format_int;
+ Scaler const * scaler = Scaler::from_id (scaler_id);
+ if (post_process == "none") {
+ post_process = "";
+ }
+
+ shared_ptr<SimpleImage> image (new SimpleImage (pixel_format, in_size));
+
+ for (int i = 0; i < image->components(); ++i) {
+ int line_size;
+ s >> line_size;
+ image->set_line_size (i, line_size);
+ }
+
+ for (int i = 0; i < image->components(); ++i) {
+ socket->read_definite_and_consume (image->data()[i], image->line_size()[i] * image->lines(i), 30);
+ }
+
+ DCPVideoFrame dcp_video_frame (image, out_size, padding, scaler, frame, frames_per_second, post_process, colour_lut_index, j2k_bandwidth, _log);
+ shared_ptr<EncodedData> encoded = dcp_video_frame.encode_locally ();
+ encoded->send (socket);
+
+ return frame;
+}
+
+void
+Server::worker_thread ()
+{
+ while (1) {
+ mutex::scoped_lock lock (_worker_mutex);
+ while (_queue.empty ()) {
+ _worker_condition.wait (lock);
+ }
+
+ shared_ptr<Socket> socket = _queue.front ();
+ _queue.pop_front ();
+
+ lock.unlock ();
+
+ int frame = -1;
+
+ struct timeval start;
+ gettimeofday (&start, 0);
+
+ try {
+ frame = process (socket);
+ } catch (std::exception& e) {
+ cerr << "Error: " << e.what() << "\n";
+ }
+
+ socket.reset ();
+
+ lock.lock ();
+
+ if (frame >= 0) {
+ struct timeval end;
+ gettimeofday (&end, 0);
+ stringstream s;
+ s << "Encoded frame " << frame << " in " << (seconds (end) - seconds (start));
+ _log->log (s.str ());
+ }
+
+ _worker_condition.notify_all ();
+ }
+}
+
+void
+Server::run (int num_threads)
+{
+ stringstream s;
+ s << "Server starting with " << num_threads << " threads.";
+ _log->log (s.str ());
+
+ for (int i = 0; i < num_threads; ++i) {
+ _worker_threads.push_back (new thread (bind (&Server::worker_thread, this)));
+ }
+
+ asio::io_service io_service;
+ asio::ip::tcp::acceptor acceptor (io_service, asio::ip::tcp::endpoint (asio::ip::tcp::v4(), Config::instance()->server_port ()));
+ while (1) {
+ shared_ptr<Socket> socket (new Socket);
+ acceptor.accept (socket->socket ());
+
+ mutex::scoped_lock lock (_worker_mutex);
+
+ /* Wait until the queue has gone down a bit */
+ while (int (_queue.size()) >= num_threads * 2) {
+ _worker_condition.wait (lock);
+ }
+
+ _queue.push_back (socket);
+ _worker_condition.notify_all ();
+ }
+}
diff --git a/src/lib/server.h b/src/lib/server.h
index d06df34e9..32ba8dc4b 100644
--- a/src/lib/server.h
+++ b/src/lib/server.h
@@ -19,21 +19,27 @@
/** @file src/server.h
* @brief Class to describe a server to which we can send
- * encoding work.
+ * encoding work, and a class to implement such a server.
*/
#include <string>
+#include <boost/thread.hpp>
+#include <boost/asio.hpp>
+#include <boost/thread/condition.hpp>
+#include "log.h"
-/** @class Server
+class Socket;
+
+/** @class ServerDescription
* @brief Class to describe a server to which we can send encoding work.
*/
-class Server
+class ServerDescription
{
public:
/** @param h Server host name or IP address in string form.
* @param t Number of threads to use on the server.
*/
- Server (std::string h, int t)
+ ServerDescription (std::string h, int t)
: _host_name (h)
, _threads (t)
{}
@@ -58,7 +64,7 @@ public:
std::string as_metadata () const;
- static Server * create_from_metadata (std::string v);
+ static ServerDescription * create_from_metadata (std::string v);
private:
/** server's host name */
@@ -66,3 +72,21 @@ private:
/** number of threads to use on the server */
int _threads;
};
+
+class Server
+{
+public:
+ Server (Log* log);
+
+ void run (int num_threads);
+
+private:
+ void worker_thread ();
+ int process (boost::shared_ptr<Socket> socket);
+
+ std::vector<boost::thread *> _worker_threads;
+ std::list<boost::shared_ptr<Socket> > _queue;
+ boost::mutex _worker_mutex;
+ boost::condition _worker_condition;
+ Log* _log;
+};
diff --git a/src/lib/tiff_encoder.cc b/src/lib/tiff_encoder.cc
index 2cf238006..19e34741d 100644
--- a/src/lib/tiff_encoder.cc
+++ b/src/lib/tiff_encoder.cc
@@ -73,5 +73,5 @@ TIFFEncoder::process_video (shared_ptr<Image> image, int frame)
TIFFClose (output);
boost::filesystem::rename (tmp_file, _opt->frame_out_path (frame, false));
- frame_done ();
+ frame_done (frame);
}
diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc
index 652a18441..9113593f0 100644
--- a/src/lib/transcode_job.cc
+++ b/src/lib/transcode_job.cc
@@ -78,7 +78,6 @@ TranscodeJob::run ()
_log->log (s.str ());
throw;
-
}
}
@@ -88,13 +87,30 @@ TranscodeJob::status () const
if (!_encoder) {
return "0%";
}
+
+ if (_encoder->skipping () && !finished ()) {
+ return "skipping already-encoded frames";
+ }
+
float const fps = _encoder->current_frames_per_second ();
if (fps == 0) {
return Job::status ();
}
-
+
stringstream s;
- s << Job::status () << "; about " << fixed << setprecision (1) << fps << " frames per second.";
+
+ s << Job::status () << "; " << fixed << setprecision (1) << fps << " frames per second";
return s.str ();
}
+
+int
+TranscodeJob::remaining_time () const
+{
+ float fps = _encoder->current_frames_per_second ();
+ if (fps == 0) {
+ return 0;
+ }
+
+ return ((_fs->length - _encoder->last_frame()) / fps);
+}
diff --git a/src/lib/transcode_job.h b/src/lib/transcode_job.h
index aa640f697..737f10de9 100644
--- a/src/lib/transcode_job.h
+++ b/src/lib/transcode_job.h
@@ -38,6 +38,9 @@ public:
void run ();
std::string status () const;
+protected:
+ int remaining_time () const;
+
private:
boost::shared_ptr<Encoder> _encoder;
};
diff --git a/src/lib/util.cc b/src/lib/util.cc
index 1478bab2e..935566440 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -33,6 +33,8 @@
#include <libssh/libssh.h>
#include <signal.h>
#include <boost/algorithm/string.hpp>
+#include <boost/bind.hpp>
+#include <boost/lambda/lambda.hpp>
#include <openjpeg.h>
#include <openssl/md5.h>
#include <magick/MagickCore.h>
@@ -59,10 +61,6 @@ extern "C" {
#include "player_manager.h"
#endif
-#ifdef DEBUG_HASH
-#include <mhash.h>
-#endif
-
using namespace std;
using namespace boost;
@@ -286,88 +284,6 @@ seconds (struct timeval t)
return t.tv_sec + (double (t.tv_usec) / 1e6);
}
-/** @param socket Socket to read from */
-SocketReader::SocketReader (shared_ptr<asio::ip::tcp::socket> socket)
- : _socket (socket)
- , _buffer_data (0)
-{
-
-}
-
-/** Mark some data as being `consumed', so that it will not be returned
- * as data again.
- * @param size Amount of data to consume, in bytes.
- */
-void
-SocketReader::consume (int size)
-{
- assert (_buffer_data >= size);
-
- _buffer_data -= size;
- if (_buffer_data > 0) {
- /* Shift still-valid data to the start of the buffer */
- memmove (_buffer, _buffer + size, _buffer_data);
- }
-}
-
-/** Read a definite amount of data from our socket, and mark
- * it as consumed.
- * @param data Where to put the data.
- * @param size Number of bytes to read.
- */
-void
-SocketReader::read_definite_and_consume (uint8_t* data, int size)
-{
- int const from_buffer = min (_buffer_data, size);
- if (from_buffer > 0) {
- /* Get data from our buffer */
- memcpy (data, _buffer, from_buffer);
- consume (from_buffer);
- /* Update our output state */
- data += from_buffer;
- size -= from_buffer;
- }
-
- /* read() the rest */
- while (size > 0) {
- int const n = asio::read (*_socket, asio::buffer (data, size));
- if (n <= 0) {
- throw NetworkError ("could not read");
- }
-
- data += n;
- size -= n;
- }
-}
-
-/** Read as much data as is available, up to some limit.
- * @param data Where to put the data.
- * @param size Maximum amount of data to read.
- */
-void
-SocketReader::read_indefinite (uint8_t* data, int size)
-{
- assert (size < int (sizeof (_buffer)));
-
- /* Amount of extra data we need to read () */
- int to_read = size - _buffer_data;
- while (to_read > 0) {
- /* read as much of it as we can (into our buffer) */
- int const n = asio::read (*_socket, asio::buffer (_buffer + _buffer_data, to_read));
- if (n <= 0) {
- throw NetworkError ("could not read");
- }
-
- to_read -= n;
- _buffer_data += n;
- }
-
- assert (_buffer_data >= size);
-
- /* copy data into the output buffer */
- assert (size >= _buffer_data);
- memcpy (data, _buffer, size);
-}
#ifdef DVDOMATIC_POSIX
void
@@ -427,28 +343,26 @@ split_at_spaces_considering_quotes (string s)
return out;
}
-#ifdef DEBUG_HASH
-void
-md5_data (string title, void const * data, int size)
+string
+md5_digest (void const * data, int size)
{
- MHASH ht = mhash_init (MHASH_MD5);
- if (ht == MHASH_FAILED) {
- throw EncodeError ("could not create hash thread");
- }
-
- mhash (ht, data, size);
-
- uint8_t hash[16];
- mhash_deinit (ht, hash);
+ MD5_CTX md5_context;
+ MD5_Init (&md5_context);
+ MD5_Update (&md5_context, data, size);
+ unsigned char digest[MD5_DIGEST_LENGTH];
+ MD5_Final (digest, &md5_context);
- printf ("%s [%d]: ", title.c_str (), size);
- for (int i = 0; i < int (mhash_get_block_size (MHASH_MD5)); ++i) {
- printf ("%.2x", hash[i]);
+ stringstream s;
+ for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
+ s << hex << setfill('0') << setw(2) << ((int) digest[i]);
}
- printf ("\n");
+
+ return s.str ();
}
-#endif
+/** @param file File name.
+ * @return MD5 digest of file's contents.
+ */
string
md5_digest (string file)
{
@@ -484,6 +398,9 @@ md5_digest (string file)
return s.str ();
}
+/** @param An arbitrary sampling rate.
+ * @return The appropriate DCP-approved sampling rate (48kHz or 96kHz).
+ */
int
dcp_audio_sample_rate (int fs)
{
@@ -504,6 +421,9 @@ bool operator!= (Crop const & a, Crop const & b)
return !(a == b);
}
+/** @param index Colour LUT index.
+ * @return Human-readable name.
+ */
string
colour_lut_index_to_name (int index)
{
@@ -518,5 +438,165 @@ colour_lut_index_to_name (int index)
return "";
}
-
-
+Socket::Socket ()
+ : _deadline (_io_service)
+ , _socket (_io_service)
+ , _buffer_data (0)
+{
+ _deadline.expires_at (posix_time::pos_infin);
+ check ();
+}
+
+void
+Socket::check ()
+{
+ if (_deadline.expires_at() <= asio::deadline_timer::traits_type::now ()) {
+ _socket.close ();
+ _deadline.expires_at (posix_time::pos_infin);
+ }
+
+ _deadline.async_wait (boost::bind (&Socket::check, this));
+}
+
+/** Blocking connect with timeout.
+ * @param endpoint End-point to connect to.
+ * @param timeout Time-out in seconds.
+ */
+void
+Socket::connect (asio::ip::basic_resolver_entry<asio::ip::tcp> const & endpoint, int timeout)
+{
+ system::error_code ec = asio::error::would_block;
+ _socket.async_connect (endpoint, lambda::var(ec) = lambda::_1);
+ do {
+ _io_service.run_one();
+ } while (ec == asio::error::would_block);
+
+ if (ec || !_socket.is_open ()) {
+ throw NetworkError ("connect timed out");
+ }
+}
+
+/** Blocking write with timeout.
+ * @param data Buffer to write.
+ * @param size Number of bytes to write.
+ * @param timeout Time-out, in seconds.
+ */
+void
+Socket::write (uint8_t const * data, int size, int timeout)
+{
+ _deadline.expires_from_now (posix_time::seconds (timeout));
+ system::error_code ec = asio::error::would_block;
+
+ asio::async_write (_socket, asio::buffer (data, size), lambda::var(ec) = lambda::_1);
+ do {
+ _io_service.run_one ();
+ } while (ec == asio::error::would_block);
+
+ if (ec) {
+ throw NetworkError ("write timed out");
+ }
+}
+
+/** Blocking read with timeout.
+ * @param data Buffer to read to.
+ * @param size Number of bytes to read.
+ * @param timeout Time-out, in seconds.
+ */
+int
+Socket::read (uint8_t* data, int size, int timeout)
+{
+ _deadline.expires_from_now (posix_time::seconds (timeout));
+ system::error_code ec = asio::error::would_block;
+
+ int amount_read = 0;
+
+ _socket.async_read_some (
+ asio::buffer (data, size),
+ (lambda::var(ec) = lambda::_1, lambda::var(amount_read) = lambda::_2)
+ );
+
+ do {
+ _io_service.run_one ();
+ } while (ec == asio::error::would_block);
+
+ if (ec) {
+ amount_read = 0;
+ }
+
+ return amount_read;
+}
+
+/** Mark some data as being `consumed', so that it will not be returned
+ * as data again.
+ * @param size Amount of data to consume, in bytes.
+ */
+void
+Socket::consume (int size)
+{
+ assert (_buffer_data >= size);
+
+ _buffer_data -= size;
+ if (_buffer_data > 0) {
+ /* Shift still-valid data to the start of the buffer */
+ memmove (_buffer, _buffer + size, _buffer_data);
+ }
+}
+
+/** Read a definite amount of data from our socket, and mark
+ * it as consumed.
+ * @param data Where to put the data.
+ * @param size Number of bytes to read.
+ */
+void
+Socket::read_definite_and_consume (uint8_t* data, int size, int timeout)
+{
+ int const from_buffer = min (_buffer_data, size);
+ if (from_buffer > 0) {
+ /* Get data from our buffer */
+ memcpy (data, _buffer, from_buffer);
+ consume (from_buffer);
+ /* Update our output state */
+ data += from_buffer;
+ size -= from_buffer;
+ }
+
+ /* read() the rest */
+ while (size > 0) {
+ int const n = read (data, size, timeout);
+ if (n <= 0) {
+ throw NetworkError ("could not read");
+ }
+
+ data += n;
+ size -= n;
+ }
+}
+
+/** Read as much data as is available, up to some limit.
+ * @param data Where to put the data.
+ * @param size Maximum amount of data to read.
+ */
+void
+Socket::read_indefinite (uint8_t* data, int size, int timeout)
+{
+ assert (size < int (sizeof (_buffer)));
+
+ /* Amount of extra data we need to read () */
+ int to_read = size - _buffer_data;
+ while (to_read > 0) {
+ /* read as much of it as we can (into our buffer) */
+ int const n = read (_buffer + _buffer_data, to_read, timeout);
+ if (n <= 0) {
+ throw NetworkError ("could not read");
+ }
+
+ to_read -= n;
+ _buffer_data += n;
+ }
+
+ assert (_buffer_data >= size);
+
+ /* copy data into the output buffer */
+ assert (size >= _buffer_data);
+ memcpy (data, _buffer, size);
+}
diff --git a/src/lib/util.h b/src/lib/util.h
index 568fe05d0..bc5a00fc4 100644
--- a/src/lib/util.h
+++ b/src/lib/util.h
@@ -46,39 +46,13 @@ extern double seconds (struct timeval);
extern void dvdomatic_setup ();
extern std::vector<std::string> split_at_spaces_considering_quotes (std::string);
extern std::string md5_digest (std::string);
+extern std::string md5_digest (void const *, int);
enum ContentType {
STILL,
VIDEO
};
-#ifdef DEBUG_HASH
-extern void md5_data (std::string, void const *, int);
-#endif
-
-/** @class SocketReader
- * @brief A helper class from reading from sockets.
- *
- * You can probably do this stuff directly in boost, but I'm not sure how.
- */
-class SocketReader
-{
-public:
- SocketReader (boost::shared_ptr<boost::asio::ip::tcp::socket>);
-
- void read_definite_and_consume (uint8_t *, int);
- void read_indefinite (uint8_t *, int);
- void consume (int);
-
-private:
- /** socket we are reading from */
- boost::shared_ptr<boost::asio::ip::tcp::socket> _socket;
- /** a buffer for small reads */
- uint8_t _buffer[256];
- /** amount of valid data in the buffer */
- int _buffer_data;
-};
-
/** @class Size
* @brief Representation of the size of something */
struct Size
@@ -103,19 +77,25 @@ struct Size
int height;
};
+/** A description of the crop of an image or video. */
struct Crop
{
Crop () : left (0), right (0), top (0), bottom (0) {}
-
+
+ /** Number of pixels to remove from the left-hand side */
int left;
+ /** Number of pixels to remove from the right-hand side */
int right;
+ /** Number of pixels to remove from the top */
int top;
+ /** Number of pixels to remove from the bottom */
int bottom;
};
extern bool operator== (Crop const & a, Crop const & b);
extern bool operator!= (Crop const & a, Crop const & b);
+/** A position */
struct Position
{
Position ()
@@ -128,7 +108,9 @@ struct Position
, y (y_)
{}
+ /** x coordinate */
int x;
+ /** y coordinate */
int y;
};
@@ -136,4 +118,44 @@ extern std::string crop_string (Position, Size);
extern int dcp_audio_sample_rate (int);
extern std::string colour_lut_index_to_name (int index);
+/** @class Socket
+ * @brief A class to wrap a boost::asio::ip::tcp::socket with some things
+ * that are useful for DVD-o-matic.
+ *
+ * This class wraps some things that I could not work out how to do with boost;
+ * most notably, sync read/write calls with timeouts, and the ability to peak into
+ * data being read.
+ */
+class Socket
+{
+public:
+ Socket ();
+
+ /** @return Our underlying socket */
+ boost::asio::ip::tcp::socket& socket () {
+ return _socket;
+ }
+
+ void connect (boost::asio::ip::basic_resolver_entry<boost::asio::ip::tcp> const & endpoint, int timeout);
+ void write (uint8_t const * data, int size, int timeout);
+
+ void read_definite_and_consume (uint8_t* data, int size, int timeout);
+ void read_indefinite (uint8_t* data, int size, int timeout);
+ void consume (int amount);
+
+private:
+ void check ();
+ int read (uint8_t* data, int size, int timeout);
+
+ Socket (Socket const &);
+
+ boost::asio::io_service _io_service;
+ boost::asio::deadline_timer _deadline;
+ boost::asio::ip::tcp::socket _socket;
+ /** a buffer for small reads */
+ uint8_t _buffer[256];
+ /** amount of valid data in the buffer */
+ int _buffer_data;
+};
+
#endif
diff --git a/src/lib/wscript b/src/lib/wscript
index b001fff2a..c809226ce 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -1,8 +1,3 @@
-def configure(conf):
- if conf.options.debug_hash:
- conf.env.append_value('CXXFLAGS', '-DDEBUG_HASH')
- conf.check_cc(msg = 'Checking for library libmhash', function_name = 'mhash_init', header_name = 'mhash.h', lib = 'mhash', uselib_store = 'MHASH')
-
def build(bld):
obj = bld(features = 'cxx cxxshlib')
obj.name = 'libdvdomatic'
@@ -10,11 +5,10 @@ def build(bld):
obj.uselib = 'AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE SNDFILE BOOST_FILESYSTEM BOOST_THREAD OPENJPEG POSTPROC TIFF SIGC++ MAGICK SSH DCP GLIB'
if bld.env.TARGET_WINDOWS:
obj.uselib += ' WINSOCK2'
- if bld.env.DEBUG_HASH:
- obj.uselib += ' MHASH'
obj.source = """
ab_transcode_job.cc
ab_transcoder.cc
+ check_hashes_job.cc
config.cc
copy_from_dvd_job.cc
cross.cc
@@ -28,6 +22,7 @@ def build(bld):
encoder.cc
encoder_factory.cc
examine_content_job.cc
+ ffmpeg_compatibility.cc
ffmpeg_decoder.cc
film.cc
film_state.cc
diff --git a/src/tools/dvdomatic.cc b/src/tools/dvdomatic.cc
index df38e9d69..c42321300 100644
--- a/src/tools/dvdomatic.cc
+++ b/src/tools/dvdomatic.cc
@@ -30,6 +30,7 @@
//#include "gtk/dvd_title_dialog.h"
#include "wx/wx_util.h"
#include "wx/new_film_dialog.h"
+#include "wx/properties_dialog.h"
#include "lib/film.h"
#include "lib/format.h"
#include "lib/config.h"
@@ -125,6 +126,7 @@ enum {
ID_file_new = 1,
ID_file_open,
ID_file_save,
+ ID_file_properties,
ID_file_quit,
ID_edit_preferences,
ID_jobs_make_dcp,
@@ -144,6 +146,8 @@ setup_menu (wxMenuBar* m)
file->AppendSeparator ();
add_item (file, "&Save", ID_file_save, NEEDS_FILM);
file->AppendSeparator ();
+ add_item (file, "&Properties...", ID_file_properties, NEEDS_FILM);
+ file->AppendSeparator ();
add_item (file, "&Quit", ID_file_quit, ALWAYS);
wxMenu* edit = new wxMenu;
@@ -188,6 +192,7 @@ public:
Connect (ID_file_new, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_new));
Connect (ID_file_open, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_open));
Connect (ID_file_save, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_save));
+ Connect (ID_file_properties, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_properties));
Connect (ID_file_quit, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::file_quit));
Connect (ID_edit_preferences, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::edit_preferences));
Connect (ID_jobs_make_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_make_dcp));
@@ -285,6 +290,13 @@ public:
{
film->write_metadata ();
}
+
+ void file_properties (wxCommandEvent &)
+ {
+ PropertiesDialog* d = new PropertiesDialog (this, film);
+ d->ShowModal ();
+ d->Destroy ();
+ }
void file_quit (wxCommandEvent &)
{
diff --git a/src/tools/servomatic.cc b/src/tools/servomatic.cc
deleted file mode 100644
index a9c45b3df..000000000
--- a/src/tools/servomatic.cc
+++ /dev/null
@@ -1,194 +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.
-
-*/
-
-#include <iostream>
-#include <stdexcept>
-#include <sstream>
-#include <cstring>
-#include <vector>
-#include <unistd.h>
-#include <errno.h>
-#include <boost/array.hpp>
-#include <boost/asio.hpp>
-#include <boost/algorithm/string.hpp>
-#include <boost/thread.hpp>
-#include <boost/thread/mutex.hpp>
-#include <boost/thread/condition.hpp>
-#include "config.h"
-#include "dcp_video_frame.h"
-#include "exceptions.h"
-#include "util.h"
-#include "config.h"
-#include "scaler.h"
-#include "image.h"
-#include "log.h"
-
-#define BACKLOG 8
-
-using namespace std;
-using namespace boost;
-
-static vector<thread *> worker_threads;
-
-static std::list<shared_ptr<asio::ip::tcp::socket> > queue;
-static mutex worker_mutex;
-static condition worker_condition;
-static Log log_ ("servomatic.log");
-
-int
-process (shared_ptr<asio::ip::tcp::socket> socket)
-{
- SocketReader reader (socket);
-
- char buffer[128];
- reader.read_indefinite ((uint8_t *) buffer, sizeof (buffer));
- reader.consume (strlen (buffer) + 1);
-
- stringstream s (buffer);
-
- string command;
- s >> command;
- if (command != "encode") {
- return -1;
- }
-
- Size in_size;
- int pixel_format_int;
- Size out_size;
- int padding;
- string scaler_id;
- int frame;
- float frames_per_second;
- string post_process;
- int colour_lut_index;
- int j2k_bandwidth;
-
- s >> in_size.width >> in_size.height
- >> pixel_format_int
- >> out_size.width >> out_size.height
- >> padding
- >> scaler_id
- >> frame
- >> frames_per_second
- >> post_process
- >> colour_lut_index
- >> j2k_bandwidth;
-
- PixelFormat pixel_format = (PixelFormat) pixel_format_int;
- Scaler const * scaler = Scaler::from_id (scaler_id);
- if (post_process == "none") {
- post_process = "";
- }
-
- shared_ptr<SimpleImage> image (new SimpleImage (pixel_format, in_size));
-
- for (int i = 0; i < image->components(); ++i) {
- int line_size;
- s >> line_size;
- image->set_line_size (i, line_size);
- }
-
- for (int i = 0; i < image->components(); ++i) {
- reader.read_definite_and_consume (image->data()[i], image->line_size()[i] * image->lines(i));
- }
-
-#ifdef DEBUG_HASH
- image->hash ("Image for encoding (as received by server)");
-#endif
-
- DCPVideoFrame dcp_video_frame (image, out_size, padding, scaler, frame, frames_per_second, post_process, colour_lut_index, j2k_bandwidth, &log_);
- shared_ptr<EncodedData> encoded = dcp_video_frame.encode_locally ();
- encoded->send (socket);
-
-#ifdef DEBUG_HASH
- encoded->hash ("Encoded image (as made by server and as sent back)");
-#endif
-
- return frame;
-}
-
-void
-worker_thread ()
-{
- while (1) {
- mutex::scoped_lock lock (worker_mutex);
- while (queue.empty ()) {
- worker_condition.wait (lock);
- }
-
- shared_ptr<asio::ip::tcp::socket> socket = queue.front ();
- queue.pop_front ();
-
- lock.unlock ();
-
- int frame = -1;
-
- struct timeval start;
- gettimeofday (&start, 0);
-
- try {
- frame = process (socket);
- } catch (std::exception& e) {
- cerr << "Error: " << e.what() << "\n";
- }
-
- socket.reset ();
-
- lock.lock ();
-
- if (frame >= 0) {
- struct timeval end;
- gettimeofday (&end, 0);
- cout << "Encoded frame " << frame << " in " << (seconds (end) - seconds (start)) << "\n";
- }
-
- worker_condition.notify_all ();
- }
-}
-
-int
-main ()
-{
- Scaler::setup_scalers ();
-
- int const num_threads = Config::instance()->num_local_encoding_threads ();
-
- for (int i = 0; i < num_threads; ++i) {
- worker_threads.push_back (new thread (worker_thread));
- }
-
- asio::io_service io_service;
- asio::ip::tcp::acceptor acceptor (io_service, asio::ip::tcp::endpoint (asio::ip::tcp::v4(), Config::instance()->server_port ()));
- while (1) {
- shared_ptr<asio::ip::tcp::socket> socket (new asio::ip::tcp::socket (io_service));
- acceptor.accept (*socket);
-
- mutex::scoped_lock lock (worker_mutex);
-
- /* Wait until the queue has gone down a bit */
- while (int (queue.size()) >= num_threads * 2) {
- worker_condition.wait (lock);
- }
-
- queue.push_back (socket);
- worker_condition.notify_all ();
- }
-
- return 0;
-}
diff --git a/src/tools/servomatic_cli.cc b/src/tools/servomatic_cli.cc
new file mode 100644
index 000000000..f8e713193
--- /dev/null
+++ b/src/tools/servomatic_cli.cc
@@ -0,0 +1,94 @@
+/*
+ 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.
+
+*/
+
+#include "lib/server.h"
+#include <iostream>
+#include <stdexcept>
+#include <sstream>
+#include <cstring>
+#include <vector>
+#include <unistd.h>
+#include <errno.h>
+#include <getopt.h>
+#include <boost/array.hpp>
+#include <boost/asio.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/mutex.hpp>
+#include <boost/thread/condition.hpp>
+#include "config.h"
+#include "dcp_video_frame.h"
+#include "exceptions.h"
+#include "util.h"
+#include "config.h"
+#include "scaler.h"
+#include "image.h"
+#include "log.h"
+#include "version.h"
+
+using namespace std;
+
+static void
+help (string n)
+{
+ cerr << "Syntax: " << n << " [OPTION]\n"
+ << " -v, --version show DVD-o-matic version\n"
+ << " -h, --help show this help\n"
+ << " -t, --threads number of parallel encoding threads to use\n";
+}
+
+int
+main (int argc, char* argv[])
+{
+ int num_threads = Config::instance()->num_local_encoding_threads ();
+
+ int option_index = 0;
+ while (1) {
+ static struct option long_options[] = {
+ { "version", no_argument, 0, 'v'},
+ { "help", no_argument, 0, 'h'},
+ { "threads", required_argument, 0, 't'},
+ { 0, 0, 0, 0 }
+ };
+
+ int c = getopt_long (argc, argv, "vht:", long_options, &option_index);
+
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'v':
+ cout << "dvdomatic version " << dvdomatic_version << " " << dvdomatic_git_commit << "\n";
+ exit (EXIT_SUCCESS);
+ case 'h':
+ help (argv[0]);
+ exit (EXIT_SUCCESS);
+ case 't':
+ num_threads = atoi (optarg);
+ break;
+ }
+ }
+
+ Scaler::setup_scalers ();
+ FileLog log ("servomatic.log");
+ Server server (&log);
+ server.run (num_threads);
+ return 0;
+}
diff --git a/src/tools/servomatic_gui.cc b/src/tools/servomatic_gui.cc
new file mode 100644
index 000000000..610ba8005
--- /dev/null
+++ b/src/tools/servomatic_gui.cc
@@ -0,0 +1,151 @@
+/*
+ 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.
+
+*/
+
+#include <boost/thread.hpp>
+#include <wx/taskbar.h>
+#include <wx/icon.h>
+#include "wx_util.h"
+#include "lib/util.h"
+#include "lib/server.h"
+#include "lib/config.h"
+
+using namespace std;
+using namespace boost;
+
+enum {
+ ID_status = 1,
+ ID_quit,
+ ID_timer
+};
+
+class MemoryLog : public Log
+{
+public:
+
+ string get () const {
+ boost::mutex::scoped_lock (_mutex);
+ return _log;
+ }
+
+private:
+ void do_log (string m)
+ {
+ _log = m;
+ }
+
+ string _log;
+};
+
+static MemoryLog memory_log;
+
+class StatusDialog : public wxDialog
+{
+public:
+ StatusDialog ()
+ : wxDialog (0, wxID_ANY, _("DVD-o-matic encode server"), wxDefaultPosition, wxSize (600, 80), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
+ , _timer (this, ID_timer)
+ {
+ _sizer = new wxFlexGridSizer (1, 6, 6);
+ _sizer->AddGrowableCol (0, 1);
+
+ _text = new wxTextCtrl (this, wxID_ANY, _(""), wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
+ _sizer->Add (_text, 1, wxEXPAND);
+
+ SetSizer (_sizer);
+ _sizer->Layout ();
+
+ Connect (ID_timer, wxEVT_TIMER, wxTimerEventHandler (StatusDialog::update));
+ _timer.Start (1000);
+ }
+
+private:
+ void update (wxTimerEvent &)
+ {
+ _text->ChangeValue (std_to_wx (memory_log.get ()));
+ _sizer->Layout ();
+ }
+
+ wxFlexGridSizer* _sizer;
+ wxTextCtrl* _text;
+ wxTimer _timer;
+};
+
+class TaskBarIcon : public wxTaskBarIcon
+{
+public:
+ TaskBarIcon ()
+ {
+ wxIcon icon (std_to_wx ("taskbar_icon"));
+ SetIcon (icon, std_to_wx ("DVD-o-matic encode server"));
+
+ Connect (ID_status, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (TaskBarIcon::status));
+ Connect (ID_quit, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (TaskBarIcon::quit));
+ }
+
+ wxMenu* CreatePopupMenu ()
+ {
+ wxMenu* menu = new wxMenu;
+ menu->Append (ID_status, std_to_wx ("Status..."));
+ menu->Append (ID_quit, std_to_wx ("Quit"));
+ return menu;
+ }
+
+private:
+ void status (wxCommandEvent &)
+ {
+ StatusDialog* d = new StatusDialog;
+ d->Show ();
+ }
+
+ void quit (wxCommandEvent &)
+ {
+ wxTheApp->ExitMainLoop ();
+ }
+};
+
+class App : public wxApp
+{
+public:
+ App ()
+ : wxApp ()
+ , _thread (0)
+ {}
+
+private:
+
+ bool OnInit ()
+ {
+ dvdomatic_setup ();
+
+ new TaskBarIcon;
+
+ _thread = new thread (bind (&App::main_thread, this));
+ return true;
+ }
+
+ void main_thread ()
+ {
+ Server server (&memory_log);
+ server.run (Config::instance()->num_local_encoding_threads ());
+ }
+
+ boost::thread* _thread;
+};
+
+IMPLEMENT_APP (App)
diff --git a/src/tools/servomatictest.cc b/src/tools/servomatictest.cc
index 0f37e73a5..d6804c981 100644
--- a/src/tools/servomatictest.cc
+++ b/src/tools/servomatictest.cc
@@ -47,12 +47,8 @@ process_video (shared_ptr<Image> image, int frame)
shared_ptr<DCPVideoFrame> local (new DCPVideoFrame (image, Size (1024, 1024), 0, Scaler::from_id ("bicubic"), frame, 24, "", 0, 250000000, &log_));
shared_ptr<DCPVideoFrame> remote (new DCPVideoFrame (image, Size (1024, 1024), 0, Scaler::from_id ("bicubic"), frame, 24, "", 0, 250000000, &log_));
-#if defined(DEBUG_HASH)
- cout << "Frame " << frame << ":\n";
-#else
cout << "Frame " << frame << ": ";
cout.flush ();
-#endif
shared_ptr<EncodedData> local_encoded = local->encode_locally ();
shared_ptr<EncodedData> remote_encoded;
@@ -64,11 +60,6 @@ process_video (shared_ptr<Image> image, int frame)
remote_error = e.what ();
}
-#if defined(DEBUG_HASH)
- cout << "Frame " << frame << ": ";
- cout.flush ();
-#endif
-
if (!remote_error.empty ()) {
cout << "\033[0;31mnetwork problem: " << remote_error << "\033[0m\n";
return;
diff --git a/src/tools/wscript b/src/tools/wscript
index be3d44e6d..048bdff07 100644
--- a/src/tools/wscript
+++ b/src/tools/wscript
@@ -1,5 +1,5 @@
def build(bld):
- for t in ['makedcp', 'fixlengths', 'servomatic']:
+ for t in ['makedcp', 'fixlengths', 'servomatic_cli']:
obj = bld(features = 'cxx cxxprogram')
obj.uselib = 'BOOST_THREAD'
obj.includes = ['..']
@@ -9,7 +9,7 @@ def build(bld):
if not bld.env.DISABLE_GUI:
# p = ['dvdomatic', 'alignomatic']
- p = ['dvdomatic']
+ p = ['dvdomatic', 'servomatic_gui']
if not bld.env.DISABLE_PLAYER:
p.append('playomatic')
for t in p:
diff --git a/src/wscript b/src/wscript
index 2ebeba210..3f17b3e6c 100644
--- a/src/wscript
+++ b/src/wscript
@@ -1,5 +1,4 @@
def configure(conf):
- conf.recurse('lib')
if not conf.env.DISABLE_GUI:
conf.recurse('wx')
diff --git a/src/wx/config_dialog.cc b/src/wx/config_dialog.cc
index ebf5be460..b0bd6f2ee 100644
--- a/src/wx/config_dialog.cc
+++ b/src/wx/config_dialog.cc
@@ -145,7 +145,7 @@ ConfigDialog::ConfigDialog (wxWindow* parent)
_colour_lut->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (ConfigDialog::colour_lut_changed), 0, this);
_j2k_bandwidth->SetRange (50, 250);
- _j2k_bandwidth->SetValue (config->j2k_bandwidth() / 1e6);
+ _j2k_bandwidth->SetValue (rint ((double) config->j2k_bandwidth() / 1e6));
_j2k_bandwidth->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ConfigDialog::j2k_bandwidth_changed), 0, this);
_reference_scaler->SetSelection (Scaler::as_index (config->reference_scaler ()));
@@ -155,8 +155,8 @@ ConfigDialog::ConfigDialog (wxWindow* parent)
_reference_filters->SetLabel (std_to_wx (p.first + " " + p.second));
_reference_filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ConfigDialog::edit_reference_filters_clicked), 0, this);
- vector<Server*> servers = config->servers ();
- for (vector<Server*>::iterator i = servers.begin(); i != servers.end(); ++i) {
+ vector<ServerDescription*> servers = config->servers ();
+ for (vector<ServerDescription*>::iterator i = servers.begin(); i != servers.end(); ++i) {
add_server_to_control (*i);
}
@@ -170,7 +170,7 @@ ConfigDialog::ConfigDialog (wxWindow* parent)
server_selection_changed (ev);
wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
- overall_sizer->Add (table, 1, wxEXPAND);
+ overall_sizer->Add (table, 1, wxEXPAND | wxALL, 6);
wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
if (buttons) {
@@ -225,7 +225,7 @@ ConfigDialog::j2k_bandwidth_changed (wxCommandEvent &)
}
void
-ConfigDialog::add_server_to_control (Server* s)
+ConfigDialog::add_server_to_control (ServerDescription* s)
{
wxListItem item;
int const n = _servers->GetItemCount ();
@@ -240,11 +240,11 @@ ConfigDialog::add_server_clicked (wxCommandEvent &)
{
ServerDialog* d = new ServerDialog (this, 0);
d->ShowModal ();
- Server* s = d->server ();
+ ServerDescription* s = d->server ();
d->Destroy ();
add_server_to_control (s);
- vector<Server*> o = Config::instance()->servers ();
+ vector<ServerDescription*> o = Config::instance()->servers ();
o.push_back (s);
Config::instance()->set_servers (o);
}
@@ -262,22 +262,15 @@ ConfigDialog::edit_server_clicked (wxCommandEvent &)
item.SetColumn (0);
_servers->GetItem (item);
- vector<Server*> servers = Config::instance()->servers ();
- vector<Server*>::iterator j = servers.begin();
- while (j != servers.end() && (*j)->host_name() != wx_to_std (item.GetText ())) {
- ++j;
- }
-
- if (j == servers.end()) {
- return;
- }
+ vector<ServerDescription*> servers = Config::instance()->servers ();
+ assert (i >= 0 && i < int (servers.size ()));
- ServerDialog* d = new ServerDialog (this, *j);
+ ServerDialog* d = new ServerDialog (this, servers[i]);
d->ShowModal ();
d->Destroy ();
- _servers->SetItem (i, 0, std_to_wx ((*j)->host_name ()));
- _servers->SetItem (i, 1, std_to_wx (boost::lexical_cast<string> ((*j)->threads ())));
+ _servers->SetItem (i, 0, std_to_wx (servers[i]->host_name ()));
+ _servers->SetItem (i, 1, std_to_wx (boost::lexical_cast<string> (servers[i]->threads ())));
}
void
@@ -287,6 +280,10 @@ ConfigDialog::remove_server_clicked (wxCommandEvent &)
if (i >= 0) {
_servers->DeleteItem (i);
}
+
+ vector<ServerDescription*> o = Config::instance()->servers ();
+ o.erase (o.begin() + i);
+ Config::instance()->set_servers (o);
}
void
diff --git a/src/wx/config_dialog.h b/src/wx/config_dialog.h
index c9ca8034f..b1d3eb84d 100644
--- a/src/wx/config_dialog.h
+++ b/src/wx/config_dialog.h
@@ -26,7 +26,7 @@
#include <wx/listctrl.h>
class Screen;
-class Server;
+class ServerDescription;
/** @class ConfigDialog
* @brief A dialogue to edit DVD-o-matic configuration.
@@ -52,7 +52,7 @@ private:
void remove_server_clicked (wxCommandEvent &);
void server_selection_changed (wxListEvent &);
- void add_server_to_control (Server *);
+ void add_server_to_control (ServerDescription *);
wxTextCtrl* _tms_ip;
wxTextCtrl* _tms_path;
diff --git a/src/wx/dcp_range_dialog.cc b/src/wx/dcp_range_dialog.cc
index 572d0c628..aed6808cb 100644
--- a/src/wx/dcp_range_dialog.cc
+++ b/src/wx/dcp_range_dialog.cc
@@ -69,7 +69,7 @@ DCPRangeDialog::DCPRangeDialog (wxWindow* p, Film* f)
_n_frames->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (DCPRangeDialog::n_frames_changed), 0, this);
wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
- overall_sizer->Add (table);
+ overall_sizer->Add (table, 0, wxALL, 6);
wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
if (buttons) {
diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc
index f35996644..6de3af9e7 100644
--- a/src/wx/film_editor.cc
+++ b/src/wx/film_editor.cc
@@ -132,7 +132,7 @@ FilmEditor::FilmEditor (Film* f, wxWindow* parent)
video_control (add_label_to_sizer (_sizer, this, "Frames Per Second"));
_frames_per_second = new wxStaticText (this, wxID_ANY, wxT (""));
- _sizer->Add (video_control (_frames_per_second));
+ _sizer->Add (video_control (_frames_per_second), 1, wxALIGN_CENTER_VERTICAL);
video_control (add_label_to_sizer (_sizer, this, "Original Size"));
_original_size = new wxStaticText (this, wxID_ANY, wxT (""));
@@ -648,11 +648,24 @@ FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &)
{
GainCalculatorDialog* d = new GainCalculatorDialog (this);
d->ShowModal ();
+
+ if (d->wanted_fader() == 0 || d->actual_fader() == 0) {
+ d->Destroy ();
+ return;
+ }
+
_audio_gain->SetValue (
Config::instance()->sound_processor()->db_for_fader_change (
d->wanted_fader (),
d->actual_fader ()
)
);
+
+ /* This appears to be necessary, as the change is not signalled,
+ I think.
+ */
+ wxCommandEvent dummy;
+ audio_gain_changed (dummy);
+
d->Destroy ();
}
diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc
index 8398b8162..0d17baf83 100644
--- a/src/wx/film_viewer.cc
+++ b/src/wx/film_viewer.cc
@@ -47,6 +47,7 @@ public:
{
}
+ /** Handle a paint event */
void paint_event (wxPaintEvent& ev)
{
if (_current_image != _pending_image) {
@@ -67,6 +68,7 @@ public:
}
}
+ /** Handle a size event */
void size_event (wxSizeEvent &)
{
if (!_image) {
@@ -101,6 +103,7 @@ public:
}
}
+ /** Clear our thumbnail image */
void clear ()
{
delete _bitmap;
@@ -237,6 +240,7 @@ FilmViewer::set_film (Film* f)
}
_film->Changed.connect (sigc::mem_fun (*this, &FilmViewer::film_changed));
+ film_changed (Film::CROP);
film_changed (Film::THUMBS);
_thumb_panel->refresh ();
setup_visibility ();
diff --git a/src/wx/filter_dialog.cc b/src/wx/filter_dialog.cc
index 9ec169395..028d082b4 100644
--- a/src/wx/filter_dialog.cc
+++ b/src/wx/filter_dialog.cc
@@ -32,7 +32,7 @@ FilterDialog::FilterDialog (wxWindow* parent, vector<Filter const *> const & f)
, _filters (new FilterView (this, f))
{
wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
- sizer->Add (_filters, 1, wxEXPAND);
+ sizer->Add (_filters, 1, wxEXPAND | wxALL, 6);
_filters->ActiveChanged.connect (sigc::mem_fun (*this, &FilterDialog::active_changed));
diff --git a/src/wx/gain_calculator_dialog.cc b/src/wx/gain_calculator_dialog.cc
index 431a4672d..3f07faf06 100644
--- a/src/wx/gain_calculator_dialog.cc
+++ b/src/wx/gain_calculator_dialog.cc
@@ -38,7 +38,7 @@ GainCalculatorDialog::GainCalculatorDialog (wxWindow* parent)
table->Add (_actual, 1, wxEXPAND);
wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
- overall_sizer->Add (table, 1, wxEXPAND);
+ overall_sizer->Add (table, 1, wxEXPAND | wxALL, 6);
wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
if (buttons) {
@@ -52,11 +52,19 @@ GainCalculatorDialog::GainCalculatorDialog (wxWindow* parent)
float
GainCalculatorDialog::wanted_fader () const
{
+ if (_wanted->GetValue().IsEmpty()) {
+ return 0;
+ }
+
return lexical_cast<float> (wx_to_std (_wanted->GetValue ()));
}
float
GainCalculatorDialog::actual_fader () const
{
+ if (_actual->GetValue().IsEmpty()) {
+ return 0;
+ }
+
return lexical_cast<float> (wx_to_std (_actual->GetValue ()));
}
diff --git a/src/wx/job_manager_view.cc b/src/wx/job_manager_view.cc
index c9e120135..1d5c855ea 100644
--- a/src/wx/job_manager_view.cc
+++ b/src/wx/job_manager_view.cc
@@ -67,15 +67,21 @@ JobManagerView::update ()
{
list<shared_ptr<Job> > jobs = JobManager::instance()->get ();
+ int index = 0;
+
for (list<shared_ptr<Job> >::iterator i = jobs.begin(); i != jobs.end(); ++i) {
if (_job_records.find (*i) == _job_records.end ()) {
- add_label_to_sizer (_table, _panel, (*i)->name ());
+ wxStaticText* m = new wxStaticText (_panel, wxID_ANY, std_to_wx ((*i)->name ()));
+ _table->Insert (index, m, 0, wxALIGN_CENTER_VERTICAL | wxALL, 6);
+
JobRecord r;
r.gauge = new wxGauge (_panel, wxID_ANY, 100);
- _table->Add (r.gauge, 1, wxEXPAND | wxLEFT | wxRIGHT);
+ _table->Insert (index + 1, r.gauge, 1, wxEXPAND | wxLEFT | wxRIGHT);
+
r.informed_of_finish = false;
- r.message = add_label_to_sizer (_table, _panel, "", 1);
+ r.message = new wxStaticText (_panel, wxID_ANY, std_to_wx (""));
+ _table->Insert (index + 2, r.message, 1, wxALIGN_CENTER_VERTICAL | wxALL, 6);
_job_records[*i] = r;
}
@@ -112,6 +118,8 @@ JobManagerView::update ()
_job_records[*i].informed_of_finish = true;
}
+
+ index += 3;
}
_table->Layout ();
diff --git a/src/wx/job_wrapper.cc b/src/wx/job_wrapper.cc
index 4c037ae28..ad83aa271 100644
--- a/src/wx/job_wrapper.cc
+++ b/src/wx/job_wrapper.cc
@@ -36,11 +36,7 @@ JobWrapper::make_dcp (wxWindow* parent, Film* film, bool transcode)
film->make_dcp (transcode);
} catch (BadSettingError& e) {
stringstream s;
- if (e.setting() == "dcp_long_name") {
- s << "Could not make DCP: long name is invalid (" << e.what() << ")";
- } else {
- s << "Bad setting for " << e.setting() << "(" << e.what() << ")";
- }
+ s << "Bad setting for " << e.setting() << "(" << e.what() << ")";
error_dialog (parent, s.str ());
} catch (std::exception& e) {
stringstream s;
diff --git a/src/wx/properties_dialog.cc b/src/wx/properties_dialog.cc
new file mode 100644
index 000000000..67f1fc91b
--- /dev/null
+++ b/src/wx/properties_dialog.cc
@@ -0,0 +1,89 @@
+/*
+ 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.
+
+*/
+
+#include <iomanip>
+#include <boost/lexical_cast.hpp>
+#include <boost/bind.hpp>
+#include "lib/film.h"
+#include "lib/config.h"
+#include "properties_dialog.h"
+#include "wx_util.h"
+
+using namespace std;
+using namespace boost;
+
+PropertiesDialog::PropertiesDialog (wxWindow* parent, Film* film)
+ : wxDialog (parent, wxID_ANY, _("Film Properties"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
+ , _film (film)
+{
+ wxFlexGridSizer* table = new wxFlexGridSizer (2, 3, 6);
+
+ add_label_to_sizer (table, this, "Frames");
+ _frames = new wxStaticText (this, wxID_ANY, std_to_wx (""));
+ table->Add (_frames, 1, wxALIGN_CENTER_VERTICAL);
+
+ add_label_to_sizer (table, this, "Disk space required for frames");
+ _disk_for_frames = new wxStaticText (this, wxID_ANY, std_to_wx (""));
+ table->Add (_disk_for_frames, 1, wxALIGN_CENTER_VERTICAL);
+
+ add_label_to_sizer (table, this, "Total disk space required");
+ _total_disk = new wxStaticText (this, wxID_ANY, std_to_wx (""));
+ table->Add (_total_disk, 1, wxALIGN_CENTER_VERTICAL);
+
+ add_label_to_sizer (table, this, "Frames already encoded");
+ _encoded = new ThreadedStaticText (this, "counting...", boost::bind (&PropertiesDialog::frames_already_encoded, this));
+ table->Add (_encoded, 1, wxALIGN_CENTER_VERTICAL);
+
+ _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->length ())));
+ double const disk = ((double) Config::instance()->j2k_bandwidth() / 8) * _film->length() / (_film->frames_per_second () * 1073741824);
+ stringstream s;
+ s << fixed << setprecision (1) << disk << "Gb";
+ _disk_for_frames->SetLabel (std_to_wx (s.str ()));
+
+ stringstream t;
+ t << fixed << setprecision (1) << (disk * 2) << "Gb";
+ _total_disk->SetLabel (std_to_wx (t.str ()));
+
+ wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
+ overall_sizer->Add (table, 0, wxALL, 6);
+
+ wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
+ if (buttons) {
+ overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+ }
+
+ SetSizer (overall_sizer);
+ overall_sizer->SetSizeHints (this);
+}
+
+string
+PropertiesDialog::frames_already_encoded () const
+{
+ stringstream u;
+ try {
+ u << _film->encoded_frames ();
+ } catch (thread_interrupted &) {
+ return "";
+ }
+
+ if (_film->length()) {
+ u << " (" << (_film->encoded_frames() * 100 / _film->length()) << "%)";
+ }
+ return u.str ();
+}
diff --git a/src/wx/properties_dialog.h b/src/wx/properties_dialog.h
new file mode 100644
index 000000000..f72c83419
--- /dev/null
+++ b/src/wx/properties_dialog.h
@@ -0,0 +1,39 @@
+/*
+ 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.
+
+*/
+
+#include <wx/wx.h>
+
+class Film;
+class ThreadedStaticText;
+
+class PropertiesDialog : public wxDialog
+{
+public:
+ PropertiesDialog (wxWindow *, Film *);
+
+private:
+ std::string frames_already_encoded () const;
+
+ Film* _film;
+ wxStaticText* _frames;
+ wxStaticText* _disk_for_frames;
+ wxStaticText* _total_disk;
+ ThreadedStaticText* _encoded;
+};
+
diff --git a/src/wx/server_dialog.cc b/src/wx/server_dialog.cc
index 0ae34b1fc..7b394a484 100644
--- a/src/wx/server_dialog.cc
+++ b/src/wx/server_dialog.cc
@@ -21,13 +21,13 @@
#include "server_dialog.h"
#include "wx_util.h"
-ServerDialog::ServerDialog (wxWindow* parent, Server* server)
+ServerDialog::ServerDialog (wxWindow* parent, ServerDescription* server)
: wxDialog (parent, wxID_ANY, wxString (_("Server")))
{
if (server) {
_server = server;
} else {
- _server = new Server ("localhost", 1);
+ _server = new ServerDescription ("localhost", 1);
}
wxFlexGridSizer* table = new wxFlexGridSizer (2, 4, 4);
@@ -49,7 +49,7 @@ ServerDialog::ServerDialog (wxWindow* parent, Server* server)
_threads->SetValue (_server->threads ());
wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
- overall_sizer->Add (table, 1, wxEXPAND);
+ overall_sizer->Add (table, 1, wxEXPAND | wxALL, 6);
wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
if (buttons) {
@@ -73,7 +73,7 @@ ServerDialog::threads_changed (wxCommandEvent &)
_server->set_threads (_threads->GetValue ());
}
-Server *
+ServerDescription *
ServerDialog::server () const
{
return _server;
diff --git a/src/wx/server_dialog.h b/src/wx/server_dialog.h
index 05630c377..0912fd60f 100644
--- a/src/wx/server_dialog.h
+++ b/src/wx/server_dialog.h
@@ -20,20 +20,20 @@
#include <wx/wx.h>
#include <wx/spinctrl.h>
-class Server;
+class ServerDescription;
class ServerDialog : public wxDialog
{
public:
- ServerDialog (wxWindow *, Server *);
+ ServerDialog (wxWindow *, ServerDescription *);
- Server* server () const;
+ ServerDescription* server () const;
private:
void host_changed (wxCommandEvent &);
void threads_changed (wxCommandEvent &);
- Server* _server;
+ ServerDescription* _server;
wxTextCtrl* _host;
wxSpinCtrl* _threads;
};
diff --git a/src/wx/wscript b/src/wx/wscript
index 348a9cb0a..38107bb54 100644
--- a/src/wx/wscript
+++ b/src/wx/wscript
@@ -22,6 +22,7 @@ def build(bld):
server_dialog.cc
new_film_dialog.cc
dir_picker_ctrl.cc
+ properties_dialog.cc
"""
# alignment.cc
diff --git a/src/wx/wx_util.cc b/src/wx/wx_util.cc
index 7655fe60d..4277ed12d 100644
--- a/src/wx/wx_util.cc
+++ b/src/wx/wx_util.cc
@@ -18,13 +18,21 @@
*/
/** @file src/wx/wx_util.cc
- * @brief Some utility functions.
+ * @brief Some utility functions and classes.
*/
+#include <boost/thread.hpp>
#include "wx_util.h"
using namespace std;
+using namespace boost;
+/** Add a wxStaticText to a wxSizer, aligning it at vertical centre.
+ * @param s Sizer to add to.
+ * @param p Parent window for the wxStaticText.
+ * @param t Text for the wxStaticText.
+ * @param prop Properties to pass when calling Add() on the wxSizer.
+ */
wxStaticText *
add_label_to_sizer (wxSizer* s, wxWindow* p, string t, int prop)
{
@@ -33,6 +41,10 @@ add_label_to_sizer (wxSizer* s, wxWindow* p, string t, int prop)
return m;
}
+/** Pop up an error dialogue box.
+ * @param parent Parent.
+ * @param m Message.
+ */
void
error_dialog (wxWindow* parent, string m)
{
@@ -41,14 +53,56 @@ error_dialog (wxWindow* parent, string m)
d->Destroy ();
}
+/** @param s wxWidgets string.
+ * @return Corresponding STL string.
+ */
string
wx_to_std (wxString s)
{
return string (s.mb_str ());
}
+/** @param s STL string.
+ * @return Corresponding wxWidgets string.
+ */
wxString
std_to_wx (string s)
{
return wxString (s.c_str(), wxConvUTF8);
}
+
+int const ThreadedStaticText::_update_event_id = 10000;
+
+/** @param parent Parent for the wxStaticText.
+ * @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, string initial, function<string ()> fn)
+ : wxStaticText (parent, wxID_ANY, std_to_wx (initial))
+{
+ Connect (_update_event_id, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ThreadedStaticText::thread_finished), 0, this);
+ _thread = new thread (bind (&ThreadedStaticText::run, this, fn));
+}
+
+ThreadedStaticText::~ThreadedStaticText ()
+{
+ _thread->interrupt ();
+ _thread->join ();
+ delete _thread;
+}
+
+/** Run our thread and post the result to the GUI thread via AddPendingEvent */
+void
+ThreadedStaticText::run (function<string ()> fn)
+{
+ wxCommandEvent ev (wxEVT_COMMAND_TEXT_UPDATED, _update_event_id);
+ ev.SetString (std_to_wx (fn ()));
+ GetEventHandler()->AddPendingEvent (ev);
+}
+
+/** Called in the GUI thread when our worker thread has finished */
+void
+ThreadedStaticText::thread_finished (wxCommandEvent& ev)
+{
+ SetLabel (ev.GetString ());
+}
diff --git a/src/wx/wx_util.h b/src/wx/wx_util.h
index d0b838c36..12a6e8837 100644
--- a/src/wx/wx_util.h
+++ b/src/wx/wx_util.h
@@ -18,12 +18,35 @@
*/
#include <wx/wx.h>
+#include <boost/function.hpp>
+#include <boost/thread.hpp>
/** @file src/wx/wx_util.h
- * @brief Some utility functions.
+ * @brief Some utility functions and classes.
*/
extern void error_dialog (wxWindow *, std::string);
extern wxStaticText* add_label_to_sizer (wxSizer *, wxWindow *, std::string, int prop = 0);
extern std::string wx_to_std (wxString);
extern wxString std_to_wx (std::string);
+
+/** @class ThreadedStaticText
+ *
+ * @brief A wxStaticText whose content is computed in a separate thread, to avoid holding
+ * up the GUI while work is done.
+ */
+class ThreadedStaticText : public wxStaticText
+{
+public:
+ ThreadedStaticText (wxWindow* parent, std::string initial, boost::function<std::string ()> fn);
+ ~ThreadedStaticText ();
+
+private:
+ void run (boost::function<std::string ()> fn);
+ void thread_finished (wxCommandEvent& ev);
+
+ /** Thread to do our work in */
+ boost::thread* _thread;
+
+ static const int _update_event_id;
+};
diff --git a/test/test.cc b/test/test.cc
index f564a8f86..638d526e0 100644
--- a/test/test.cc
+++ b/test/test.cc
@@ -29,6 +29,11 @@
#include "exceptions.h"
#include "dvd.h"
#include "delay_line.h"
+#include "image.h"
+#include "log.h"
+#include "dcp_video_frame.h"
+#include "config.h"
+#include "server.h"
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE dvdomatic_test
#include <boost/test/unit_test.hpp>
@@ -248,3 +253,65 @@ BOOST_AUTO_TEST_CASE (paths_test)
s.content = "foo/bar/baz";
BOOST_CHECK_EQUAL (s.content_path(), "build/test/a/b/c/d/e/foo/bar/baz");
}
+
+void
+do_remote_encode (shared_ptr<DCPVideoFrame> 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_AUTO_TEST_CASE (client_server_test)
+{
+ shared_ptr<SimpleImage> image (new SimpleImage (PIX_FMT_RGB24, Size (1998, 1080)));
+ image->set_line_size (0, 1998 * 3);
+
+ uint8_t* p = image->data()[0];
+
+ for (int y = 0; y < 1080; ++y) {
+ for (int x = 0; x < 1998; ++x) {
+ *p++ = x % 256;
+ *p++ = y % 256;
+ *p++ = (x + y) % 256;
+ }
+ }
+
+ FileLog log ("build/test/client_server_test.log");
+
+ shared_ptr<DCPVideoFrame> frame (
+ new DCPVideoFrame (
+ image,
+ Size (1998, 1080),
+ 0,
+ Scaler::from_id ("bicubic"),
+ 0,
+ 24,
+ "",
+ 0,
+ 200000000,
+ &log
+ )
+ );
+
+ shared_ptr<EncodedData> locally_encoded = frame->encode_locally ();
+
+ Config::instance()->set_server_port (61920);
+ Server* server = new Server (&log);
+
+ new thread (boost::bind (&Server::run, server, 2));
+
+ ServerDescription description ("localhost", 2);
+
+ list<thread*> threads;
+ for (int i = 0; i < 8; ++i) {
+ threads.push_back (new thread (boost::bind (do_remote_encode, frame, &description, locally_encoded)));
+ }
+
+ for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
+ (*i)->join ();
+ }
+}
diff --git a/windows/dvdomatic.rc b/windows/dvdomatic.rc
index e6ce66597..97bfac229 100644
--- a/windows/dvdomatic.rc
+++ b/windows/dvdomatic.rc
@@ -1,2 +1,3 @@
-#include "wx-2.8/wx/msw/wx.rc"
id ICON "dvdomatic.ico"
+taskbar_icon ICON "dvdomatic_taskbar.ico"
+#include "wx-2.8/wx/msw/wx.rc"
diff --git a/windows/dvdomatic_taskbar.ico b/windows/dvdomatic_taskbar.ico
new file mode 100644
index 000000000..f4489fa14
--- /dev/null
+++ b/windows/dvdomatic_taskbar.ico
Binary files differ
diff --git a/windows/installer.nsi.in b/windows/installer.nsi.in
index 31e7e763c..95f159c3d 100644
--- a/windows/installer.nsi.in
+++ b/windows/installer.nsi.in
@@ -84,12 +84,16 @@ File "%deps%/bin/libxml2-2.dll"
File "%binaries%/src/wx/dvdomatic-wx.dll"
File "%binaries%/src/lib/dvdomatic.dll"
File "%binaries%/src/tools/dvdomatic.exe"
+File "%binaries%/src/tools/servomatic_cli.exe"
+File "%binaries%/src/tools/servomatic_gui.exe"
CreateShortCut "$DESKTOP\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" ""
+CreateShortCut "$DESKTOP\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" ""
CreateDirectory "$SMPROGRAMS\DVD-o-matic"
CreateShortCut "$SMPROGRAMS\DVD-o-matic\Uninstall.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" "" "$INSTDIR\bin\dvdomatic.exe" 0
+CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" "" "$INSTDIR\bin\servomatic_gui.exe" 0
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic" "DisplayName" "DVD-o-matic (remove only)"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\DVD-o-matic" "UninstallString" "$INSTDIR\Uninstall.exe"
@@ -104,6 +108,7 @@ Section "Uninstall"
RMDir /r "$INSTDIR\*.*"
RMDir "$INSTDIR"
Delete "$DESKTOP\DVD-o-matic.lnk"
+Delete "$DESKTOP\DVD-o-matic encode server.lnk"
Delete "$SMPROGRAMS\DVD-o-matic\*.*"
RmDir "$SMPROGRAMS\DVD-o-matic"
DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\DVD-o-matic"
diff --git a/wrapper/makedcp b/wrapper/makedcp
new file mode 100755
index 000000000..8c3a84efd
--- /dev/null
+++ b/wrapper/makedcp
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+dir=`dirname $0`
+LD_LIBRARY_PATH=$dir/../lib:$LD_LIBRARY_PATH $dir/makedcp-bin $*
+
diff --git a/wscript b/wscript
index 08ecad4de..7f6c3722f 100644
--- a/wscript
+++ b/wscript
@@ -3,13 +3,12 @@ import os
import sys
APPNAME = 'dvdomatic'
-VERSION = '0.47pre'
+VERSION = '0.54pre'
def options(opt):
opt.load('compiler_cxx')
opt.load('winres')
- opt.add_option('--debug-hash', action='store_true', default = False, help = 'print hashes of data at various points')
opt.add_option('--enable-debug', action='store_true', default = False, help = 'build with debugging information and without optimisation')
opt.add_option('--disable-gui', action='store_true', default = False, help = 'disable building of GUI tools')
opt.add_option('--disable-player', action='store_true', default = False, help = 'disable building of the player components')
@@ -37,7 +36,6 @@ def configure(conf):
boost_lib_suffix = ''
boost_thread = 'boost_thread'
- conf.env.DEBUG_HASH = conf.options.debug_hash
conf.env.TARGET_WINDOWS = conf.options.target_windows
conf.env.DISABLE_GUI = conf.options.disable_gui
conf.env.DISABLE_PLAYER = conf.options.disable_player
@@ -57,10 +55,10 @@ def configure(conf):
conf.check_cfg(package = 'libavcodec', args = '--cflags --libs', uselib_store = 'AVCODEC', mandatory = True)
conf.check_cfg(package = 'libavutil', args = '--cflags --libs', uselib_store = 'AVUTIL', mandatory = True)
conf.check_cfg(package = 'libswscale', args = '--cflags --libs', uselib_store = 'SWSCALE', mandatory = True)
- conf.check_cfg(package = 'libswresample', args = '--cflags --libs', uselib_store = 'SWRESAMPLE', mandatory = True)
+ conf.check_cfg(package = 'libswresample', args = '--cflags --libs', uselib_store = 'SWRESAMPLE', mandatory = False)
conf.check_cfg(package = 'libpostproc', args = '--cflags --libs', uselib_store = 'POSTPROC', mandatory = True)
conf.check_cfg(package = 'sndfile', args = '--cflags --libs', uselib_store = 'SNDFILE', mandatory = True)
- conf.check_cfg(package = 'libdcp', atleast_version = '0.11', args = '--cflags --libs', uselib_store = 'DCP', mandatory = True)
+ conf.check_cfg(package = 'libdcp', atleast_version = '0.21', args = '--cflags --libs', uselib_store = 'DCP', mandatory = True)
conf.check_cfg(package = 'glib-2.0', args = '--cflags --libs', uselib_store = 'GLIB', mandatory = True)
conf.check_cfg(package = '', path = 'Magick++-config', args = '--cppflags --cxxflags --libs', uselib_store = 'MAGICK', mandatory = True)
conf.check_cc(msg = 'Checking for library libtiff', function_name = 'TIFFOpen', header_name = 'tiffio.h', lib = 'tiff', uselib_store = 'TIFF')
@@ -126,6 +124,8 @@ def build(bld):
for r in ['22x22', '32x32', '48x48', '64x64', '128x128']:
bld.install_files('${PREFIX}/share/icons/hicolor/%s/apps' % r, 'icons/%s/dvdomatic.png' % r)
+ bld.add_post_fun(post)
+
def dist(ctx):
ctx.excl = 'TODO core *~ src/wx/*~ src/lib/*~ .waf* build .git deps alignment hacks sync *.tar.bz2 *.exe .lock* *build-windows doc/manual/pdf doc/manual/html'
@@ -150,3 +150,6 @@ def create_version_cc(version):
print('Could not open src/lib/version.cc for writing\n')
sys.exit(-1)
+def post(ctx):
+ if ctx.cmd == 'install':
+ ctx.exec_command('/sbin/ldconfig')