Merge branch 'master' of /home/carl/git/dvdomatic
authorCarl Hetherington <cth@carlh.net>
Fri, 28 Sep 2012 22:09:15 +0000 (23:09 +0100)
committerCarl Hetherington <cth@carlh.net>
Fri, 28 Sep 2012 22:09:15 +0000 (23:09 +0100)
85 files changed:
.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/bugs/1c2591d1-0d87-43e7-bf1d-5d16c1c8a16e/values [new file with mode: 0644]
.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/bugs/53c8f102-7b42-456a-86e2-3e99de0bd883/values [new file with mode: 0644]
.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/bugs/666e8ba3-f1a3-4245-9d00-9a3a5e048ed9/values [new file with mode: 0644]
.be/aff5ca2c-44ee-4ed6-800b-4abe9c3e794c/settings [new file with mode: 0644]
.be/version [new file with mode: 0644]
.gitignore
ChangeLog
Doxyfile
README
build-windows [deleted file]
builds/ubuntu-12.04-64 [new file with mode: 0755]
builds/windows [new file with mode: 0755]
doc/mainpage.txt
doc/manual/dvdomatic.xml
icons/16x16/dvdomatic.png [new file with mode: 0644]
rebuild-windows [deleted file]
run/servomatic [deleted file]
run/servomatic_cli [new file with mode: 0755]
run/servomatic_gui [new file with mode: 0755]
src/lib/check_hashes_job.cc [new file with mode: 0644]
src/lib/check_hashes_job.h [new file with mode: 0644]
src/lib/config.cc
src/lib/config.h
src/lib/dcp_video_frame.cc
src/lib/dcp_video_frame.h
src/lib/decoder.cc
src/lib/decoder.h
src/lib/encoder.cc
src/lib/encoder.h
src/lib/exceptions.h
src/lib/ffmpeg_compatibility.cc [new file with mode: 0644]
src/lib/ffmpeg_decoder.cc
src/lib/film.cc
src/lib/film.h
src/lib/film_state.cc
src/lib/format.cc
src/lib/image.cc
src/lib/image.h
src/lib/j2k_still_encoder.cc
src/lib/j2k_wav_encoder.cc
src/lib/j2k_wav_encoder.h
src/lib/job.cc
src/lib/job.h
src/lib/job_manager.cc
src/lib/job_manager.h
src/lib/log.cc
src/lib/log.h
src/lib/make_dcp_job.cc
src/lib/server.cc
src/lib/server.h
src/lib/tiff_encoder.cc
src/lib/transcode_job.cc
src/lib/transcode_job.h
src/lib/util.cc
src/lib/util.h
src/lib/wscript
src/tools/dvdomatic.cc
src/tools/servomatic.cc [deleted file]
src/tools/servomatic_cli.cc [new file with mode: 0644]
src/tools/servomatic_gui.cc [new file with mode: 0644]
src/tools/servomatictest.cc
src/tools/wscript
src/wscript
src/wx/config_dialog.cc
src/wx/config_dialog.h
src/wx/dcp_range_dialog.cc
src/wx/film_editor.cc
src/wx/film_viewer.cc
src/wx/filter_dialog.cc
src/wx/gain_calculator_dialog.cc
src/wx/job_manager_view.cc
src/wx/job_wrapper.cc
src/wx/properties_dialog.cc [new file with mode: 0644]
src/wx/properties_dialog.h [new file with mode: 0644]
src/wx/server_dialog.cc
src/wx/server_dialog.h
src/wx/wscript
src/wx/wx_util.cc
src/wx/wx_util.h
test/test.cc
windows/dvdomatic.rc
windows/dvdomatic_taskbar.ico [new file with mode: 0644]
windows/installer.nsi.in
wrapper/makedcp [new file with mode: 0755]
wscript

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 (file)
index 0000000..365d04d
--- /dev/null
@@ -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 (file)
index 0000000..61b3c91
--- /dev/null
@@ -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 (file)
index 0000000..8861f1d
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/.be/version b/.be/version
new file mode 100644 (file)
index 0000000..e7aade4
--- /dev/null
@@ -0,0 +1 @@
+Bugs Everywhere Directory v1.4
index 757f42eced83e9910a9404e8679792b5026c3f32..0ddc1034587674153dc7625824083a5aa157f362 100644 (file)
@@ -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
index c09b14fcb34dc9a84cc6dfa42a6f82611f31dae6..b260b8745c4b29dc3c3161a886e8101136dd1501 100644 (file)
--- 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.
index 8efb7066239f24a3601dfdcac0cac5fa0f9fd412..56f7e1d3c334c7a085aa50517b638f1ee5aad790 100644 (file)
--- 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 d79685f57e140b0bfb99fcb78d63eaa6c5909f62..fd3983c29e04f02d36f219aeb61f151eedeee0f3 100644 (file)
--- 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 (executable)
index a59cd80..0000000
+++ /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 (executable)
index 0000000..5f01850
--- /dev/null
@@ -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/builds/windows b/builds/windows
new file mode 100755 (executable)
index 0000000..686641a
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+export MINGW_CXX="i686-w64-mingw32-g++"
+export MINGW_WINDRES="i686-w64-mingw32-windres"
+export MINGW_PREFIX="/usr/i686-w64-mingw32"
+export WINDOWS_PREFIX="/home/carl/Environments/windows"
+
+export PKG_CONFIG_LIBDIR=$WINDOWS_PREFIX/lib/pkgconfig
+
+./waf clean
+
+export PATH=$WINDOWS_PREFIX/bin:$PATH
+
+CXX=$MINGW_CXX WINRC=$MINGW_WINDRES \
+  CXXFLAGS="-I$WINDOWS_PREFIX/include -I$MINGW_PREFIX/include" \
+  LINKFLAGS="-L$WINDOWS_PREFIX/lib -L$MINGW_PREFIX/lib" \
+  ./waf configure --target-windows
+if [ "$?" != "0" ]; then
+  exit 1
+fi
+
+./waf
+if [ "$?" != "0" ]; then
+  exit 1
+fi
+
+d=`pwd`
+
+cp build/windows/installer.nsi build/windows/installer2.nsi
+
+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
index 81c3b45583e9a073eca81651dad25828700a0e1a..e89ca8d26a4ae3e79e94d501ab0565c0b527604d 100644 (file)
@@ -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.
index cd7932ecacf75766a82f76ad59e8a021815f64c7..3143772c7e743c1f211b4bc0e3cc818b3729c117 100644 (file)
@@ -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 (file)
index 0000000..3c5a10f
Binary files /dev/null and b/icons/16x16/dvdomatic.png differ
diff --git a/rebuild-windows b/rebuild-windows
deleted file mode 100755 (executable)
index 686641a..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/bash
-
-export MINGW_CXX="i686-w64-mingw32-g++"
-export MINGW_WINDRES="i686-w64-mingw32-windres"
-export MINGW_PREFIX="/usr/i686-w64-mingw32"
-export WINDOWS_PREFIX="/home/carl/Environments/windows"
-
-export PKG_CONFIG_LIBDIR=$WINDOWS_PREFIX/lib/pkgconfig
-
-./waf clean
-
-export PATH=$WINDOWS_PREFIX/bin:$PATH
-
-CXX=$MINGW_CXX WINRC=$MINGW_WINDRES \
-  CXXFLAGS="-I$WINDOWS_PREFIX/include -I$MINGW_PREFIX/include" \
-  LINKFLAGS="-L$WINDOWS_PREFIX/lib -L$MINGW_PREFIX/lib" \
-  ./waf configure --target-windows
-if [ "$?" != "0" ]; then
-  exit 1
-fi
-
-./waf
-if [ "$?" != "0" ]; then
-  exit 1
-fi
-
-d=`pwd`
-
-cp build/windows/installer.nsi build/windows/installer2.nsi
-
-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/run/servomatic b/run/servomatic
deleted file mode 100755 (executable)
index 100d0a8..0000000
+++ /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 (executable)
index 0000000..3dd67d2
--- /dev/null
@@ -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 (executable)
index 0000000..4f1c617
--- /dev/null
@@ -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 (file)
index 0000000..5a927f7
--- /dev/null
@@ -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 (file)
index 0000000..b59cf03
--- /dev/null
@@ -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;
+};
index 53674645d864abea3a988852c7ccce84707a29f3..44d110689352e5b81c92305bceb078690c608182 100644 (file)
@@ -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";
        }
 
