--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+Bugs Everywhere Directory v1.4
sync
doc/manual/html
doc/manual/pdf
-doc/manual/extensions.ent
\ No newline at end of file
+doc/manual/extensions.ent
+.be/id-cache
+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.
-# 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.
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
# 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 =
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
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
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
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
# 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
#---------------------------------------------------------------------------
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.
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.
# 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
#---------------------------------------------------------------------------
# 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
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.
# 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
# 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 =
# 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
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,
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).
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
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
# 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
# (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
# 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
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
#---------------------------------------------------------------------------
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.
# 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 =
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
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
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.
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.
# 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.
FFmpeg version 0.9.x or higher
libtiff
boost thread and filesystem
- libopenjpeg
+ libopenjpeg 1.5.0 or higher
wxWidgets
libsndfile
libssh
+++ /dev/null
-#!/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
--- /dev/null
+#!/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
+
--- /dev/null
+#!/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
* 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.
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>
</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 ‘master’
+machine runs DVD-o-matic, and the ‘server’ machines run
+a small program called ‘servomatic’.
+</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 ‘DVD-o-matic encode
+server’ 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>
+++ /dev/null
-#!/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
+++ /dev/null
-#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+/*
+ 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 ();
+}
--- /dev/null
+/*
+ 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;
+};
} 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") {
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";
}
#include <boost/shared_ptr.hpp>
#include <sigc++/signal.h>
-class Server;
+class ServerDescription;
class Screen;
class Scaler;
class Filter;
}
/** @return J2K encoding servers to use */
- std::vector<Server*> servers () const {
+ std::vector<ServerDescription*> servers () const {
return _servers;
}
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;
}
}
/** @param s New list of servers */
- void set_servers (std::vector<Server*> s) {
+ void set_servers (std::vector<ServerDescription*> s) {
_servers = s;
Changed ();
}
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 ();
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 */
#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;
/* 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);
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 ());
}
* @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 "
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 ());
}
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)
class FilmState;
class Options;
-class Server;
+class ServerDescription;
class Scaler;
class Image;
class Log;
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;
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;
#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>
}
, _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)
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(),
);
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;
_audio_frames_processed = 0;
}
+/** Finish off a decode processing run */
void
Decoder::process_end ()
{
+#if HAVE_SWRESAMPLE
if (_swr_context) {
int mop = 0;
swr_free (&_swr_context);
}
+#endif
if (_delay_in_bytes < 0) {
uint8_t remainder[-_delay_in_bytes];
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;
+ }
}
}
/* 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) {
uint8_t* out_buffer = 0;
/* Maybe sample-rate convert */
+#if HAVE_SWRESAMPLE
if (_swr_context) {
uint8_t const * in[2] = {
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 ());
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 ();
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.");
#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) {
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 << ":"
<< 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.");
}
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.");
}
#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;
AVFilterContext* _buffer_src_context;
AVFilterContext* _buffer_sink_context;
+#if HAVE_SWRESAMPLE
SwrContext* _swr_context;
+#endif
bool _have_setup_video_filters;
DelayLine* _delay_line;
/* 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
: _fs (s)
, _opt (o)
, _log (l)
+ , _just_skipped (false)
+ , _last_frame (0)
{
}
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);
_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;
+}
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;
/** 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
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)
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;
};
--- /dev/null
+/*
+ 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
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
if (_audio_codec_context == 0) {
return 0;
}
-
+
return _audio_codec_context->channels;
}
#include <stdexcept>
#include <iostream>
+#include <algorithm>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include "scaler.h"
#include "decoder_factory.h"
#include "config.h"
+#include "check_hashes_job.h"
using namespace std;
using namespace boost;
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;
read_metadata ();
- _log = new Log (_state.file ("log"));
+ _log = new FileLog (_state.file ("log"));
}
/** Copy constructor */
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;
filesystem::path p;
-
/* Start with j2c */
p /= "j2c";
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 ())));
}
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;
+}
#include <vector>
#include <inttypes.h>
#include <boost/thread/mutex.hpp>
+#include <boost/thread.hpp>
#include <sigc++/signal.h>
extern "C" {
#include <libavcodec/avcodec.h>
return _log;
}
+ int encoded_frames () const;
+
/** Emitted when some metadata property has changed */
mutable sigc::signal1<void, Property> Changed;
} 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 ());
- }
}
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;
}
_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"));
#include "exceptions.h"
#include "scaler.h"
-#ifdef DEBUG_HASH
-#include <mhash.h>
-#endif
-
using namespace std;
using namespace boost;
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.
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 {
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);
}
}
#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"
));
_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
if (encoded) {
encoded->write (_opt, vf->frame ());
- frame_done ();
+ frame_done (vf->frame ());
} else {
lock.lock ();
_queue.push_front (vf);
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)));
}
{
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);
}
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
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() << ".";
#include <sndfile.h>
#include "encoder.h"
-class Server;
+class ServerDescription;
class DCPVideoFrame;
class Image;
class Log;
private:
- void encoder_thread (Server *);
+ void encoder_thread (ServerDescription *);
void close_sound_files ();
void terminate_worker_threads ();
_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 ()
{
_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) << ")";
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();
+}
#include <string>
#include <boost/thread/mutex.hpp>
+#include <boost/enable_shared_from_this.hpp>
#include <sigc++/sigc++.h>
class Log;
/** @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);
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
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:
/** 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 {
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;
}
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;
using namespace std;
-/** @param f Filename to write log to */
-Log::Log (string f)
- : _file (f)
- , _level (VERBOSE)
+Log::Log ()
+ : _level (VERBOSE)
{
}
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
_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";
+}
+
*/
+#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,
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
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 (
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);
/** @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 (" "));
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 ();
+ }
+}
/** @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)
{}
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 */
/** 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;
+};
TIFFClose (output);
boost::filesystem::rename (tmp_file, _opt->frame_out_path (frame, false));
- frame_done ();
+ frame_done (frame);
}
_log->log (s.str ());
throw;
-
}
}
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);
+}
void run ();
std::string status () const;
+protected:
+ int remaining_time () const;
+
private:
boost::shared_ptr<Encoder> _encoder;
};
#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>
#include "player_manager.h"
#endif
-#ifdef DEBUG_HASH
-#include <mhash.h>
-#endif
-
using namespace std;
using namespace boost;
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
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)
{
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)
{
return !(a == b);
}
+/** @param index Colour LUT index.
+ * @return Human-readable name.
+ */
string
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);
+}
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
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 ()
, y (y_)
{}
+ /** x coordinate */
int x;
+ /** y coordinate */
int y;
};
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
-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'
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
encoder.cc
encoder_factory.cc
examine_content_job.cc
+ ffmpeg_compatibility.cc
ffmpeg_decoder.cc
film.cc
film_state.cc
//#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"
ID_file_new = 1,
ID_file_open,
ID_file_save,
+ ID_file_properties,
ID_file_quit,
ID_edit_preferences,
ID_jobs_make_dcp,
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;
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));
{
film->write_metadata ();
}
+
+ void file_properties (wxCommandEvent &)
+ {
+ PropertiesDialog* d = new PropertiesDialog (this, film);
+ d->ShowModal ();
+ d->Destroy ();
+ }
void file_quit (wxCommandEvent &)
{
+++ /dev/null
-/*
- 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;
-}
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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)
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;
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;
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 = ['..']
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:
def configure(conf):
- conf.recurse('lib')
if not conf.env.DISABLE_GUI:
conf.recurse('wx')
_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 ()));
_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);
}
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) {
}
void
-ConfigDialog::add_server_to_control (Server* s)
+ConfigDialog::add_server_to_control (ServerDescription* s)
{
wxListItem item;
int const n = _servers->GetItemCount ();
{
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);
}
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
if (i >= 0) {
_servers->DeleteItem (i);
}
+
+ vector<ServerDescription*> o = Config::instance()->servers ();
+ o.erase (o.begin() + i);
+ Config::instance()->set_servers (o);
}
void
#include <wx/listctrl.h>
class Screen;
-class Server;
+class ServerDescription;
/** @class ConfigDialog
* @brief A dialogue to edit DVD-o-matic configuration.
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;
_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) {
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 (""));
{
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 ();
}
{
}
+ /** Handle a paint event */
void paint_event (wxPaintEvent& ev)
{
if (_current_image != _pending_image) {
}
}
+ /** Handle a size event */
void size_event (wxSizeEvent &)
{
if (!_image) {
}
}
+ /** Clear our thumbnail image */
void clear ()
{
delete _bitmap;
}
_film->Changed.connect (sigc::mem_fun (*this, &FilmViewer::film_changed));
+ film_changed (Film::CROP);
film_changed (Film::THUMBS);
_thumb_panel->refresh ();
setup_visibility ();
, _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));
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) {
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 ()));
}
{
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;
}
_job_records[*i].informed_of_finish = true;
}
+
+ index += 3;
}
_table->Layout ();
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;
--- /dev/null
+/*
+ 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 ();
+}
--- /dev/null
+/*
+ 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;
+};
+
#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);
_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) {
_server->set_threads (_threads->GetValue ());
}
-Server *
+ServerDescription *
ServerDialog::server () const
{
return _server;
#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;
};
server_dialog.cc
new_film_dialog.cc
dir_picker_ctrl.cc
+ properties_dialog.cc
"""
# alignment.cc
*/
/** @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)
{
return m;
}
+/** Pop up an error dialogue box.
+ * @param parent Parent.
+ * @param m Message.
+ */
void
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 ());
+}
*/
#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;
+};
#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>
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 ();
+ }
+}
-#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"
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"
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"
--- /dev/null
+#!/bin/bash
+
+dir=`dirname $0`
+LD_LIBRARY_PATH=$dir/../lib:$LD_LIBRARY_PATH $dir/makedcp-bin $*
+
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')
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
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')
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'
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')