index 14b541ee69563cfdf43ec2a2491b3c31914d0cf4..840dcdaef7a5ebcea32f0c5bd680c6ab0de1b878 100644 (file)
@@ -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 */
index 24cdda2e6031dd6e6c670c8a94cbe3e168fff1db..da7133c4ba2a7f6552d76819dd7acc01ab22cac8 100644 (file)
@@ -36,6 +36,7 @@
 #include <iomanip>
 #include <sstream>
 #include <iostream>
+#include <fstream>
 #include <unistd.h>
 #include <errno.h>
 #include <boost/array.hpp>
 #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)
index 464d4851550430b1234fb430b169f78ed9a17ad9..72f885e45e6e0804f675d0f61abf62814314b3bd 100644 (file)
@@ -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;
index faee5beceec118fa0c6bd1bfa9541738e32bc6d0..973582ca49237e3bd88739edce39389fe0fc88af 100644 (file)
@@ -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.");
        }
index 5c69e12d06ed5a62133fc4427ee3668aec458bd8..14b25c7b018e6f2ff60acbd471fc1dbefc46cb3f 100644 (file)
 #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
index c8eb24c807ab6264d442b9ddcf7fd5d8c27a97e3..62ba922daffc5c66f881afe11ee9cc8e63a7765b 100644 (file)
@@ -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;
+}
index bed2c09882172f47cbd709e5ec7980db3c9f00fe..539b2912ce9580c36b6aad8766dbf5e92ee9b871 100644 (file)
@@ -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
index 6b567805b1fcd46ac7d1c78c13423624ef1f5b3d..8ef09875ba89383af42fe35ad2cdd99b08fa574a 100644 (file)
@@ -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 (file)
index 0000000..c47cdf5
--- /dev/null
@@ -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
index c12e6728df299d310607bd46a17ecc5223e8fec6..3471ffaab1b5ae50ba733ee4c41d9b2b366b12af 100644 (file)
@@ -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;
 }
 
index f8a3b192da0147b37b3202b68c66457279b1c621..583a15e19cc472af6ae2ecca5c73077b174ecd17 100644 (file)
@@ -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;
+}
index 3ff671fbe7e7792308ec5ed488ce123f23ee16a1..cd3b1b8a8dac19c830c088bc383afbb5cbadad4f 100644 (file)
@@ -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;
        
index e0ad20417adbee8f450d241df6df144e70918cf1..e472434ce97442373117b38da39f8119924242dc 100644 (file)
@@ -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;
        }
index ff3a5b2023ae45509141cb7bda8fbc4261440726..e689aa05db5b54c424e28a1cef1dacdd6e808e3a 100644 (file)
@@ -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"));
index f16bb9f7791a3a7220a079717d590046ce3aefd8..620e71aa7a188d43aaaa560b2d48ca9a3c9c5e45 100644 (file)
@@ -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.
index 97ab1d5ff8c2868a9d24550e3876131681b3d4f2..0161d2b01f9850b28f4deb47fc9a184e21f6b10f 100644 (file)
@@ -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 {
index 5243f066860bcab2d2fa7319b6ce5bdf12f112a9..8f3339a0afbcc41f7f2d2163f4df2bf054ff96d0 100644 (file)
@@ -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);
        }
 }
index 2f29f9021d2ba7561e53bb8406df0f5c35f6ba1a..9ae01c774287256e667f3d490eeb3a8914b29bad 100644 (file)
@@ -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() << ".";
index 656af8321321940471f84ef2731724c59c6fa9ca..1c2f5006590028aa503876c507173787dcd21f58 100644 (file)
@@ -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 ();
 
index 0feb73d31b22b00f59d6c5cd6c3dea12c8d38a2e..22754eb909659ffd8693b48c77a9c184b6c40f9b 100644 (file)
@@ -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();
+}
index 2a77f78f7775e43009e5642fddb5710fcaf1c934..b391304792e0a3e9ab0a926ae440b3dfcf806e8d 100644 (file)
@@ -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 {
index 93fdbd27a8d43472a1a93b64aad2a05b4a978c67..a166b5924dd0e3c472c89bdcc24bdd378c07797a 100644 (file)
@@ -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;
 }
 
index f2f5e0057c946c6bc02e767bc755252d7f5adb2e..d1d33cfc2ce7e7dc3ac4693d579d7309e0ec30e5 100644 (file)
@@ -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;
 
index accf3694d3775d24609c0149658ab7f76b5a7d87..7f1eea20634f8cba480d8d6884765f7e1be14268 100644 (file)
 
 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";
+}
+
index d4de8ebde6ff466110b2b49ae67e51ab6ae6c6c9..2a242e24c96e8f49bfb171d81bac463085ad9f02 100644 (file)
@@ -17,6 +17,9 @@
 
 */
 
+#ifndef DVDOMATIC_LOG_H
+#define DVDOMATIC_LOG_H
+
 /** @file src/log.h
  *  @brief A very simple logging class.
  */
 
 /** @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
index 525f76c0e26b26f2dce5c49867232c7312948497..8d3547cae8d0808422bef01877fbe1682dcfd9bc 100644 (file)
@@ -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);
index 8a5b5cfca604564aadaf5abe0531060d052800f3..f8c4425d991f42b4a538e2593b198514ddcd491c 100644 (file)
 
 /** @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 ();
+       }
+}
index d06df34e9e649dd84d49a85dc6472f6c5cf2b87a..32ba8dc4b96757fe0d3938317e2fe1c0c9abff2b 100644 (file)
 
 /** @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;
+};
index 2cf238006f2f6f4eece0245a0e140ad371427968..19e34741d9326f3d72e38506d97dd2b723c7f183 100644 (file)
@@ -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);
 }
index 652a184419892164acc816b299db7fcfe95b0356..9113593f0c1976c4967df5df7e9c7bea509d38d4 100644 (file)
@@ -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);
+}
index aa640f6979976105e2a214e34426840cd08613fa..737f10de968ea4b9ee0500cf51bfe2f3b6bc060c 100644 (file)
@@ -38,6 +38,9 @@ public:
        void run ();
        std::string status () const;
 
+protected:
+       int remaining_time () const;
+
 private:
        boost::shared_ptr<Encoder> _encoder;
 };
index 1478bab2e52dca65bab8631e59f3cd3cae6a1c9b..935566440276f563ba5adcb86c44075c06eb5fa7 100644 (file)
@@ -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);
+}
index 568fe05d035ea56589be3e98639323da74df903b..bc5a00fc47e2a63b8d8edd3db5f18fd4f52d65c6 100644 (file)
@@ -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
index b001fff2a89a97a968824e43bc02a70554aa0221..c809226ce59adbb8a5377d8bcd8b500b205953b9 100644 (file)
@@ -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
index df38e9d696e714f7c84f386aafd57370af1dd35e..c4232130026c3dbe796db5945c15a6cda837a401 100644 (file)
@@ -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 (file)
index a9c45b3..0000000
+++ /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 (file)
index 0000000..f8e7131
--- /dev/null
@@ -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 (file)
index 0000000..610ba80
--- /dev/null
@@ -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)
index 0f37e73a5f9f9880e242f9e93ea78858fde7bfbf..d6804c981bac2fd2a130888650e5d842badd6e47 100644 (file)
@@ -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;
index be3d44e6dddfc2786a1440b87fe5bd336d6d1968..048bdff076ec1dcec97f366d63bcdba15e957ac2 100644 (file)
@@ -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:
index 2ebeba210d12bfc349e0af062c79af28dd38c6d0..3f17b3e6ce026106366bca7733169797f46378bd 100644 (file)
@@ -1,5 +1,4 @@
 def configure(conf):
-    conf.recurse('lib')
     if not conf.env.DISABLE_GUI:
         conf.recurse('wx')
 
index ebf5be4603e4050b3d17510c917b1d27656f3005..b0bd6f2ee5514c31b332ffc650846a03b704a721 100644 (file)
@@ -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
index c9ca8034faf4dd069d58716c802792af30c44491..b1d3eb84d8f118d9eb4c12e8f5481ae6cd8d3fc5 100644 (file)
@@ -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;
index 572d0c628ad5a5d9366360c760ca5057e97d0cb5..aed6808cbd595470a1969745e445c640034e2a15 100644 (file)
@@ -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) {
index f35996644dd71d8c30f1c5e06b9a2618a7911f22..6de3af9e70d6a8d7d2d9e00b53dd891197a5d996 100644 (file)
@@ -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 ();
 }
index 8398b8162634c2ac80a9af5aa1770dd45dba8a33..0d17baf8325f2a22805eb3a63c51b91ff0c44a90 100644 (file)
@@ -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 ();
index 9ec169395421918b1d9910dcef891593d824b231..028d082b4f8f6deadde30a1d84c1056e4fc6bdf2 100644 (file)
@@ -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));
 
index 431a4672d9c70c790cca332f14a63e143ad0a02f..3f07faf062e4239835d692f8466d30c438fb7822 100644 (file)
@@ -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 ()));
 }
index c9e120135af557f449ad80c10be6ab8f57f633fc..1d5c855eada8a990b0cf57dfbd2e89556738faf8 100644 (file)
@@ -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 ();
index 4c037ae284508f82feb4ac8cd091f412ae664189..ad83aa271da0d8c43d08b7fe6b9b5c67aeeee58e 100644 (file)
@@ -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 (file)
index 0000000..67f1fc9
--- /dev/null
@@ -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 (file)
index 0000000..f72c834
--- /dev/null
@@ -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;
+};
+
index 0ae34b1fcac3888ddc2d4da2feda6ce71d052139..7b394a484addd3a75114cd7fdb61c25b54a75285 100644 (file)
 #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;
index 05630c377bf416ded597e32bc14f208206269c93..0912fd60f19291a866b57cc5456320d9f7e24e00 100644 (file)
 #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;
 };
index 348a9cb0ad085a3eb0072241151a50f4c58e4434..38107bb547ec7b4624651ea3b25b0e201539f58e 100644 (file)
@@ -22,6 +22,7 @@ def build(bld):
                  server_dialog.cc
                  new_film_dialog.cc
                  dir_picker_ctrl.cc
+                 properties_dialog.cc
                  """
 
 #                 alignment.cc
index 7655fe60ddcf061ceffa85468985412aeb57c28e..4277ed12dc03a9f26d1242977ebb5d38b557f9a3 100644 (file)
 */
 
 /** @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 ());
+}
index d0b838c365e61825db0623d8dbafbcaf7aa35670..12a6e8837d071b353d4536a18fc4c7634d4a53fb 100644 (file)
 */
 
 #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;
+};
index f564a8f86cf10126076599c7d9cbc2c58cec287b..638d526e05f760303a6b98945e86f098e27c961a 100644 (file)
 #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 ();
+       }
+}
index e6ce66597029e20b4a248af61b4ed17d7965da02..97bfac229fcbbf8c5ebbac02ef3096f75b639879 100644 (file)
@@ -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 (file)
index 0000000..f4489fa
Binary files /dev/null and b/windows/dvdomatic_taskbar.ico differ
index 31e7e763cd1720419861937c78a8fdab56c7a0ac..95f159c3dc8c24a53f9dd5f92cfc61fc0e31f5e7 100644 (file)
@@ -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 (executable)
index 0000000..8c3a84e
--- /dev/null
@@ -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 08ecad4de784dc31273df298e0bb7150645c5eb5..7f6c3722fec781916cdc1eaa70119cfeb6369584 100644 (file)
--- 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')