From: Carl Hetherington Date: Sat, 14 Jul 2012 23:14:28 +0000 (+0100) Subject: Move things round a bit. X-Git-Tag: v2.0.48~1977 X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=bb767c7e338414beee132af3e96829c1448e214b;hp=66c9be6bdb1361e5681e094a0c8170d268aa9518 Move things round a bit. --- diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..d60c31a97 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 000000000..d864d8af1 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,90 @@ +2012-07-15 Carl Hetherington + + * Remove code to use `standard' format DCP long names, + as in the wild their use seems to be decreasing, and it + makes the GUI simpler. + + * Fix some bugs with sending to servomatic introduced + in the adjustments to padding. + +2012-07-14 Carl Hetherington + + * Version 0.25 released. + +2012-07-14 Carl Hetherington + + * Various GUI cleanups. + + * Remove player from the GUI for now. + + * Fix hash down the left-hand side of encoded DCPs. + + * Add option to black-out the end of an encode, in order + to remove unwanted frames of video whilst keeping sound. + + * Fixes to copy-to-server. + + * Fix name of 16:9 format. + +2012-07-08 Carl Hetherington + + * Version 0.24 released. + +2012-07-08 Carl Hetherington + + * Add support for generating static DCPs from single + image files. + + * Add option to copy DCP to a remote server (e.g. a TMS) + via SCP. + + * Auto-update thumbs when content changes. + +2012-06-10 Carl Hetherington + + * Fix up bad padding setup when there isn't any. + + * Restore sound to playomatic; add assert for bad format. + +2012-05-26 Carl Hetherington + + * Fix crash on attempting to use a non-existant filter. + + * src/lib/filter.cc: Fix typo in filter name. + + * Allow configuration of the reference scalers and filters in A/B mode. + + * Fix identification of formats in metadata. + +2012-05-26 Carl Hetherington + + * Version 0.23 released. + +2012-05-28 Carl Hetherington + + * src/lib/player_manager.cc: possible fix to crash when stopping + playback. + + * Fix crash in A/B mode. + +2012-05-26 Carl Hetherington + + * Version 0.21 released. + +2012-05-25 Carl Hetherington + + * Add option to delay audio with respect to video. + + * src/tools/fixlengths.cc: add a few more options. + +2012-05-22 Carl Hetherington + + * src/tools/dvdomatic.cc: fix website address. + + * test: fix up a few test bits. + + * README: very brief introduction to a few things. + +2012-05-22 Carl Hetherington + + * Version 0.20 released. diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 000000000..8efb70662 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,1716 @@ +# Doxyfile 1.7.4 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +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. + +PROJECT_NAME = DVD-o-matic + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = build/doc + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +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. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +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. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = 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 +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +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. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# 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 = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = src doc/mainpage.txt + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# 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. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is adviced 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! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +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 +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +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). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +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. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +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. + +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 +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +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. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the +# mathjax.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 = http://www.mathjax.org/mathjax + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = YES + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# 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. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +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. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +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. + +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_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES 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. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# 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. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/README b/README new file mode 100644 index 000000000..26b857be1 --- /dev/null +++ b/README @@ -0,0 +1,82 @@ +dvd-o-matic +----------- + +Hello! + + +Building +-------- + +./waf configure +./waf +sudo ./waf install + + +Dependencies +------------ + +You will need these libraries: + + FFmpeg + libtiff + boost thread and filesystem + libopenjpeg + +and also the command line tools: + + opendcp_mxf and opendcp_xml (from OpenDCP http://code.google.com/p/opendcp/) + vobcopy (if you want to rip DVDs straight into DVD-o-matic) + + +In a nutshell +------------- + +The `dvdomatic' program is a GTK front-end which is probably easiest +to use. It will create a directory for a particular project, and write +its data to that directory. The basic approach is: + +"File->New"; specify a directory. +Choose "Jobs->Copy from DVD" to read a DVD from your drive, if you have one. +Fill in the fields in the window (most importantly the `content' field: + specify your video, and the `Name' field: give your project [and hence DCP] + a name.) +Move the slider to see thumbnails; adjust crop if necessary. +Click "Make DCP" and go and make a really strong cup of tea. +The DCP will be written to the project's directory; copy this to your + media server and ingest. + +The `Format' field dictates what size your image will be: + +- 4:3 within Flat: 1.33:1 images inside a flat (1.85:1) frame; play + back using the DCI 1.85 / DCI Flat preset on your projector. +- Academy: 1.37:1; play using DCI 1.37 if you have such a thing. +- 16:9 within Flat: 1.78:1 images inside a 1.85:1 frame. +- Flat: 1.85:1 images to the DCI spec. +- Scope: 2.39:1 images to the DCI spec. + + + +Server/client +------------- + +Running the `servomatic' program on a remote machine will make it +listen on port 6192 (by default) and process requests from a dvdomatic +instance. This has been written with no thought to security, so don't +do it over the public internet! The connection will probably need to +be 1 Gb/s to make it worthwhile. + +To tell the client about available servers you will need to go to +Edit->Preferences and add the server's IP address and the number of +parallel threads that the server should execute (make it equal to the +number of CPUs or cores). + + + +Problems +-------- + +Email me at cth@carlh.net in the first instance. + + +Carl Hetherington +July 2012 diff --git a/TODO b/TODO new file mode 100644 index 000000000..042b761f8 --- /dev/null +++ b/TODO @@ -0,0 +1,108 @@ +Don't start without opendcp tools on path +Sort out fight between Job::status and overridden versions; a bit ugly +Format name in ~/.dvdomatic screws up with spaces; use ID or something +Thumbnails are poorly named +x-thread signaller +Restartable jobs somehow +More logging +Nice error when trying to thumbnail with no content. +Destroy _buffer_src_context / _buffer_sink_context +Don't start later jobs when one breaks. +Compute time remaining based on more recent information. +Start frame later than ... error? +Use lexical_cast more +Do deps better + +options summary + +1: L +2: R +3: C +4: Lfe +5: Ls +6: Rs + +City Screen + +Screen 1: "1.37" masking preset, projector only has DCI 133 preset. + +With 1480x1080 alignment in DCI 133: bottom you see purple, yellow; top purple; left and right no lines +With 1480x1080 alignment in DCI Flat: outside masks, but you see bottom purple, yellow; left/right all; top purple + + +Screen 2: no real masking preset, projector has DCI 133 and DCI 137 + +1480x1080, DCI 133 +L yellow purple +R none +B purple +T none +1480x1080, DCI 137 +L all +R all but blue +T purple +B purple + + +Screen 3: projector has DCI 1.38 + +1480x1080 +L, R, T none +B purple + yellow + + +films-0.6: Dolby Countdown looks as though it's 3D. THX Terminator 2 fucked +(these on default settings) +fq/gradfun --- no obvious effect +hqdn3d --- pretty good denoising +ow --- no obvious effect +tn --- interesting; much noise reduction, bad artefacts on movement, colour tint even in black +unsharp --- worse + +Benchmark SWS options: lanczos ? +hqdn3d=0:0:6 ? (turn off chroma/luma blurring) + +Lanczos; no visible effect on Ghostbusters. + + +THX_Monster with master Intel Core 2 Duo E4600 (2.4GHz), slave Intel Core i3 M350 (2.27GHz) +1920 x 1080 original -> DCI Flat +240 frames + +[Gbit: gigabit ethernet rather than 100Mbit] +[im-mod: after modification to memcpy RGB data then to RGB -> XYZ in the encode thread +[hack1]: after modification to pass YUV and to swscale in the encode thread (includes im-mod) +[hack2]: modified hack1 + Time Seconds FPS Speedup relative to 1 local +1 local: 20m57 1257 0.19 x 1 +2 local: 11m24 684 0.35 x 1.84 +2 local [im-mod]: 13m13 +2 local + 1 slave: 6m34 394 0.61 x 3.19 +2 local + 2 slave: 5m13 313 0.77 x 4.02 +2 local + 4 slave: 5m05 303 0.79 x 4.15 +2 local + 4 slave [Gbit]: 2m50 170 1.41 x 7.39 +2 local + 4 slave [Gbit,im-mod]:2m33 +2 local + 4 slave [Gbit,hack1]: 3m20 +2 local + 4 slave [Gbit,hack2]: 2m22 +1 local + 8 slave [Gbit]: 2m28 148 1.62 x 8.49 +2 local + 8 slave [Gbit]: 2m41 161 1.49 x 7.81 +2 local + 8 slave [Gbit,im-mod]:2m35 + + + +Just encode 52s +Encode + Image create 1m27 +Encode + Image create (memcpy, not convert) 53s. + +THX_Monster with master Intel Core i3 M350 (2.27GHz), slave Intel Core 2 Duo E4600 (2.4GHz) +1920 x 1080 original -> DCI Flat +240 frames + + +4 local: 2m45 +4 local [im-mod]: 2m53 +4 local + 2 slave [Gbit]: 2m22 +4 local + 4 slave [Gbit]: 2m21 +4 local + 4 slave [Gbit,in-mod]:2m21 + + diff --git a/doc/mainpage.txt b/doc/mainpage.txt new file mode 100644 index 000000000..a944238bf --- /dev/null +++ b/doc/mainpage.txt @@ -0,0 +1,37 @@ +/** @mainpage DVD-o-matic + * + * DVD-o-matic is a tool to create digital cinema packages (DCPs) from + * video files, or from sets of TIFF image files. It is written in C++ + * and distributed under the GPL. + * + * Video files are decoded using FFmpeg (http://ffmpeg.org), so any video + * supported by FFmpeg should be usable with DVD-o-matic. Most testing has, however, + * happened with files from DVD. DVD-o-matic's output has been tested on Christie + * digital projectors with Doremi servers. + * + * DVD-o-matic allows you to crop black borders from movies, scale them to the correct + * aspect ratio and apply FFmpeg filters. The time-consuming encoding of JPEG2000 files + * can be parallelised amongst any number of processors on the local host and any number + * of servers over a network. + * + * Portions of DVD-o-matic are based on OpenDCP (http://code.google.com/p/opendcp), + * written by Terrence Meiczinger, and two of OpenDCP's command-line tools are called + * by DVD-o-matic. + * + * DVD-o-matic uses libopenjpeg (http://code.google.com/p/openjpeg/) for JPEG2000 encoding + * 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/) + * for debugging purposes. + * + * Thanks are due to the authors and communities of all DVD-o-matic's dependencies. + * + * DVD-o-matic is distributed in the hope that there are still cinemas with projectionists + * who might want to use it. As Mark Kermode says, "if it doesn't have a projectionist + * it's not a cinema - it's a sweetshop with a video-screen." + * + * Email correspondance is welcome to cth@carlh.net + * + * More details can be found at http://carlh.net/dvdomatic + */ diff --git a/dvdomatic.desktop.in b/dvdomatic.desktop.in new file mode 100644 index 000000000..65067eb3b --- /dev/null +++ b/dvdomatic.desktop.in @@ -0,0 +1,10 @@ +[Desktop Entry] +Encoding=UTF-8 +Version=1.0 +Type=Application +Terminal=false +Exec=@PREFIX@/bin/dvdomatic +Name=DVD-o-matic +Icon=dvdomatic +Comment=DCP generator +Categories=AudioVideo;Video diff --git a/icons/128x128/dvdomatic.png b/icons/128x128/dvdomatic.png new file mode 100644 index 000000000..9936b39af Binary files /dev/null and b/icons/128x128/dvdomatic.png differ diff --git a/icons/22x22/dvdomatic.png b/icons/22x22/dvdomatic.png new file mode 100644 index 000000000..dddb86298 Binary files /dev/null and b/icons/22x22/dvdomatic.png differ diff --git a/icons/32x32/dvdomatic.png b/icons/32x32/dvdomatic.png new file mode 100644 index 000000000..8cecf08f8 Binary files /dev/null and b/icons/32x32/dvdomatic.png differ diff --git a/icons/48x48/dvdomatic.png b/icons/48x48/dvdomatic.png new file mode 100644 index 000000000..07bf2d10b Binary files /dev/null and b/icons/48x48/dvdomatic.png differ diff --git a/icons/64x64/dvdomatic.png b/icons/64x64/dvdomatic.png new file mode 100644 index 000000000..35564a8a2 Binary files /dev/null and b/icons/64x64/dvdomatic.png differ diff --git a/icons/finish-trace.svg b/icons/finish-trace.svg new file mode 100644 index 000000000..ef10952de --- /dev/null +++ b/icons/finish-trace.svg @@ -0,0 +1,139 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/finish.jpg b/icons/finish.jpg new file mode 100644 index 000000000..3bfc27ab4 Binary files /dev/null and b/icons/finish.jpg differ diff --git a/icons/finish2.jpg b/icons/finish2.jpg new file mode 100644 index 000000000..f05a2e76e Binary files /dev/null and b/icons/finish2.jpg differ diff --git a/icons/finish3.jpg b/icons/finish3.jpg new file mode 100644 index 000000000..b1fc4753d Binary files /dev/null and b/icons/finish3.jpg differ diff --git a/run/alignomatic b/run/alignomatic new file mode 100755 index 000000000..9cc8c2245 --- /dev/null +++ b/run/alignomatic @@ -0,0 +1,10 @@ +#!/bin/bash + +export LD_LIBRARY_PATH=build/src/lib:build/src/gtk:$LD_LIBRARY_PATH +if [ "$1" == "--debug" ]; then + gdb --args build/src/tools/alignomatic $2 +elif [ "$1" == "--valgrind" ]; then + valgrind --tool="memcheck" build/src/tools/alignomatic $2 +else + build/src/tools/alignomatic "$1" +fi diff --git a/run/dvdomatic b/run/dvdomatic new file mode 100755 index 000000000..23b7d22ed --- /dev/null +++ b/run/dvdomatic @@ -0,0 +1,10 @@ +#!/bin/bash + +export LD_LIBRARY_PATH=build/src/lib:build/src/gtk:$LD_LIBRARY_PATH +if [ "$1" == "--debug" ]; then + gdb --args build/src/tools/dvdomatic $2 +elif [ "$1" == "--valgrind" ]; then + valgrind --tool="memcheck" build/src/tools/dvdomatic $2 +else + build/src/tools/dvdomatic "$1" +fi diff --git a/run/fixlengths b/run/fixlengths new file mode 100755 index 000000000..1a6cc0a17 --- /dev/null +++ b/run/fixlengths @@ -0,0 +1,10 @@ +#!/bin/bash + +export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH:build/src +if [ "$1" == "--debug" ]; then + gdb --args build/src/tools/fixlengths "$@" +elif [ "$1" == "--valgrind" ]; then + valgrind --tool="memcheck" --leak-check=full --show-reachable=yes build/src/tools/fixlengths "$@" +else + build/src/tools/fixlengths "$@" +fi diff --git a/run/long-tests b/run/long-tests new file mode 100755 index 000000000..6c2925495 --- /dev/null +++ b/run/long-tests @@ -0,0 +1,10 @@ +#!/bin/bash + +export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH +if [ "$1" == "--debug" ]; then + gdb --args build/test/long-unit-tests +elif [ "$1" == "--valgrind" ]; then + valgrind --tool="memcheck" --leak-check=full build/test/long-unit-tests +else + build/test/long-unit-tests +fi diff --git a/run/makedcp b/run/makedcp new file mode 100755 index 000000000..f5adbd917 --- /dev/null +++ b/run/makedcp @@ -0,0 +1,10 @@ +#!/bin/bash + +export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH:build/src +if [ "$1" == "--debug" ]; then + gdb --args build/src/tools/makedcp "$@" +elif [ "$1" == "--valgrind" ]; then + valgrind --tool="memcheck" --leak-check=full --show-reachable=yes build/src/tools/makedcp "$@" +else + build/src/tools/makedcp "$@" +fi diff --git a/run/playomatic b/run/playomatic new file mode 100755 index 000000000..9fe191a51 --- /dev/null +++ b/run/playomatic @@ -0,0 +1,10 @@ +#!/bin/bash + +export LD_LIBRARY_PATH=build/src/lib:build/src/gtk:$LD_LIBRARY_PATH +if [ "$1" == "--debug" ]; then + gdb --args build/src/tools/playomatic $2 +elif [ "$1" == "--valgrind" ]; then + valgrind --tool="memcheck" build/src/tools/playomatic $2 +else + build/src/tools/playomatic "$1" +fi diff --git a/run/servomatic b/run/servomatic new file mode 100755 index 000000000..100d0a8bd --- /dev/null +++ b/run/servomatic @@ -0,0 +1,10 @@ +#!/bin/bash + +export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH +if [ "$1" == "--debug" ]; then + gdb --args build/src/tools/servomatic +elif [ "$1" == "--valgrind" ]; then + valgrind --tool="memcheck" build/src/tools/servomatic +else + build/src/tools/servomatic +fi diff --git a/run/servomatictest b/run/servomatictest new file mode 100755 index 000000000..58cea8815 --- /dev/null +++ b/run/servomatictest @@ -0,0 +1,12 @@ +#!/bin/bash + +export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH +if [ "$1" == "--debug" ]; then + shift + gdb --args build/src/tools/servomatictest $* +elif [ "$1" == "--valgrind" ]; then + shift + valgrind --tool="memcheck" build/src/tools/servomatictest $* +else + build/src/tools/servomatictest $* +fi diff --git a/run/short-tests b/run/short-tests new file mode 100755 index 000000000..4f2567029 --- /dev/null +++ b/run/short-tests @@ -0,0 +1,11 @@ +#!/bin/bash + +export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH +if [ "$1" == "--debug" ]; then + gdb --args build/test/short-unit-tests +elif [ "$1" == "--valgrind" ]; then + valgrind --tool="memcheck" --leak-check=full build/test/short-unit-tests +else + build/test/short-unit-tests +fi + diff --git a/splitchapters b/splitchapters new file mode 100755 index 000000000..1e5cff084 --- /dev/null +++ b/splitchapters @@ -0,0 +1,30 @@ +#!/usr/bin/python + +import os +import sys + +if len(sys.argv) < 2: + print 'Syntax: %s ' % sys.argv[0] + sys.exit(1) + +lsdvd = os.popen('lsdvd -c "%s"' % sys.argv[1]) +lines = lsdvd.readlines() + +N = None + +for l in lines: + w = l.split() + if len(w) > 5 and w[4] == 'Chapters:': + N = int(w[5][:-1]) + +if N == None: + print 'Could not get chapter count.' + sys.exit(1) + +for i in range(1, N + 1): + os.mkdir('%d' % i) + c = 'mplayer dvd:// -chapter %d-%d -dvd-device "%s" -dumpstream -dumpfile %d/%d.vob' % (i, i, sys.argv[1], i, i) + print c + os.system(c) + + diff --git a/src/gtk/alignment.cc b/src/gtk/alignment.cc new file mode 100644 index 000000000..ee4ca51c1 --- /dev/null +++ b/src/gtk/alignment.cc @@ -0,0 +1,167 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include +#include "alignment.h" + +using namespace std; + +class AlignmentWidget : public Gtk::DrawingArea +{ +public: + void set_text_line (int n, string t) + { + if (int(_text.size()) < (n + 1)) { + _text.resize (n + 1); + } + + _text[n] = t; + queue_draw (); + } + +private: + bool on_expose_event (GdkEventExpose* ev) + { + if (!get_window ()) { + return false; + } + + Cairo::RefPtr c = get_window()->create_cairo_context (); + + Gtk::Allocation a = get_allocation (); + int const w = a.get_width (); + int const h = a.get_height (); + + c->rectangle (0, 0, w, h); + c->set_source_rgb (0, 0, 0); + c->fill (); + + c->set_source_rgb (1, 1, 1); + c->set_line_width (1); + + int const arrow_size = h / 8; + int const head_size = h / 32; + + /* arrow to left edge */ + c->move_to (arrow_size, h / 2); + c->line_to (0, h / 2); + c->rel_line_to (head_size, head_size); + c->move_to (0, h / 2); + c->rel_line_to (head_size, -head_size); + c->stroke (); + + /* arrow to right edge */ + c->move_to (w - arrow_size, h / 2); + c->line_to (w, h / 2); + c->rel_line_to (-head_size, head_size); + c->move_to (w, h / 2); + c->rel_line_to (-head_size, -head_size); + c->stroke (); + + /* arrow to top edge */ + c->move_to (w / 2, arrow_size); + c->line_to (w / 2, 0); + c->rel_line_to (head_size, head_size); + c->move_to (w / 2, 0); + c->rel_line_to (-head_size, head_size); + c->stroke (); + + /* arrow to bottom edge */ + c->move_to (w / 2, h - h / 8); + c->line_to (w / 2, h); + c->rel_line_to (head_size, -head_size); + c->move_to (w / 2, h); + c->rel_line_to (-head_size, -head_size); + c->stroke (); + + /* arrow to top-left corner */ + c->move_to (arrow_size, arrow_size); + c->line_to (0, 0); + c->rel_line_to (head_size, 0); + c->move_to (0, 0); + c->rel_line_to (0, head_size); + c->stroke (); + + /* arrow to top-right corner */ + c->move_to (w - arrow_size, arrow_size); + c->line_to (w, 0); + c->rel_line_to (0, head_size); + c->move_to (w, 0); + c->rel_line_to (-head_size, 0); + c->stroke (); + + /* arrow to bottom-left corner */ + c->move_to (arrow_size, h - arrow_size); + c->line_to (0, h); + c->rel_line_to (head_size, 0); + c->move_to (0, h); + c->rel_line_to (0, -head_size); + c->stroke (); + + /* arrow to bottom-right corner */ + c->move_to (w - arrow_size, h - arrow_size); + c->line_to (w, h); + c->rel_line_to (-head_size, 0); + c->line_to (w, h); + c->rel_line_to (0, -head_size); + c->stroke (); + + /* text */ + int max_height = 0; + for (vector::iterator i = _text.begin(); i != _text.end(); ++i) { + Cairo::TextExtents e; + c->get_text_extents (*i, e); + max_height = max (max_height, int (e.height)); + } + + int total_height = max_height * _text.size() * 2; + + for (vector::size_type i = 0; i < _text.size(); ++i) { + Cairo::TextExtents e; + c->get_text_extents (_text[i], e); + c->move_to ((w - e.width) / 2, ((h - total_height) / 2) + ((i * 2) + 1) * max_height); + c->text_path (_text[i]); + c->stroke (); + } + + return true; + } + + std::vector _text; +}; + +Alignment::Alignment (Position p, Size s) +{ + _widget = Gtk::manage (new AlignmentWidget); + add (*_widget); + show_all (); + + set_decorated (false); + set_resizable (false); + set_size_request (s.width, s.height); + move (p.x, p.y); +} + +void +Alignment::set_text_line (int n, string t) +{ + _widget->set_text_line (n, t); +} diff --git a/src/gtk/alignment.h b/src/gtk/alignment.h new file mode 100644 index 000000000..fb740b7c0 --- /dev/null +++ b/src/gtk/alignment.h @@ -0,0 +1,35 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include "lib/util.h" + +class AlignmentWidget; + +class Alignment : public Gtk::Window +{ +public: + Alignment (Position, Size); + + void set_text_line (int, std::string); + +private: + AlignmentWidget* _widget; +}; diff --git a/src/gtk/config_dialog.cc b/src/gtk/config_dialog.cc new file mode 100644 index 000000000..03f5b99a0 --- /dev/null +++ b/src/gtk/config_dialog.cc @@ -0,0 +1,369 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/config_dialog.cc + * @brief A dialogue to edit DVD-o-matic configuration. + */ + +#include +#include +#include "lib/config.h" +#include "lib/server.h" +#include "lib/screen.h" +#include "lib/format.h" +#include "lib/scaler.h" +#include "lib/filter.h" +#include "config_dialog.h" +#include "gtk_util.h" +#include "filter_dialog.h" + +using namespace std; +using namespace boost; + +ConfigDialog::ConfigDialog () + : Gtk::Dialog ("DVD-o-matic Configuration") + , _reference_filters_button ("Edit...") + , _add_server ("Add Server") + , _remove_server ("Remove Server") + , _add_screen ("Add Screen") + , _remove_screen ("Remove Screen") +{ + Gtk::Table* t = manage (new Gtk::Table); + t->set_row_spacings (6); + t->set_col_spacings (6); + t->set_border_width (6); + + Config* config = Config::instance (); + + _tms_ip.set_text (config->tms_ip ()); + _tms_ip.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::tms_ip_changed)); + _tms_path.set_text (config->tms_path ()); + _tms_path.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::tms_path_changed)); + _tms_user.set_text (config->tms_user ()); + _tms_user.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::tms_user_changed)); + _tms_password.set_text (config->tms_password ()); + _tms_password.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::tms_password_changed)); + + _num_local_encoding_threads.set_range (1, 128); + _num_local_encoding_threads.set_increments (1, 4); + _num_local_encoding_threads.set_value (config->num_local_encoding_threads ()); + _num_local_encoding_threads.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::num_local_encoding_threads_changed)); + + _colour_lut.append_text ("sRGB"); + _colour_lut.append_text ("Rec 709"); + _colour_lut.set_active (config->colour_lut_index ()); + _colour_lut.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::colour_lut_changed)); + + _j2k_bandwidth.set_range (50, 250); + _j2k_bandwidth.set_increments (10, 50); + _j2k_bandwidth.set_value (config->j2k_bandwidth() / 1e6); + _j2k_bandwidth.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::j2k_bandwidth_changed)); + + vector const sc = Scaler::all (); + for (vector::const_iterator i = sc.begin(); i != sc.end(); ++i) { + _reference_scaler.append_text ((*i)->name ()); + } + _reference_scaler.set_active (Scaler::as_index (config->reference_scaler ())); + _reference_scaler.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::reference_scaler_changed)); + + _reference_filters.set_alignment (0, 0.5); + pair p = Filter::ffmpeg_strings (config->reference_filters ()); + _reference_filters.set_text (p.first + " " + p.second); + _reference_filters_button.signal_clicked().connect (sigc::mem_fun (*this, &ConfigDialog::edit_reference_filters_clicked)); + + _servers_store = Gtk::ListStore::create (_servers_columns); + vector servers = config->servers (); + for (vector::iterator i = servers.begin(); i != servers.end(); ++i) { + add_server_to_store (*i); + } + + _servers_view.set_model (_servers_store); + _servers_view.append_column_editable ("Host Name", _servers_columns._host_name); + _servers_view.append_column_editable ("Threads", _servers_columns._threads); + + _add_server.signal_clicked().connect (sigc::mem_fun (*this, &ConfigDialog::add_server_clicked)); + _remove_server.signal_clicked().connect (sigc::mem_fun (*this, &ConfigDialog::remove_server_clicked)); + + _servers_view.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::server_selection_changed)); + server_selection_changed (); + + _screens_store = Gtk::TreeStore::create (_screens_columns); + vector > screens = config->screens (); + for (vector >::iterator i = screens.begin(); i != screens.end(); ++i) { + add_screen_to_store (*i); + } + + _screens_view.set_model (_screens_store); + _screens_view.append_column_editable ("Screen", _screens_columns._name); + _screens_view.append_column ("Format", _screens_columns._format_name); + _screens_view.append_column_editable ("x", _screens_columns._x); + _screens_view.append_column_editable ("y", _screens_columns._y); + _screens_view.append_column_editable ("Width", _screens_columns._width); + _screens_view.append_column_editable ("Height", _screens_columns._height); + + _add_screen.signal_clicked().connect (sigc::mem_fun (*this, &ConfigDialog::add_screen_clicked)); + _remove_screen.signal_clicked().connect (sigc::mem_fun (*this, &ConfigDialog::remove_screen_clicked)); + + _screens_view.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::screen_selection_changed)); + screen_selection_changed (); + + int n = 0; + t->attach (left_aligned_label ("TMS IP address"), 0, 1, n, n + 1); + t->attach (_tms_ip, 1, 2, n, n + 1); + ++n; + t->attach (left_aligned_label ("TMS target path"), 0, 1, n, n + 1); + t->attach (_tms_path, 1, 2, n, n + 1); + ++n; + t->attach (left_aligned_label ("TMS user name"), 0, 1, n, n + 1); + t->attach (_tms_user, 1, 2, n, n + 1); + ++n; + t->attach (left_aligned_label ("TMS password"), 0, 1, n, n + 1); + t->attach (_tms_password, 1, 2, n, n + 1); + ++n; + t->attach (left_aligned_label ("Threads to use for encoding on this host"), 0, 1, n, n + 1); + t->attach (_num_local_encoding_threads, 1, 2, n, n + 1); + ++n; + t->attach (left_aligned_label ("Colour look-up table"), 0, 1, n, n + 1); + t->attach (_colour_lut, 1, 2, n, n + 1); + ++n; + t->attach (left_aligned_label ("JPEG2000 bandwidth"), 0, 1, n, n + 1); + t->attach (_j2k_bandwidth, 1, 2, n, n + 1); + t->attach (left_aligned_label ("MBps"), 2, 3, n, n + 1); + ++n; + t->attach (left_aligned_label ("Reference scaler for A/B"), 0, 1, n, n + 1); + t->attach (_reference_scaler, 1, 2, n, n + 1); + ++n; + t->attach (left_aligned_label ("Reference filters for A/B"), 0, 1, n, n + 1); + Gtk::HBox* fb = Gtk::manage (new Gtk::HBox); + fb->set_spacing (4); + fb->pack_start (_reference_filters, true, true); + fb->pack_start (_reference_filters_button, false, false); + t->attach (*fb, 1, 2, n, n + 1); + ++n; + t->attach (left_aligned_label ("Encoding Servers"), 0, 1, n, n + 1); + t->attach (_servers_view, 1, 2, n, n + 1); + Gtk::VBox* b = manage (new Gtk::VBox); + b->pack_start (_add_server, false, false); + b->pack_start (_remove_server, false, false); + t->attach (*b, 2, 3, n, n + 1); + ++n; + t->attach (left_aligned_label ("Screens"), 0, 1, n, n + 1); + t->attach (_screens_view, 1, 2, n, n + 1); + b = manage (new Gtk::VBox); + b->pack_start (_add_screen, false, false); + b->pack_start (_remove_screen, false, false); + t->attach (*b, 2, 3, n, n + 1); + ++n; + + t->show_all (); + get_vbox()->pack_start (*t); + + get_vbox()->set_border_width (24); + + add_button ("Close", Gtk::RESPONSE_CLOSE); +} + +void +ConfigDialog::tms_ip_changed () +{ + Config::instance()->set_tms_ip (_tms_ip.get_text ()); +} + +void +ConfigDialog::tms_path_changed () +{ + Config::instance()->set_tms_path (_tms_path.get_text ()); +} + +void +ConfigDialog::tms_user_changed () +{ + Config::instance()->set_tms_user (_tms_user.get_text ()); +} + +void +ConfigDialog::tms_password_changed () +{ + Config::instance()->set_tms_password (_tms_password.get_text ()); +} + + +void +ConfigDialog::num_local_encoding_threads_changed () +{ + Config::instance()->set_num_local_encoding_threads (_num_local_encoding_threads.get_value ()); +} + +void +ConfigDialog::colour_lut_changed () +{ + Config::instance()->set_colour_lut_index (_colour_lut.get_active_row_number ()); +} + +void +ConfigDialog::j2k_bandwidth_changed () +{ + Config::instance()->set_j2k_bandwidth (_j2k_bandwidth.get_value() * 1e6); +} + +void +ConfigDialog::on_response (int r) +{ + vector servers; + + Gtk::TreeModel::Children c = _servers_store->children (); + for (Gtk::TreeModel::Children::iterator i = c.begin(); i != c.end(); ++i) { + Gtk::TreeModel::Row r = *i; + Server* s = new Server (r[_servers_columns._host_name], r[_servers_columns._threads]); + servers.push_back (s); + } + + Config::instance()->set_servers (servers); + + vector > screens; + + c = _screens_store->children (); + for (Gtk::TreeModel::Children::iterator i = c.begin(); i != c.end(); ++i) { + + Gtk::TreeModel::Row r = *i; + shared_ptr s (new Screen (r[_screens_columns._name])); + + Gtk::TreeModel::Children cc = r.children (); + for (Gtk::TreeModel::Children::iterator j = cc.begin(); j != cc.end(); ++j) { + Gtk::TreeModel::Row r = *j; + string const x_ = r[_screens_columns._x]; + string const y_ = r[_screens_columns._y]; + string const width_ = r[_screens_columns._width]; + string const height_ = r[_screens_columns._height]; + s->set_geometry ( + Format::from_nickname (r[_screens_columns._format_nickname]), + Position (lexical_cast (x_), lexical_cast (y_)), + Size (lexical_cast (width_), lexical_cast (height_)) + ); + } + + screens.push_back (s); + } + + Config::instance()->set_screens (screens); + + Gtk::Dialog::on_response (r); +} + +void +ConfigDialog::add_server_to_store (Server* s) +{ + Gtk::TreeModel::iterator i = _servers_store->append (); + Gtk::TreeModel::Row r = *i; + r[_servers_columns._host_name] = s->host_name (); + r[_servers_columns._threads] = s->threads (); +} + +void +ConfigDialog::add_server_clicked () +{ + Server s ("localhost", 1); + add_server_to_store (&s); +} + +void +ConfigDialog::remove_server_clicked () +{ + Gtk::TreeModel::iterator i = _servers_view.get_selection()->get_selected (); + if (i) { + _servers_store->erase (i); + } +} + +void +ConfigDialog::server_selection_changed () +{ + Gtk::TreeModel::iterator i = _servers_view.get_selection()->get_selected (); + _remove_server.set_sensitive (i); +} + + +void +ConfigDialog::add_screen_to_store (shared_ptr s) +{ + Gtk::TreeModel::iterator i = _screens_store->append (); + Gtk::TreeModel::Row r = *i; + r[_screens_columns._name] = s->name (); + + Screen::GeometryMap geoms = s->geometries (); + for (Screen::GeometryMap::const_iterator j = geoms.begin(); j != geoms.end(); ++j) { + i = _screens_store->append (r.children ()); + Gtk::TreeModel::Row c = *i; + c[_screens_columns._format_name] = j->first->name (); + c[_screens_columns._format_nickname] = j->first->nickname (); + c[_screens_columns._x] = lexical_cast (j->second.position.x); + c[_screens_columns._y] = lexical_cast (j->second.position.y); + c[_screens_columns._width] = lexical_cast (j->second.size.width); + c[_screens_columns._height] = lexical_cast (j->second.size.height); + } +} + +void +ConfigDialog::add_screen_clicked () +{ + shared_ptr s (new Screen ("New Screen")); + add_screen_to_store (s); +} + +void +ConfigDialog::remove_screen_clicked () +{ + Gtk::TreeModel::iterator i = _screens_view.get_selection()->get_selected (); + if (i) { + _screens_store->erase (i); + } +} + +void +ConfigDialog::screen_selection_changed () +{ + Gtk::TreeModel::iterator i = _screens_view.get_selection()->get_selected (); + _remove_screen.set_sensitive (i); +} + + +void +ConfigDialog::reference_scaler_changed () +{ + int const n = _reference_scaler.get_active_row_number (); + if (n >= 0) { + Config::instance()->set_reference_scaler (Scaler::from_index (n)); + } +} + +void +ConfigDialog::edit_reference_filters_clicked () +{ + FilterDialog d (Config::instance()->reference_filters ()); + d.ActiveChanged.connect (sigc::mem_fun (*this, &ConfigDialog::reference_filters_changed)); + d.run (); +} + +void +ConfigDialog::reference_filters_changed (vector f) +{ + Config::instance()->set_reference_filters (f); + pair p = Filter::ffmpeg_strings (Config::instance()->reference_filters ()); + _reference_filters.set_text (p.first + " " + p.second); +} diff --git a/src/gtk/config_dialog.h b/src/gtk/config_dialog.h new file mode 100644 index 000000000..ec345750a --- /dev/null +++ b/src/gtk/config_dialog.h @@ -0,0 +1,113 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/config_dialog.h + * @brief A dialogue to edit DVD-o-matic configuration. + */ + +#include + +class Screen; +class Server; + +/** @class ConfigDialog + * @brief A dialogue to edit DVD-o-matic configuration. + */ +class ConfigDialog : public Gtk::Dialog +{ +public: + ConfigDialog (); + +private: + void on_response (int); + + void tms_ip_changed (); + void tms_path_changed (); + void tms_user_changed (); + void tms_password_changed (); + void num_local_encoding_threads_changed (); + void colour_lut_changed (); + void j2k_bandwidth_changed (); + void add_server_clicked (); + void remove_server_clicked (); + void server_selection_changed (); + void add_screen_clicked (); + void remove_screen_clicked (); + void screen_selection_changed (); + void reference_scaler_changed (); + void edit_reference_filters_clicked (); + void reference_filters_changed (std::vector); + + void add_screen_to_store (boost::shared_ptr); + void add_server_to_store (Server *); + + struct ServersModelColumns : public Gtk::TreeModelColumnRecord + { + ServersModelColumns () { + add (_host_name); + add (_threads); + } + + Gtk::TreeModelColumn _host_name; + Gtk::TreeModelColumn _threads; + }; + + struct ScreensModelColumns : public Gtk::TreeModelColumnRecord + { + ScreensModelColumns () { + add (_name); + add (_format_name); + add (_format_nickname); + add (_x); + add (_y); + add (_width); + add (_height); + } + + Gtk::TreeModelColumn _name; + Gtk::TreeModelColumn _format_name; + Gtk::TreeModelColumn _format_nickname; + Gtk::TreeModelColumn _x; + Gtk::TreeModelColumn _y; + Gtk::TreeModelColumn _width; + Gtk::TreeModelColumn _height; + }; + + Gtk::Entry _tms_ip; + Gtk::Entry _tms_path; + Gtk::Entry _tms_user; + Gtk::Entry _tms_password; + Gtk::SpinButton _num_local_encoding_threads; + Gtk::ComboBoxText _colour_lut; + Gtk::SpinButton _j2k_bandwidth; + Gtk::ComboBoxText _reference_scaler; + Gtk::Label _reference_filters; + Gtk::Button _reference_filters_button; + ServersModelColumns _servers_columns; + Glib::RefPtr _servers_store; + Gtk::TreeView _servers_view; + Gtk::Button _add_server; + Gtk::Button _remove_server; + ScreensModelColumns _screens_columns; + Glib::RefPtr _screens_store; + Gtk::TreeView _screens_view; + Gtk::Button _add_screen; + Gtk::Button _remove_screen; +}; + diff --git a/src/gtk/dcp_range_dialog.cc b/src/gtk/dcp_range_dialog.cc new file mode 100644 index 000000000..d1fef0e8b --- /dev/null +++ b/src/gtk/dcp_range_dialog.cc @@ -0,0 +1,117 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "dcp_range_dialog.h" +#include "lib/film.h" + +DCPRangeDialog::DCPRangeDialog (Film* f) + : _film (f) + , _whole ("Whole film") + , _first ("First") + , _cut ("Cut remainder") + , _black_out ("Black-out remainder") +{ + set_title ("DCP range"); + + Gtk::Table* table = Gtk::manage (new Gtk::Table ()); + table->set_border_width (6); + table->set_row_spacings (6); + table->set_col_spacings (6); + table->attach (_whole, 0, 4, 0, 1); + table->attach (_first, 0, 1, 1, 2); + table->attach (_n_frames, 1, 2, 1, 2); + table->attach (*manage (new Gtk::Label ("frames")), 2, 3, 1, 2); + table->attach (_cut, 1, 2, 2, 3); + table->attach (_black_out, 1, 2, 3, 4); + + Gtk::RadioButtonGroup g = _whole.get_group (); + _first.set_group (g); + + g = _black_out.get_group (); + _cut.set_group (g); + + _n_frames.set_range (1, INT_MAX - 1); + _n_frames.set_increments (24, 24 * 60); + if (_film->dcp_frames() > 0) { + _whole.set_active (false); + _first.set_active (true); + _n_frames.set_value (_film->dcp_frames ()); + } else { + _whole.set_active (true); + _first.set_active (false); + _n_frames.set_value (24); + } + + _black_out.set_active (_film->dcp_trim_action() == BLACK_OUT); + _cut.set_active (_film->dcp_trim_action() == CUT); + + _whole.signal_toggled().connect (sigc::mem_fun (*this, &DCPRangeDialog::whole_toggled)); + _cut.signal_toggled().connect (sigc::mem_fun (*this, &DCPRangeDialog::cut_toggled)); + _n_frames.signal_value_changed().connect (sigc::mem_fun (*this, &DCPRangeDialog::n_frames_changed)); + + get_vbox()->pack_start (*table); + + add_button ("Close", Gtk::RESPONSE_CLOSE); + show_all_children (); + + set_sensitivity (); +} + +void +DCPRangeDialog::whole_toggled () +{ + set_sensitivity (); + emit_changed (); +} + +void +DCPRangeDialog::set_sensitivity () +{ + _n_frames.set_sensitive (_first.get_active ()); + _black_out.set_sensitive (_first.get_active ()); + _cut.set_sensitive (_first.get_active ()); +} + +void +DCPRangeDialog::cut_toggled () +{ + emit_changed (); +} + +void +DCPRangeDialog::n_frames_changed () +{ + emit_changed (); +} + +void +DCPRangeDialog::emit_changed () +{ + int frames = 0; + if (!_whole.get_active ()) { + frames = _n_frames.get_value_as_int (); + } + + TrimAction action = CUT; + if (_black_out.get_active ()) { + action = BLACK_OUT; + } + + Changed (frames, action); +} diff --git a/src/gtk/dcp_range_dialog.h b/src/gtk/dcp_range_dialog.h new file mode 100644 index 000000000..7469a2576 --- /dev/null +++ b/src/gtk/dcp_range_dialog.h @@ -0,0 +1,46 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include "lib/trim_action.h" + +class Film; + +class DCPRangeDialog : public Gtk::Dialog +{ +public: + DCPRangeDialog (Film *); + + sigc::signal2 Changed; + +private: + void whole_toggled (); + void cut_toggled (); + void n_frames_changed (); + + void set_sensitivity (); + void emit_changed (); + + Film* _film; + Gtk::RadioButton _whole; + Gtk::RadioButton _first; + Gtk::SpinButton _n_frames; + Gtk::RadioButton _cut; + Gtk::RadioButton _black_out; +}; diff --git a/src/gtk/film_editor.cc b/src/gtk/film_editor.cc new file mode 100644 index 000000000..0e410aa6b --- /dev/null +++ b/src/gtk/film_editor.cc @@ -0,0 +1,572 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/film_editor.cc + * @brief A GTK widget to edit a film's metadata, and perform various functions. + */ + +#include +#include +#include +#include +#include "lib/format.h" +#include "lib/film.h" +#include "lib/transcode_job.h" +#include "lib/exceptions.h" +#include "lib/ab_transcode_job.h" +#include "lib/thumbs_job.h" +#include "lib/make_mxf_job.h" +#include "lib/job_manager.h" +#include "lib/filter.h" +#include "lib/screen.h" +#include "lib/config.h" +#include "lib/scp_dcp_job.h" +#include "filter_dialog.h" +#include "gtk_util.h" +#include "film_editor.h" +#include "dcp_range_dialog.h" + +using namespace std; +using namespace boost; +using namespace Gtk; + +/** @param f Film to edit */ +FilmEditor::FilmEditor (Film* f) + : _film (f) + , _filters_button ("Edit...") + , _change_dcp_range_button ("Edit...") + , _dcp_ab ("A/B") +{ + _vbox.set_border_width (12); + _vbox.set_spacing (12); + + /* Set up our editing widgets */ + _left_crop.set_range (0, 1024); + _left_crop.set_increments (1, 16); + _top_crop.set_range (0, 1024); + _top_crop.set_increments (1, 16); + _right_crop.set_range (0, 1024); + _right_crop.set_increments (1, 16); + _bottom_crop.set_range (0, 1024); + _bottom_crop.set_increments (1, 16); + _filters.set_alignment (0, 0.5); + _audio_gain.set_range (-60, 60); + _audio_gain.set_increments (1, 3); + _audio_delay.set_range (-1000, 1000); + _audio_delay.set_increments (1, 20); + _still_duration.set_range (0, 60 * 60); + _still_duration.set_increments (1, 5); + _dcp_range.set_alignment (0, 0.5); + + vector fmt = Format::all (); + for (vector::iterator i = fmt.begin(); i != fmt.end(); ++i) { + _format.append_text ((*i)->name ()); + } + + _frames_per_second.set_increments (1, 5); + _frames_per_second.set_digits (2); + _frames_per_second.set_range (0, 60); + + vector const ct = DCPContentType::all (); + for (vector::const_iterator i = ct.begin(); i != ct.end(); ++i) { + _dcp_content_type.append_text ((*i)->pretty_name ()); + } + + vector const sc = Scaler::all (); + for (vector::const_iterator i = sc.begin(); i != sc.end(); ++i) { + _scaler.append_text ((*i)->name ()); + } + + _original_size.set_alignment (0, 0.5); + _length.set_alignment (0, 0.5); + _audio.set_alignment (0, 0.5); + + /* And set their values from the Film */ + set_film (f); + + /* Now connect to them, since initial values are safely set */ + _name.signal_changed().connect (sigc::mem_fun (*this, &FilmEditor::name_changed)); + _frames_per_second.signal_changed().connect (sigc::mem_fun (*this, &FilmEditor::frames_per_second_changed)); + _format.signal_changed().connect (sigc::mem_fun (*this, &FilmEditor::format_changed)); + _content.signal_file_set().connect (sigc::mem_fun (*this, &FilmEditor::content_changed)); + _left_crop.signal_value_changed().connect (sigc::mem_fun (*this, &FilmEditor::left_crop_changed)); + _right_crop.signal_value_changed().connect (sigc::mem_fun (*this, &FilmEditor::right_crop_changed)); + _top_crop.signal_value_changed().connect (sigc::mem_fun (*this, &FilmEditor::top_crop_changed)); + _bottom_crop.signal_value_changed().connect (sigc::mem_fun (*this, &FilmEditor::bottom_crop_changed)); + _filters_button.signal_clicked().connect (sigc::mem_fun (*this, &FilmEditor::edit_filters_clicked)); + _scaler.signal_changed().connect (sigc::mem_fun (*this, &FilmEditor::scaler_changed)); + _dcp_content_type.signal_changed().connect (sigc::mem_fun (*this, &FilmEditor::dcp_content_type_changed)); + _dcp_ab.signal_toggled().connect (sigc::mem_fun (*this, &FilmEditor::dcp_ab_toggled)); + _audio_gain.signal_value_changed().connect (sigc::mem_fun (*this, &FilmEditor::audio_gain_changed)); + _audio_delay.signal_value_changed().connect (sigc::mem_fun (*this, &FilmEditor::audio_delay_changed)); + _still_duration.signal_value_changed().connect (sigc::mem_fun (*this, &FilmEditor::still_duration_changed)); + _change_dcp_range_button.signal_clicked().connect (sigc::mem_fun (*this, &FilmEditor::change_dcp_range_clicked)); + + /* Set up the table */ + + Table* t = manage (new Table); + + t->set_row_spacings (4); + t->set_col_spacings (12); + + int n = 0; + t->attach (left_aligned_label ("Name"), 0, 1, n, n + 1); + t->attach (_name, 1, 2, n, n + 1); + ++n; + t->attach (left_aligned_label ("Content"), 0, 1, n, n + 1); + t->attach (_content, 1, 2, n, n + 1); + ++n; + t->attach (left_aligned_label ("Content Type"), 0, 1, n, n + 1); + t->attach (_dcp_content_type, 1, 2, n, n + 1); + ++n; + t->attach (video_widget (left_aligned_label ("Frames Per Second")), 0, 1, n, n + 1); + t->attach (video_widget (_frames_per_second), 1, 2, n, n + 1); + ++n; + t->attach (left_aligned_label ("Format"), 0, 1, n, n + 1); + t->attach (_format, 1, 2, n, n + 1); + ++n; + t->attach (left_aligned_label ("Crop"), 0, 1, n, n + 1); + HBox* c = manage (new HBox); + c->set_spacing (4); + c->pack_start (left_aligned_label ("L"), false, false); + c->pack_start (_left_crop, true, true); + c->pack_start (left_aligned_label ("R"), false, false); + c->pack_start (_right_crop, true, true); + c->pack_start (left_aligned_label ("T"), false, false); + c->pack_start (_top_crop, true, true); + c->pack_start (left_aligned_label ("B"), false, false); + c->pack_start (_bottom_crop, true, true); + t->attach (*c, 1, 2, n, n + 1); + ++n; + + /* VIDEO-only stuff */ + int const special = n; + t->attach (video_widget (left_aligned_label ("Filters")), 0, 1, n, n + 1); + HBox* fb = manage (new HBox); + fb->set_spacing (4); + fb->pack_start (video_widget (_filters), true, true); + fb->pack_start (video_widget (_filters_button), false, false); + t->attach (*fb, 1, 2, n, n + 1); + ++n; + t->attach (video_widget (left_aligned_label ("Scaler")), 0, 1, n, n + 1); + t->attach (video_widget (_scaler), 1, 2, n, n + 1); + ++n; + t->attach (video_widget (left_aligned_label ("Audio Gain")), 0, 1, n, n + 1); + t->attach (video_widget (_audio_gain), 1, 2, n, n + 1); + t->attach (video_widget (left_aligned_label ("dB")), 2, 3, n, n + 1); + ++n; + t->attach (video_widget (left_aligned_label ("Audio Delay")), 0, 1, n, n + 1); + t->attach (video_widget (_audio_delay), 1, 2, n, n + 1); + t->attach (video_widget (left_aligned_label ("ms")), 2, 3, n, n + 1); + ++n; + t->attach (video_widget (left_aligned_label ("Original Size")), 0, 1, n, n + 1); + t->attach (video_widget (_original_size), 1, 2, n, n + 1); + ++n; + t->attach (video_widget (left_aligned_label ("Length")), 0, 1, n, n + 1); + t->attach (video_widget (_length), 1, 2, n, n + 1); + ++n; + t->attach (video_widget (left_aligned_label ("Audio")), 0, 1, n, n + 1); + t->attach (video_widget (_audio), 1, 2, n, n + 1); + ++n; + t->attach (video_widget (left_aligned_label ("Range")), 0, 1, n, n + 1); + Gtk::HBox* db = manage (new Gtk::HBox); + db->pack_start (_dcp_range, true, true); + db->pack_start (_change_dcp_range_button, false, false); + t->attach (*db, 1, 2, n, n + 1); + ++n; + t->attach (_dcp_ab, 0, 3, n, n + 1); + + /* STILL-only stuff */ + n = special; + t->attach (still_widget (left_aligned_label ("Duration")), 0, 1, n, n + 1); + t->attach (still_widget (_still_duration), 1, 2, n, n + 1); + t->attach (still_widget (left_aligned_label ("s")), 2, 3, n, n + 1); + ++n; + + t->show_all (); + _vbox.pack_start (*t, false, false); +} + +/** @return Our main widget, which contains everything else */ +Widget& +FilmEditor::widget () +{ + return _vbox; +} + +/** Called when the left crop widget has been changed */ +void +FilmEditor::left_crop_changed () +{ + if (_film) { + _film->set_left_crop (_left_crop.get_value ()); + } +} + +/** Called when the right crop widget has been changed */ +void +FilmEditor::right_crop_changed () +{ + if (_film) { + _film->set_right_crop (_right_crop.get_value ()); + } +} + +/** Called when the top crop widget has been changed */ +void +FilmEditor::top_crop_changed () +{ + if (_film) { + _film->set_top_crop (_top_crop.get_value ()); + } +} + +/** Called when the bottom crop value has been changed */ +void +FilmEditor::bottom_crop_changed () +{ + if (_film) { + _film->set_bottom_crop (_bottom_crop.get_value ()); + } +} + +/** Called when the content filename has been changed */ +void +FilmEditor::content_changed () +{ + if (!_film) { + return; + } + + try { + _film->set_content (_content.get_filename ()); + } catch (std::exception& e) { + _content.set_filename (_film->directory ()); + stringstream m; + m << "Could not set content: " << e.what() << "."; + Gtk::MessageDialog d (m.str(), false, MESSAGE_ERROR); + d.set_title ("DVD-o-matic"); + d.run (); + } +} + +/** Called when the DCP A/B switch has been toggled */ +void +FilmEditor::dcp_ab_toggled () +{ + if (_film) { + _film->set_dcp_ab (_dcp_ab.get_active ()); + } +} + +/** Called when the name widget has been changed */ +void +FilmEditor::name_changed () +{ + if (_film) { + _film->set_name (_name.get_text ()); + } +} + +/** Called when the metadata stored in the Film object has changed; + * so that we can update the GUI. + * @param p Property of the Film that has changed. + */ +void +FilmEditor::film_changed (Film::Property p) +{ + if (!_film) { + return; + } + + stringstream s; + + switch (p) { + case Film::CONTENT: + _content.set_filename (_film->content ()); + setup_visibility (); + break; + case Film::FORMAT: + _format.set_active (Format::as_index (_film->format ())); + break; + case Film::LEFT_CROP: + _left_crop.set_value (_film->left_crop ()); + break; + case Film::RIGHT_CROP: + _right_crop.set_value (_film->right_crop ()); + break; + case Film::TOP_CROP: + _top_crop.set_value (_film->top_crop ()); + break; + case Film::BOTTOM_CROP: + _bottom_crop.set_value (_film->bottom_crop ()); + break; + case Film::FILTERS: + { + pair p = Filter::ffmpeg_strings (_film->filters ()); + _filters.set_text (p.first + " " + p.second); + break; + } + case Film::NAME: + _name.set_text (_film->name ()); + break; + case Film::FRAMES_PER_SECOND: + _frames_per_second.set_value (_film->frames_per_second ()); + break; + case Film::AUDIO_CHANNELS: + case Film::AUDIO_SAMPLE_RATE: + s << _film->audio_channels () << " channels, " << _film->audio_sample_rate() << "Hz"; + _audio.set_text (s.str ()); + break; + case Film::SIZE: + s << _film->size().width << " x " << _film->size().height; + _original_size.set_text (s.str ()); + break; + case Film::LENGTH: + if (_film->frames_per_second() > 0) { + s << _film->length() << " frames; " << seconds_to_hms (_film->length() / _film->frames_per_second()); + } else { + s << _film->length() << " frames"; + } + _length.set_text (s.str ()); + break; + case Film::DCP_CONTENT_TYPE: + _dcp_content_type.set_active (DCPContentType::as_index (_film->dcp_content_type ())); + break; + case Film::THUMBS: + break; + case Film::DCP_FRAMES: + if (_film->dcp_frames() == 0) { + _dcp_range.set_text ("Whole film"); + } else { + stringstream s; + s << "First " << _film->dcp_frames() << " frames"; + _dcp_range.set_text (s.str ()); + } + break; + case Film::DCP_TRIM_ACTION: + break; + case Film::DCP_AB: + _dcp_ab.set_active (_film->dcp_ab ()); + break; + case Film::SCALER: + _scaler.set_active (Scaler::as_index (_film->scaler ())); + break; + case Film::AUDIO_GAIN: + _audio_gain.set_value (_film->audio_gain ()); + break; + case Film::AUDIO_DELAY: + _audio_delay.set_value (_film->audio_delay ()); + break; + case Film::STILL_DURATION: + _still_duration.set_value (_film->still_duration ()); + break; + } +} + +/** Called when the format widget has been changed */ +void +FilmEditor::format_changed () +{ + if (_film) { + int const n = _format.get_active_row_number (); + if (n >= 0) { + _film->set_format (Format::from_index (n)); + } + } +} + +/** Called when the DCP content type widget has been changed */ +void +FilmEditor::dcp_content_type_changed () +{ + if (_film) { + int const n = _dcp_content_type.get_active_row_number (); + if (n >= 0) { + _film->set_dcp_content_type (DCPContentType::from_index (n)); + } + } +} + +/** Sets the Film that we are editing */ +void +FilmEditor::set_film (Film* f) +{ + _film = f; + + set_things_sensitive (_film != 0); + + if (_film) { + _film->Changed.connect (sigc::mem_fun (*this, &FilmEditor::film_changed)); + } + + if (_film) { + FileChanged (_film->directory ()); + } else { + FileChanged (""); + } + + film_changed (Film::NAME); + film_changed (Film::CONTENT); + film_changed (Film::DCP_CONTENT_TYPE); + film_changed (Film::FORMAT); + film_changed (Film::LEFT_CROP); + film_changed (Film::RIGHT_CROP); + film_changed (Film::TOP_CROP); + film_changed (Film::BOTTOM_CROP); + film_changed (Film::FILTERS); + film_changed (Film::DCP_FRAMES); + film_changed (Film::DCP_TRIM_ACTION); + film_changed (Film::DCP_AB); + film_changed (Film::SIZE); + film_changed (Film::LENGTH); + film_changed (Film::FRAMES_PER_SECOND); + film_changed (Film::AUDIO_CHANNELS); + film_changed (Film::AUDIO_SAMPLE_RATE); + film_changed (Film::SCALER); + film_changed (Film::AUDIO_GAIN); + film_changed (Film::AUDIO_DELAY); + film_changed (Film::STILL_DURATION); +} + +/** Updates the sensitivity of lots of widgets to a given value. + * @param s true to make sensitive, false to make insensitive. + */ +void +FilmEditor::set_things_sensitive (bool s) +{ + _name.set_sensitive (s); + _frames_per_second.set_sensitive (s); + _format.set_sensitive (s); + _content.set_sensitive (s); + _left_crop.set_sensitive (s); + _right_crop.set_sensitive (s); + _top_crop.set_sensitive (s); + _bottom_crop.set_sensitive (s); + _filters_button.set_sensitive (s); + _scaler.set_sensitive (s); + _dcp_content_type.set_sensitive (s); + _dcp_range.set_sensitive (s); + _change_dcp_range_button.set_sensitive (s); + _dcp_ab.set_sensitive (s); + _audio_gain.set_sensitive (s); + _audio_delay.set_sensitive (s); + _still_duration.set_sensitive (s); +} + +/** Called when the `Edit filters' button has been clicked */ +void +FilmEditor::edit_filters_clicked () +{ + FilterDialog d (_film->filters ()); + d.ActiveChanged.connect (sigc::mem_fun (*_film, &Film::set_filters)); + d.run (); +} + +/** Called when the scaler widget has been changed */ +void +FilmEditor::scaler_changed () +{ + if (_film) { + int const n = _scaler.get_active_row_number (); + if (n >= 0) { + _film->set_scaler (Scaler::from_index (n)); + } + } +} + +/** Called when the frames per second widget has been changed */ +void +FilmEditor::frames_per_second_changed () +{ + if (_film) { + _film->set_frames_per_second (_frames_per_second.get_value ()); + } +} + +void +FilmEditor::audio_gain_changed () +{ + if (_film) { + _film->set_audio_gain (_audio_gain.get_value ()); + } +} + +void +FilmEditor::audio_delay_changed () +{ + if (_film) { + _film->set_audio_delay (_audio_delay.get_value ()); + } +} + +Widget& +FilmEditor::video_widget (Widget& w) +{ + _video_widgets.push_back (&w); + return w; +} + +Widget& +FilmEditor::still_widget (Widget& w) +{ + _still_widgets.push_back (&w); + return w; +} + +void +FilmEditor::setup_visibility () +{ + if (!_film) { + return; + } + + ContentType const c = _film->content_type (); + + for (list::iterator i = _video_widgets.begin(); i != _video_widgets.end(); ++i) { + (*i)->property_visible() = (c == VIDEO); + } + + for (list::iterator i = _still_widgets.begin(); i != _still_widgets.end(); ++i) { + (*i)->property_visible() = (c == STILL); + } +} + +void +FilmEditor::still_duration_changed () +{ + if (_film) { + _film->set_still_duration (_still_duration.get_value ()); + } +} + +void +FilmEditor::change_dcp_range_clicked () +{ + DCPRangeDialog d (_film); + d.Changed.connect (sigc::mem_fun (*this, &FilmEditor::dcp_range_changed)); + d.run (); +} + +void +FilmEditor::dcp_range_changed (int frames, TrimAction action) +{ + _film->set_dcp_frames (frames); + _film->set_dcp_trim_action (action); +} diff --git a/src/gtk/film_editor.h b/src/gtk/film_editor.h new file mode 100644 index 000000000..9d15b436d --- /dev/null +++ b/src/gtk/film_editor.h @@ -0,0 +1,125 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/film_editor.h + * @brief A GTK widget to edit a film's metadata, and perform various functions. + */ + +#include + +class Film; + +/** @class FilmEditor + * @brief A GTK widget to edit a film's metadata, and perform various functions. + */ +class FilmEditor +{ +public: + FilmEditor (Film *); + + Gtk::Widget& widget (); + + void set_film (Film *); + void setup_visibility (); + + sigc::signal1 FileChanged; + +private: + /* Handle changes to the view */ + void name_changed (); + void left_crop_changed (); + void right_crop_changed (); + void top_crop_changed (); + void bottom_crop_changed (); + void content_changed (); + void frames_per_second_changed (); + void format_changed (); + void dcp_range_changed (int, TrimAction); + void dcp_content_type_changed (); + void dcp_ab_toggled (); + void scaler_changed (); + void audio_gain_changed (); + void audio_delay_changed (); + void still_duration_changed (); + + /* Handle changes to the model */ + void film_changed (Film::Property); + + /* Button clicks */ + void edit_filters_clicked (); + void change_dcp_range_clicked (); + + void set_things_sensitive (bool); + + Gtk::Widget & video_widget (Gtk::Widget &); + Gtk::Widget & still_widget (Gtk::Widget &); + + /** The film we are editing */ + Film* _film; + /** The overall VBox containing our widget */ + Gtk::VBox _vbox; + /** The Film's name */ + Gtk::Entry _name; + /** The Film's frames per second */ + Gtk::SpinButton _frames_per_second; + /** The Film's format */ + Gtk::ComboBoxText _format; + /** The Film's content file */ + Gtk::FileChooserButton _content; + /** The Film's left crop */ + Gtk::SpinButton _left_crop; + /** The Film's right crop */ + Gtk::SpinButton _right_crop; + /** The Film's top crop */ + Gtk::SpinButton _top_crop; + /** The Film's bottom crop */ + Gtk::SpinButton _bottom_crop; + /** Currently-applied filters */ + Gtk::Label _filters; + /** Button to open the filters dialogue */ + Gtk::Button _filters_button; + /** The Film's scaler */ + Gtk::ComboBoxText _scaler; + /** The Film's audio gain */ + Gtk::SpinButton _audio_gain; + /** The Film's audio delay */ + Gtk::SpinButton _audio_delay; + /** The Film's DCP content type */ + Gtk::ComboBoxText _dcp_content_type; + /** The Film's original size */ + Gtk::Label _original_size; + /** The Film's length */ + Gtk::Label _length; + /** The Film's audio details */ + Gtk::Label _audio; + /** The Film's duration for still sources */ + Gtk::SpinButton _still_duration; + + /** Button to start making a DCP from existing J2K and WAV files */ + Gtk::Button _make_dcp_from_existing_button; + /** Display of the range of frames that will be used */ + Gtk::Label _dcp_range; + /** Button to change the range */ + Gtk::Button _change_dcp_range_button; + /** Selector to generate an A/B comparison DCP */ + Gtk::CheckButton _dcp_ab; + + std::list _video_widgets; + std::list _still_widgets; +}; diff --git a/src/gtk/film_list.cc b/src/gtk/film_list.cc new file mode 100644 index 000000000..1a9854450 --- /dev/null +++ b/src/gtk/film_list.cc @@ -0,0 +1,65 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include "lib/film.h" +#include "film_list.h" + +using namespace std; +using namespace boost; + +FilmList::FilmList (string d) + : _directory (d) + , _list (1) +{ + for (filesystem::directory_iterator i = filesystem::directory_iterator (_directory); i != filesystem::directory_iterator(); ++i) { + if (is_directory (*i)) { + filesystem::path m = filesystem::path (*i) / filesystem::path ("metadata"); + if (is_regular_file (m)) { + Film* f = new Film (i->path().string()); + _films.push_back (f); + } + } + } + + for (vector::iterator i = _films.begin(); i != _films.end(); ++i) { + _list.append_text ((*i)->name ()); + } + + _list.set_headers_visible (false); + _list.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &FilmList::selection_changed)); +} + +Gtk::Widget& +FilmList::widget () +{ + return _list; +} + +void +FilmList::selection_changed () +{ + Gtk::ListViewText::SelectionList s = _list.get_selected (); + if (s.empty ()) { + return; + } + + assert (s[0] < int (_films.size ())); + SelectionChanged (_films[s[0]]); +} diff --git a/src/gtk/film_list.h b/src/gtk/film_list.h new file mode 100644 index 000000000..5a4ac3cc1 --- /dev/null +++ b/src/gtk/film_list.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include + +class Film; + +class FilmList +{ +public: + FilmList (std::string); + + Gtk::Widget& widget (); + + sigc::signal SelectionChanged; + +private: + void selection_changed (); + + std::string _directory; + std::vector _films; + Gtk::ListViewText _list; +}; diff --git a/src/gtk/film_player.cc b/src/gtk/film_player.cc new file mode 100644 index 000000000..63e6e49ee --- /dev/null +++ b/src/gtk/film_player.cc @@ -0,0 +1,310 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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/screen.h" +#include "lib/config.h" +#include "lib/player_manager.h" +#include "lib/film.h" +#include "film_player.h" +#include "gtk_util.h" + +using namespace std; +using namespace boost; + +FilmPlayer::FilmPlayer (Film const * f) + : _play ("Play") + , _pause ("Pause") + , _stop ("Stop") + , _ab ("A/B") + , _ignore_position_changed (false) +{ + set_film (f); + + vector > const scr = Config::instance()->screens (); + for (vector >::const_iterator i = scr.begin(); i != scr.end(); ++i) { + _screen.append_text ((*i)->name ()); + } + + if (!scr.empty ()) { + _screen.set_active (0); + } + + _status.set_use_markup (true); + + _position.set_digits (0); + + _main_vbox.set_spacing (12); + + Gtk::HBox* l = manage (new Gtk::HBox); + l->pack_start (_play); + l->pack_start (_pause); + l->pack_start (_stop); + + Gtk::VBox* r = manage (new Gtk::VBox); + r->pack_start (_screen, false, false); + r->pack_start (_ab, false, false); + r->pack_start (*manage (new Gtk::Label ("")), true, true); + + Gtk::HBox* t = manage (new Gtk::HBox); + t->pack_start (*l, true, true); + t->pack_start (*r, false, false); + + _main_vbox.pack_start (*t, true, true); + _main_vbox.pack_start (_position, false, false); + _main_vbox.pack_start (_status, false, false); + + _play.signal_clicked().connect (sigc::mem_fun (*this, &FilmPlayer::play_clicked)); + _pause.signal_clicked().connect (sigc::mem_fun (*this, &FilmPlayer::pause_clicked)); + _stop.signal_clicked().connect (sigc::mem_fun (*this, &FilmPlayer::stop_clicked)); + _position.signal_value_changed().connect (sigc::mem_fun (*this, &FilmPlayer::position_changed)); + _position.signal_format_value().connect (sigc::mem_fun (*this, &FilmPlayer::format_position)); + + set_button_states (); + Glib::signal_timeout().connect (sigc::bind_return (sigc::mem_fun (*this, &FilmPlayer::update), true), 1000); + + Config::instance()->Changed.connect (sigc::mem_fun (*this, &FilmPlayer::update_screens)); +} + +void +FilmPlayer::set_film (Film const * f) +{ + _film = f; + + if (_film && _film->length() != 0 && _film->frames_per_second() != 0) { + _position.set_range (0, _film->length() / _film->frames_per_second()); + } + + if (_film) { + _film->Changed.connect (sigc::mem_fun (*this, &FilmPlayer::film_changed)); + } +} + +Gtk::Widget & +FilmPlayer::widget () +{ + return _main_vbox; +} + +void +FilmPlayer::set_button_states () +{ + if (_film == 0) { + _play.set_sensitive (false); + _pause.set_sensitive (false); + _stop.set_sensitive (false); + _screen.set_sensitive (false); + _position.set_sensitive (false); + _ab.set_sensitive (false); + return; + } + + PlayerManager::State s = PlayerManager::instance()->state (); + + switch (s) { + case PlayerManager::QUIESCENT: + _play.set_sensitive (true); + _pause.set_sensitive (false); + _stop.set_sensitive (false); + _screen.set_sensitive (true); + _position.set_sensitive (false); + _ab.set_sensitive (true); + break; + case PlayerManager::PLAYING: + _play.set_sensitive (false); + _pause.set_sensitive (true); + _stop.set_sensitive (true); + _screen.set_sensitive (false); + _position.set_sensitive (true); + _ab.set_sensitive (false); + break; + case PlayerManager::PAUSED: + _play.set_sensitive (true); + _pause.set_sensitive (false); + _stop.set_sensitive (true); + _screen.set_sensitive (false); + _position.set_sensitive (false); + _ab.set_sensitive (false); + break; + } +} + +void +FilmPlayer::play_clicked () +{ + PlayerManager* p = PlayerManager::instance (); + + switch (p->state ()) { + case PlayerManager::QUIESCENT: + _last_play_fs = _film->state_copy (); + if (_ab.get_active ()) { + shared_ptr fs_a = _film->state_copy (); + fs_a->filters.clear (); + /* This is somewhat arbitrary, but hey ho */ + fs_a->scaler = Scaler::from_id ("bicubic"); + p->setup (fs_a, _last_play_fs, screen ()); + } else { + p->setup (_last_play_fs, screen ()); + } + p->pause_or_unpause (); + break; + case PlayerManager::PLAYING: + break; + case PlayerManager::PAUSED: + p->pause_or_unpause (); + break; + } +} + +void +FilmPlayer::pause_clicked () +{ + PlayerManager* p = PlayerManager::instance (); + + switch (p->state ()) { + case PlayerManager::QUIESCENT: + break; + case PlayerManager::PLAYING: + p->pause_or_unpause (); + break; + case PlayerManager::PAUSED: + break; + } +} + +void +FilmPlayer::stop_clicked () +{ + PlayerManager::instance()->stop (); +} + +shared_ptr +FilmPlayer::screen () const +{ + vector > const s = Config::instance()->screens (); + if (s.empty ()) { + return shared_ptr (); + } + + int const r = _screen.get_active_row_number (); + if (r >= int (s.size ())) { + return s[0]; + } + + return s[r]; +} + +void +FilmPlayer::update () +{ + set_button_states (); + set_status (); +} + +void +FilmPlayer::set_status () +{ + PlayerManager::State s = PlayerManager::instance()->state (); + + stringstream m; + switch (s) { + case PlayerManager::QUIESCENT: + m << "Idle"; + break; + case PlayerManager::PLAYING: + m << "PLAYING"; + break; + case PlayerManager::PAUSED: + m << "Paused"; + break; + } + + _ignore_position_changed = true; + + if (s != PlayerManager::QUIESCENT) { + float const p = PlayerManager::instance()->position (); + if (_last_play_fs->frames_per_second != 0 && _last_play_fs->length != 0) { + m << " (" << seconds_to_hms (_last_play_fs->length / _last_play_fs->frames_per_second - p) << " remaining)"; + } + + _position.set_value (p); + } else { + _position.set_value (0); + } + + _ignore_position_changed = false; + + _status.set_markup (m.str ()); +} + +void +FilmPlayer::position_changed () +{ + if (_ignore_position_changed) { + return; + } + + PlayerManager::instance()->set_position (_position.get_value ()); +} + +string +FilmPlayer::format_position (double v) +{ + return seconds_to_hms (v); +} + +void +FilmPlayer::update_screens () +{ + string const c = _screen.get_active_text (); + + _screen.clear (); + + vector > const scr = Config::instance()->screens (); + bool have_last_active_text = false; + for (vector >::const_iterator i = scr.begin(); i != scr.end(); ++i) { + _screen.append_text ((*i)->name ()); + if ((*i)->name() == c) { + have_last_active_text = true; + } + } + + if (have_last_active_text) { + _screen.set_active_text (c); + } else if (!scr.empty ()) { + _screen.set_active (0); + } +} + +void +FilmPlayer::film_changed (Film::Property p) +{ + if (p == Film::CONTENT) { + setup_visibility (); + } +} + +void +FilmPlayer::setup_visibility () +{ + if (!_film) { + return; + } + + widget().property_visible() = (_film->content_type() == VIDEO); +} diff --git a/src/gtk/film_player.h b/src/gtk/film_player.h new file mode 100644 index 000000000..bb60eeb3b --- /dev/null +++ b/src/gtk/film_player.h @@ -0,0 +1,63 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include "lib/film.h" + +class Film; +class Screen; +class FilmState; + +class FilmPlayer +{ +public: + FilmPlayer (Film const * f = 0); + + Gtk::Widget& widget (); + + void set_film (Film const *); + void setup_visibility (); + +private: + void play_clicked (); + void pause_clicked (); + void stop_clicked (); + void position_changed (); + std::string format_position (double); + void film_changed (Film::Property); + + void set_button_states (); + boost::shared_ptr screen () const; + void set_status (); + void update (); + void update_screens (); + + Film const * _film; + boost::shared_ptr _last_play_fs; + + Gtk::VBox _main_vbox; + Gtk::Button _play; + Gtk::Button _pause; + Gtk::Button _stop; + Gtk::Label _status; + Gtk::CheckButton _ab; + Gtk::ComboBoxText _screen; + Gtk::HScale _position; + bool _ignore_position_changed; +}; diff --git a/src/gtk/film_viewer.cc b/src/gtk/film_viewer.cc new file mode 100644 index 000000000..0408d50b8 --- /dev/null +++ b/src/gtk/film_viewer.cc @@ -0,0 +1,224 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/film_viewer.cc + * @brief A GTK widget to view `thumbnails' of a Film. + */ + +#include +#include +#include "lib/film.h" +#include "lib/format.h" +#include "lib/util.h" +#include "lib/thumbs_job.h" +#include "lib/job_manager.h" +#include "lib/film_state.h" +#include "lib/options.h" +#include "film_viewer.h" + +using namespace std; +using namespace boost; + +FilmViewer::FilmViewer (Film* f) + : _film (f) + , _update_button ("Update") +{ + _scroller.add (_image); + + Gtk::HBox* controls = manage (new Gtk::HBox); + controls->set_spacing (6); + controls->pack_start (_update_button, false, false); + controls->pack_start (_position_slider); + + _vbox.pack_start (_scroller, true, true); + _vbox.pack_start (*controls, false, false); + _vbox.set_border_width (12); + + _update_button.signal_clicked().connect (sigc::mem_fun (*this, &FilmViewer::update_thumbs)); + + _position_slider.set_digits (0); + _position_slider.signal_format_value().connect (sigc::mem_fun (*this, &FilmViewer::format_position_slider_value)); + _position_slider.signal_value_changed().connect (sigc::mem_fun (*this, &FilmViewer::position_slider_changed)); + + _scroller.signal_size_allocate().connect (sigc::mem_fun (*this, &FilmViewer::scroller_size_allocate)); + + set_film (_film); +} + +void +FilmViewer::load_thumbnail (int n) +{ + if (_film == 0 || _film->num_thumbs() <= n) { + return; + } + + int const left = _film->left_crop (); + int const right = _film->right_crop (); + int const top = _film->top_crop (); + int const bottom = _film->bottom_crop (); + + _pixbuf = Gdk::Pixbuf::create_from_file (_film->thumb_file (n)); + + int const cw = _film->size().width - left - right; + int const ch = _film->size().height - top - bottom; + _cropped_pixbuf = Gdk::Pixbuf::create_subpixbuf (_pixbuf, left, top, cw, ch); + update_scaled_pixbuf (); + _image.set (_scaled_pixbuf); +} + +void +FilmViewer::reload_current_thumbnail () +{ + load_thumbnail (_position_slider.get_value ()); +} + +void +FilmViewer::position_slider_changed () +{ + reload_current_thumbnail (); +} + +string +FilmViewer::format_position_slider_value (double v) const +{ + stringstream s; + + if (_film && int (v) < _film->num_thumbs ()) { + int const f = _film->thumb_frame (int (v)); + s << f << " " << seconds_to_hms (f / _film->frames_per_second ()); + } else { + s << "-"; + } + + return s.str (); +} + +void +FilmViewer::film_changed (Film::Property p) +{ + if (p == Film::LEFT_CROP || p == Film::RIGHT_CROP || p == Film::TOP_CROP || p == Film::BOTTOM_CROP) { + reload_current_thumbnail (); + } else if (p == Film::THUMBS) { + if (_film && _film->num_thumbs() > 1) { + _position_slider.set_range (0, _film->num_thumbs () - 1); + } else { + _image.clear (); + _position_slider.set_range (0, 1); + } + + _position_slider.set_value (0); + reload_current_thumbnail (); + } else if (p == Film::FORMAT) { + reload_current_thumbnail (); + } else if (p == Film::CONTENT) { + setup_visibility (); + update_thumbs (); + } +} + +void +FilmViewer::set_film (Film* f) +{ + _film = f; + + _update_button.set_sensitive (_film != 0); + + if (!_film) { + _image.clear (); + return; + } + + _film->Changed.connect (sigc::mem_fun (*this, &FilmViewer::film_changed)); + + film_changed (Film::THUMBS); +} + +pair +FilmViewer::scaled_pixbuf_size () const +{ + if (_film == 0) { + return make_pair (0, 0); + } + + int const cw = _film->size().width - _film->left_crop() - _film->right_crop(); + int const ch = _film->size().height - _film->top_crop() - _film->bottom_crop(); + + float ratio = 1; + if (_film->format()) { + ratio = _film->format()->ratio_as_float() * ch / cw; + } + + Gtk::Allocation const a = _scroller.get_allocation (); + float const zoom = min (float (a.get_width()) / (cw * ratio), float (a.get_height()) / cw); + return make_pair (cw * zoom * ratio, ch * zoom); +} + +void +FilmViewer::update_scaled_pixbuf () +{ + pair const s = scaled_pixbuf_size (); + + if (s.first > 0 && s.second > 0 && _cropped_pixbuf) { + _scaled_pixbuf = _cropped_pixbuf->scale_simple (s.first, s.second, Gdk::INTERP_HYPER); + _image.set (_scaled_pixbuf); + } +} + +void +FilmViewer::update_thumbs () +{ + if (!_film) { + return; + } + + _film->update_thumbs_pre_gui (); + + shared_ptr s = _film->state_copy (); + shared_ptr o (new Options (s->dir ("thumbs"), ".tiff", "")); + o->out_size = _film->size (); + o->apply_crop = false; + o->decode_audio = false; + o->decode_video_frequency = 128; + + shared_ptr j (new ThumbsJob (s, o, _film->log ())); + j->Finished.connect (sigc::mem_fun (_film, &Film::update_thumbs_post_gui)); + JobManager::instance()->add (j); +} + +void +FilmViewer::scroller_size_allocate (Gtk::Allocation a) +{ + if (a.get_width() != _last_scroller_allocation.get_width() || a.get_height() != _last_scroller_allocation.get_height()) { + update_scaled_pixbuf (); + } + + _last_scroller_allocation = a; +} + +void +FilmViewer::setup_visibility () +{ + if (!_film) { + return; + } + + ContentType const c = _film->content_type (); + _update_button.property_visible() = (c == VIDEO); + _position_slider.property_visible() = (c == VIDEO); +} diff --git a/src/gtk/film_viewer.h b/src/gtk/film_viewer.h new file mode 100644 index 000000000..e01d6c096 --- /dev/null +++ b/src/gtk/film_viewer.h @@ -0,0 +1,63 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/film_viewer.h + * @brief A GTK widget to view `thumbnails' of a Film. + */ + +#include +#include "lib/film.h" + +/** @class FilmViewer + * @brief A GTK widget to view `thumbnails' of a Film. + */ +class FilmViewer +{ +public: + FilmViewer (Film *); + + Gtk::Widget& widget () { + return _vbox; + } + + void set_film (Film *); + void setup_visibility (); + +private: + void position_slider_changed (); + void update_thumbs (); + std::string format_position_slider_value (double) const; + void load_thumbnail (int); + void film_changed (Film::Property); + void reload_current_thumbnail (); + void update_scaled_pixbuf (); + std::pair scaled_pixbuf_size () const; + void scroller_size_allocate (Gtk::Allocation); + + Film* _film; + Gtk::VBox _vbox; + Gtk::ScrolledWindow _scroller; + Gtk::Image _image; + Glib::RefPtr _pixbuf; + Glib::RefPtr _cropped_pixbuf; + Glib::RefPtr _scaled_pixbuf; + Gtk::HScale _position_slider; + Gtk::Button _update_button; + Gtk::Allocation _last_scroller_allocation; +}; diff --git a/src/gtk/filter_dialog.cc b/src/gtk/filter_dialog.cc new file mode 100644 index 000000000..e52efb68b --- /dev/null +++ b/src/gtk/filter_dialog.cc @@ -0,0 +1,47 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/filter_dialog.cc + * @brief A dialog to select FFmpeg filters. + */ + +#include "lib/film.h" +#include "filter_dialog.h" + +using namespace std; + +FilterDialog::FilterDialog (vector const & f) + : Gtk::Dialog ("Filters") + , _filters (f) +{ + get_vbox()->pack_start (_filters.widget ()); + + _filters.ActiveChanged.connect (sigc::mem_fun (*this, &FilterDialog::active_changed)); + + add_button ("Close", Gtk::RESPONSE_CLOSE); + + show_all (); +} + + +void +FilterDialog::active_changed () +{ + ActiveChanged (_filters.active ()); +} diff --git a/src/gtk/filter_dialog.h b/src/gtk/filter_dialog.h new file mode 100644 index 000000000..84c6e2966 --- /dev/null +++ b/src/gtk/filter_dialog.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/filter_dialog.h + * @brief A dialog to select FFmpeg filters. + */ + +#include +#include "filter_view.h" + +class Film; + +/** @class FilterDialog + * @brief A dialog to select FFmpeg filters. + */ +class FilterDialog : public Gtk::Dialog +{ +public: + FilterDialog (std::vector const &); + + sigc::signal1 > ActiveChanged; + +private: + void active_changed (); + + FilterView _filters; +}; diff --git a/src/gtk/filter_view.cc b/src/gtk/filter_view.cc new file mode 100644 index 000000000..f686c204d --- /dev/null +++ b/src/gtk/filter_view.cc @@ -0,0 +1,72 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/filter_view.cc + * @brief A widget to select FFmpeg filters. + */ + +#include +#include "lib/filter.h" +#include "filter_view.h" + +using namespace std; + +FilterView::FilterView (vector const & active) +{ + _box.set_spacing (4); + + vector filters = Filter::all (); + + for (vector::iterator i = filters.begin(); i != filters.end(); ++i) { + Gtk::CheckButton* b = Gtk::manage (new Gtk::CheckButton ((*i)->name())); + bool const a = find (active.begin(), active.end(), *i) != active.end (); + b->set_active (a); + _filters[*i] = a; + b->signal_toggled().connect (sigc::bind (sigc::mem_fun (*this, &FilterView::filter_toggled), *i)); + _box.pack_start (*b, false, false); + } + + _box.show_all (); +} + +Gtk::Widget & +FilterView::widget () +{ + return _box; +} + +void +FilterView::filter_toggled (Filter const * f) +{ + _filters[f] = !_filters[f]; + ActiveChanged (); +} + +vector +FilterView::active () const +{ + vector active; + for (map::const_iterator i = _filters.begin(); i != _filters.end(); ++i) { + if (i->second) { + active.push_back (i->first); + } + } + + return active; +} diff --git a/src/gtk/filter_view.h b/src/gtk/filter_view.h new file mode 100644 index 000000000..0c96b0e14 --- /dev/null +++ b/src/gtk/filter_view.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/filter_view.h + * @brief A widget to select FFmpeg filters. + */ + +#include +#include + +class Filter; + +/** @class FilterView + * @brief A widget to select FFmpeg filters. + */ +class FilterView +{ +public: + FilterView (std::vector const &); + + Gtk::Widget & widget (); + std::vector active () const; + + sigc::signal0 ActiveChanged; + +private: + void filter_toggled (Filter const *); + + Gtk::VBox _box; + std::map _filters; +}; diff --git a/src/gtk/gpl.cc b/src/gtk/gpl.cc new file mode 100644 index 000000000..b7bcae9e0 --- /dev/null +++ b/src/gtk/gpl.cc @@ -0,0 +1,370 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/gpl.cc + * @brief The GPL. + */ + +const char* gpl = "\n\ +DVD-o-matic comes with NO WARRANTY. It is free software, and you are \n\ +welcome to redistribute it under the terms of the GNU Public License,\n\ +shown below.\n \ +\n\ + GNU GENERAL PUBLIC LICENSE\n\ + Version 2, June 1991\n\ +\n\ + Copyright (C) 1989, 1991 Free Software Foundation, Inc.\n\ + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n\ + Everyone is permitted to copy and distribute verbatim copies\n\ + of this license document, but changing it is not allowed.\n\ +\n\ + Preamble\n\ +\n\ + The licenses for most software are designed to take away your\n\ +freedom to share and change it. By contrast, the GNU General Public\n\ +License is intended to guarantee your freedom to share and change free\n\ +software--to make sure the software is free for all its users. This\n\ +General Public License applies to most of the Free Software\n\ +Foundation's software and to any other program whose authors commit to\n\ +using it. (Some other Free Software Foundation software is covered by\n\ +the GNU Library General Public License instead.) You can apply it to\n\ +your programs, too.\n\ +\n\ + When we speak of free software, we are referring to freedom, not\n\ +price. Our General Public Licenses are designed to make sure that you\n\ +have the freedom to distribute copies of free software (and charge for\n\ +this service if you wish), that you receive source code or can get it\n\ +if you want it, that you can change the software or use pieces of it\n\ +in new free programs; and that you know you can do these things.\n\ +\n\ + To protect your rights, we need to make restrictions that forbid\n\ +anyone to deny you these rights or to ask you to surrender the rights.\n\ +These restrictions translate to certain responsibilities for you if you\n\ +distribute copies of the software, or if you modify it.\n\ +\n\ + For example, if you distribute copies of such a program, whether\n\ +gratis or for a fee, you must give the recipients all the rights that\n\ +you have. You must make sure that they, too, receive or can get the\n\ +source code. And you must show them these terms so they know their\n\ +rights.\n\ +\n\ + We protect your rights with two steps: (1) copyright the software, and\n\ +(2) offer you this license which gives you legal permission to copy,\n\ +distribute and/or modify the software.\n\ +\n\ + Also, for each author's protection and ours, we want to make certain\n\ +that everyone understands that there is no warranty for this free\n\ +software. If the software is modified by someone else and passed on, we\n\ +want its recipients to know that what they have is not the original, so\n\ +that any problems introduced by others will not reflect on the original\n\ +authors' reputations.\n\ +\n\ + Finally, any free program is threatened constantly by software\n\ +patents. We wish to avoid the danger that redistributors of a free\n\ +program will individually obtain patent licenses, in effect making the\n\ +program proprietary. To prevent this, we have made it clear that any\n\ +patent must be licensed for everyone's free use or not licensed at all.\n\ +\n\ + The precise terms and conditions for copying, distribution and\n\ +modification follow.\n\ +\n\ + GNU GENERAL PUBLIC LICENSE\n\ + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\ +\n\ + 0. This License applies to any program or other work which contains\n\ +a notice placed by the copyright holder saying it may be distributed\n\ +under the terms of this General Public License. The \"Program\", below,\n\ +refers to any such program or work, and a \"work based on the Program\"\n\ +means either the Program or any derivative work under copyright law:\n\ +that is to say, a work containing the Program or a portion of it,\n\ +either verbatim or with modifications and/or translated into another\n\ +language. (Hereinafter, translation is included without limitation in\n\ +the term \"modification\".) Each licensee is addressed as \"you\".\n\ +\n\ +Activities other than copying, distribution and modification are not\n\ +covered by this License; they are outside its scope. The act of\n\ +running the Program is not restricted, and the output from the Program\n\ +is covered only if its contents constitute a work based on the\n\ +Program (independent of having been made by running the Program).\n\ +Whether that is true depends on what the Program does.\n\ +\n\ + 1. You may copy and distribute verbatim copies of the Program's\n\ +source code as you receive it, in any medium, provided that you\n\ +conspicuously and appropriately publish on each copy an appropriate\n\ +copyright notice and disclaimer of warranty; keep intact all the\n\ +notices that refer to this License and to the absence of any warranty;\n\ +and give any other recipients of the Program a copy of this License\n\ +along with the Program.\n\ +\n\ +You may charge a fee for the physical act of transferring a copy, and\n\ +you may at your option offer warranty protection in exchange for a fee.\n\ +\n\ + 2. You may modify your copy or copies of the Program or any portion\n\ +of it, thus forming a work based on the Program, and copy and\n\ +distribute such modifications or work under the terms of Section 1\n\ +above, provided that you also meet all of these conditions:\n\ +\n\ + a) You must cause the modified files to carry prominent notices\n\ + stating that you changed the files and the date of any change.\n\ +\n\ + b) You must cause any work that you distribute or publish, that in\n\ + whole or in part contains or is derived from the Program or any\n\ + part thereof, to be licensed as a whole at no charge to all third\n\ + parties under the terms of this License.\n\ +\n\ + c) If the modified program normally reads commands interactively\n\ + when run, you must cause it, when started running for such\n\ + interactive use in the most ordinary way, to print or display an\n\ + announcement including an appropriate copyright notice and a\n\ + notice that there is no warranty (or else, saying that you provide\n\ + a warranty) and that users may redistribute the program under\n\ + these conditions, and telling the user how to view a copy of this\n\ + License. (Exception: if the Program itself is interactive but\n\ + does not normally print such an announcement, your work based on\n\ + the Program is not required to print an announcement.)\n\ +\n\ +These requirements apply to the modified work as a whole. If\n\ +identifiable sections of that work are not derived from the Program,\n\ +and can be reasonably considered independent and separate works in\n\ +themselves, then this License, and its terms, do not apply to those\n\ +sections when you distribute them as separate works. But when you\n\ +distribute the same sections as part of a whole which is a work based\n\ +on the Program, the distribution of the whole must be on the terms of\n\ +this License, whose permissions for other licensees extend to the\n\ +entire whole, and thus to each and every part regardless of who wrote it.\n\ +\n\ +Thus, it is not the intent of this section to claim rights or contest\n\ +your rights to work written entirely by you; rather, the intent is to\n\ +exercise the right to control the distribution of derivative or\n\ +collective works based on the Program.\n\ +\n\ +In addition, mere aggregation of another work not based on the Program\n\ +with the Program (or with a work based on the Program) on a volume of\n\ +a storage or distribution medium does not bring the other work under\n\ +the scope of this License.\n\ +\n\ + 3. You may copy and distribute the Program (or a work based on it,\n\ +under Section 2) in object code or executable form under the terms of\n\ +Sections 1 and 2 above provided that you also do one of the following:\n\ +\n\ + a) Accompany it with the complete corresponding machine-readable\n\ + source code, which must be distributed under the terms of Sections\n\ + 1 and 2 above on a medium customarily used for software interchange; or,\n\ +\n\ + b) Accompany it with a written offer, valid for at least three\n\ + years, to give any third party, for a charge no more than your\n\ + cost of physically performing source distribution, a complete\n\ + machine-readable copy of the corresponding source code, to be\n\ + distributed under the terms of Sections 1 and 2 above on a medium\n\ + customarily used for software interchange; or,\n\ +\n\ + c) Accompany it with the information you received as to the offer\n\ + to distribute corresponding source code. (This alternative is\n\ + allowed only for noncommercial distribution and only if you\n\ + received the program in object code or executable form with such\n\ + an offer, in accord with Subsection b above.)\n\ +\n\ +The source code for a work means the preferred form of the work for\n\ +making modifications to it. For an executable work, complete source\n\ +code means all the source code for all modules it contains, plus any\n\ +associated interface definition files, plus the scripts used to\n\ +control compilation and installation of the executable. However, as a\n\ +special exception, the source code distributed need not include\n\ +anything that is normally distributed (in either source or binary\n\ +form) with the major components (compiler, kernel, and so on) of the\n\ +operating system on which the executable runs, unless that component\n\ +itself accompanies the executable.\n\ +\n\ +If distribution of executable or object code is made by offering\n\ +access to copy from a designated place, then offering equivalent\n\ +access to copy the source code from the same place counts as\n\ +distribution of the source code, even though third parties are not\n\ +compelled to copy the source along with the object code.\n\ + \n\ + 4. You may not copy, modify, sublicense, or distribute the Program\n\ +except as expressly provided under this License. Any attempt\n\ +otherwise to copy, modify, sublicense or distribute the Program is\n\ +void, and will automatically terminate your rights under this License.\n\ +However, parties who have received copies, or rights, from you under\n\ +this License will not have their licenses terminated so long as such\n\ +parties remain in full compliance.\n\ +\n\ + 5. You are not required to accept this License, since you have not\n\ +signed it. However, nothing else grants you permission to modify or\n\ +distribute the Program or its derivative works. These actions are\n\ +prohibited by law if you do not accept this License. Therefore, by\n\ +modifying or distributing the Program (or any work based on the\n\ +Program), you indicate your acceptance of this License to do so, and\n\ +all its terms and conditions for copying, distributing or modifying\n\ +the Program or works based on it.\n\ +\n\ + 6. Each time you redistribute the Program (or any work based on the\n\ +Program), the recipient automatically receives a license from the\n\ +original licensor to copy, distribute or modify the Program subject to\n\ +these terms and conditions. You may not impose any further\n\ +restrictions on the recipients' exercise of the rights granted herein.\n\ +You are not responsible for enforcing compliance by third parties to\n\ +this License.\n\ +\n\ + 7. If, as a consequence of a court judgment or allegation of patent\n\ +infringement or for any other reason (not limited to patent issues),\n\ +conditions are imposed on you (whether by court order, agreement or\n\ +otherwise) that contradict the conditions of this License, they do not\n\ +excuse you from the conditions of this License. If you cannot\n\ +distribute so as to satisfy simultaneously your obligations under this\n\ +License and any other pertinent obligations, then as a consequence you\n\ +may not distribute the Program at all. For example, if a patent\n\ +license would not permit royalty-free redistribution of the Program by\n\ +all those who receive copies directly or indirectly through you, then\n\ +the only way you could satisfy both it and this License would be to\n\ +refrain entirely from distribution of the Program.\n\ +\n\ +If any portion of this section is held invalid or unenforceable under\n\ +any particular circumstance, the balance of the section is intended to\n\ +apply and the section as a whole is intended to apply in other\n\ +circumstances.\n\ +\n\ +It is not the purpose of this section to induce you to infringe any\n\ +patents or other property right claims or to contest validity of any\n\ +such claims; this section has the sole purpose of protecting the\n\ +integrity of the free software distribution system, which is\n\ +implemented by public license practices. Many people have made\n\ +generous contributions to the wide range of software distributed\n\ +through that system in reliance on consistent application of that\n\ +system; it is up to the author/donor to decide if he or she is willing\n\ +to distribute software through any other system and a licensee cannot\n\ +impose that choice.\n\ +\n\ +This section is intended to make thoroughly clear what is believed to\n\ +be a consequence of the rest of this License.\n\ +\n\ + 8. If the distribution and/or use of the Program is restricted in\n\ +certain countries either by patents or by copyrighted interfaces, the\n\ +original copyright holder who places the Program under this License\n\ +may add an explicit geographical distribution limitation excluding\n\ +those countries, so that distribution is permitted only in or among\n\ +countries not thus excluded. In such case, this License incorporates\n\ +the limitation as if written in the body of this License.\n\ +\n\ + 9. The Free Software Foundation may publish revised and/or new versions\n\ +of the General Public License from time to time. Such new versions will\n\ +be similar in spirit to the present version, but may differ in detail to\n\ +address new problems or concerns.\n\ +\n\ +Each version is given a distinguishing version number. If the Program\n\ +specifies a version number of this License which applies to it and \"any\n\ +later version\", you have the option of following the terms and conditions\n\ +either of that version or of any later version published by the Free\n\ +Software Foundation. If the Program does not specify a version number of\n\ +this License, you may choose any version ever published by the Free Software\n\ +Foundation.\n\ +\n\ + 10. If you wish to incorporate parts of the Program into other free\n\ +programs whose distribution conditions are different, write to the author\n\ +to ask for permission. For software which is copyrighted by the Free\n\ +Software Foundation, write to the Free Software Foundation; we sometimes\n\ +make exceptions for this. Our decision will be guided by the two goals\n\ +of preserving the free status of all derivatives of our free software and\n\ +of promoting the sharing and reuse of software generally.\n\ +\n\ + NO WARRANTY\n\ +\n\ + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\n\ +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN\n\ +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\n\ +PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\n\ +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\n\ +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS\n\ +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE\n\ +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\n\ +REPAIR OR CORRECTION.\n\ +\n\ + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\n\ +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\n\ +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\n\ +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\n\ +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\n\ +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\n\ +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\n\ +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\n\ +POSSIBILITY OF SUCH DAMAGES.\n\ +\n\ + END OF TERMS AND CONDITIONS\n\ +\n\ + How to Apply These Terms to Your New Programs\n\ +\n\ + If you develop a new program, and you want it to be of the greatest\n\ +possible use to the public, the best way to achieve this is to make it\n\ +free software which everyone can redistribute and change under these terms.\n\ +\n\ + To do so, attach the following notices to the program. It is safest\n\ +to attach them to the start of each source file to most effectively\n\ +convey the exclusion of warranty; and each file should have at least\n\ +the \"copyright\" line and a pointer to where the full notice is found.\n\ +\n\ + \n\ + Copyright (C) \n\ +\n\ + This program is free software; you can redistribute it and/or modify\n\ + it under the terms of the GNU General Public License as published by\n\ + the Free Software Foundation; either version 2 of the License, or\n\ + (at your option) any later version.\n\ +\n\ + This program is distributed in the hope that it will be useful,\n\ + but WITHOUT ANY WARRANTY; without even the implied warranty of\n\ + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\ + GNU General Public License for more details.\n\ +\n\ + You should have received a copy of the GNU General Public License\n\ + along with this program; if not, write to the Free Software\n\ + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n\ +\n\ +\n\ +Also add information on how to contact you by electronic and paper mail.\n\ +\n\ +If the program is interactive, make it output a short notice like this\n\ +when it starts in an interactive mode:\n\ +\n\ + Gnomovision version 69, Copyright (C) year name of author\n\ + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n\ + This is free software, and you are welcome to redistribute it\n\ + under certain conditions; type `show c' for details.\n\ +\n\ +The hypothetical commands `show w' and `show c' should show the appropriate\n\ +parts of the General Public License. Of course, the commands you use may\n\ +be called something other than `show w' and `show c'; they could even be\n\ +mouse-clicks or menu items--whatever suits your program.\n\ +\n\ +You should also get your employer (if you work as a programmer) or your\n\ +school, if any, to sign a \"copyright disclaimer\" for the program, if\n\ +necessary. Here is a sample; alter the names:\n\ +\n\ + Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n\ + `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\ +\n\ + , 1 April 1989\n\ + Ty Coon, President of Vice\n\ +\n\ +This General Public License does not permit incorporating your program into\n\ +proprietary programs. If your program is a subroutine library, you may\n\ +consider it more useful to permit linking proprietary applications with the\n\ +library. If this is what you want to do, use the GNU Library General\n\ +Public License instead of this License.\n\ +"; + diff --git a/src/gtk/gpl.h b/src/gtk/gpl.h new file mode 100644 index 000000000..c9c4ffe46 --- /dev/null +++ b/src/gtk/gpl.h @@ -0,0 +1,24 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/gpl.h + * @brief The GPL. + */ + +extern const char * gpl; diff --git a/src/gtk/gtk_util.cc b/src/gtk/gtk_util.cc new file mode 100644 index 000000000..41f8cb5b5 --- /dev/null +++ b/src/gtk/gtk_util.cc @@ -0,0 +1,45 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/gtk/util.cc + * @brief Some utility functions. + */ + +#include + +using namespace std; + +/** @param t Label text. + * @return GTK label containing t, left-aligned (passed through Gtk::manage) + */ +Gtk::Label & +left_aligned_label (string t) +{ + Gtk::Label* l = Gtk::manage (new Gtk::Label (t)); + l->set_alignment (0, 0.5); + return *l; +} + +void +error_dialog (string m) +{ + Gtk::MessageDialog d (m, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); + d.set_title ("DVD-o-matic"); + d.run (); +} diff --git a/src/gtk/gtk_util.h b/src/gtk/gtk_util.h new file mode 100644 index 000000000..518842872 --- /dev/null +++ b/src/gtk/gtk_util.h @@ -0,0 +1,27 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 + +/** @file src/gtk/util.h + * @brief Some utility functions. + */ + +extern void error_dialog (std::string); +extern Gtk::Label & left_aligned_label (std::string); diff --git a/src/gtk/job_manager_view.cc b/src/gtk/job_manager_view.cc new file mode 100644 index 000000000..60c13990d --- /dev/null +++ b/src/gtk/job_manager_view.cc @@ -0,0 +1,134 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/job_manager_view.cc + * @brief Class generating a GTK widget to show the progress of jobs. + */ + +#include "lib/job_manager.h" +#include "lib/job.h" +#include "lib/util.h" +#include "lib/exceptions.h" +#include "job_manager_view.h" +#include "gtk_util.h" + +using namespace std; +using namespace boost; + +/** Must be called in the GUI thread */ +JobManagerView::JobManagerView () +{ + _scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS); + + _store = Gtk::TreeStore::create (_columns); + _view.set_model (_store); + _view.append_column ("Name", _columns.name); + + Gtk::CellRendererProgress* r = Gtk::manage (new Gtk::CellRendererProgress ()); + int const n = _view.append_column ("Progress", *r); + Gtk::TreeViewColumn* c = _view.get_column (n - 1); + c->add_attribute (r->property_value(), _columns.progress); + c->add_attribute (r->property_pulse(), _columns.progress_unknown); + c->add_attribute (r->property_text(), _columns.text); + + _scroller.add (_view); + _scroller.set_size_request (-1, 150); + + update (); +} + +/** Update the view by examining the state of each jobs. + * Must be called in the GUI thread. + */ +void +JobManagerView::update () +{ + list > jobs = JobManager::instance()->get (); + + for (list >::iterator i = jobs.begin(); i != jobs.end(); ++i) { + Gtk::ListStore::iterator j = _store->children().begin(); + while (j != _store->children().end()) { + Gtk::TreeRow r = *j; + shared_ptr job = r[_columns.job]; + if (job == *i) { + break; + } + ++j; + } + + Gtk::TreeRow r; + if (j == _store->children().end ()) { + j = _store->append (); + r = *j; + r[_columns.name] = (*i)->name (); + r[_columns.job] = *i; + r[_columns.progress_unknown] = -1; + r[_columns.informed_of_finish] = false; + } else { + r = *j; + } + + bool inform_of_finish = false; + string const st = (*i)->status (); + + if (!(*i)->finished ()) { + float const p = (*i)->overall_progress (); + if (p >= 0) { + r[_columns.text] = st; + r[_columns.progress] = p * 100; + } else { + r[_columns.progress_unknown] = r[_columns.progress_unknown] + 1; + } + } + + /* Hack to work around our lack of cross-thread + signalling; we tell the job to emit_finished() + from here (the GUI thread). + */ + + if ((*i)->finished_ok ()) { + bool i = r[_columns.informed_of_finish]; + if (!i) { + r[_columns.progress_unknown] = -1; + r[_columns.progress] = 100; + r[_columns.text] = st; + inform_of_finish = true; + } + } else if ((*i)->finished_in_error ()) { + bool i = r[_columns.informed_of_finish]; + if (!i) { + r[_columns.progress_unknown] = -1; + r[_columns.progress] = 100; + r[_columns.text] = st; + inform_of_finish = true; + } + } + + if (inform_of_finish) { + try { + (*i)->emit_finished (); + } catch (OpenFileError& e) { + stringstream s; + s << "Error: " << e.what(); + error_dialog (s.str ()); + } + r[_columns.informed_of_finish] = true; + } + } +} diff --git a/src/gtk/job_manager_view.h b/src/gtk/job_manager_view.h new file mode 100644 index 000000000..c88a1ce9a --- /dev/null +++ b/src/gtk/job_manager_view.h @@ -0,0 +1,83 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/job_manager_view.h + * @brief Class generating a GTK widget to show the progress of jobs. + */ + +#include +#include +#include + +class Job; + +/** @class JobManagerView + * @brief Class generating a GTK widget to show the progress of jobs. + */ +class JobManagerView +{ +public: + JobManagerView (); + + /** @return Our main widget, which contains everything else */ + Gtk::Widget& widget () { + return _scroller; + } + + void update (); + +private: + /** Scroller for all our contents */ + Gtk::ScrolledWindow _scroller; + /** View for the jobs */ + Gtk::TreeView _view; + /** Store for the jobs */ + Glib::RefPtr _store; + + /** The TreeModelColumnRecord for the store */ + class StoreColumns : public Gtk::TreeModelColumnRecord + { + public: + StoreColumns () + { + add (name); + add (job); + add (progress); + add (progress_unknown); + add (text); + add (informed_of_finish); + } + + /** Job name */ + Gtk::TreeModelColumn name; + /** Job */ + Gtk::TreeModelColumn > job; + /** Progress */ + Gtk::TreeModelColumn progress; + /** An increasing integer number if the progress is unknown */ + Gtk::TreeModelColumn progress_unknown; + /** Text to write into the progress bar */ + Gtk::TreeModelColumn text; + /** true if the job has been informed of its finish */ + Gtk::TreeModelColumn informed_of_finish; + }; + + /** The columns for the store */ + StoreColumns _columns; +}; diff --git a/src/gtk/job_wrapper.cc b/src/gtk/job_wrapper.cc new file mode 100644 index 000000000..be214b0ac --- /dev/null +++ b/src/gtk/job_wrapper.cc @@ -0,0 +1,50 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include "lib/film.h" +#include "lib/exceptions.h" +#include "job_wrapper.h" +#include "gtk_util.h" + +using namespace std; + +void +JobWrapper::make_dcp (Film* film, bool transcode) +{ + if (!film) { + return; + } + + try { + 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() << ")"; + } + error_dialog (s.str ()); + } catch (std::exception& e) { + stringstream s; + s << "Could not make DCP: " << e.what () << "."; + error_dialog (s.str ()); + } +} diff --git a/src/gtk/job_wrapper.h b/src/gtk/job_wrapper.h new file mode 100644 index 000000000..b8760f6e5 --- /dev/null +++ b/src/gtk/job_wrapper.h @@ -0,0 +1,27 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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. + +*/ + +class Film; + +namespace JobWrapper +{ + +void make_dcp (Film *, bool); + +} diff --git a/src/gtk/wscript b/src/gtk/wscript new file mode 100644 index 000000000..c68efea90 --- /dev/null +++ b/src/gtk/wscript @@ -0,0 +1,22 @@ +def build(bld): + obj = bld(features = 'cxx cxxshlib') + obj.name = 'libdvdomatic-gtk' + obj.includes = [ '..' ] + obj.export_includes = ['.'] + obj.uselib = 'GLIB GTKMM CAIROMM' + obj.source = """ + alignment.cc + config_dialog.cc + dcp_range_dialog.cc + film_editor.cc + film_list.cc + film_player.cc + film_viewer.cc + filter_dialog.cc + filter_view.cc + gpl.cc + job_manager_view.cc + gtk_util.cc + job_wrapper.cc + """ + obj.target = 'dvdomatic-gtk' diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc new file mode 100644 index 000000000..1a6104251 --- /dev/null +++ b/src/lib/ab_transcode_job.cc @@ -0,0 +1,69 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include "ab_transcode_job.h" +#include "j2k_wav_encoder.h" +#include "film.h" +#include "format.h" +#include "filter.h" +#include "ab_transcoder.h" +#include "film_state.h" +#include "encoder_factory.h" +#include "config.h" + +using namespace std; +using namespace boost; + +/** @param s FilmState to compare (with filters and/or a non-bicubic scaler). + * @param o Options. + * @Param l A log that we can write to. + */ +ABTranscodeJob::ABTranscodeJob (shared_ptr s, shared_ptr o, Log* l) + : Job (s, o, l) +{ + _fs_b.reset (new FilmState (*_fs)); + _fs_b->scaler = Config::instance()->reference_scaler (); + _fs_b->filters = Config::instance()->reference_filters (); +} + +string +ABTranscodeJob::name () const +{ + stringstream s; + s << "A/B transcode " << _fs->name; + return s.str (); +} + +void +ABTranscodeJob::run () +{ + try { + /* _fs_b is the one with no filters */ + ABTranscoder w (_fs_b, _fs, _opt, this, _log, encoder_factory (_fs, _opt, _log)); + w.go (); + set_progress (1); + set_state (FINISHED_OK); + + } catch (std::exception& e) { + + set_state (FINISHED_ERROR); + + } +} diff --git a/src/lib/ab_transcode_job.h b/src/lib/ab_transcode_job.h new file mode 100644 index 000000000..478049068 --- /dev/null +++ b/src/lib/ab_transcode_job.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/ab_transcode_job.h + * @brief Job to run a transcoder which produces output for A/B comparison of various settings. + */ + +#include +#include "job.h" + +/** @class ABTranscodeJob + * @brief Job to run a transcoder which produces output for A/B comparison of various settings. + * + * The right half of the frame will be processed using the FilmState supplied; + * the left half will be processed using the same state but *without* filters + * and with the scaler set to SWS_BICUBIC. + */ +class ABTranscodeJob : public Job +{ +public: + ABTranscodeJob (boost::shared_ptr s, boost::shared_ptr o, Log* l); + + std::string name () const; + void run (); + +private: + /** Copy of our FilmState with filters removed and scaler set back to bicubic; + * this is the `reference' (left-half-frame) state. + */ + boost::shared_ptr _fs_b; +}; diff --git a/src/lib/ab_transcoder.cc b/src/lib/ab_transcoder.cc new file mode 100644 index 000000000..aabaf2d03 --- /dev/null +++ b/src/lib/ab_transcoder.cc @@ -0,0 +1,127 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include +#include "ab_transcoder.h" +#include "film.h" +#include "decoder.h" +#include "encoder.h" +#include "job.h" +#include "film_state.h" +#include "options.h" +#include "image.h" +#include "decoder_factory.h" + +/** @file src/ab_transcoder.cc + * @brief A transcoder which uses one FilmState for the left half of the screen, and a different one + * for the right half (to facilitate A/B comparisons of settings) + */ + +using namespace std; +using namespace boost; + +/** @param a FilmState to use for the left half of the screen. + * @param b FilmState to use for the right half of the screen. + * @param o Options. + * @param j Job that we are associated with. + * @param l Log. + * @param e Encoder to use. + */ + +ABTranscoder::ABTranscoder ( + shared_ptr a, shared_ptr b, shared_ptr o, Job* j, Log* l, shared_ptr e) + : _fs_a (a) + , _fs_b (b) + , _opt (o) + , _job (j) + , _log (l) + , _encoder (e) + , _last_frame (0) +{ + _da = decoder_factory (_fs_a, o, j, _log); + _db = decoder_factory (_fs_b, o, j, _log); + + _da->Video.connect (sigc::bind (sigc::mem_fun (*this, &ABTranscoder::process_video), 0)); + _db->Video.connect (sigc::bind (sigc::mem_fun (*this, &ABTranscoder::process_video), 1)); + _da->Audio.connect (sigc::mem_fun (*e, &Encoder::process_audio)); +} + +ABTranscoder::~ABTranscoder () +{ + +} + +void +ABTranscoder::process_video (shared_ptr yuv, int frame, int index) +{ + if (index == 0) { + /* Keep this image around until we get the other half */ + _image = yuv; + } else { + /* Copy the right half of yuv into _image */ + for (int i = 0; i < yuv->components(); ++i) { + int const line_size = yuv->line_size()[i]; + int const half_line_size = line_size / 2; + + uint8_t* p = _image->data()[i]; + uint8_t* q = yuv->data()[i]; + + for (int j = 0; j < yuv->lines (i); ++j) { + memcpy (p + half_line_size, q + half_line_size, half_line_size); + p += line_size; + q += line_size; + } + } + + /* And pass it to the encoder */ + _encoder->process_video (_image, frame); + _image.reset (); + } + + _last_frame = frame; +} + + +void +ABTranscoder::go () +{ + _encoder->process_begin (); + _da->process_begin (); + _db->process_begin (); + + while (1) { + bool const a = _da->pass (); + bool const b = _db->pass (); + + if (_job) { + _job->set_progress (float (_last_frame) / _da->decoding_frames ()); + } + + if (a && b) { + break; + } + } + + _encoder->process_end (); + _da->process_end (); + _db->process_end (); +} + diff --git a/src/lib/ab_transcoder.h b/src/lib/ab_transcoder.h new file mode 100644 index 000000000..0310bb923 --- /dev/null +++ b/src/lib/ab_transcoder.h @@ -0,0 +1,69 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/ab_transcoder.h + * @brief A transcoder which uses one FilmState for the left half of the screen, and a different one + * for the right half (to facilitate A/B comparisons of settings) + */ + +#include +#include + +class Job; +class Encoder; +class Decoder; +class FilmState; +class Options; +class Image; +class Log; + +/** @class ABTranscoder + * @brief A transcoder which uses one FilmState for the left half of the screen, and a different one + * for the right half (to facilitate A/B comparisons of settings) + */ +class ABTranscoder +{ +public: + ABTranscoder ( + boost::shared_ptr a, + boost::shared_ptr b, + boost::shared_ptr o, + Job* j, + Log* l, + boost::shared_ptr e + ); + + ~ABTranscoder (); + + void go (); + +private: + void process_video (boost::shared_ptr, int, int); + + boost::shared_ptr _fs_a; + boost::shared_ptr _fs_b; + boost::shared_ptr _opt; + Job* _job; + Log* _log; + boost::shared_ptr _encoder; + boost::shared_ptr _da; + boost::shared_ptr _db; + int _last_frame; + boost::shared_ptr _image; +}; diff --git a/src/lib/config.cc b/src/lib/config.cc new file mode 100644 index 000000000..6d31ccd9e --- /dev/null +++ b/src/lib/config.cc @@ -0,0 +1,139 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include +#include "config.h" +#include "server.h" +#include "scaler.h" +#include "screen.h" +#include "filter.h" + +using namespace std; +using namespace boost; + +Config* Config::_instance = 0; + +/** Construct default configuration */ +Config::Config () + : _num_local_encoding_threads (2) + , _server_port (6192) + , _colour_lut_index (0) + , _j2k_bandwidth (250000000) + , _reference_scaler (Scaler::from_id ("bicubic")) + , _tms_path (".") +{ + ifstream f (file().c_str ()); + string line; + while (getline (f, line)) { + if (line.empty ()) { + continue; + } + + if (line[0] == '#') { + continue; + } + + size_t const s = line.find (' '); + if (s == string::npos) { + continue; + } + + string const k = line.substr (0, s); + string const v = line.substr (s + 1); + + if (k == "num_local_encoding_threads") { + _num_local_encoding_threads = atoi (v.c_str ()); + } else if (k == "server_port") { + _server_port = atoi (v.c_str ()); + } else if (k == "colour_lut_index") { + _colour_lut_index = atoi (v.c_str ()); + } else if (k == "j2k_bandwidth") { + _j2k_bandwidth = atoi (v.c_str ()); + } else if (k == "reference_scaler") { + _reference_scaler = Scaler::from_id (v); + } 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)); + } else if (k == "screen") { + _screens.push_back (Screen::create_from_metadata (v)); + } else if (k == "tms_ip") { + _tms_ip = v; + } else if (k == "tms_path") { + _tms_path = v; + } else if (k == "tms_user") { + _tms_user = v; + } else if (k == "tms_password") { + _tms_password = v; + } + } + + Changed (); +} + +/** @return Filename to write configuration to */ +string +Config::file () const +{ + stringstream s; + s << getenv ("HOME") << "/.dvdomatic"; + return s.str (); +} + +/** @return Singleton instance */ +Config * +Config::instance () +{ + if (_instance == 0) { + _instance = new Config; + } + + return _instance; +} + +/** Write our configuration to disk */ +void +Config::write () const +{ + ofstream f (file().c_str ()); + f << "num_local_encoding_threads " << _num_local_encoding_threads << "\n" + << "server_port " << _server_port << "\n" + << "colour_lut_index " << _colour_lut_index << "\n" + << "j2k_bandwidth " << _j2k_bandwidth << "\n" + << "reference_scaler " << _reference_scaler->id () << "\n"; + + for (vector::const_iterator i = _reference_filters.begin(); i != _reference_filters.end(); ++i) { + f << "reference_filter " << (*i)->id () << "\n"; + } + + for (vector::const_iterator i = _servers.begin(); i != _servers.end(); ++i) { + f << "server " << (*i)->as_metadata () << "\n"; + } + + for (vector >::const_iterator i = _screens.begin(); i != _screens.end(); ++i) { + f << "screen " << (*i)->as_metadata () << "\n"; + } + + f << "tms_ip " << _tms_ip << "\n"; + f << "tms_path " << _tms_path << "\n"; + f << "tms_user " << _tms_user << "\n"; + f << "tms_password " << _tms_password << "\n"; +} diff --git a/src/lib/config.h b/src/lib/config.h new file mode 100644 index 000000000..62fcebbc3 --- /dev/null +++ b/src/lib/config.h @@ -0,0 +1,207 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/config.h + * @brief Class holding configuration. + */ + +#ifndef DVDOMATIC_CONFIG_H +#define DVDOMATIC_CONFIG_H + +#include +#include +#include + +class Server; +class Screen; +class Scaler; +class Filter; + +/** @class Config + * @brief A singleton class holding configuration. + */ +class Config +{ +public: + + /** @return number of threads to use for J2K encoding on the local machine */ + int num_local_encoding_threads () const { + return _num_local_encoding_threads; + } + + /** @return port to use for J2K encoding servers */ + int server_port () const { + return _server_port; + } + + /** @return index of colour LUT to use when converting RGB to XYZ. + * 0: sRGB + * 1: Rec 709 + * 2: DC28 + */ + int colour_lut_index () const { + return _colour_lut_index; + } + + /** @return bandwidth for J2K files in bits per second */ + int j2k_bandwidth () const { + return _j2k_bandwidth; + } + + /** @return J2K encoding servers to use */ + std::vector servers () const { + return _servers; + } + + std::vector > screens () const { + return _screens; + } + + Scaler const * reference_scaler () const { + return _reference_scaler; + } + + std::vector reference_filters () const { + return _reference_filters; + } + + std::string tms_ip () const { + return _tms_ip; + } + + std::string tms_path () const { + return _tms_path; + } + + std::string tms_user () const { + return _tms_user; + } + + std::string tms_password () const { + return _tms_password; + } + + /** @param n New number of local encoding threads */ + void set_num_local_encoding_threads (int n) { + _num_local_encoding_threads = n; + Changed (); + } + + /** @param p New server port */ + void set_sever_port (int p) { + _server_port = p; + Changed (); + } + + /** @param i New colour LUT index */ + void set_colour_lut_index (int i) { + _colour_lut_index = i; + Changed (); + } + + /** @param b New J2K bandwidth */ + void set_j2k_bandwidth (int b) { + _j2k_bandwidth = b; + Changed (); + } + + /** @param s New list of servers */ + void set_servers (std::vector s) { + _servers = s; + Changed (); + } + + void set_screens (std::vector > s) { + _screens = s; + Changed (); + } + + void set_reference_scaler (Scaler const * s) { + _reference_scaler = s; + Changed (); + } + + void set_reference_filters (std::vector const & f) { + _reference_filters = f; + Changed (); + } + + void set_tms_ip (std::string i) { + _tms_ip = i; + Changed (); + } + + void set_tms_path (std::string p) { + _tms_path = p; + Changed (); + } + + void set_tms_user (std::string u) { + _tms_user = u; + Changed (); + } + + void set_tms_password (std::string p) { + _tms_password = p; + Changed (); + } + + void write () const; + + sigc::signal0 Changed; + + static Config* instance (); + +private: + Config (); + std::string file () const; + + /** number of threads to use for J2K encoding on the local machine */ + int _num_local_encoding_threads; + /** port to use for J2K encoding servers */ + int _server_port; + /** index of colour LUT to use when converting RGB to XYZ + * (see colour_lut_index ()) + */ + int _colour_lut_index; + /** bandwidth for J2K files in Mb/s */ + int _j2k_bandwidth; + + /** J2K encoding servers to use */ + std::vector _servers; + + /** Screen definitions */ + std::vector > _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 _reference_filters; + + std::string _tms_ip; + std::string _tms_path; + std::string _tms_user; + std::string _tms_password; + + /** Singleton instance, or 0 */ + static Config* _instance; +}; + +#endif diff --git a/src/lib/copy_from_dvd_job.cc b/src/lib/copy_from_dvd_job.cc new file mode 100644 index 000000000..b82169ad1 --- /dev/null +++ b/src/lib/copy_from_dvd_job.cc @@ -0,0 +1,103 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/copy_from_dvd_job.cc + * @brief A job to copy a film from a DVD. + */ + +#include +#include +#include +#include "copy_from_dvd_job.h" +#include "film_state.h" +#include "dvd.h" + +using namespace std; +using namespace boost; + +/** @param fs FilmState for the film to write DVD data into. + * @param l Log that we can write to. + */ +CopyFromDVDJob::CopyFromDVDJob (shared_ptr fs, Log* l) + : Job (fs, shared_ptr (), l) +{ + +} + +string +CopyFromDVDJob::name () const +{ + return "Copy film from DVD"; +} + +void +CopyFromDVDJob::run () +{ + /* Remove any old DVD rips */ + filesystem::remove_all (_fs->dir ("dvd")); + + string const dvd = find_dvd (); + if (dvd.empty ()) { + set_error ("could not find DVD"); + set_state (FINISHED_ERROR); + } + + vector const t = dvd_titles (dvd); + if (t.empty ()) { + set_error ("no titles found on DVD"); + set_state (FINISHED_ERROR); + } + + int longest_title = 0; + uint64_t longest_size = 0; + for (vector::size_type i = 0; i < t.size(); ++i) { + if (longest_size < t[i]) { + longest_size = t[i]; + longest_title = i; + } + } + + stringstream c; + c << "vobcopy -n " << longest_title << " -l -o \"" << _fs->dir ("dvd") << "\" 2>&1"; + + FILE* f = popen (c.str().c_str(), "r"); + if (f == 0) { + set_error ("could not run vobcopy command"); + set_state (FINISHED_ERROR); + return; + } + + while (!feof (f)) { + char buf[256]; + if (fscanf (f, "%s", buf)) { + string s (buf); + if (!s.empty () && s[s.length() - 1] == '%') { + set_progress (atof (s.substr(0, s.length() - 1).c_str()) / 100.0); + } + } + } + + int const r = pclose (f); + if (WEXITSTATUS (r) != 0) { + set_error ("call to vobcopy failed"); + set_state (FINISHED_ERROR); + } else { + set_state (FINISHED_OK); + } +} diff --git a/src/lib/copy_from_dvd_job.h b/src/lib/copy_from_dvd_job.h new file mode 100644 index 000000000..6b56f6f0a --- /dev/null +++ b/src/lib/copy_from_dvd_job.h @@ -0,0 +1,36 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/copy_from_dvd_job.h + * @brief A job to copy a film from a DVD. + */ + +#include "job.h" + +/** @class CopyFromDVDJob + * @brief A job to copy a film from a DVD using `vobcopy'. + */ +class CopyFromDVDJob : public Job +{ +public: + CopyFromDVDJob (boost::shared_ptr, Log *); + + std::string name () const; + void run (); +}; diff --git a/src/lib/dcp_content_type.cc b/src/lib/dcp_content_type.cc new file mode 100644 index 000000000..72a26e407 --- /dev/null +++ b/src/lib/dcp_content_type.cc @@ -0,0 +1,91 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/content_type.cc + * @brief A description of the type of content for a DCP (e.g. feature, trailer etc.) + */ + +#include +#include "dcp_content_type.h" + +using namespace std; + +vector DCPContentType::_dcp_content_types; + +DCPContentType::DCPContentType (string p, string o) + : _pretty_name (p) + , _opendcp_name (o) +{ + +} + +void +DCPContentType::setup_dcp_content_types () +{ + _dcp_content_types.push_back (new DCPContentType ("Feature", "feature")); + _dcp_content_types.push_back (new DCPContentType ("Short", "short")); + _dcp_content_types.push_back (new DCPContentType ("Trailer", "trailer")); + _dcp_content_types.push_back (new DCPContentType ("Test", "test")); + _dcp_content_types.push_back (new DCPContentType ("Transitional", "transitional")); + _dcp_content_types.push_back (new DCPContentType ("Rating", "rating")); + _dcp_content_types.push_back (new DCPContentType ("Teaser", "teaster")); + _dcp_content_types.push_back (new DCPContentType ("Policy", "policy")); + _dcp_content_types.push_back (new DCPContentType ("Public Service Announcement", "psa")); + _dcp_content_types.push_back (new DCPContentType ("Advertisement", "advertisement")); +} + +DCPContentType const * +DCPContentType::from_pretty_name (string n) +{ + for (vector::const_iterator i = _dcp_content_types.begin(); i != _dcp_content_types.end(); ++i) { + if ((*i)->pretty_name() == n) { + return *i; + } + } + + return 0; +} + +DCPContentType const * +DCPContentType::from_index (int n) +{ + assert (n >= 0 && n < int (_dcp_content_types.size ())); + return _dcp_content_types[n]; +} + +int +DCPContentType::as_index (DCPContentType const * c) +{ + vector::size_type i = 0; + while (i < _dcp_content_types.size() && _dcp_content_types[i] != c) { + ++i; + } + + if (i == _dcp_content_types.size ()) { + return -1; + } + + return i; +} + +vector +DCPContentType::all () +{ + return _dcp_content_types; +} diff --git a/src/lib/dcp_content_type.h b/src/lib/dcp_content_type.h new file mode 100644 index 000000000..cade673bc --- /dev/null +++ b/src/lib/dcp_content_type.h @@ -0,0 +1,58 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/content_type.h + * @brief A description of the type of content for a DCP (e.g. feature, trailer etc.) + */ + +#include +#include + +/** @class DCPContentType + * @brief A description of the type of content for a DCP (e.g. feature, trailer etc.) + */ +class DCPContentType +{ +public: + DCPContentType (std::string, std::string); + + /** @return user-visible `pretty' name */ + std::string pretty_name () const { + return _pretty_name; + } + + /** @return name as understood by OpenDCP */ + std::string opendcp_name () const { + return _opendcp_name; + } + + static DCPContentType const * from_pretty_name (std::string); + static DCPContentType const * from_index (int); + static int as_index (DCPContentType const *); + static std::vector all (); + static void setup_dcp_content_types (); + +private: + std::string _pretty_name; + std::string _opendcp_name; + + /** All available DCP content types */ + static std::vector _dcp_content_types; +}; + diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc new file mode 100644 index 000000000..cbd70898e --- /dev/null +++ b/src/lib/dcp_video_frame.cc @@ -0,0 +1,433 @@ +/* + Copyright (C) 2012 Carl Hetherington + Taken from code Copyright (C) 2010-2011 Terrence Meiczinger + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/dcp_video_frame.cc + * @brief A single frame of video destined for a DCP. + * + * Given an Image and some settings, this class knows how to encode + * the image to J2K either on the local host or on a remote server. + * + * Objects of this class are used for the queue that we keep + * of images that require encoding. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "film.h" +#include "dcp_video_frame.h" +#include "lut.h" +#include "config.h" +#include "film_state.h" +#include "options.h" +#include "exceptions.h" +#include "server.h" +#include "util.h" +#include "scaler.h" +#include "image.h" +#include "log.h" + +#ifdef DEBUG_HASH +#include +#endif + +using namespace std; +using namespace boost; + +/** Construct a DCP video frame. + * @param input Input image. + * @param out Required size of output, in pixels (including any padding). + * @param s Scaler to use. + * @param p Number of pixels of padding either side of the image. + * @param f Index of the frame within the Film. + * @param fps Frames per second of the Film. + * @param pp FFmpeg post-processing string to use. + * @param clut Colour look-up table to use (see Config::colour_lut_index ()) + * @param bw J2K bandwidth to use (see Config::j2k_bandwidth ()) + * @param l Log to write to. + */ +DCPVideoFrame::DCPVideoFrame ( + shared_ptr yuv, Size out, int p, Scaler const * s, int f, float fps, string pp, int clut, int bw, Log* l) + : _input (yuv) + , _out_size (out) + , _padding (p) + , _scaler (s) + , _frame (f) + /* we round here; not sure if this is right */ + , _frames_per_second (rint (fps)) + , _post_process (pp) + , _colour_lut_index (clut) + , _j2k_bandwidth (bw) + , _log (l) + , _image (0) + , _parameters (0) + , _cinfo (0) + , _cio (0) +{ + +} + +/** Create a libopenjpeg container suitable for our output image */ +void +DCPVideoFrame::create_openjpeg_container () +{ + for (int i = 0; i < 3; ++i) { + _cmptparm[i].dx = 1; + _cmptparm[i].dy = 1; + _cmptparm[i].w = _out_size.width; + _cmptparm[i].h = _out_size.height; + _cmptparm[i].x0 = 0; + _cmptparm[i].y0 = 0; + _cmptparm[i].prec = 12; + _cmptparm[i].bpp = 12; + _cmptparm[i].sgnd = 0; + } + + _image = opj_image_create (3, &_cmptparm[0], CLRSPC_SRGB); + if (_image == 0) { + throw EncodeError ("could not create libopenjpeg image"); + } + + _image->x0 = 0; + _image->y0 = 0; + _image->x1 = _out_size.width; + _image->y1 = _out_size.height; +} + +DCPVideoFrame::~DCPVideoFrame () +{ + if (_image) { + opj_image_destroy (_image); + } + + if (_cio) { + opj_cio_close (_cio); + } + + if (_cinfo) { + opj_destroy_compress (_cinfo); + } + + if (_parameters) { + free (_parameters->cp_comment); + free (_parameters->cp_matrice); + } + + delete _parameters; +} + +/** J2K-encode this frame on the local host. + * @return Encoded data. + */ +shared_ptr +DCPVideoFrame::encode_locally () +{ + shared_ptr prepared = _input; + + if (!_post_process.empty ()) { + prepared = prepared->post_process (_post_process); + } + + prepared = prepared->scale_and_convert_to_rgb (_out_size, _padding, _scaler); + + create_openjpeg_container (); + + int const size = _out_size.width * _out_size.height; + + struct { + double r, g, b; + } s; + + struct { + double x, y, z; + } d; + + /* Copy our RGB into the openjpeg container, converting to XYZ in the process */ + + uint8_t* p = prepared->data()[0]; + for (int i = 0; i < size; ++i) { + /* In gamma LUT (converting 8-bit input to 12-bit) */ + s.r = lut_in[_colour_lut_index][*p++ << 4]; + s.g = lut_in[_colour_lut_index][*p++ << 4]; + s.b = lut_in[_colour_lut_index][*p++ << 4]; + + /* RGB to XYZ Matrix */ + d.x = ((s.r * color_matrix[_colour_lut_index][0][0]) + (s.g * color_matrix[_colour_lut_index][0][1]) + (s.b * color_matrix[_colour_lut_index][0][2])); + d.y = ((s.r * color_matrix[_colour_lut_index][1][0]) + (s.g * color_matrix[_colour_lut_index][1][1]) + (s.b * color_matrix[_colour_lut_index][1][2])); + d.z = ((s.r * color_matrix[_colour_lut_index][2][0]) + (s.g * color_matrix[_colour_lut_index][2][1]) + (s.b * color_matrix[_colour_lut_index][2][2])); + + /* DCI companding */ + d.x = d.x * DCI_COEFFICENT * (DCI_LUT_SIZE - 1); + d.y = d.y * DCI_COEFFICENT * (DCI_LUT_SIZE - 1); + d.z = d.z * DCI_COEFFICENT * (DCI_LUT_SIZE - 1); + + /* Out gamma LUT */ + _image->comps[0].data[i] = lut_out[LO_DCI][(int) d.x]; + _image->comps[1].data[i] = lut_out[LO_DCI][(int) d.y]; + _image->comps[2].data[i] = lut_out[LO_DCI][(int) d.z]; + } + + /* Set the max image and component sizes based on frame_rate */ + int const max_cs_len = ((float) _j2k_bandwidth) / 8 / _frames_per_second; + int const max_comp_size = max_cs_len / 1.25; + + /* Set encoding parameters to default values */ + _parameters = new opj_cparameters_t; + opj_set_default_encoder_parameters (_parameters); + + /* Set default cinema parameters */ + _parameters->tile_size_on = false; + _parameters->cp_tdx = 1; + _parameters->cp_tdy = 1; + + /* Tile part */ + _parameters->tp_flag = 'C'; + _parameters->tp_on = 1; + + /* Tile and Image shall be at (0,0) */ + _parameters->cp_tx0 = 0; + _parameters->cp_ty0 = 0; + _parameters->image_offset_x0 = 0; + _parameters->image_offset_y0 = 0; + + /* Codeblock size = 32x32 */ + _parameters->cblockw_init = 32; + _parameters->cblockh_init = 32; + _parameters->csty |= 0x01; + + /* The progression order shall be CPRL */ + _parameters->prog_order = CPRL; + + /* No ROI */ + _parameters->roi_compno = -1; + + _parameters->subsampling_dx = 1; + _parameters->subsampling_dy = 1; + + /* 9-7 transform */ + _parameters->irreversible = 1; + + _parameters->tcp_rates[0] = 0; + _parameters->tcp_numlayers++; + _parameters->cp_disto_alloc = 1; + _parameters->cp_rsiz = CINEMA2K; + _parameters->cp_comment = strdup ("OpenDCP"); + _parameters->cp_cinema = CINEMA2K_24; + + /* 3 components, so use MCT */ + _parameters->tcp_mct = 1; + + /* set max image */ + _parameters->max_comp_size = max_comp_size; + _parameters->tcp_rates[0] = ((float) (3 * _image->comps[0].w * _image->comps[0].h * _image->comps[0].prec)) / (max_cs_len * 8); + + /* get a J2K compressor handle */ + _cinfo = opj_create_compress (CODEC_J2K); + + /* Set event manager to null (openjpeg 1.3 bug) */ + _cinfo->event_mgr = 0; + +#ifdef DEBUG_HASH + md5_data ("J2K in X", _image->comps[0].data, size * sizeof (int)); + md5_data ("J2K in Y", _image->comps[1].data, size * sizeof (int)); + md5_data ("J2K in Z", _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); + + _cio = opj_cio_open ((opj_common_ptr) _cinfo, 0, 0); + + int const r = opj_encode (_cinfo, _cio, _image, 0); + if (r == 0) { + throw EncodeError ("jpeg2000 encoding failed"); + } + +#ifdef DEBUG_HASH + md5_data ("J2K out", _cio->buffer, cio_tell (_cio)); +#endif + + { + stringstream s; + s << "Finished locally-encoded frame " << _frame << " length " << cio_tell (_cio); + _log->log (s.str ()); + } + + return shared_ptr (new LocallyEncodedData (_cio->buffer, cio_tell (_cio))); +} + +/** Send this frame to a remote server for J2K encoding, then read the result. + * @param serv Server to send to. + * @return Encoded data. + */ +shared_ptr +DCPVideoFrame::encode_remotely (Server const * serv) +{ + int const fd = socket (AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + throw NetworkError ("could not create socket"); + } + + struct timeval tv; + tv.tv_sec = 20; + tv.tv_usec = 0; + + if (setsockopt (fd, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv, sizeof (tv)) < 0) { + close (fd); + throw NetworkError ("setsockopt failed"); + } + + if (setsockopt (fd, SOL_SOCKET, SO_SNDTIMEO, (void *) &tv, sizeof (tv)) < 0) { + close (fd); + throw NetworkError ("setsockopt failed"); + } + + struct hostent* server = gethostbyname (serv->host_name().c_str ()); + if (server == 0) { + close (fd); + throw NetworkError ("gethostbyname failed"); + } + + struct sockaddr_in server_address; + memset (&server_address, 0, sizeof (server_address)); + server_address.sin_family = AF_INET; + memcpy (&server_address.sin_addr.s_addr, server->h_addr, server->h_length); + server_address.sin_port = htons (Config::instance()->server_port ()); + if (connect (fd, (struct sockaddr *) &server_address, sizeof (server_address)) < 0) { + close (fd); + stringstream s; + s << "could not connect (" << strerror (errno) << ")"; + throw NetworkError (s.str()); + } + +#ifdef DEBUG_HASH + _input->hash ("Input for remote encoding (before sending)"); +#endif + + stringstream s; + s << "encode " + << _input->size().width << " " << _input->size().height << " " + << _input->pixel_format() << " " + << _out_size.width << " " << _out_size.height << " " + << _padding << " " + << _scaler->id () << " " + << _frame << " " + << _frames_per_second << " " + << (_post_process.empty() ? "none" : _post_process) << " " + << Config::instance()->colour_lut_index () << " " + << Config::instance()->j2k_bandwidth () << " "; + + for (int i = 0; i < _input->components(); ++i) { + s << _input->line_size()[i] << " "; + } + + socket_write (fd, (uint8_t *) s.str().c_str(), s.str().length() + 1); + + for (int i = 0; i < _input->components(); ++i) { + socket_write (fd, _input->data()[i], _input->line_size()[i] * _input->lines(i)); + } + + SocketReader reader (fd); + + char buffer[32]; + reader.read_indefinite ((uint8_t *) buffer, sizeof (buffer)); + reader.consume (strlen (buffer) + 1); + shared_ptr 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 + + { + stringstream s; + s << "Finished remotely-encoded frame " << _frame << " length " << e->size(); + _log->log (s.str ()); + } + + close (fd); + return e; +} + +/** Write this data to a J2K file. + * @param opt Options. + * @param frame Frame index. + */ +void +EncodedData::write (shared_ptr opt, int frame) +{ + string const tmp_j2k = opt->frame_out_path (frame, true); + + FILE* f = fopen (tmp_j2k.c_str (), "wb"); + + if (!f) { + throw WriteFileError (tmp_j2k, errno); + } + + fwrite (_data, 1, _size, f); + fclose (f); + + /* 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)); +} + +/** Send this data to a file descriptor. + * @param fd File descriptor. + */ +void +EncodedData::send (int fd) +{ + stringstream s; + s << _size; + socket_write (fd, (uint8_t *) s.str().c_str(), s.str().length() + 1); + socket_write (fd, _data, _size); +} + +#ifdef DEBUG_HASH +void +EncodedData::hash (string n) const +{ + md5_data (n, _data, _size); +} +#endif + +/** @param s Size of data in bytes */ +RemotelyEncodedData::RemotelyEncodedData (int s) + : EncodedData (new uint8_t[s], s) +{ + +} + +RemotelyEncodedData::~RemotelyEncodedData () +{ + delete[] _data; +} diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h new file mode 100644 index 000000000..26b44ad43 --- /dev/null +++ b/src/lib/dcp_video_frame.h @@ -0,0 +1,143 @@ +/* + Copyright (C) 2012 Carl Hetherington + Taken from code Copyright (C) 2010-2011 Terrence Meiczinger + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include +#include "util.h" + +/** @file src/dcp_video_frame.h + * @brief A single frame of video destined for a DCP. + */ + +class FilmState; +class Options; +class Server; +class Scaler; +class Image; +class Log; + +/** @class EncodedData + * @brief Container for J2K-encoded data. + */ +class EncodedData +{ +public: + /** @param d Data (will not be freed by this class, but may be by subclasses) + * @param s Size of data, in bytes. + */ + EncodedData (uint8_t* d, int s) + : _data (d) + , _size (s) + {} + + virtual ~EncodedData () {} + + void send (int); + void write (boost::shared_ptr, int); + +#ifdef DEBUG_HASH + void hash (std::string) const; +#endif + + /** @return data */ + uint8_t* data () const { + return _data; + } + + /** @return data size, in bytes */ + int size () const { + return _size; + } + +protected: + uint8_t* _data; ///< data + int _size; ///< data size in bytes +}; + +/** @class LocallyEncodedData + * @brief EncodedData that was encoded locally; this class + * just keeps a pointer to the data, but does no memory + * management. + */ +class LocallyEncodedData : public EncodedData +{ +public: + /** @param d Data (which will not be freed by this class) + * @param s Size of data, in bytes. + */ + LocallyEncodedData (uint8_t* d, int s) + : EncodedData (d, s) + {} +}; + +/** @class RemotelyEncodedData + * @brief EncodedData that is being read from a remote server; + * this class allocates and manages memory for the data. + */ +class RemotelyEncodedData : public EncodedData +{ +public: + RemotelyEncodedData (int s); + ~RemotelyEncodedData (); +}; + +/** @class DCPVideoFrame + * @brief A single frame of video destined for a DCP. + * + * Given an Image and some settings, this class knows how to encode + * the image to J2K either on the local host or on a remote server. + * + * Objects of this class are used for the queue that we keep + * of images that require encoding. + */ +class DCPVideoFrame +{ +public: + DCPVideoFrame (boost::shared_ptr, Size, int, Scaler const *, int, float, std::string, int, int, Log *); + virtual ~DCPVideoFrame (); + + boost::shared_ptr encode_locally (); + boost::shared_ptr encode_remotely (Server const *); + + int frame () const { + return _frame; + } + +private: + void create_openjpeg_container (); + void write_encoded (boost::shared_ptr, uint8_t *, int); + + boost::shared_ptr _input; ///< the input image + Size _out_size; ///< the required size of the output, in pixels + int _padding; + Scaler const * _scaler; ///< scaler to use + int _frame; ///< frame index within the Film + int _frames_per_second; ///< Frames per second that we will use for the DCP (rounded) + std::string _post_process; ///< FFmpeg post-processing string to use + int _colour_lut_index; ///< Colour look-up table to use (see Config::colour_lut_index ()) + int _j2k_bandwidth; ///< J2K bandwidth to use (see Config::j2k_bandwidth ()) + + Log* _log; ///< log + + opj_image_cmptparm_t _cmptparm[3]; ///< libopenjpeg's opj_image_cmptparm_t + opj_image* _image; ///< libopenjpeg's image container + opj_cparameters_t* _parameters; ///< libopenjpeg's parameters + opj_cinfo_t* _cinfo; ///< libopenjpeg's opj_cinfo_t + opj_cio_t* _cio; ///< libopenjpeg's opj_cio_t +}; diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc new file mode 100644 index 000000000..a904e085b --- /dev/null +++ b/src/lib/decoder.cc @@ -0,0 +1,300 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/decoder.cc + * @brief Parent class for decoders of content. + */ + +#include +#include +extern "C" { +#include +#include +#include +} +#include "film.h" +#include "format.h" +#include "job.h" +#include "film_state.h" +#include "options.h" +#include "exceptions.h" +#include "image.h" +#include "util.h" +#include "log.h" +#include "decoder.h" +#include "filter.h" +#include "delay_line.h" + +using namespace std; +using namespace boost; + +/** @param s FilmState of the Film. + * @param o Options. + * @param j Job that we are running within, or 0 + * @param l Log to use. + * @param minimal true to do the bare minimum of work; just run through the content. Useful for acquiring + * accurate frame counts as quickly as possible. This generates no video or audio output. + * @param ignore_length Ignore the content's claimed length when computing progress. + */ +Decoder::Decoder (boost::shared_ptr s, boost::shared_ptr o, Job* j, Log* l, bool minimal, bool ignore_length) + : _fs (s) + , _opt (o) + , _job (j) + , _log (l) + , _minimal (minimal) + , _ignore_length (ignore_length) + , _video_frame (0) + , _buffer_src_context (0) + , _buffer_sink_context (0) + , _have_setup_video_filters (false) + , _delay_line (0) + , _delay_in_bytes (0) +{ + if (_opt->decode_video_frequency != 0 && _fs->length == 0) { + throw DecodeError ("cannot do a partial decode if length == 0"); + } +} + +Decoder::~Decoder () +{ + delete _delay_line; +} + +void +Decoder::process_begin () +{ + /* This assumes 2 bytes per sample */ + _delay_in_bytes = _fs->audio_delay * _fs->audio_sample_rate * _fs->audio_channels * 2 / 1000; + delete _delay_line; + _delay_line = new DelayLine (_delay_in_bytes); +} + +void +Decoder::process_end () +{ + if (_delay_in_bytes < 0) { + uint8_t remainder[-_delay_in_bytes]; + _delay_line->get_remaining (remainder); + Audio (remainder, _delay_in_bytes); + } +} + +/** Start decoding */ +void +Decoder::go () +{ + process_begin (); + + if (_job && _ignore_length) { + _job->set_progress_unknown (); + } + + while (pass () == false) { + if (_job && !_ignore_length) { + _job->set_progress (float (_video_frame) / decoding_frames ()); + } + } + + process_end (); +} + +/** @return Number of frames that we will be decoding */ +int +Decoder::decoding_frames () const +{ + if (_opt->num_frames > 0) { + return _opt->num_frames; + } + + return _fs->length; +} + +/** Run one pass. This may or may not generate any actual video / audio data; + * some decoders may require several passes to generate a single frame. + * @return true if we have finished processing all data; otherwise false. + */ +bool +Decoder::pass () +{ + if (!_have_setup_video_filters) { + setup_video_filters (); + _have_setup_video_filters = true; + } + + if (_opt->num_frames != 0 && _video_frame >= _opt->num_frames) { + return true; + } + + return do_pass (); +} + +/** Called by subclasses to tell the world that some audio data is ready */ +void +Decoder::process_audio (uint8_t* data, int channels, int size) +{ + if (_fs->audio_gain != 0) { + float const linear_gain = pow (10, _fs->audio_gain / 20); + uint8_t* p = data; + int const samples = size / 2; + switch (_fs->audio_sample_format) { + case AV_SAMPLE_FMT_S16: + for (int i = 0; i < samples; ++i) { + /* XXX: assumes little-endian; also we should probably be dithering here */ + int const ou = p[0] | (p[1] << 8); + int const os = ou >= 0x8000 ? (- 0x10000 + ou) : ou; + int const gs = int (os * linear_gain); + int const gu = gs > 0 ? gs : (0x10000 + gs); + p[0] = gu & 0xff; + p[1] = (gu & 0xff00) >> 8; + p += 2; + } + break; + default: + assert (false); + } + } + + int available = _delay_line->feed (data, size); + Audio (data, available); +} + +/** Called by subclasses to tell the world that some video data is ready. + * We do some post-processing / filtering then emit it for listeners. + * @param frame to decode; caller manages memory. + */ +void +Decoder::process_video (AVFrame* frame) +{ + if (_minimal) { + ++_video_frame; + return; + } + + /* Use FilmState::length here as our one may be wrong */ + + int gap = 0; + if (_opt->decode_video_frequency != 0) { + gap = _fs->length / _opt->decode_video_frequency; + } + + if (_opt->decode_video_frequency != 0 && gap != 0 && (_video_frame % gap) != 0) { + ++_video_frame; + return; + } + + if (av_vsrc_buffer_add_frame (_buffer_src_context, frame, 0) < 0) { + throw DecodeError ("could not push buffer into filter chain."); + } + + while (avfilter_poll_frame (_buffer_sink_context->inputs[0])) { + AVFilterBufferRef* filter_buffer; + if (av_buffersink_get_buffer_ref (_buffer_sink_context, &filter_buffer, 0) >= 0) { + + /* This takes ownership of filter_buffer */ + shared_ptr image (new FilterBufferImage ((PixelFormat) frame->format, filter_buffer)); + + if (_opt->black_after > 0 && _video_frame > _opt->black_after) { + image->make_black (); + } + + Video (image, _video_frame); + ++_video_frame; + } + } +} + +void +Decoder::setup_video_filters () +{ + stringstream fs; + Size size_after_crop; + + if (_opt->apply_crop) { + size_after_crop = _fs->cropped_size (native_size ()); + fs << crop_string (Position (_fs->left_crop, _fs->top_crop), size_after_crop); + } else { + size_after_crop = native_size (); + fs << crop_string (Position (0, 0), size_after_crop); + } + + string filters = Filter::ffmpeg_strings (_fs->filters).first; + if (!filters.empty ()) { + filters += ","; + } + + filters += fs.str (); + + avfilter_register_all (); + + AVFilterGraph* graph = avfilter_graph_alloc(); + if (graph == 0) { + throw DecodeError ("Could not create filter graph."); + } + + AVFilter* buffer_src = avfilter_get_by_name("buffer"); + if (buffer_src == 0) { + throw DecodeError ("Could not create buffer src filter"); + } + + AVFilter* buffer_sink = avfilter_get_by_name("buffersink"); + if (buffer_sink == 0) { + throw DecodeError ("Could not create buffer sink filter"); + } + + stringstream a; + a << native_size().width << ":" + << native_size().height << ":" + << pixel_format() << ":" + << time_base_numerator() << ":" + << time_base_denominator() << ":" + << sample_aspect_ratio_numerator() << ":" + << 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) { + throw DecodeError ("could not create buffer sink."); + } + + AVFilterInOut* outputs = avfilter_inout_alloc (); + outputs->name = av_strdup("in"); + outputs->filter_ctx = _buffer_src_context; + outputs->pad_idx = 0; + outputs->next = 0; + + AVFilterInOut* inputs = avfilter_inout_alloc (); + inputs->name = av_strdup("out"); + inputs->filter_ctx = _buffer_sink_context; + inputs->pad_idx = 0; + inputs->next = 0; + + _log->log ("Using filter chain `" + filters + "'"); + if (avfilter_graph_parse (graph, filters.c_str(), &inputs, &outputs, 0) < 0) { + throw DecodeError ("could not set up filter graph."); + } + + if (avfilter_graph_config (graph, 0) < 0) { + throw DecodeError ("could not configure filter graph."); + } +} + diff --git a/src/lib/decoder.h b/src/lib/decoder.h new file mode 100644 index 000000000..db51879a1 --- /dev/null +++ b/src/lib/decoder.h @@ -0,0 +1,136 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/decoder.h + * @brief Parent class for decoders of content. + */ + +#ifndef DVDOMATIC_DECODER_H +#define DVDOMATIC_DECODER_H + +#include +#include +#include +#include +#include +#include "util.h" + +class Job; +class FilmState; +class Options; +class Image; +class Log; +class DelayLine; + +/** @class Decoder. + * @brief Parent class for decoders of content. + * + * These classes can be instructed run through their content + * (by calling ::go), and they emit signals when video or audio data is ready for something else + * to process. + */ +class Decoder +{ +public: + Decoder (boost::shared_ptr, boost::shared_ptr, Job *, Log *, bool, bool); + virtual ~Decoder (); + + /* Methods to query our input video */ + + /** @return length in video frames */ + virtual int length_in_frames () const = 0; + /** @return video frames per second, or 0 if unknown */ + virtual float frames_per_second () const = 0; + /** @return native size in pixels */ + virtual Size native_size () const = 0; + /** @return number of audio channels */ + virtual int audio_channels () const = 0; + /** @return audio sampling rate in Hz */ + virtual int audio_sample_rate () const = 0; + /** @return format of audio samples */ + virtual AVSampleFormat audio_sample_format () const = 0; + + void process_begin (); + bool pass (); + void process_end (); + void go (); + + /** @return the index of the last video frame to be processed */ + int last_video_frame () const { + return _video_frame; + } + + int decoding_frames () const; + + /** Emitted when a video frame is ready. + * First parameter is the frame. + * Second parameter is its index within the content. + */ + sigc::signal, int> Video; + + /** Emitted when some audio data is ready. + * First parameter is the interleaved sample data, format is given in the FilmState. + * Second parameter is the size of the data. + */ + sigc::signal Audio; + +protected: + /** perform a single pass at our content */ + virtual bool do_pass () = 0; + virtual PixelFormat pixel_format () const = 0; + virtual int time_base_numerator () const = 0; + virtual int time_base_denominator () const = 0; + virtual int sample_aspect_ratio_numerator () const = 0; + virtual int sample_aspect_ratio_denominator () const = 0; + + void process_video (AVFrame *); + void process_audio (uint8_t *, int, int); + + /** our FilmState */ + boost::shared_ptr _fs; + /** our options */ + boost::shared_ptr _opt; + /** associated Job, or 0 */ + Job* _job; + /** log that we can write to */ + Log* _log; + + /** true to do the bare minimum of work; just run through the content. Useful for acquiring + * accurate frame counts as quickly as possible. This generates no video or audio output. + */ + bool _minimal; + + /** ignore_length Ignore the content's claimed length when computing progress */ + bool _ignore_length; + +private: + void setup_video_filters (); + + /** last video frame to be processed */ + int _video_frame; + + AVFilterContext* _buffer_src_context; + AVFilterContext* _buffer_sink_context; + + bool _have_setup_video_filters; + DelayLine* _delay_line; + int _delay_in_bytes; +}; + +#endif diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc new file mode 100644 index 000000000..5f8fc55b3 --- /dev/null +++ b/src/lib/decoder_factory.cc @@ -0,0 +1,48 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/decoder_factory.cc + * @brief A method to create an appropriate decoder for some content. + */ + +#include +#include "ffmpeg_decoder.h" +#include "tiff_decoder.h" +#include "imagemagick_decoder.h" +#include "film_state.h" + +using namespace std; +using namespace boost; + +shared_ptr +decoder_factory ( + shared_ptr fs, shared_ptr o, Job* j, Log* l, bool minimal = false, bool ignore_length = false + ) +{ + if (filesystem::is_directory (fs->content_path ())) { + /* Assume a directory contains TIFFs */ + return shared_ptr (new TIFFDecoder (fs, o, j, l, minimal, ignore_length)); + } + + if (fs->content_type() == STILL) { + return shared_ptr (new ImageMagickDecoder (fs, o, j, l, minimal, ignore_length)); + } + + return shared_ptr (new FFmpegDecoder (fs, o, j, l, minimal, ignore_length)); +} diff --git a/src/lib/decoder_factory.h b/src/lib/decoder_factory.h new file mode 100644 index 000000000..36c14951f --- /dev/null +++ b/src/lib/decoder_factory.h @@ -0,0 +1,32 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/decoder_factory.h + * @brief A method to create an appropriate decoder for some content. + */ + +class Decoder; +class FilmState; +class Options; +class Job; +class Log; + +extern boost::shared_ptr decoder_factory ( + boost::shared_ptr, boost::shared_ptr, Job *, Log *, bool minimal = false, bool ignore_length = false + ); diff --git a/src/lib/delay_line.cc b/src/lib/delay_line.cc new file mode 100644 index 000000000..c510fb4e3 --- /dev/null +++ b/src/lib/delay_line.cc @@ -0,0 +1,110 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include +#include +#include "delay_line.h" + +using namespace std; + +/** Construct a DelayLine delaying by some number of bytes. + * @param d Number of bytes to delay by; +ve moves data later. + */ +DelayLine::DelayLine (int d) + : _delay (d) + , _buffer (0) + , _negative_delay_remaining (0) +{ + if (d > 0) { + /* We need a buffer to keep some data in */ + _buffer = new uint8_t[d]; + memset (_buffer, 0, d); + } else if (d < 0) { + /* We can do -ve delays just by chopping off + the start, so no buffer needed. + */ + _negative_delay_remaining = -d; + } +} + +DelayLine::~DelayLine () +{ + delete[] _buffer; +} + +int +DelayLine::feed (uint8_t* data, int size) +{ + int available = size; + + if (_delay > 0) { + + /* Copy the input data */ + uint8_t input[size]; + memcpy (input, data, size); + + int to_do = size; + + /* Write some of our buffer to the output */ + int const from_buffer = min (to_do, _delay); + memcpy (data, _buffer, from_buffer); + to_do -= from_buffer; + + /* Write some of the input to the output */ + int const from_input = min (to_do, size); + memcpy (data + from_buffer, input, from_input); + + int const left_in_buffer = _delay - from_buffer; + + /* Shuffle our buffer down */ + memmove (_buffer, _buffer + from_buffer, left_in_buffer); + + /* Copy remaining input data to our buffer */ + memcpy (_buffer + left_in_buffer, input + from_input, size - from_input); + + } else if (_delay < 0) { + + /* Chop the initial data off until _negative_delay_remaining + is zero, then just pass data. + */ + + int const to_do = min (size, _negative_delay_remaining); + available = size - to_do; + memmove (data, data + to_do, available); + _negative_delay_remaining -= to_do; + + } + + return available; +} + +/** With -ve delays, the DelayLine will have data to give after + * all input data has been passed to ::feed(). + * Call this method after passing all input data. + * + * @param buffer Pointer to buffer of _delay bytes in length, + * which will be filled with remaining data. + */ +void +DelayLine::get_remaining (uint8_t* buffer) +{ + memset (buffer, 0, -_delay); +} diff --git a/src/lib/delay_line.h b/src/lib/delay_line.h new file mode 100644 index 000000000..377553de4 --- /dev/null +++ b/src/lib/delay_line.h @@ -0,0 +1,36 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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. + +*/ + +/** A class which can be fed a stream of bytes and which can + * delay them by a positive or negative amount. + */ +class DelayLine +{ +public: + DelayLine (int); + ~DelayLine (); + + int feed (uint8_t *, int); + void get_remaining (uint8_t *); + +private: + int _delay; ///< delay in bytes, +ve to move data later + uint8_t* _buffer; ///< buffer for +ve delays, or 0 + int _negative_delay_remaining; ///< number of bytes of negative delay that remain to emit +}; diff --git a/src/lib/dvd.cc b/src/lib/dvd.cc new file mode 100644 index 000000000..629ba1ac8 --- /dev/null +++ b/src/lib/dvd.cc @@ -0,0 +1,78 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include + +using namespace std; +using namespace boost; + +string +find_dvd () +{ + ifstream f ("/etc/mtab"); + while (f.good ()) { + string s; + getline (f, s); + vector b; + split (b, s, is_any_of (" ")); + if (b.size() >= 3 && b[2] == "udf") { + replace_all (b[1], "\\040", " "); + return b[1]; + } + } + + return ""; +} + +vector +dvd_titles (string dvd) +{ + filesystem::path video (dvd); + video /= "VIDEO_TS"; + + vector sizes; + + for (filesystem::directory_iterator i = filesystem::directory_iterator (video); i != filesystem::directory_iterator(); ++i) { +#if BOOST_FILESYSTEM_VERSION == 3 + string const n = filesystem::path(*i).filename().generic_string(); +#else + string const n = filesystem::path(*i).filename(); +#endif + if (starts_with (n, "VTS_") && ends_with (n, ".VOB")) { + uint64_t const size = filesystem::file_size (filesystem::path (*i)); + vector p; + split (p, n, is_any_of ("_.")); + if (p.size() == 4) { + int const a = atoi (p[1].c_str ()); + int const b = atoi (p[2].c_str ()); + while (a >= int (sizes.size())) { + sizes.push_back (0); + } + + if (b > 0) { + sizes[a] += size; + } + } + } + } + + return sizes; +} diff --git a/src/lib/dvd.h b/src/lib/dvd.h new file mode 100644 index 000000000..170472121 --- /dev/null +++ b/src/lib/dvd.h @@ -0,0 +1,21 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 std::vector dvd_titles (std::string); +extern std::string find_dvd (); diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc new file mode 100644 index 000000000..c8eb24c80 --- /dev/null +++ b/src/lib/encoder.cc @@ -0,0 +1,71 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/encoder.h + * @brief Parent class for classes which can encode video and audio frames. + */ + +#include "encoder.h" +#include "util.h" + +using namespace boost; + +int const Encoder::_history_size = 25; + +/** @param s FilmState of the film that we are encoding. + * @param o Options. + * @param l Log. + */ +Encoder::Encoder (shared_ptr s, shared_ptr o, Log* l) + : _fs (s) + , _opt (o) + , _log (l) +{ + +} + + +/** @return an estimate of the current number of frames we are encoding per second, + * or 0 if not known. + */ +float +Encoder::current_frames_per_second () const +{ + boost::mutex::scoped_lock lock (_history_mutex); + if (int (_time_history.size()) < _history_size) { + return 0; + } + + struct timeval now; + gettimeofday (&now, 0); + + return _history_size / (seconds (now) - seconds (_time_history.back ())); +} + +void +Encoder::frame_done () +{ + boost::mutex::scoped_lock lock (_history_mutex); + struct timeval tv; + gettimeofday (&tv, 0); + _time_history.push_front (tv); + if (int (_time_history.size()) > _history_size) { + _time_history.pop_back (); + } +} diff --git a/src/lib/encoder.h b/src/lib/encoder.h new file mode 100644 index 000000000..bed2c0988 --- /dev/null +++ b/src/lib/encoder.h @@ -0,0 +1,87 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef DVDOMATIC_ENCODER_H +#define DVDOMATIC_ENCODER_H + +/** @file src/encoder.h + * @brief Parent class for classes which can encode video and audio frames. + */ + +#include +#include +#include +#include + +class FilmState; +class Options; +class Image; +class Log; + +/** @class Encoder + * @brief Parent class for classes which can encode video and audio frames. + * + * Video is supplied to process_video as YUV frames, and audio + * is supplied as uncompressed PCM in blocks of various sizes. + * + * The subclass is expected to encode the video and/or audio in + * some way and write it to disk. + */ + +class Encoder +{ +public: + Encoder (boost::shared_ptr s, boost::shared_ptr o, Log* l); + + /** Called to indicate that a processing run is about to begin */ + virtual void process_begin () = 0; + + /** Called with a frame of video. + * @param i Video frame image. + * @param f Frame number within the film. + */ + virtual void process_video (boost::shared_ptr i, int f) = 0; + + /** Called with some audio data. + * @param d Data. + * @param s Size of data (in bytes) + */ + virtual void process_audio (uint8_t* d, int s) = 0; + + /** Called when a processing run has finished */ + virtual void process_end () = 0; + + float current_frames_per_second () const; + +protected: + void frame_done (); + + /** FilmState of the film that we are encoding */ + boost::shared_ptr _fs; + /** Options */ + boost::shared_ptr _opt; + /** Log */ + Log* _log; + + mutable boost::mutex _history_mutex; + std::list _time_history; + static int const _history_size; +}; + +#endif diff --git a/src/lib/encoder_factory.cc b/src/lib/encoder_factory.cc new file mode 100644 index 000000000..d16150fa6 --- /dev/null +++ b/src/lib/encoder_factory.cc @@ -0,0 +1,40 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/encoder_factory.cc + * @brief A method to create an appropriate encoder for some content. + */ + +#include +#include "j2k_wav_encoder.h" +#include "j2k_still_encoder.h" +#include "film_state.h" + +using namespace std; +using namespace boost; + +shared_ptr +encoder_factory (shared_ptr fs, shared_ptr o, Log* l) +{ + if (!filesystem::is_directory (fs->content_path()) && fs->content_type() == STILL) { + return shared_ptr (new J2KStillEncoder (fs, o, l)); + } + + return shared_ptr (new J2KWAVEncoder (fs, o, l)); +} diff --git a/src/lib/encoder_factory.h b/src/lib/encoder_factory.h new file mode 100644 index 000000000..2803de6f0 --- /dev/null +++ b/src/lib/encoder_factory.h @@ -0,0 +1,30 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/encoder_factory.h + * @brief A method to create an appropriate encoder for some content. + */ + +class Encoder; +class FilmState; +class Options; +class Job; +class Log; + +extern boost::shared_ptr encoder_factory (boost::shared_ptr, boost::shared_ptr, Log *); diff --git a/src/lib/examine_content_job.cc b/src/lib/examine_content_job.cc new file mode 100644 index 000000000..6927715bd --- /dev/null +++ b/src/lib/examine_content_job.cc @@ -0,0 +1,69 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/examine_content_job.cc + * @brief A class to run through content at high speed to find its length. + */ + +#include "examine_content_job.h" +#include "options.h" +#include "film_state.h" +#include "decoder_factory.h" +#include "decoder.h" + +using namespace std; +using namespace boost; + +ExamineContentJob::ExamineContentJob (shared_ptr fs, Log* l) + : Job (fs, shared_ptr (), l) +{ + +} + +ExamineContentJob::~ExamineContentJob () +{ +} + +string +ExamineContentJob::name () const +{ + stringstream s; + s << "Examine content of " << _fs->name; + return s.str (); +} + +void +ExamineContentJob::run () +{ + shared_ptr o (new Options ("", "", "")); + o->out_size = Size (512, 512); + o->apply_crop = false; + + _decoder = decoder_factory (_fs, o, this, _log, true, true); + _decoder->go (); + + set_progress (1); + set_state (FINISHED_OK); +} + +int +ExamineContentJob::last_video_frame () const +{ + return _decoder->last_video_frame (); +} diff --git a/src/lib/examine_content_job.h b/src/lib/examine_content_job.h new file mode 100644 index 000000000..d149341b4 --- /dev/null +++ b/src/lib/examine_content_job.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/examine_content_job.h + * @brief A class to run through content at high speed to find its length. + */ + +#include "job.h" + +class Decoder; + +/** @class ExamineContentJob + * @brief A class to run through content at high speed to find its length. + */ +class ExamineContentJob : public Job +{ +public: + ExamineContentJob (boost::shared_ptr, Log *); + ~ExamineContentJob (); + + std::string name () const; + void run (); + + int last_video_frame () const; + +private: + boost::shared_ptr _decoder; +}; + diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h new file mode 100644 index 000000000..b16275c20 --- /dev/null +++ b/src/lib/exceptions.h @@ -0,0 +1,215 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/exceptions.h + * @brief Our exceptions. + */ + +#include +#include +#include + +/** @class StringError + * @brief A parent class for exceptions using messages held in a std::string + */ +class StringError : public std::exception +{ +public: + /** @param w Error message */ + StringError (std::string w) { + _what = w; + } + + virtual ~StringError () throw () {} + + /** @return error message */ + char const * what () const throw () { + return _what.c_str (); + } + +protected: + /** error message */ + std::string _what; +}; + +/** @class DecodeError + * @brief A low-level problem with the decoder (possibly due to the nature + * of a source file). + */ +class DecodeError : public StringError +{ +public: + DecodeError (std::string s) + : StringError (s) + {} +}; + +/** @class EncodeError + * @brief A low-level problem with an encoder. + */ +class EncodeError : public StringError +{ +public: + EncodeError (std::string s) + : StringError (s) + {} +}; + +/** @class FileError. + * @brief Parent class for file-related errors. + */ +class FileError : public StringError +{ +public: + FileError (std::string m, std::string f) + : StringError (m) + , _file (f) + {} + + virtual ~FileError () throw () {} + + std::string file () const { + return _file; + } + +private: + std::string _file; +}; + + +/** @class OpenFileError. + * @brief Indicates that some error occurred when trying to open a file. + */ +class OpenFileError : public FileError +{ +public: + /** @param f File that we were trying to open */ + OpenFileError (std::string f) + : FileError ("could not open file " + f, f) + {} +}; + +/** @class CreateFileError. + * @brief Indicates that some error occurred when trying to create a file. + */ +class CreateFileError : public FileError +{ +public: + /** @param f File that we were trying to create */ + CreateFileError (std::string f) + : FileError ("could not create file " + f, f) + {} +}; + +/** @class WriteFileError. + * @brief Indicates that some error occurred when trying to write to a file + */ +class WriteFileError : public FileError +{ +public: + /** @param f File that we were trying to write to. + * @param e errno value, or 0. + */ + WriteFileError (std::string f, int e) + : FileError ("", f) + { + std::stringstream s; + s << "could not write to file " << f; + if (e) { + s << " (" << strerror (e) << ")"; + } + _what = s.str (); + } +}; + +/** @class SettingError. + * @brief Indicates that something is wrong with a setting. + */ +class SettingError : public StringError +{ +public: + /** @param s Name of setting that was required. + * @param m Message. + */ + SettingError (std::string s, std::string m) + : StringError (m) + , _setting (s) + {} + + virtual ~SettingError () throw () {} + + /** @return name of setting in question */ + std::string setting () const { + return _setting; + } + +private: + std::string _setting; +}; + +/** @class MissingSettingError. + * @brief Indicates that a Film is missing a setting that is required for some operation. + */ +class MissingSettingError : public SettingError +{ +public: + /** @param s Name of setting that was required */ + MissingSettingError (std::string s) + : SettingError (s, "missing required setting " + s) + {} +}; + +/** @class BadSettingError + * @brief Indicates that a setting is bad in some way. + */ +class BadSettingError : public SettingError +{ +public: + /** @param s Name of setting that is bad */ + BadSettingError (std::string s, std::string m) + : SettingError (s, m) + {} +}; + +/** @class NetworkError. + * @brief Indicates some problem with communication on the network. + */ +class NetworkError : public StringError +{ +public: + NetworkError (std::string s) + : StringError (s) + {} +}; + +class PlayError : public StringError +{ +public: + PlayError (std::string s) + : StringError (s) + {} +}; + +class DVDError : public StringError +{ +public: + DVDError (std::string s) + : StringError (s) + {} +}; diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc new file mode 100644 index 000000000..af258f381 --- /dev/null +++ b/src/lib/ffmpeg_decoder.cc @@ -0,0 +1,256 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/ffmpeg_decoder.cc + * @brief A decoder using FFmpeg to decode content. + */ + +#include +#include +#include +#include +#include +#include +extern "C" { +#include +#include +#include +#include +#include +} +#include +#include "film.h" +#include "format.h" +#include "transcoder.h" +#include "job.h" +#include "filter.h" +#include "film_state.h" +#include "options.h" +#include "exceptions.h" +#include "image.h" +#include "util.h" +#include "log.h" +#include "ffmpeg_decoder.h" + +using namespace std; +using namespace boost; + +FFmpegDecoder::FFmpegDecoder (boost::shared_ptr s, boost::shared_ptr o, Job* j, Log* l, bool minimal, bool ignore_length) + : Decoder (s, o, j, l, minimal, ignore_length) + , _format_context (0) + , _video_stream (-1) + , _audio_stream (-1) + , _frame (0) + , _video_codec_context (0) + , _video_codec (0) + , _audio_codec_context (0) + , _audio_codec (0) +{ + setup_general (); + setup_video (); + setup_audio (); +} + +FFmpegDecoder::~FFmpegDecoder () +{ + if (_audio_codec_context) { + avcodec_close (_audio_codec_context); + } + + if (_video_codec_context) { + avcodec_close (_video_codec_context); + } + + av_free (_frame); + avformat_close_input (&_format_context); +} + +void +FFmpegDecoder::setup_general () +{ + int r; + + av_register_all (); + + if ((r = avformat_open_input (&_format_context, _fs->content_path().c_str(), 0, 0)) != 0) { + throw OpenFileError (_fs->content_path ()); + } + + if (avformat_find_stream_info (_format_context, 0) < 0) { + throw DecodeError ("could not find stream information"); + } + + for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { + if (_format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + _video_stream = i; + } else if (_format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + _audio_stream = i; + } + } + + if (_video_stream < 0) { + throw DecodeError ("could not find video stream"); + } + if (_audio_stream < 0) { + throw DecodeError ("could not find audio stream"); + } + + _frame = avcodec_alloc_frame (); + if (_frame == 0) { + throw DecodeError ("could not allocate frame"); + } +} + +void +FFmpegDecoder::setup_video () +{ + _video_codec_context = _format_context->streams[_video_stream]->codec; + _video_codec = avcodec_find_decoder (_video_codec_context->codec_id); + + if (_video_codec == 0) { + throw DecodeError ("could not find video decoder"); + } + + if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) { + throw DecodeError ("could not open video decoder"); + } +} + +void +FFmpegDecoder::setup_audio () +{ + _audio_codec_context = _format_context->streams[_audio_stream]->codec; + _audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id); + + 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"); + } +} + +bool +FFmpegDecoder::do_pass () +{ + int r = av_read_frame (_format_context, &_packet); + if (r < 0) { + return true; + } + + if (_packet.stream_index == _video_stream && _opt->decode_video) { + + int frame_finished; + if (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { + process_video (_frame); + } + + } else if (_packet.stream_index == _audio_stream && _opt->decode_audio) { + + avcodec_get_frame_defaults (_frame); + + int frame_finished; + if (avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { + int const data_size = av_samples_get_buffer_size ( + 0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1 + ); + + process_audio (_frame->data[0], _audio_codec_context->channels, data_size); + } + } + + av_free_packet (&_packet); + return false; +} + +int +FFmpegDecoder::length_in_frames () const +{ + return (_format_context->duration / AV_TIME_BASE) * frames_per_second (); +} + +float +FFmpegDecoder::frames_per_second () const +{ + return av_q2d (_format_context->streams[_video_stream]->avg_frame_rate); +} + +int +FFmpegDecoder::audio_channels () const +{ + if (_audio_codec_context == 0) { + return 0; + } + + return _audio_codec_context->channels; +} + +int +FFmpegDecoder::audio_sample_rate () const +{ + if (_audio_codec_context == 0) { + return 0; + } + + return _audio_codec_context->sample_rate; +} + +AVSampleFormat +FFmpegDecoder::audio_sample_format () const +{ + return _audio_codec_context->sample_fmt; +} + +Size +FFmpegDecoder::native_size () const +{ + return Size (_video_codec_context->width, _video_codec_context->height); +} + +PixelFormat +FFmpegDecoder::pixel_format () const +{ + return _video_codec_context->pix_fmt; +} + +int +FFmpegDecoder::time_base_numerator () const +{ + return _video_codec_context->time_base.num; +} + +int +FFmpegDecoder::time_base_denominator () const +{ + return _video_codec_context->time_base.den; +} + +int +FFmpegDecoder::sample_aspect_ratio_numerator () const +{ + return _video_codec_context->sample_aspect_ratio.num; +} + +int +FFmpegDecoder::sample_aspect_ratio_denominator () const +{ + return _video_codec_context->sample_aspect_ratio.den; +} + diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h new file mode 100644 index 000000000..d66acad48 --- /dev/null +++ b/src/lib/ffmpeg_decoder.h @@ -0,0 +1,90 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/ffmpeg_decoder.h + * @brief A decoder using FFmpeg to decode content. + */ + +#include +#include +#include +#include +extern "C" { +#include +#include +} +#include "util.h" +#include "decoder.h" + +struct AVFilterGraph; +struct AVCodecContext; +struct AVFilterContext; +struct AVFormatContext; +struct AVFrame; +struct AVBufferContext; +struct AVCodec; +class Job; +class FilmState; +class Options; +class Image; +class Log; + +/** @class FFmpegDecoder + * @brief A decoder using FFmpeg to decode content. + */ +class FFmpegDecoder : public Decoder +{ +public: + FFmpegDecoder (boost::shared_ptr, boost::shared_ptr, Job *, Log *, bool, bool); + ~FFmpegDecoder (); + + /* Methods to query our input video */ + int length_in_frames () const; + int decoding_frames () const; + float frames_per_second () const; + Size native_size () const; + int audio_channels () const; + int audio_sample_rate () const; + AVSampleFormat audio_sample_format () const; + +private: + + bool do_pass (); + PixelFormat pixel_format () const; + int time_base_numerator () const; + int time_base_denominator () const; + int sample_aspect_ratio_numerator () const; + int sample_aspect_ratio_denominator () const; + + void setup_general (); + void setup_video (); + void setup_audio (); + + AVFormatContext* _format_context; + int _video_stream; + int _audio_stream; + AVFrame* _frame; + + AVCodecContext* _video_codec_context; + AVCodec* _video_codec; + AVCodecContext* _audio_codec_context; + AVCodec* _audio_codec; + + AVPacket _packet; +}; diff --git a/src/lib/film.cc b/src/lib/film.cc new file mode 100644 index 000000000..3eea41c25 --- /dev/null +++ b/src/lib/film.cc @@ -0,0 +1,631 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "film.h" +#include "format.h" +#include "tiff_encoder.h" +#include "job.h" +#include "filter.h" +#include "transcoder.h" +#include "util.h" +#include "job_manager.h" +#include "ab_transcode_job.h" +#include "transcode_job.h" +#include "make_mxf_job.h" +#include "scp_dcp_job.h" +#include "copy_from_dvd_job.h" +#include "make_dcp_job.h" +#include "film_state.h" +#include "log.h" +#include "options.h" +#include "exceptions.h" +#include "examine_content_job.h" +#include "scaler.h" +#include "decoder_factory.h" +#include "config.h" + +using namespace std; +using namespace boost; + +/** Construct a Film object in a given directory, reading any metadata + * file that exists in that directory. An exception will be thrown if + * must_exist is true, and the specified directory does not exist. + * + * @param d Film directory. + * @param must_exist true to throw an exception if does not exist. + */ + +Film::Film (string d, bool must_exist) + : _dirty (false) +{ + /* Make _state.directory a complete path without ..s (where possible) + (Code swiped from Adam Bowen on stackoverflow) + */ + + filesystem::path p (filesystem::system_complete (d)); + filesystem::path result; + for(filesystem::path::iterator i = p.begin(); i != p.end(); ++i) { + if (*i == "..") { + if (filesystem::is_symlink (result) || result.filename() == "..") { + result /= *i; + } else { + result = result.parent_path (); + } + } else if (*i != ".") { + result /= *i; + } + } + + _state.directory = result.string (); + + if (must_exist && !filesystem::exists (_state.directory)) { + throw OpenFileError (_state.directory); + } + + read_metadata (); + + _log = new Log (_state.file ("log")); +} + +/** Copy constructor */ +Film::Film (Film const & other) + : _state (other._state) + , _dirty (other._dirty) +{ + +} + +Film::~Film () +{ + delete _log; +} + +/** Read the `metadata' file inside this Film's directory, and fill the + * object's data with its content. + */ + +void +Film::read_metadata () +{ + ifstream f (metadata_file().c_str ()); + string line; + while (getline (f, line)) { + if (line.empty ()) { + continue; + } + + if (line[0] == '#') { + continue; + } + + size_t const s = line.find (' '); + if (s == string::npos) { + continue; + } + + _state.read_metadata (line.substr (0, s), line.substr (s + 1)); + } + + _dirty = false; +} + +/** Write our state to a file `metadata' inside the Film's directory */ +void +Film::write_metadata () const +{ + filesystem::create_directories (_state.directory); + + ofstream f (metadata_file().c_str ()); + if (!f.good ()) { + throw CreateFileError (metadata_file ()); + } + + _state.write_metadata (f); + + _dirty = false; +} + +/** Set the name by which DVD-o-matic refers to this Film */ +void +Film::set_name (string n) +{ + _state.name = n; + signal_changed (NAME); +} + +/** Set the content file for this film. + * @param c New content file; if specified as an absolute path, the content should + * be within the film's _state.directory; if specified as a relative path, the content + * will be assumed to be within the film's _state.directory. + */ +void +Film::set_content (string c) +{ + if (filesystem::path(c).has_root_directory () && starts_with (c, _state.directory)) { + c = c.substr (_state.directory.length() + 1); + } + + if (c == _state.content) { + return; + } + + /* Create a temporary decoder so that we can get information + about the content. + */ + shared_ptr s = state_copy (); + s->content = c; + shared_ptr o (new Options ("", "", "")); + o->out_size = Size (1024, 1024); + + shared_ptr d = decoder_factory (s, o, 0, _log); + + _state.size = d->native_size (); + _state.length = d->length_in_frames (); + _state.frames_per_second = d->frames_per_second (); + _state.audio_channels = d->audio_channels (); + _state.audio_sample_rate = d->audio_sample_rate (); + _state.audio_sample_format = d->audio_sample_format (); + + _state.content = c; + + signal_changed (SIZE); + signal_changed (LENGTH); + signal_changed (FRAMES_PER_SECOND); + signal_changed (AUDIO_CHANNELS); + signal_changed (AUDIO_SAMPLE_RATE); + signal_changed (CONTENT); +} + +/** Set the format that this Film should be shown in */ +void +Film::set_format (Format const * f) +{ + _state.format = f; + signal_changed (FORMAT); +} + +/** Set the type to specify the DCP as having + * (feature, trailer etc.) + */ +void +Film::set_dcp_content_type (DCPContentType const * t) +{ + _state.dcp_content_type = t; + signal_changed (DCP_CONTENT_TYPE); +} + +/** Set the number of pixels by which to crop the left of the source video */ +void +Film::set_left_crop (int c) +{ + if (c == _state.left_crop) { + return; + } + + _state.left_crop = c; + signal_changed (LEFT_CROP); +} + +/** Set the number of pixels by which to crop the right of the source video */ +void +Film::set_right_crop (int c) +{ + if (c == _state.right_crop) { + return; + } + + _state.right_crop = c; + signal_changed (RIGHT_CROP); +} + +/** Set the number of pixels by which to crop the top of the source video */ +void +Film::set_top_crop (int c) +{ + if (c == _state.top_crop) { + return; + } + + _state.top_crop = c; + signal_changed (TOP_CROP); +} + +/** Set the number of pixels by which to crop the bottom of the source video */ +void +Film::set_bottom_crop (int c) +{ + if (c == _state.bottom_crop) { + return; + } + + _state.bottom_crop = c; + signal_changed (BOTTOM_CROP); +} + +/** Set the filters to apply to the image when generating thumbnails + * or a DCP. + */ +void +Film::set_filters (vector const & f) +{ + _state.filters = f; + signal_changed (FILTERS); +} + +/** Set the number of frames to put in any generated DCP (from + * the start of the film). 0 indicates that all frames should + * be used. + */ +void +Film::set_dcp_frames (int n) +{ + _state.dcp_frames = n; + signal_changed (DCP_FRAMES); +} + +void +Film::set_dcp_trim_action (TrimAction a) +{ + _state.dcp_trim_action = a; + signal_changed (DCP_TRIM_ACTION); +} + +/** Set whether or not to generate a A/B comparison DCP. + * Such a DCP has the left half of its frame as the Film + * content without any filtering or post-processing; the + * right half is rendered with filters and post-processing. + */ +void +Film::set_dcp_ab (bool a) +{ + _state.dcp_ab = a; + signal_changed (DCP_AB); +} + +void +Film::set_audio_gain (float g) +{ + _state.audio_gain = g; + signal_changed (AUDIO_GAIN); +} + +void +Film::set_audio_delay (int d) +{ + _state.audio_delay = d; + signal_changed (AUDIO_DELAY); +} + +/** @return path of metadata file */ +string +Film::metadata_file () const +{ + return _state.file ("metadata"); +} + +/** @return full path of the content (actual video) file + * of this Film. + */ +string +Film::content () const +{ + return _state.content_path (); +} + +/** The pre-processing GUI part of a thumbs update. + * Must be called from the GUI thread. + */ +void +Film::update_thumbs_pre_gui () +{ + _state.thumbs.clear (); + filesystem::remove_all (_state.dir ("thumbs")); + + /* This call will recreate the directory */ + _state.dir ("thumbs"); +} + +/** The post-processing GUI part of a thumbs update. + * Must be called from the GUI thread. + */ +void +Film::update_thumbs_post_gui () +{ + string const tdir = _state.dir ("thumbs"); + + for (filesystem::directory_iterator i = filesystem::directory_iterator (tdir); i != filesystem::directory_iterator(); ++i) { + + /* Aah, the sweet smell of progress */ +#if BOOST_FILESYSTEM_VERSION == 3 + string const l = filesystem::path(*i).leaf().generic_string(); +#else + string const l = i->leaf (); +#endif + + size_t const d = l.find (".tiff"); + if (d != string::npos) { + _state.thumbs.push_back (atoi (l.substr (0, d).c_str())); + } + } + + sort (_state.thumbs.begin(), _state.thumbs.end()); + + write_metadata (); + signal_changed (THUMBS); +} + +/** @return the number of thumbnail images that we have */ +int +Film::num_thumbs () const +{ + return _state.thumbs.size (); +} + +/** @param n A thumb index. + * @return The frame within the Film that it is for. + */ +int +Film::thumb_frame (int n) const +{ + return _state.thumb_frame (n); +} + +/** @param n A thumb index. + * @return The path to the thumb's image file. + */ +string +Film::thumb_file (int n) const +{ + return _state.thumb_file (n); +} + +/** @return The path to the directory to write JPEG2000 files to */ +string +Film::j2k_dir () const +{ + assert (format()); + + stringstream s; + + /* Start with j2c */ + s << "j2c/"; + + pair f = Filter::ffmpeg_strings (filters ()); + + /* Write stuff to specify the filter / post-processing settings that are in use, + so that we don't get confused about J2K files generated using different + settings. + */ + s << _state.format->nickname() + << "_" << _state.content + << "_" << left_crop() << "_" << right_crop() << "_" << top_crop() << "_" << bottom_crop() + << "_" << f.first << "_" << f.second + << "_" << _state.scaler->id(); + + /* Similarly for the A/B case */ + if (dcp_ab()) { + pair fa = Filter::ffmpeg_strings (Config::instance()->reference_filters()); + s << "/ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second; + } + + return _state.dir (s.str ()); +} + +/** Handle a change to the Film's metadata */ +void +Film::signal_changed (Property p) +{ + _dirty = true; + Changed (p); +} + +/** Add suitable Jobs to the JobManager to create a DCP for this Film. + * @param true to transcode, false to use the WAV and J2K files that are already there. + */ +void +Film::make_dcp (bool transcode, int freq) +{ + string const t = name (); + if (t.find ("/") != string::npos) { + throw BadSettingError ("name", "cannot contain slashes"); + } + + { + stringstream s; + s << "DVD-o-matic " << DVDOMATIC_VERSION << " using " << dependency_version_summary (); + log()->log (s.str ()); + } + + { + char buffer[128]; + gethostname (buffer, sizeof (buffer)); + stringstream s; + s << "Starting to make a DCP on " << buffer; + log()->log (s.str ()); + } + + if (format() == 0) { + throw MissingSettingError ("format"); + } + + if (content().empty ()) { + throw MissingSettingError ("content"); + } + + if (dcp_content_type() == 0) { + throw MissingSettingError ("content type"); + } + + if (name().empty()) { + throw MissingSettingError ("name"); + } + + shared_ptr fs = state_copy (); + shared_ptr o (new Options (j2k_dir(), ".j2c", _state.dir ("wavs"))); + o->out_size = format()->dcp_size (); + if (dcp_frames() == 0) { + /* Decode the whole film, no blacking */ + o->num_frames = 0; + o->black_after = 0; + } else { + switch (dcp_trim_action()) { + case CUT: + /* Decode only part of the film, no blacking */ + o->num_frames = dcp_frames (); + o->black_after = 0; + break; + case BLACK_OUT: + /* Decode the whole film, but black some frames out */ + o->num_frames = 0; + o->black_after = dcp_frames (); + } + } + + o->decode_video_frequency = freq; + o->padding = format()->dcp_padding (); + o->ratio = format()->ratio_as_float (); + + if (transcode) { + if (_state.dcp_ab) { + JobManager::instance()->add (shared_ptr (new ABTranscodeJob (fs, o, log ()))); + } else { + JobManager::instance()->add (shared_ptr (new TranscodeJob (fs, o, log ()))); + } + } + + JobManager::instance()->add (shared_ptr (new MakeMXFJob (fs, o, log (), MakeMXFJob::VIDEO))); + if (audio_channels() > 0) { + JobManager::instance()->add (shared_ptr (new MakeMXFJob (fs, o, log (), MakeMXFJob::AUDIO))); + } + JobManager::instance()->add (shared_ptr (new MakeDCPJob (fs, o, log ()))); +} + +shared_ptr +Film::state_copy () const +{ + return shared_ptr (new FilmState (_state)); +} + +void +Film::copy_from_dvd_post_gui () +{ + const string dvd_dir = _state.dir ("dvd"); + + string largest_file; + uintmax_t largest_size = 0; + for (filesystem::directory_iterator i = filesystem::directory_iterator (dvd_dir); i != filesystem::directory_iterator(); ++i) { + uintmax_t const s = filesystem::file_size (*i); + if (s > largest_size) { + +#if BOOST_FILESYSTEM_VERSION == 3 + largest_file = filesystem::path(*i).generic_string(); +#else + largest_file = i->string (); +#endif + largest_size = s; + } + } + + set_content (largest_file); +} + +void +Film::examine_content () +{ + if (_examine_content_job) { + return; + } + + _examine_content_job.reset (new ExamineContentJob (state_copy (), log ())); + _examine_content_job->Finished.connect (sigc::mem_fun (*this, &Film::examine_content_post_gui)); + JobManager::instance()->add (_examine_content_job); +} + +void +Film::examine_content_post_gui () +{ + _state.length = _examine_content_job->last_video_frame (); + signal_changed (LENGTH); + + _examine_content_job.reset (); +} + +void +Film::set_scaler (Scaler const * s) +{ + _state.scaler = s; + signal_changed (SCALER); +} + +void +Film::set_frames_per_second (float f) +{ + _state.frames_per_second = f; + signal_changed (FRAMES_PER_SECOND); +} + +/** @return full paths to any audio files that this Film has */ +vector +Film::audio_files () const +{ + vector f; + for (filesystem::directory_iterator i = filesystem::directory_iterator (_state.dir("wavs")); i != filesystem::directory_iterator(); ++i) { + f.push_back (i->path().string ()); + } + + return f; +} + +ContentType +Film::content_type () const +{ + return _state.content_type (); +} + +void +Film::set_still_duration (int d) +{ + _state.still_duration = d; + signal_changed (STILL_DURATION); +} + +void +Film::send_dcp_to_tms () +{ + shared_ptr j (new SCPDCPJob (state_copy (), log ())); + JobManager::instance()->add (j); +} + +void +Film::copy_from_dvd () +{ + shared_ptr j (new CopyFromDVDJob (state_copy (), log ())); + j->Finished.connect (sigc::mem_fun (*this, &Film::copy_from_dvd_post_gui)); + JobManager::instance()->add (j); +} + diff --git a/src/lib/film.h b/src/lib/film.h new file mode 100644 index 000000000..f746da480 --- /dev/null +++ b/src/lib/film.h @@ -0,0 +1,275 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/film.h + * @brief A representation of a piece of video (with sound), including naming, + * the source content file, and how it should be presented in a DCP. + */ + +#ifndef DVDOMATIC_FILM_H +#define DVDOMATIC_FILM_H + +#include +#include +#include +#include +#include +extern "C" { +#include +} +#include "dcp_content_type.h" +#include "film_state.h" + +class Format; +class Job; +class Filter; +class Log; +class ExamineContentJob; + +/** @class Film + * @brief A representation of a video with sound. + * + * A representation of a piece of video (with sound), including naming, + * the source content file, and how it should be presented in a DCP. + */ +class Film +{ +public: + Film (std::string d, bool must_exist = true); + Film (Film const &); + ~Film (); + + void write_metadata () const; + + /** @return complete path to directory containing the film metadata */ + std::string directory () const { + return _state.directory; + } + + std::string content () const; + ContentType content_type () const; + + /** @return name for DVD-o-matic */ + std::string name () const { + return _state.name; + } + + /** @return number of pixels to crop from the top of the original picture */ + int top_crop () const { + return _state.top_crop; + } + + /** @return number of pixels to crop from the bottom of the original picture */ + int bottom_crop () const { + return _state.bottom_crop; + } + + /** @return number of pixels to crop from the left-hand side of the original picture */ + int left_crop () const { + return _state.left_crop; + } + + /** @return number of pixels to crop from the right-hand side of the original picture */ + int right_crop () const { + return _state.right_crop; + } + + /** @return the format to present this film in (flat, scope, etc.) */ + Format const * format () const { + return _state.format; + } + + /** @return video filters that should be used when generating DCPs */ + std::vector filters () const { + return _state.filters; + } + + /** @return scaler algorithm to use */ + Scaler const * scaler () const { + return _state.scaler; + } + + /** @return number of frames to put in the DCP, or 0 for all */ + int dcp_frames () const { + return _state.dcp_frames; + } + + TrimAction dcp_trim_action () const { + return _state.dcp_trim_action; + } + + /** @return true to create an A/B comparison DCP, where the left half of the image + * is the video without any filters or post-processing, and the right half + * has the specified filters and post-processing. + */ + bool dcp_ab () const { + return _state.dcp_ab; + } + + float audio_gain () const { + return _state.audio_gain; + } + + int audio_delay () const { + return _state.audio_delay; + } + + int still_duration () const { + return _state.still_duration; + } + + void set_filters (std::vector const &); + + void set_scaler (Scaler const *); + + /** @return the type of content that this Film represents (feature, trailer etc.) */ + DCPContentType const * dcp_content_type () { + return _state.dcp_content_type; + } + + void set_dcp_frames (int); + void set_dcp_trim_action (TrimAction); + void set_dcp_ab (bool); + + void set_name (std::string); + void set_content (std::string); + void set_top_crop (int); + void set_bottom_crop (int); + void set_left_crop (int); + void set_right_crop (int); + void set_frames_per_second (float); + void set_format (Format const *); + void set_dcp_content_type (DCPContentType const *); + void set_audio_gain (float); + void set_audio_delay (int); + void set_still_duration (int); + + /** @return size, in pixels, of the source (ignoring cropping) */ + Size size () const { + return _state.size; + } + + /** @return length, in video frames */ + int length () const { + return _state.length; + } + + /** @return nnumber of video frames per second */ + float frames_per_second () const { + return _state.frames_per_second; + } + + /** @return number of audio channels */ + int audio_channels () const { + return _state.audio_channels; + } + + /** @return audio sample rate, in Hz */ + int audio_sample_rate () const { + return _state.audio_sample_rate; + } + + /** @return format of the audio samples */ + AVSampleFormat audio_sample_format () const { + return _state.audio_sample_format; + } + + std::string j2k_dir () const; + + std::vector audio_files () const; + + void update_thumbs_pre_gui (); + void update_thumbs_post_gui (); + int num_thumbs () const; + int thumb_frame (int) const; + std::string thumb_file (int) const; + + void copy_from_dvd_post_gui (); + void examine_content (); + void examine_content_post_gui (); + void send_dcp_to_tms (); + void copy_from_dvd (); + + /** @return true if our metadata has been modified since it was last saved */ + bool dirty () const { + return _dirty; + } + + void make_dcp (bool, int freq = 0); + + enum Property { + NAME, + CONTENT, + DCP_CONTENT_TYPE, + FORMAT, + LEFT_CROP, + RIGHT_CROP, + TOP_CROP, + BOTTOM_CROP, + FILTERS, + SCALER, + DCP_FRAMES, + DCP_TRIM_ACTION, + DCP_AB, + AUDIO_GAIN, + AUDIO_DELAY, + THUMBS, + SIZE, + LENGTH, + FRAMES_PER_SECOND, + AUDIO_CHANNELS, + AUDIO_SAMPLE_RATE, + STILL_DURATION + }; + + boost::shared_ptr state_copy () const; + + /** @return Logger. + * It is safe to call this from any thread. + */ + Log* log () const { + return _log; + } + + /** Emitted when some metadata property has changed */ + mutable sigc::signal1 Changed; + +private: + void read_metadata (); + std::string metadata_file () const; + void update_dimensions (); + void signal_changed (Property); + + /** The majority of our state. Kept in a separate object + * so that it can easily be copied for passing onto long-running + * jobs (which then have an unchanging set of parameters). + */ + FilmState _state; + + /** true if our metadata has changed since it was last written to disk */ + mutable bool _dirty; + + /** Log to write to */ + Log* _log; + + /** Any running ExamineContentJob, or 0 */ + boost::shared_ptr _examine_content_job; +}; + +#endif diff --git a/src/lib/film_state.cc b/src/lib/film_state.cc new file mode 100644 index 000000000..16378086c --- /dev/null +++ b/src/lib/film_state.cc @@ -0,0 +1,254 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/film_state.cc + * @brief The state of a Film. This is separate from Film so that + * state can easily be copied and kept around for reference + * by long-running jobs. This avoids the jobs getting confused + * by the user changing Film settings during their run. + */ + +#include +#include +#include +#include +#include +#include "film_state.h" +#include "scaler.h" +#include "filter.h" +#include "format.h" +#include "dcp_content_type.h" +#include "util.h" + +using namespace std; +using namespace boost; + +/** Write state to a stream. + * @param f Stream to write to. + */ +void +FilmState::write_metadata (ofstream& f) const +{ + /* User stuff */ + f << "name " << name << "\n"; + f << "content " << content << "\n"; + if (dcp_content_type) { + f << "dcp_content_type " << dcp_content_type->pretty_name () << "\n"; + } + f << "frames_per_second " << frames_per_second << "\n"; + if (format) { + f << "format " << format->as_metadata () << "\n"; + } + f << "left_crop " << left_crop << "\n"; + f << "right_crop " << right_crop << "\n"; + f << "top_crop " << top_crop << "\n"; + f << "bottom_crop " << bottom_crop << "\n"; + for (vector::const_iterator i = filters.begin(); i != filters.end(); ++i) { + f << "filter " << (*i)->id () << "\n"; + } + f << "scaler " << scaler->id () << "\n"; + f << "dcp_frames " << dcp_frames << "\n"; + + f << "dcp_trim_action "; + switch (dcp_trim_action) { + case CUT: + f << "cut\n"; + break; + case BLACK_OUT: + f << "black_out\n"; + break; + } + + f << "dcp_ab " << (dcp_ab ? "1" : "0") << "\n"; + f << "audio_gain " << audio_gain << "\n"; + f << "audio_delay " << audio_delay << "\n"; + f << "still_duration " << still_duration << "\n"; + + /* Cached stuff; this is information about our content; we could + look it up each time, but that's slow. + */ + for (vector::const_iterator i = thumbs.begin(); i != thumbs.end(); ++i) { + f << "thumb " << *i << "\n"; + } + f << "width " << size.width << "\n"; + f << "height " << size.height << "\n"; + f << "length " << length << "\n"; + f << "audio_channels " << audio_channels << "\n"; + f << "audio_sample_rate " << audio_sample_rate << "\n"; + f << "audio_sample_format " << audio_sample_format_to_string (audio_sample_format) << "\n"; +} + +/** Read state from a key / value pair. + * @param k Key. + * @param v Value. + */ +void +FilmState::read_metadata (string k, string v) +{ + /* User-specified stuff */ + if (k == "name") { + name = v; + } else if (k == "content") { + content = v; + } else if (k == "dcp_content_type") { + dcp_content_type = DCPContentType::from_pretty_name (v); + } else if (k == "frames_per_second") { + frames_per_second = atof (v.c_str ()); + } else if (k == "format") { + format = Format::from_metadata (v); + } else if (k == "left_crop") { + left_crop = atoi (v.c_str ()); + } else if (k == "right_crop") { + right_crop = atoi (v.c_str ()); + } else if (k == "top_crop") { + top_crop = atoi (v.c_str ()); + } else if (k == "bottom_crop") { + bottom_crop = atoi (v.c_str ()); + } else if (k == "filter") { + filters.push_back (Filter::from_id (v)); + } else if (k == "scaler") { + scaler = Scaler::from_id (v); + } else if (k == "dcp_frames") { + dcp_frames = atoi (v.c_str ()); + } else if (k == "dcp_trim_action") { + if (v == "cut") { + dcp_trim_action = CUT; + } else if (v == "black_out") { + dcp_trim_action = BLACK_OUT; + } + } else if (k == "dcp_ab") { + dcp_ab = (v == "1"); + } else if (k == "audio_gain") { + audio_gain = atof (v.c_str ()); + } else if (k == "audio_delay") { + audio_delay = atoi (v.c_str ()); + } else if (k == "still_duration") { + still_duration = atoi (v.c_str ()); + } + + /* Cached stuff */ + if (k == "thumb") { + int const n = atoi (v.c_str ()); + /* Only add it to the list if it still exists */ + if (filesystem::exists (thumb_file_for_frame (n))) { + thumbs.push_back (n); + } + } else if (k == "width") { + size.width = atoi (v.c_str ()); + } else if (k == "height") { + size.height = atoi (v.c_str ()); + } else if (k == "length") { + length = atof (v.c_str ()); + } else if (k == "audio_channels") { + audio_channels = atoi (v.c_str ()); + } else if (k == "audio_sample_rate") { + audio_sample_rate = atoi (v.c_str ()); + } else if (k == "audio_sample_format") { + audio_sample_format = audio_sample_format_from_string (v); + } +} + + +/** @param n A thumb index. + * @return The path to the thumb's image file. + */ +string +FilmState::thumb_file (int n) const +{ + return thumb_file_for_frame (thumb_frame (n)); +} + +/** @param n A frame index within the Film. + * @return The path to the thumb's image file for this frame; + * we assume that it exists. + */ +string +FilmState::thumb_file_for_frame (int n) const +{ + stringstream s; + s << dir ("thumbs") << "/"; + s.width (8); + s << setfill('0') << n << ".tiff"; + return s.str (); +} + + +/** @param n A thumb index. + * @return The frame within the Film that it is for. + */ +int +FilmState::thumb_frame (int n) const +{ + assert (n < int (thumbs.size ())); + return thumbs[n]; +} + +Size +FilmState::cropped_size (Size s) const +{ + s.width -= left_crop + right_crop; + s.height -= top_crop + bottom_crop; + return s; +} + +/** Given a directory name, return its full path within the Film's directory. + * The directory (and its parents) will be created if they do not exist. + */ +string +FilmState::dir (string d) const +{ + stringstream s; + s << directory << "/" << d; + filesystem::create_directories (s.str ()); + return s.str (); +} + +/** Given a file or directory name, return its full path within the Film's directory */ +string +FilmState::file (string f) const +{ + stringstream s; + s << directory << "/" << f; + return s.str (); +} + +string +FilmState::content_path () const +{ + if (filesystem::path(content).has_root_directory ()) { + return content; + } + + return file (content); +} + +ContentType +FilmState::content_type () const +{ +#if BOOST_FILESYSTEM_VERSION == 3 + string const ext = filesystem::path(content).extension().string(); +#else + string const ext = filesystem::path(content).extension(); +#endif + if (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png") { + return STILL; + } + + return VIDEO; +} diff --git a/src/lib/film_state.h b/src/lib/film_state.h new file mode 100644 index 000000000..52525ecd4 --- /dev/null +++ b/src/lib/film_state.h @@ -0,0 +1,155 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/film_state.h + * @brief The state of a Film. This is separate from Film so that + * state can easily be copied and kept around for reference + * by long-running jobs. This avoids the jobs getting confused + * by the user changing Film settings during their run. + */ + +#ifndef DVDOMATIC_FILM_STATE_H +#define DVDOMATIC_FILM_STATE_H + +extern "C" { +#include +#include +} +#include "scaler.h" +#include "util.h" +#include "trim_action.h" + +class Format; +class DCPContentType; +class Filter; + +/** @class FilmState + * @brief The state of a Film. + * + * This is separate from Film so that state can easily be copied and + * kept around for reference by long-running jobs. This avoids the + * jobs getting confused by the user changing Film settings during + * their run. + */ + +class FilmState +{ +public: + FilmState () + : dcp_content_type (0) + , frames_per_second (0) + , format (0) + , left_crop (0) + , right_crop (0) + , top_crop (0) + , bottom_crop (0) + , scaler (Scaler::from_id ("bicubic")) + , dcp_frames (0) + , dcp_trim_action (CUT) + , dcp_ab (false) + , audio_gain (0) + , audio_delay (0) + , still_duration (10) + , length (0) + , audio_channels (0) + , audio_sample_rate (0) + , audio_sample_format (AV_SAMPLE_FMT_NONE) + {} + + std::string file (std::string f) const; + std::string dir (std::string d) const; + + std::string content_path () const; + ContentType content_type () const; + + bool content_is_dvd () const; + + std::string thumb_file (int) const; + int thumb_frame (int) const; + + void write_metadata (std::ofstream &) const; + void read_metadata (std::string, std::string); + + Size cropped_size (Size) const; + + /** Complete path to directory containing the film metadata; + must not be relative. + */ + std::string directory; + /** Name for DVD-o-matic */ + std::string name; + /** File or directory containing content; may be relative to our directory + * or an absolute path. + */ + std::string content; + /** The type of content that this Film represents (feature, trailer etc.) */ + DCPContentType const * dcp_content_type; + /** Frames per second of the source */ + float frames_per_second; + /** The format to present this Film in (flat, scope, etc.) */ + Format const * format; + /** Number of pixels to crop from the left-hand side of the original picture */ + int left_crop; + /** Number of pixels to crop from the right-hand side of the original picture */ + int right_crop; + /** Number of pixels to crop from the top of the original picture */ + int top_crop; + /** Number of pixels to crop from the bottom of the original picture */ + int bottom_crop; + /** Video filters that should be used when generating DCPs */ + std::vector filters; + /** Scaler algorithm to use */ + Scaler const * scaler; + /** Number of frames to put in the DCP, or 0 for all */ + int dcp_frames; + + TrimAction dcp_trim_action; + + /** true to create an A/B comparison DCP, where the left half of the image + is the video without any filters or post-processing, and the right half + has the specified filters and post-processing. + */ + bool dcp_ab; + /** Gain to apply to audio in dB */ + float audio_gain; + /** Delay to apply to audio (positive moves audio later) in milliseconds */ + int audio_delay; + /** Duration to make still-sourced films (in seconds) */ + int still_duration; + + /* Data which is cached to speed things up */ + + /** Vector of frame indices for each of our `thumbnails */ + std::vector thumbs; + /** Size, in pixels, of the source (ignoring cropping) */ + Size size; + /** Length in frames */ + int length; + /** Number of audio channels */ + int audio_channels; + /** Sample rate of the audio, in Hz */ + int audio_sample_rate; + /** Format of the audio samples */ + AVSampleFormat audio_sample_format; + +private: + std::string thumb_file_for_frame (int) const; +}; + +#endif diff --git a/src/lib/filter.cc b/src/lib/filter.cc new file mode 100644 index 000000000..ab5a6316f --- /dev/null +++ b/src/lib/filter.cc @@ -0,0 +1,131 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/filter.cc + * @brief A class to describe one of FFmpeg's video or post-processing filters. + */ + +#include "filter.h" + +using namespace std; + +vector Filter::_filters; + +/** @param i Our id. + * @param n User-visible name. + * @param v String for a FFmpeg video filter descriptor, or "". + * @param p String for a FFmpeg post-processing descriptor, or "". + */ +Filter::Filter (string i, string n, string v, string p) + : _id (i) + , _name (n) + , _vf (v) + , _pp (p) +{ + +} + +/** @return All available filters */ +vector +Filter::all () +{ + return _filters; +} + + +/** Set up the static _filters vector; must be called before from_* + * methods are used. + */ +void +Filter::setup_filters () +{ + /* Note: "none" is a magic id name, so don't use it here */ + + _filters.push_back (new Filter ("pphb", "Horizontal deblocking filter", "", "hb")); + _filters.push_back (new Filter ("ppvb", "Vertical deblocking filter", "", "vb")); + _filters.push_back (new Filter ("ppha", "Horizontal deblocking filter A", "", "ha")); + _filters.push_back (new Filter ("ppva", "Vertical deblocking filter A", "", "va")); + _filters.push_back (new Filter ("pph1", "Experimental horizontal deblocking filter 1", "", "h1")); + _filters.push_back (new Filter ("pphv", "Experimental vertical deblocking filter 1", "", "v1")); + _filters.push_back (new Filter ("ppdr", "Deringing filter", "", "dr")); + _filters.push_back (new Filter ("pplb", "Linear blend deinterlacer", "", "lb")); + _filters.push_back (new Filter ("ppli", "Linear interpolating deinterlacer", "", "li")); + _filters.push_back (new Filter ("ppci", "Cubic interpolating deinterlacer", "", "ci")); + _filters.push_back (new Filter ("ppmd", "Median deinterlacer", "", "md")); + _filters.push_back (new Filter ("ppfd", "FFMPEG deinterlacer", "", "fd")); + _filters.push_back (new Filter ("ppl5", "FIR low-pass deinterlacer", "", "l5")); + _filters.push_back (new Filter ("mcdeint", "Motion compensating deinterlacer", "mcdeint", "")); + _filters.push_back (new Filter ("kerndeint", "Kernel deinterlacer", "kerndeint", "")); + _filters.push_back (new Filter ("pptn", "Temporal noise reducer", "", "tn")); + _filters.push_back (new Filter ("ppfq", "Force quantizer", "", "fq")); + _filters.push_back (new Filter ("gradfun", "Gradient debander", "gradfun", "")); + _filters.push_back (new Filter ("unsharp", "Unsharp mask and Gaussian blur", "unsharp", "")); + _filters.push_back (new Filter ("denoise3d", "3D denoiser", "denoise3d", "")); + _filters.push_back (new Filter ("hqdn3d", "High quality 3D denoiser", "hqdn3d", "")); + _filters.push_back (new Filter ("telecine", "Telecine filter", "telecine", "")); + _filters.push_back (new Filter ("ow", "Overcomplete wavelet denoiser", "mp=ow", "")); +} + +/** @param filters Set of filters. + * @return A pair; .first is a string to pass to FFmpeg for the video filters, + * .second is a string to pass for the post-processors. + */ +pair +Filter::ffmpeg_strings (vector const & filters) +{ + string vf; + string pp; + + for (vector::const_iterator i = filters.begin(); i != filters.end(); ++i) { + if (!(*i)->vf().empty ()) { + if (!vf.empty ()) { + vf += ","; + } + vf += (*i)->vf (); + } + + if (!(*i)->pp().empty ()) { + if (!pp.empty()) { + pp += ","; + } + pp += (*i)->pp (); + } + } + + return make_pair (vf, pp); +} + +/** @param d Our id. + * @return Corresponding Filter, or 0. + */ +Filter const * +Filter::from_id (string d) +{ + vector::iterator i = _filters.begin (); + while (i != _filters.end() && (*i)->id() != d) { + ++i; + } + + if (i == _filters.end ()) { + return 0; + } + + return *i; +} + diff --git a/src/lib/filter.h b/src/lib/filter.h new file mode 100644 index 000000000..20c55049c --- /dev/null +++ b/src/lib/filter.h @@ -0,0 +1,78 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/filter.h + * @brief A class to describe one of FFmpeg's video or post-processing filters. + */ + +#ifndef DVDOMATIC_FILTER_H +#define DVDOMATIC_FILTER_H + +#include +#include + +/** @class Filter + * @brief A class to describe one of FFmpeg's video or post-processing filters. + */ +class Filter +{ +public: + Filter (std::string, std::string, std::string, std::string); + + /** @return our id */ + std::string id () const { + return _id; + } + + /** @return user-visible name */ + std::string name () const { + return _name; + } + + /** @return string for a FFmpeg video filter descriptor */ + std::string vf () const { + return _vf; + } + + /** @return string for a FFmpeg post-processing descriptor */ + std::string pp () const { + return _pp; + } + + static std::vector all (); + static Filter const * from_id (std::string); + static void setup_filters (); + static std::pair ffmpeg_strings (std::vector const &); + +private: + + /** our id */ + std::string _id; + /** user-visible name */ + std::string _name; + /** string for a FFmpeg video filter descriptor */ + std::string _vf; + /** string for a FFmpeg post-processing descriptor */ + std::string _pp; + + /** all available filters */ + static std::vector _filters; +}; + +#endif diff --git a/src/lib/format.cc b/src/lib/format.cc new file mode 100644 index 000000000..dcc884412 --- /dev/null +++ b/src/lib/format.cc @@ -0,0 +1,189 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/format.cc + * @brief Class to describe a format (aspect ratio) that a Film should + * be shown in. + */ + +#include +#include +#include +#include +#include +#include "format.h" + +using namespace std; + +vector Format::_formats; + +/** @param r Ratio multiplied by 100 (e.g. 185) + * @param dcp Size (in pixels) of the images that we should put in a DCP. + * @param id ID (e.g. 185) + * @param n Nick name (e.g. Flat) + */ +Format::Format (int r, Size dcp, string id, string n) + : _ratio (r) + , _dcp_size (dcp) + , _id (id) + , _nickname (n) +{ + +} + +/** @return A name to be presented to the user */ +string +Format::name () const +{ + stringstream s; + if (!_nickname.empty ()) { + s << _nickname << " ("; + } + + s << setprecision(3) << (_ratio / 100.0) << ":1"; + + if (!_nickname.empty ()) { + s << ")"; + } + + return s.str (); +} + +/** @return Identifier for this format as metadata for a Film's metadata file */ +string +Format::as_metadata () const +{ + return _id; +} + +/** Fill our _formats vector with all available formats */ +void +Format::setup_formats () +{ + _formats.push_back (new Format (133, Size (1998, 1080), "133-in-flat", "4:3 within Flat")); + _formats.push_back (new Format (137, Size (1480, 1080), "137", "Academy")); + _formats.push_back (new Format (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")); +} + +/** @param r Ratio multiplied by 100. + * @return Matching Format, or 0. + */ +Format const * +Format::from_ratio (int r) +{ + vector::iterator i = _formats.begin (); + while (i != _formats.end() && (*i)->ratio_as_integer() != r) { + ++i; + } + + if (i == _formats.end ()) { + return 0; + } + + return *i; +} + +/** @param n Nickname. + * @return Matching Format, or 0. + */ +Format const * +Format::from_nickname (string n) +{ + vector::iterator i = _formats.begin (); + while (i != _formats.end() && (*i)->nickname() != n) { + ++i; + } + + if (i == _formats.end ()) { + return 0; + } + + return *i; +} + +/** @param i Id. + * @return Matching Format, or 0. + */ +Format const * +Format::from_id (string i) +{ + vector::iterator j = _formats.begin (); + while (j != _formats.end() && (*j)->id() != i) { + ++j; + } + + if (j == _formats.end ()) { + return 0; + } + + return *j; +} + + +/** @param m Metadata, as returned from as_metadata(). + * @return Matching Format, or 0. + */ +Format const * +Format::from_metadata (string m) +{ + return from_id (m); +} + +/** @param f A Format. + * @return Index of f within our static list, or -1. + */ +int +Format::as_index (Format const * f) +{ + vector::size_type i = 0; + while (i < _formats.size() && _formats[i] != f) { + ++i; + } + + if (i == _formats.size ()) { + return -1; + } + + return i; +} + +/** @param i An index returned from as_index(). + * @return Corresponding Format. + */ +Format const * +Format::from_index (int i) +{ + assert (i >= 0 && i < int(_formats.size ())); + return _formats[i]; +} + +/** @return All available formats */ +vector +Format::all () +{ + return _formats; +} + +int +Format::dcp_padding () const +{ + return rint ((_dcp_size.width - (_dcp_size.height * _ratio / 100.0)) / 2.0); +} diff --git a/src/lib/format.h b/src/lib/format.h new file mode 100644 index 000000000..4b727b2e3 --- /dev/null +++ b/src/lib/format.h @@ -0,0 +1,101 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/format.h + * @brief Class to describe a format (aspect ratio) that a Film should + * be shown in. + */ + +#include +#include +#include "util.h" + +/** @class Format + * @brief Class to describe a format (aspect ratio) that a Film should + * be shown in. + */ +class Format +{ +public: + Format (int, Size, std::string, std::string); + + /** @return the aspect ratio multiplied by 100 + * (e.g. 239 for Cinemascope 2.39:1) + */ + int ratio_as_integer () const { + return _ratio; + } + + /** @return the ratio as a floating point number */ + float ratio_as_float () const { + return _ratio / 100.0; + } + + /** @return size in pixels of the images that we should + * put in a DCP for this ratio. This size will not correspond + * to the ratio when we are doing things like 16:9 in a Flat frame. + */ + Size dcp_size () const { + return _dcp_size; + } + + std::string id () const { + return _id; + } + + /** @return Full name to present to the user */ + std::string name () const; + + /** @return Nickname (e.g. Flat, Scope) */ + std::string nickname () const { + return _nickname; + } + + std::string as_metadata () const; + + int dcp_padding () const; + + static Format const * from_ratio (int); + static Format const * from_nickname (std::string n); + static Format const * from_metadata (std::string m); + static Format const * from_index (int i); + static Format const * from_id (std::string i); + static int as_index (Format const * f); + static std::vector all (); + static void setup_formats (); + +private: + + /** Ratio expressed as the actual ratio multiplied by 100 */ + int _ratio; + /** Size in pixels of the images that we should + * put in a DCP for this ratio. This size will not correspond + * to the ratio when we are doing things like 16:9 in a Flat frame. + */ + Size _dcp_size; + /** id for use in metadata */ + std::string _id; + /** nickname (e.g. Flat, Scope) */ + std::string _nickname; + + /** all available formats */ + static std::vector _formats; +}; + + diff --git a/src/lib/image.cc b/src/lib/image.cc new file mode 100644 index 000000000..7a9ac7dd5 --- /dev/null +++ b/src/lib/image.cc @@ -0,0 +1,392 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/image.cc + * @brief A set of classes to describe video images. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +} +#include "image.h" +#include "exceptions.h" +#include "scaler.h" + +#ifdef DEBUG_HASH +#include +#endif + +using namespace std; +using namespace boost; + +/** @param n Component index. + * @return Number of lines in the image for the given component. + */ +int +Image::lines (int n) const +{ + switch (_pixel_format) { + case PIX_FMT_YUV420P: + if (n == 0) { + return size().height; + } else { + return size().height / 2; + } + break; + case PIX_FMT_RGB24: + return size().height; + default: + assert (false); + } + + return 0; +} + +/** @return Number of components */ +int +Image::components () const +{ + switch (_pixel_format) { + case PIX_FMT_YUV420P: + return 3; + case PIX_FMT_RGB24: + return 1; + default: + assert (false); + } + + 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. + */ +shared_ptr +Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scaler) const +{ + assert (scaler); + + Size content_size = out_size; + content_size.width -= (padding * 2); + + shared_ptr rgb (new RGBFrameImage (content_size)); + + struct SwsContext* scale_context = sws_getContext ( + size().width, size().height, pixel_format(), + content_size.width, content_size.height, PIX_FMT_RGB24, + scaler->ffmpeg_id (), 0, 0, 0 + ); + + /* Scale and convert to RGB from whatever its currently in (which may be RGB) */ + sws_scale ( + scale_context, + data(), line_size(), + 0, size().height, + rgb->data (), rgb->line_size () + ); + + /* Put the image in the right place in a black frame if are padding; this is + a bit grubby and expensive, but probably inconsequential in the great + scheme of things. + */ + if (padding > 0) { + shared_ptr padded_rgb (new RGBFrameImage (out_size)); + padded_rgb->make_black (); + + /* XXX: we are cheating a bit here; we know the frame is RGB so we can + make assumptions about its composition. + */ + uint8_t* p = padded_rgb->data()[0] + padding * 3; + uint8_t* q = rgb->data()[0]; + for (int j = 0; j < rgb->lines(0); ++j) { + memcpy (p, q, rgb->line_size()[0]); + p += padded_rgb->line_size()[0]; + q += rgb->line_size()[0]; + } + + rgb = padded_rgb; + } + + sws_freeContext (scale_context); + + return rgb; +} + +/** Run a FFmpeg post-process on this image and return the processed version. + * @param pp Flags for the required set of post processes. + * @return Post-processed image. + */ +shared_ptr +Image::post_process (string pp) const +{ + shared_ptr out (new PostProcessImage (PIX_FMT_YUV420P, size ())); + + pp_mode* mode = pp_get_mode_by_name_and_quality (pp.c_str (), PP_QUALITY_MAX); + pp_context* context = pp_get_context (size().width, size().height, PP_FORMAT_420 | PP_CPU_CAPS_MMX2); + + pp_postprocess ( + (const uint8_t **) data(), line_size(), + out->data(), out->line_size(), + size().width, size().height, + 0, 0, mode, context, 0 + ); + + pp_free_mode (mode); + pp_free_context (context); + + return out; +} + +void +Image::make_black () +{ + switch (_pixel_format) { + case PIX_FMT_YUV420P: + memset (data()[0], 0, lines(0) * line_size()[0]); + memset (data()[1], 0x80, lines(1) * line_size()[1]); + memset (data()[2], 0x80, lines(2) * line_size()[2]); + break; + + case PIX_FMT_RGB24: + memset (data()[0], 0, lines(0) * line_size()[0]); + break; + + default: + assert (false); + } +} + +/** Construct a SimpleImage of a given size and format, allocating memory + * as required. + * + * @param p Pixel format. + * @param s Size in pixels. + */ +SimpleImage::SimpleImage (PixelFormat p, Size s) + : Image (p) + , _size (s) +{ + _data = (uint8_t **) av_malloc (components() * sizeof (uint8_t *)); + _line_size = (int *) av_malloc (components() * sizeof (int)); + + for (int i = 0; i < components(); ++i) { + _data[i] = 0; + _line_size[i] = 0; + } +} + +/** Destroy a SimpleImage */ +SimpleImage::~SimpleImage () +{ + for (int i = 0; i < components(); ++i) { + av_free (_data[i]); + } + + av_free (_data); + av_free (_line_size); +} + +/** Set the size in bytes of each horizontal line of a given component. + * @param i Component index. + * @param s Size of line in bytes. + */ +void +SimpleImage::set_line_size (int i, int s) +{ + _line_size[i] = s; + _data[i] = (uint8_t *) av_malloc (s * lines (i)); +} + +uint8_t ** +SimpleImage::data () const +{ + return _data; +} + +int * +SimpleImage::line_size () const +{ + return _line_size; +} + +Size +SimpleImage::size () const +{ + return _size; +} + + +FilterBufferImage::FilterBufferImage (PixelFormat p, AVFilterBufferRef* b) + : Image (p) + , _buffer (b) +{ + +} + +FilterBufferImage::~FilterBufferImage () +{ + avfilter_unref_buffer (_buffer); +} + +uint8_t ** +FilterBufferImage::data () const +{ + return _buffer->data; +} + +int * +FilterBufferImage::line_size () const +{ + return _buffer->linesize; +} + +Size +FilterBufferImage::size () const +{ + return Size (_buffer->video->w, _buffer->video->h); +} + +/** XXX: this could be generalised to use any format, but I don't + * understand how avpicture_fill is supposed to be called with + * multi-planar images. + */ +RGBFrameImage::RGBFrameImage (Size s) + : Image (PIX_FMT_RGB24) + , _size (s) +{ + _frame = avcodec_alloc_frame (); + if (_frame == 0) { + throw EncodeError ("could not allocate frame"); + } + + _data = (uint8_t *) av_malloc (size().width * size().height * 3); + avpicture_fill ((AVPicture *) _frame, _data, PIX_FMT_RGB24, size().width, size().height); + _frame->width = size().width; + _frame->height = size().height; + _frame->format = PIX_FMT_RGB24; +} + +RGBFrameImage::~RGBFrameImage () +{ + av_free (_data); + av_free (_frame); +} + +uint8_t ** +RGBFrameImage::data () const +{ + return _frame->data; +} + +int * +RGBFrameImage::line_size () const +{ + return _frame->linesize; +} + +Size +RGBFrameImage::size () const +{ + return _size; +} + +PostProcessImage::PostProcessImage (PixelFormat p, Size s) + : Image (p) + , _size (s) +{ + _data = new uint8_t*[4]; + _line_size = new int[4]; + + for (int i = 0; i < 4; ++i) { + _data[i] = (uint8_t *) av_malloc (s.width * s.height); + _line_size[i] = s.width; + } +} + +PostProcessImage::~PostProcessImage () +{ + for (int i = 0; i < 4; ++i) { + av_free (_data[i]); + } + + delete[] _data; + delete[] _line_size; +} + +uint8_t ** +PostProcessImage::data () const +{ + return _data; +} + +int * +PostProcessImage::line_size () const +{ + return _line_size; +} + +Size +PostProcessImage::size () const +{ + return _size; +} diff --git a/src/lib/image.h b/src/lib/image.h new file mode 100644 index 000000000..97ab1d5ff --- /dev/null +++ b/src/lib/image.h @@ -0,0 +1,164 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/image.h + * @brief A set of classes to describe video images. + */ + +#ifndef DVDOMATIC_IMAGE_H +#define DVDOMATIC_IMAGE_H + +#include +#include +extern "C" { +#include +#include +} +#include "util.h" + +class Scaler; +class RGBFrameImage; +class PostProcessImage; + +/** @class Image + * @brief Parent class for wrappers of some image, in some format, that + * can present a set of components and a size in pixels. + * + * This class also has some conversion / processing methods. + * + * The main point of this class (and its subclasses) is to abstract + * details of FFmpeg's memory management and varying data formats. + */ +class Image +{ +public: + Image (PixelFormat p) + : _pixel_format (p) + {} + + virtual ~Image () {} + + /** @return Array of pointers to arrays of the component data */ + virtual uint8_t ** data () const = 0; + + /** @return Array of sizes of each line, in pixels */ + virtual int * line_size () const = 0; + + /** @return Size of the image, in pixels */ + virtual Size size () const = 0; + + int components () const; + int lines (int) const; + boost::shared_ptr scale_and_convert_to_rgb (Size, int, Scaler const *) const; + boost::shared_ptr post_process (std::string) const; + +#ifdef DEBUG_HASH + void hash (std::string) const; +#endif + + void make_black (); + + PixelFormat pixel_format () const { + return _pixel_format; + } + +private: + PixelFormat _pixel_format; ///< FFmpeg's way of describing the pixel format of this Image +}; + +/** @class FilterBufferImage + * @brief An Image that is held in an AVFilterBufferRef. + */ +class FilterBufferImage : public Image +{ +public: + FilterBufferImage (PixelFormat, AVFilterBufferRef *); + ~FilterBufferImage (); + + uint8_t ** data () const; + int * line_size () const; + Size size () const; + +private: + AVFilterBufferRef* _buffer; +}; + +/** @class SimpleImage + * @brief An Image for which memory is allocated using a `simple' av_malloc(). + */ +class SimpleImage : public Image +{ +public: + SimpleImage (PixelFormat, Size); + ~SimpleImage (); + + uint8_t ** data () const; + int * line_size () const; + Size size () const; + + void set_line_size (int, int); + +private: + Size _size; ///< size in pixels + uint8_t** _data; ///< array of pointers to components + int* _line_size; ///< array of widths of each line, in bytes +}; + +/** @class RGBFrameImage + * @brief An RGB image that is held within an AVFrame. + */ +class RGBFrameImage : public Image +{ +public: + RGBFrameImage (Size); + ~RGBFrameImage (); + + uint8_t ** data () const; + int * line_size () const; + Size size () const; + AVFrame * frame () const { + return _frame; + } + +private: + Size _size; + AVFrame* _frame; + uint8_t* _data; +}; + +/** @class PostProcessImage + * @brief An image that is the result of an FFmpeg post-processing run. + */ +class PostProcessImage : public Image +{ +public: + PostProcessImage (PixelFormat, Size); + ~PostProcessImage (); + + uint8_t ** data () const; + int * line_size () const; + Size size () const; + +private: + Size _size; + uint8_t** _data; + int* _line_size; +}; + +#endif diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc new file mode 100644 index 000000000..7cee01ec5 --- /dev/null +++ b/src/lib/imagemagick_decoder.cc @@ -0,0 +1,55 @@ +#include +#include +#include "imagemagick_decoder.h" +#include "film_state.h" +#include "image.h" + +using namespace std; + +ImageMagickDecoder::ImageMagickDecoder ( + boost::shared_ptr s, boost::shared_ptr o, Job* j, Log* l, bool minimal, bool ignore_length) + : Decoder (s, o, j, l, minimal, ignore_length) + , _done (false) +{ + _magick_image = new Magick::Image (_fs->content_path ()); +} + +Size +ImageMagickDecoder::native_size () const +{ + return Size (_magick_image->columns(), _magick_image->rows()); +} + +bool +ImageMagickDecoder::do_pass () +{ + if (_done) { + return true; + } + + Size size = native_size (); + RGBFrameImage image (size); + + uint8_t* p = image.data()[0]; + for (int y = 0; y < size.height; ++y) { + for (int x = 0; x < size.width; ++x) { + Magick::Color c = _magick_image->pixelColor (x, y); + *p++ = c.redQuantum() * 255 / MaxRGB; + *p++ = c.greenQuantum() * 255 / MaxRGB; + *p++ = c.blueQuantum() * 255 / MaxRGB; + } + + } + + process_video (image.frame ()); + + _done = true; + return false; +} + +PixelFormat +ImageMagickDecoder::pixel_format () const +{ + return PIX_FMT_RGB24; +} + diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h new file mode 100644 index 000000000..707e6cacd --- /dev/null +++ b/src/lib/imagemagick_decoder.h @@ -0,0 +1,63 @@ +#include "decoder.h" + +namespace Magick { + class Image; +} + +class ImageMagickDecoder : public Decoder +{ +public: + ImageMagickDecoder (boost::shared_ptr, boost::shared_ptr, Job *, Log *, bool, bool); + + int length_in_frames () const { + return 1; + } + + float frames_per_second () const { + return static_frames_per_second (); + } + + Size native_size () const; + + int audio_channels () const { + return 0; + } + + int audio_sample_rate () const { + return 0; + } + + AVSampleFormat audio_sample_format () const { + return AV_SAMPLE_FMT_NONE; + } + + static float static_frames_per_second () { + return 24; + } + +protected: + bool do_pass (); + PixelFormat pixel_format () const; + + int time_base_numerator () const { + return 0; + } + + int time_base_denominator () const { + return 0; + } + + int sample_aspect_ratio_numerator () const { + /* XXX */ + return 1; + } + + int sample_aspect_ratio_denominator () const { + /* XXX */ + return 1; + } + +private: + Magick::Image* _magick_image; + bool _done; +}; diff --git a/src/lib/j2k_still_encoder.cc b/src/lib/j2k_still_encoder.cc new file mode 100644 index 000000000..f68bc5890 --- /dev/null +++ b/src/lib/j2k_still_encoder.cc @@ -0,0 +1,73 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/j2k_wav_encoder.cc + * @brief An encoder which writes JPEG2000 and WAV files. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "j2k_still_encoder.h" +#include "config.h" +#include "film_state.h" +#include "options.h" +#include "exceptions.h" +#include "dcp_video_frame.h" +#include "filter.h" +#include "log.h" +#include "imagemagick_decoder.h" + +using namespace std; +using namespace boost; + +J2KStillEncoder::J2KStillEncoder (shared_ptr s, shared_ptr o, Log* l) + : Encoder (s, o, l) +{ + +} + +void +J2KStillEncoder::process_video (shared_ptr yuv, int frame) +{ + pair const s = Filter::ffmpeg_strings (_fs->filters); + DCPVideoFrame* f = new DCPVideoFrame ( + yuv, _opt->out_size, _opt->padding, _fs->scaler, 0, _fs->frames_per_second, s.second, + Config::instance()->colour_lut_index(), Config::instance()->j2k_bandwidth(), + _log + ); + + if (!boost::filesystem::exists (_opt->frame_out_path (1, false))) { + boost::shared_ptr e = f->encode_locally (); + e->write (_opt, 1); + } + + string const real = _opt->frame_out_path (1, false); + for (int i = 2; i <= (_fs->still_duration * ImageMagickDecoder::static_frames_per_second()); ++i) { + if (!boost::filesystem::exists (_opt->frame_out_path (i, false))) { + string const link = _opt->frame_out_path (i, false); + symlink (real.c_str(), link.c_str()); + } + frame_done (); + } +} diff --git a/src/lib/j2k_still_encoder.h b/src/lib/j2k_still_encoder.h new file mode 100644 index 000000000..d4d68724e --- /dev/null +++ b/src/lib/j2k_still_encoder.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/j2k_wav_encoder.h + * @brief An encoder which writes JPEG2000 and WAV files. + */ + +#include +#include +#include "encoder.h" + +class Image; +class Log; + +/** @class J2KStillEncoder + * @brief An encoder which writes repeated JPEG2000 files from a single decoded input. + */ +class J2KStillEncoder : public Encoder +{ +public: + J2KStillEncoder (boost::shared_ptr, boost::shared_ptr, Log *); + + void process_begin () {} + void process_video (boost::shared_ptr, int); + void process_audio (uint8_t *, int) {} + void process_end () {} +}; diff --git a/src/lib/j2k_wav_encoder.cc b/src/lib/j2k_wav_encoder.cc new file mode 100644 index 000000000..e6a1b285e --- /dev/null +++ b/src/lib/j2k_wav_encoder.cc @@ -0,0 +1,289 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/j2k_wav_encoder.cc + * @brief An encoder which writes JPEG2000 and WAV files. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "j2k_wav_encoder.h" +#include "config.h" +#include "film_state.h" +#include "options.h" +#include "exceptions.h" +#include "dcp_video_frame.h" +#include "server.h" +#include "filter.h" +#include "log.h" + +using namespace std; +using namespace boost; + +J2KWAVEncoder::J2KWAVEncoder (shared_ptr s, shared_ptr o, Log* l) + : Encoder (s, o, l) + , _deinterleave_buffer_size (8192) + , _deinterleave_buffer (0) + , _process_end (false) +{ + /* Create sound output files with .tmp suffixes; we will rename + them if and when we complete. + */ + for (int i = 0; i < _fs->audio_channels; ++i) { + SF_INFO sf_info; + sf_info.samplerate = _fs->audio_sample_rate; + /* We write mono files */ + sf_info.channels = 1; + sf_info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_24; + SNDFILE* f = sf_open (_opt->multichannel_audio_out_path (i, true).c_str (), SFM_WRITE, &sf_info); + if (f == 0) { + throw CreateFileError (_opt->multichannel_audio_out_path (i, true)); + } + _sound_files.push_back (f); + } + + /* Create buffer for deinterleaving audio */ + _deinterleave_buffer = new uint8_t[_deinterleave_buffer_size]; +} + +J2KWAVEncoder::~J2KWAVEncoder () +{ + terminate_worker_threads (); + delete[] _deinterleave_buffer; + close_sound_files (); +} + +void +J2KWAVEncoder::terminate_worker_threads () +{ + boost::mutex::scoped_lock lock (_worker_mutex); + _process_end = true; + _worker_condition.notify_all (); + lock.unlock (); + + for (list::iterator i = _worker_threads.begin(); i != _worker_threads.end(); ++i) { + (*i)->join (); + delete *i; + } +} + +void +J2KWAVEncoder::close_sound_files () +{ + for (vector::iterator i = _sound_files.begin(); i != _sound_files.end(); ++i) { + sf_close (*i); + } + + _sound_files.clear (); +} + +void +J2KWAVEncoder::process_video (shared_ptr yuv, int frame) +{ + boost::mutex::scoped_lock lock (_worker_mutex); + + /* Wait until the queue has gone down a bit */ + while (_queue.size() >= _worker_threads.size() * 2 && !_process_end) { + _worker_condition.wait (lock); + } + + if (_process_end) { + return; + } + + /* Only do the processing if we don't already have a file for this frame */ + if (!boost::filesystem::exists (_opt->frame_out_path (frame, false))) { + pair const s = Filter::ffmpeg_strings (_fs->filters); + _queue.push_back (boost::shared_ptr ( + new DCPVideoFrame ( + yuv, _opt->out_size, _opt->padding, _fs->scaler, frame, _fs->frames_per_second, s.second, + Config::instance()->colour_lut_index (), Config::instance()->j2k_bandwidth (), + _log + ) + )); + + _worker_condition.notify_all (); + } +} + +void +J2KWAVEncoder::encoder_thread (Server* server) +{ + /* Number of seconds that we currently wait between attempts + to connect to the server; not relevant for localhost + encodings. + */ + int remote_backoff = 0; + + while (1) { + boost::mutex::scoped_lock lock (_worker_mutex); + while (_queue.empty () && !_process_end) { + _worker_condition.wait (lock); + } + + if (_process_end) { + return; + } + + boost::shared_ptr vf = _queue.front (); + _queue.pop_front (); + + lock.unlock (); + + shared_ptr encoded; + + if (server) { + try { + encoded = vf->encode_remotely (server); + + if (remote_backoff > 0) { + stringstream s; + s << server->host_name() << " was lost, but now she is found; removing backoff"; + _log->log (s.str ()); + } + + /* This job succeeded, so remove any backoff */ + remote_backoff = 0; + + } catch (std::exception& e) { + if (remote_backoff < 60) { + /* back off more */ + remote_backoff += 10; + } + stringstream s; + s << "Remote encode on " << server->host_name() << " failed (" << e.what() << "); thread sleeping for " << remote_backoff << "s."; + _log->log (s.str ()); + } + + } else { + try { + encoded = vf->encode_locally (); + } catch (std::exception& e) { + stringstream s; + s << "Local encode failed " << e.what() << "."; + _log->log (s.str ()); + } + } + + if (encoded) { + encoded->write (_opt, vf->frame ()); + frame_done (); + } else { + lock.lock (); + _queue.push_front (vf); + lock.unlock (); + } + + if (remote_backoff > 0) { + sleep (remote_backoff); + } + + lock.lock (); + _worker_condition.notify_all (); + } +} + +void +J2KWAVEncoder::process_begin () +{ + for (int i = 0; i < Config::instance()->num_local_encoding_threads (); ++i) { + _worker_threads.push_back (new boost::thread (boost::bind (&J2KWAVEncoder::encoder_thread, this, (Server *) 0))); + } + + vector servers = Config::instance()->servers (); + + for (vector::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))); + } + } +} + +void +J2KWAVEncoder::process_end () +{ + boost::mutex::scoped_lock lock (_worker_mutex); + + /* Keep waking workers until the queue is empty */ + while (!_queue.empty ()) { + _worker_condition.notify_all (); + _worker_condition.wait (lock); + } + + lock.unlock (); + + terminate_worker_threads (); + close_sound_files (); + + /* Rename .wav.tmp files to .wav */ + for (int i = 0; i < _fs->audio_channels; ++i) { + if (boost::filesystem::exists (_opt->multichannel_audio_out_path (i, false))) { + boost::filesystem::remove (_opt->multichannel_audio_out_path (i, false)); + } + boost::filesystem::rename (_opt->multichannel_audio_out_path (i, true), _opt->multichannel_audio_out_path (i, false)); + } +} + +void +J2KWAVEncoder::process_audio (uint8_t* data, int data_size) +{ + /* Size of a sample in bytes */ + int const sample_size = 2; + + /* XXX: we are assuming that sample_size is right, the _deinterleave_buffer_size is a multiple + of the sample size and that data_size is a multiple of _fs->audio_channels * sample_size. + */ + + /* XXX: this code is very tricksy and it must be possible to make it simpler ... */ + + /* Number of bytes left to read this time */ + int remaining = data_size; + /* Our position in the output buffers, in bytes */ + int position = 0; + while (remaining > 0) { + /* How many bytes of the deinterleaved data to do this time */ + int this_time = min (remaining / _fs->audio_channels, _deinterleave_buffer_size); + for (int i = 0; i < _fs->audio_channels; ++i) { + for (int j = 0; j < this_time; j += sample_size) { + for (int k = 0; k < sample_size; ++k) { + int const to = j + k; + int const from = position + (i * sample_size) + (j * _fs->audio_channels) + k; + _deinterleave_buffer[to] = data[from]; + } + } + + switch (_fs->audio_sample_format) { + case AV_SAMPLE_FMT_S16: + sf_write_short (_sound_files[i], (const short *) _deinterleave_buffer, this_time / sample_size); + break; + default: + throw DecodeError ("unknown audio sample format"); + } + } + + position += this_time; + remaining -= this_time * _fs->audio_channels; + } +} diff --git a/src/lib/j2k_wav_encoder.h b/src/lib/j2k_wav_encoder.h new file mode 100644 index 000000000..c8485b0bc --- /dev/null +++ b/src/lib/j2k_wav_encoder.h @@ -0,0 +1,65 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/j2k_wav_encoder.h + * @brief An encoder which writes JPEG2000 and WAV files. + */ + +#include +#include +#include +#include +#include +#include "encoder.h" + +class Server; +class DCPVideoFrame; +class Image; +class Log; + +/** @class J2KWAVEncoder + * @brief An encoder which writes JPEG2000 and WAV files. + */ +class J2KWAVEncoder : public Encoder +{ +public: + J2KWAVEncoder (boost::shared_ptr, boost::shared_ptr, Log *); + ~J2KWAVEncoder (); + + void process_begin (); + void process_video (boost::shared_ptr, int); + void process_audio (uint8_t *, int); + void process_end (); + +private: + + void encoder_thread (Server *); + void close_sound_files (); + void terminate_worker_threads (); + + std::vector _sound_files; + int _deinterleave_buffer_size; + uint8_t* _deinterleave_buffer; + + bool _process_end; + std::list > _queue; + std::list _worker_threads; + mutable boost::mutex _worker_mutex; + boost::condition _worker_condition; +}; diff --git a/src/lib/job.cc b/src/lib/job.cc new file mode 100644 index 000000000..399b235d9 --- /dev/null +++ b/src/lib/job.cc @@ -0,0 +1,242 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/job.cc + * @brief A parent class to represent long-running tasks which are run in their own thread. + */ + +#include +#include "job.h" +#include "util.h" + +using namespace std; +using namespace boost; + +/** @param s FilmState for the film that we are operating on. + * @param o Options. + * @param l A log that we can write to. + */ +Job::Job (shared_ptr s, shared_ptr o, Log* l) + : _fs (s) + , _opt (o) + , _log (l) + , _state (NEW) + , _start_time (0) + , _progress_unknown (false) +{ + assert (_log); + + descend (1); +} + +/** Start the job in a separate thread, returning immediately */ +void +Job::start () +{ + set_state (RUNNING); + _start_time = time (0); + boost::thread (boost::bind (&Job::run_wrapper, this)); +} + +/** A wrapper for the ::run() method to catch exceptions */ +void +Job::run_wrapper () +{ + try { + + run (); + + } catch (std::exception& e) { + + set_progress (1); + set_state (FINISHED_ERROR); + set_error (e.what ()); + + } +} + +/** @return true if the job is running */ +bool +Job::running () const +{ + boost::mutex::scoped_lock lm (_state_mutex); + return _state == RUNNING; +} + +/** @return true if the job has finished (either successfully or unsuccessfully) */ +bool +Job::finished () const +{ + boost::mutex::scoped_lock lm (_state_mutex); + return _state == FINISHED_OK || _state == FINISHED_ERROR; +} + +/** @return true if the job has finished successfully */ +bool +Job::finished_ok () const +{ + boost::mutex::scoped_lock lm (_state_mutex); + return _state == FINISHED_OK; +} + +/** @return true if the job has finished unsuccessfully */ +bool +Job::finished_in_error () const +{ + boost::mutex::scoped_lock lm (_state_mutex); + return _state == FINISHED_ERROR; +} + +/** Set the state of this job. + * @param s New state. + */ +void +Job::set_state (State s) +{ + boost::mutex::scoped_lock lm (_state_mutex); + _state = s; +} + +/** A hack to work around our lack of cross-thread + * signalling; this emits Finished, and listeners + * assume that it will be emitted in the GUI thread, + * so this method must be called from the GUI thread. + */ +void +Job::emit_finished () +{ + Finished (); +} + +/** @return Time (in seconds) that this job has been running */ +int +Job::elapsed_time () const +{ + if (_start_time == 0) { + return 0; + } + + return time (0) - _start_time; +} + +/** Set the progress of the current part of the job. + * @param p Progress (from 0 to 1) + */ +void +Job::set_progress (float p) +{ + boost::mutex::scoped_lock lm (_progress_mutex); + _stack.back().normalised = p; +} + +/** @return fractional overall progress, or -1 if not known */ +float +Job::overall_progress () const +{ + boost::mutex::scoped_lock lm (_progress_mutex); + if (_progress_unknown) { + return -1; + } + + float overall = 0; + float factor = 1; + for (list::const_iterator i = _stack.begin(); i != _stack.end(); ++i) { + factor *= i->allocation; + overall += i->normalised * factor; + } + + if (overall > 1) { + overall = 1; + } + + return overall; +} + +/** Ascend up one level in terms of progress reporting; see descend() */ +void +Job::ascend () +{ + boost::mutex::scoped_lock lm (_progress_mutex); + + assert (!_stack.empty ()); + float const a = _stack.back().allocation; + _stack.pop_back (); + _stack.back().normalised += a; +} + +/** Descend down one level in terms of progress reporting; e.g. if + * there is a task which is split up into N subtasks, each of which + * report their progress from 0 to 100%, call descend() before executing + * each subtask, and ascend() afterwards to ensure that overall progress + * is reported correctly. + * + * @param a Fraction (from 0 to 1) of the current task to allocate to the subtask. + */ +void +Job::descend (float a) +{ + boost::mutex::scoped_lock lm (_progress_mutex); + _stack.push_back (Level (a)); +} + +/** @return Any error string that the job has generated */ +string +Job::error () const +{ + boost::mutex::scoped_lock lm (_state_mutex); + return _error; +} + +/** Set the current error string. + * @param e New error string. + */ +void +Job::set_error (string e) +{ + boost::mutex::scoped_lock lm (_state_mutex); + _error = e; +} + +/** Set that this job's progress will always be unknown */ +void +Job::set_progress_unknown () +{ + boost::mutex::scoped_lock lm (_progress_mutex); + _progress_unknown = true; +} + +string +Job::status () const +{ + float const p = overall_progress (); + int const t = elapsed_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) { + s << rint (p * 100) << "%"; + } else if (finished_ok ()) { + s << "OK (ran for " << seconds_to_hms (t) << ")"; + } else if (finished_in_error ()) { + s << "Error (" << error() << ")"; + } + + return s.str (); +} diff --git a/src/lib/job.h b/src/lib/job.h new file mode 100644 index 000000000..2a77f78f7 --- /dev/null +++ b/src/lib/job.h @@ -0,0 +1,115 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/job.h + * @brief A parent class to represent long-running tasks which are run in their own thread. + */ + +#ifndef DVDOMATIC_JOB_H +#define DVDOMATIC_JOB_H + +#include +#include +#include + +class Log; +class FilmState; +class Options; + +/** @class Job + * @brief A parent class to represent long-running tasks which are run in their own thread. + */ +class Job +{ +public: + Job (boost::shared_ptr s, boost::shared_ptr o, Log* l); + + /** @return user-readable name of this job */ + virtual std::string name () const = 0; + /** Run this job in the current thread. */ + virtual void run () = 0; + + void start (); + + bool running () const; + bool finished () const; + bool finished_ok () const; + bool finished_in_error () const; + + std::string error () const; + + int elapsed_time () const; + virtual std::string status () const; + + void set_progress_unknown (); + void set_progress (float); + void ascend (); + void descend (float); + float overall_progress () const; + + void emit_finished (); + + /** Emitted from the GUI thread */ + sigc::signal0 Finished; + +protected: + + enum State { + NEW, ///< the job hasn't been started yet + RUNNING, ///< the job is running + FINISHED_OK, ///< the job has finished successfully + FINISHED_ERROR ///< the job has finished in error + }; + + void set_state (State); + void set_error (std::string e); + + boost::shared_ptr _fs; + boost::shared_ptr _opt; + + /** A log that this job can write to */ + Log* _log; + +private: + + void run_wrapper (); + + /** mutex for _state and _error */ + mutable boost::mutex _state_mutex; + State _state; + std::string _error; + + time_t _start_time; + + mutable boost::mutex _progress_mutex; + + struct Level { + Level (float a) : allocation (a), normalised (0) {} + + float allocation; + float normalised; + }; + + std::list _stack; + + /** true if this job's progress will always be unknown */ + bool _progress_unknown; +}; + +#endif diff --git a/src/lib/job_manager.cc b/src/lib/job_manager.cc new file mode 100644 index 000000000..dd7c62c31 --- /dev/null +++ b/src/lib/job_manager.cc @@ -0,0 +1,101 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/job_manager.cc + * @brief A simple scheduler for jobs. + */ + +#include +#include +#include "job_manager.h" +#include "job.h" + +using namespace std; +using namespace boost; + +JobManager* JobManager::_instance = 0; + +JobManager::JobManager () +{ + boost::thread (boost::bind (&JobManager::scheduler, this)); +} + +void +JobManager::add (shared_ptr j) +{ + boost::mutex::scoped_lock lm (_mutex); + + _jobs.push_back (j); +} + +list > +JobManager::get () const +{ + boost::mutex::scoped_lock lm (_mutex); + + return _jobs; +} + +bool +JobManager::work_to_do () const +{ + boost::mutex::scoped_lock lm (_mutex); + list >::const_iterator i = _jobs.begin(); + while (i != _jobs.end() && (*i)->finished()) { + ++i; + } + + return i != _jobs.end (); +} + +void +JobManager::scheduler () +{ + while (1) { + { + boost::mutex::scoped_lock lm (_mutex); + int running = 0; + shared_ptr first_new; + for (list >::iterator i = _jobs.begin(); i != _jobs.end(); ++i) { + if ((*i)->running ()) { + ++running; + } else if (!(*i)->finished () && first_new == 0) { + first_new = *i; + } + + if (running == 0 && first_new) { + first_new->start (); + break; + } + } + } + + sleep (1); + } +} + +JobManager * +JobManager::instance () +{ + if (_instance == 0) { + _instance = new JobManager (); + } + + return _instance; +} diff --git a/src/lib/job_manager.h b/src/lib/job_manager.h new file mode 100644 index 000000000..f2f5e0057 --- /dev/null +++ b/src/lib/job_manager.h @@ -0,0 +1,54 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/job_manager.h + * @brief A simple scheduler for jobs. + */ + +#include +#include + +class Job; + +/** @class JobManager + * @brief A simple scheduler for jobs. + * + * JobManager simply keeps a list of pending jobs, and assumes that all the jobs + * are sufficiently CPU intensive that there is no point running them in parallel; + * so jobs are just run one after the other. + */ +class JobManager +{ +public: + + void add (boost::shared_ptr); + std::list > get () const; + bool work_to_do () const; + + static JobManager* instance (); + +private: + JobManager (); + void scheduler (); + + mutable boost::mutex _mutex; + std::list > _jobs; + + static JobManager* _instance; +}; diff --git a/src/lib/log.cc b/src/lib/log.cc new file mode 100644 index 000000000..accf3694d --- /dev/null +++ b/src/lib/log.cc @@ -0,0 +1,63 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/log.cc + * @brief A very simple logging class. + */ + +#include +#include +#include "log.h" + +using namespace std; + +/** @param f Filename to write log to */ +Log::Log (string f) + : _file (f) + , _level (VERBOSE) +{ + +} + +/** @param n String to log */ +void +Log::log (string m, Level l) +{ + boost::mutex::scoped_lock lm (_mutex); + + if (l > _level) { + 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"; +} + +void +Log::set_level (Level l) +{ + boost::mutex::scoped_lock lm (_mutex); + _level = l; +} + diff --git a/src/lib/log.h b/src/lib/log.h new file mode 100644 index 000000000..d4de8ebde --- /dev/null +++ b/src/lib/log.h @@ -0,0 +1,55 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/log.h + * @brief A very simple logging class. + */ + +#include +#include + +/** @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); + + enum Level { + STANDARD = 0, + VERBOSE = 1 + }; + + void log (std::string m, Level l = STANDARD); + + void set_level (Level l); + +private: + /** mutex to prevent simultaneous writes to the file */ + boost::mutex _mutex; + /** filename to write to */ + std::string _file; + /** level above which to ignore log messages */ + Level _level; +}; diff --git a/src/lib/lut.h b/src/lib/lut.h new file mode 100644 index 000000000..26888a24a --- /dev/null +++ b/src/lib/lut.h @@ -0,0 +1,51 @@ +/* + Taken from OpenDCP: Builds Digital Cinema Packages + Copyright (c) 2010-2011 Terrence Meiczinger, All Rights Reserved + + This 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 3 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, see . +*/ + +/** @file src/lut.h + * @brief Look-up tables for colour conversions (from OpenDCP) + */ + +#define BIT_DEPTH 12 +#define BIT_PRECISION 16 +#define COLOR_DEPTH (4095) +#define DCI_LUT_SIZE ((COLOR_DEPTH + 1) * BIT_PRECISION) +#define DCI_GAMMA (2.6) +#define DCI_DEGAMMA (1/DCI_GAMMA) +#define DCI_COEFFICENT (48.0/52.37) + +enum COLOR_PROFILE_ENUM { + CP_SRGB = 0, + CP_REC709, + CP_DC28, + CP_MAX +}; + +enum LUT_IN_ENUM { + LI_SRGB = 0, + LI_REC709, + LI_MAX +}; + +enum LUT_OUT_ENUM { + LO_DCI = 0, + LO_MAX +}; + +extern float color_matrix[3][3][3]; +extern float lut_in[LI_MAX][4095+1]; +extern int lut_out[1][DCI_LUT_SIZE]; diff --git a/src/lib/make_dcp_job.cc b/src/lib/make_dcp_job.cc new file mode 100644 index 000000000..81deb835d --- /dev/null +++ b/src/lib/make_dcp_job.cc @@ -0,0 +1,94 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/make_dcp_job.cc + * @brief A job to create DCPs. + */ + +#include +extern "C" { +#include +} +#include "make_dcp_job.h" +#include "film_state.h" +#include "dcp_content_type.h" +#include "exceptions.h" + +using namespace std; +using namespace boost; + +/** @param s FilmState of the Film we are making the DCP for. + * @param o Options. + * @param l Log. + */ +MakeDCPJob::MakeDCPJob (shared_ptr s, shared_ptr o, Log* l) + : ShellCommandJob (s, o, l) +{ + +} + +string +MakeDCPJob::name () const +{ + stringstream s; + s << "Make DCP for " << _fs->name; + return s.str (); +} + +void +MakeDCPJob::run () +{ + set_progress_unknown (); + + string const dcp_path = _fs->dir (_fs->name); + + /* Check that we have our prerequisites */ + + if (!filesystem::exists (filesystem::path (_fs->file ("video.mxf")))) { + throw EncodeError ("missing video.mxf"); + } + + bool const have_audio = filesystem::exists (filesystem::path (_fs->file ("audio.mxf"))); + + /* Remove any old DCP */ + filesystem::remove_all (dcp_path); + + /* Re-make the DCP directory */ + _fs->dir (_fs->name); + + stringstream c; + c << "cd \"" << dcp_path << "\" && " + << " opendcp_xml -d -a " << _fs->name + << " -t \"" << _fs->name << "\"" + << " -k " << _fs->dcp_content_type->opendcp_name() + << " --reel \"" << _fs->file ("video.mxf") << "\""; + + if (have_audio) { + c << " \"" << _fs->file ("audio.mxf") << "\""; + } + + command (c.str ()); + + filesystem::rename (filesystem::path (_fs->file ("video.mxf")), filesystem::path (dcp_path + "/video.mxf")); + if (have_audio) { + filesystem::rename (filesystem::path (_fs->file ("audio.mxf")), filesystem::path (dcp_path + "/audio.mxf")); + } + + set_progress (1); +} diff --git a/src/lib/make_dcp_job.h b/src/lib/make_dcp_job.h new file mode 100644 index 000000000..4e3193572 --- /dev/null +++ b/src/lib/make_dcp_job.h @@ -0,0 +1,37 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/make_dcp_job.h + * @brief A job to create DCPs. + */ + +#include "shell_command_job.h" + +/** @class MakeDCPJob + * @brief A job to create DCPs + */ +class MakeDCPJob : public ShellCommandJob +{ +public: + MakeDCPJob (boost::shared_ptr, boost::shared_ptr, Log *); + + std::string name () const; + void run (); +}; + diff --git a/src/lib/make_mxf_job.cc b/src/lib/make_mxf_job.cc new file mode 100644 index 000000000..6b0c1a406 --- /dev/null +++ b/src/lib/make_mxf_job.cc @@ -0,0 +1,81 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/make_mxf_job.cc + * @brief A job that creates a MXF file from some data. + */ + +#include +#include "make_mxf_job.h" +#include "film.h" +#include "film_state.h" +#include "options.h" + +using namespace std; +using namespace boost; + +/** @class MakeMXFJob + * @brief A job that creates a MXF file from some data. + */ + +MakeMXFJob::MakeMXFJob (shared_ptr s, shared_ptr o, Log* l, Type t) + : ShellCommandJob (s, o, l) + , _type (t) +{ + +} + +string +MakeMXFJob::name () const +{ + stringstream s; + switch (_type) { + case VIDEO: + s << "Make video MXF for " << _fs->name; + break; + case AUDIO: + s << "Make audio MXF for " << _fs->name; + break; + } + + return s.str (); +} + +void +MakeMXFJob::run () +{ + set_progress_unknown (); + + /* We round for DCP: not sure if this is right */ + float fps = rintf (_fs->frames_per_second); + + stringstream c; + c << "opendcp_mxf -r " << fps << " -i "; + switch (_type) { + case VIDEO: + c << "\"" << _opt->frame_out_path () << "\" -o \"" << _fs->file ("video.mxf") << "\""; + break; + case AUDIO: + c << "\"" << _opt->multichannel_audio_out_path () << "\" -o \"" << _fs->file ("audio.mxf") << "\""; + break; + } + + command (c.str ()); + set_progress (1); +} diff --git a/src/lib/make_mxf_job.h b/src/lib/make_mxf_job.h new file mode 100644 index 000000000..462381d23 --- /dev/null +++ b/src/lib/make_mxf_job.h @@ -0,0 +1,48 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/make_mxf_job.h + * @brief A job that creates a MXF file from some data. + */ + +#include "shell_command_job.h" + +class FilmState; +class Options; + +/** @class MakeMXFJob + * @brief A job that creates a MXF file from some data. + */ +class MakeMXFJob : public ShellCommandJob +{ +public: + enum Type { + AUDIO, + VIDEO + }; + + MakeMXFJob (boost::shared_ptr, boost::shared_ptr, Log *, Type); + + std::string name () const; + void run (); + +private: + Type _type; +}; + diff --git a/src/lib/options.h b/src/lib/options.h new file mode 100644 index 000000000..b283e330d --- /dev/null +++ b/src/lib/options.h @@ -0,0 +1,109 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/options.h + * @brief Options for a transcoding operation. + */ + +#include +#include +#include +#include "util.h" + +/** @class Options + * @brief Options for a transcoding operation. + * + * These are settings which may be different, in different circumstances, for + * the same film; ie they are options for a particular transcode operation. + */ +class Options +{ +public: + + Options (std::string f, std::string e, std::string m) + : padding (0) + , apply_crop (true) + , num_frames (0) + , decode_video (true) + , decode_video_frequency (0) + , decode_audio (true) + , _frame_out_path (f) + , _frame_out_extension (e) + , _multichannel_audio_out_path (m) + {} + + /** @return The path to write video frames to */ + std::string frame_out_path () const { + return _frame_out_path; + } + + /** @param f Frame index. + * @param t true to return a temporary file path, otherwise a permanent one. + * @return The path to write this video frame to. + */ + std::string frame_out_path (int f, bool t) const { + std::stringstream s; + s << _frame_out_path << "/"; + s.width (8); + s << std::setfill('0') << f << _frame_out_extension; + + if (t) { + s << ".tmp"; + } + + return s.str (); + } + + /** @return Path to write multichannel audio data to */ + std::string multichannel_audio_out_path () const { + return _multichannel_audio_out_path; + } + + /** @param c Audio channel index. + * @param t true to return a temporary file path, otherwise a permanent one. + * @return The path to write this audio file to. + */ + std::string multichannel_audio_out_path (int c, bool t) const { + std::stringstream s; + s << _multichannel_audio_out_path << "/" << (c + 1) << ".wav"; + if (t) { + s << ".tmp"; + } + + return s.str (); + } + + Size out_size; ///< size of output images + float ratio; ///< ratio of the wanted output image (not considering padding) + int padding; ///< number of pixels of padding (in terms of the output size) each side of the image + bool apply_crop; ///< true to apply cropping + int num_frames; ///< number of video frames to run for, or 0 for all + int black_after; ///< first frame for which to output a black frame, rather than the actual video content, or 0 for none + bool decode_video; ///< true to decode video, otherwise false + int decode_video_frequency; ///< skip frames so that this many are decoded in all (or 0) (for generating thumbnails) + bool decode_audio; ///< true to decode audio, otherwise false + +private: + /** Path of the directory to write video frames to */ + std::string _frame_out_path; + /** Extension to use for video frame files (including the leading .) */ + std::string _frame_out_extension; + /** Path of the directory to write audio files to */ + std::string _multichannel_audio_out_path; +}; diff --git a/src/lib/player.cc b/src/lib/player.cc new file mode 100644 index 000000000..db69c66d1 --- /dev/null +++ b/src/lib/player.cc @@ -0,0 +1,227 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include +#include +#include +#include +#include +#include +#include "player.h" +#include "film_state.h" +#include "filter.h" +#include "screen.h" +#include "exceptions.h" + +using namespace std; +using namespace boost; + +Player::Player (shared_ptr fs, shared_ptr screen, Split split) + : _stdout_reader_should_run (true) + , _position (0) + , _paused (false) +{ + assert (fs->format); + + if (pipe (_mplayer_stdin) < 0) { + throw PlayError ("could not create pipe"); + } + + if (pipe (_mplayer_stdout) < 0) { + throw PlayError ("could not create pipe"); + } + + if (pipe (_mplayer_stderr) < 0) { + throw PlayError ("could not create pipe"); + } + + int const p = fork (); + if (p < 0) { + throw PlayError ("could not fork for mplayer"); + } else if (p == 0) { + close (_mplayer_stdin[1]); + dup2 (_mplayer_stdin[0], STDIN_FILENO); + + close (_mplayer_stdout[0]); + dup2 (_mplayer_stdout[1], STDOUT_FILENO); + + close (_mplayer_stderr[0]); + dup2 (_mplayer_stderr[1], STDERR_FILENO); + + char* p[] = { strdup ("TERM=xterm"), strdup ("DISPLAY=:0"), 0 }; + environ = p; + + stringstream s; + s << "/usr/local/bin/mplayer"; + + s << " -vo x11 -noaspect -noautosub -nosub -vo x11 -noborder -slave -quiet -input nodefault-bindings:conf=/dev/null"; + s << " -sws " << fs->scaler->mplayer_id (); + + stringstream vf; + + Position position = screen->position (fs->format); + Size screen_size = screen->size (fs->format); + Size const cropped_size = fs->cropped_size (fs->size); + switch (split) { + case SPLIT_NONE: + vf << crop_string (Position (fs->left_crop, fs->top_crop), cropped_size); + s << " -geometry " << position.x << ":" << position.y; + break; + case SPLIT_LEFT: + { + Size split_size = cropped_size; + split_size.width /= 2; + vf << crop_string (Position (fs->left_crop, fs->top_crop), split_size); + screen_size.width /= 2; + s << " -geometry " << position.x << ":" << position.y; + break; + } + case SPLIT_RIGHT: + { + Size split_size = cropped_size; + split_size.width /= 2; + vf << crop_string (Position (fs->left_crop + split_size.width, fs->top_crop), split_size); + screen_size.width /= 2; + s << " -geometry " << (position.x + screen_size.width) << ":" << position.y; + break; + } + } + + vf << ",scale=" << screen_size.width << ":" << screen_size.height; + + pair filters = Filter::ffmpeg_strings (fs->filters); + + if (!filters.first.empty()) { + vf << "," << filters.first; + } + + if (!filters.second.empty ()) { + vf << ",pp=" << filters.second; + } + + s << " -vf " << vf.str(); + s << " \"" << fs->content_path() << "\" "; + + string cmd (s.str ()); + + vector b = split_at_spaces_considering_quotes (cmd); + + char** cl = new char*[b.size() + 1]; + for (vector::size_type i = 0; i < b.size(); ++i) { + cl[i] = strdup (b[i].c_str ()); + } + cl[b.size()] = 0; + + execv (cl[0], cl); + + stringstream e; + e << "exec of mplayer failed " << strerror (errno); + throw PlayError (e.str ()); + + } else { + _mplayer_pid = p; + command ("pause"); + + _stdout_reader = new boost::thread (boost::bind (&Player::stdout_reader, this)); + } +} + +Player::~Player () +{ + _stdout_reader_should_run = false; + _stdout_reader->join (); + delete _stdout_reader; + + close (_mplayer_stdin[0]); + close (_mplayer_stdout[1]); + kill (_mplayer_pid, SIGTERM); +} + +void +Player::command (string c) +{ + char buf[64]; + snprintf (buf, sizeof (buf), "%s\n", c.c_str ()); + write (_mplayer_stdin[1], buf, strlen (buf)); +} + +void +Player::stdout_reader () +{ + while (_stdout_reader_should_run) { + char buf[1024]; + int r = read (_mplayer_stdout[0], buf, sizeof (buf)); + if (r > 0) { + stringstream s (buf); + while (s.good ()) { + string line; + getline (s, line); + + vector b; + split (b, line, is_any_of ("=")); + if (b.size() < 2) { + continue; + } + + if (b[0] == "ANS_time_pos") { + set_position (atof (b[1].c_str ())); + } else if (b[0] == "ANS_pause") { + set_paused (b[1] == "yes"); + } + } + } + + usleep (5e5); + + snprintf (buf, sizeof (buf), "pausing_keep_force get_property time_pos\npausing_keep_force get_property pause\n"); + write (_mplayer_stdin[1], buf, strlen (buf)); + } +} + +void +Player::set_position (float p) +{ + /* XXX: could be an atomic */ + boost::mutex::scoped_lock lm (_state_mutex); + _position = p; +} + +void +Player::set_paused (bool p) +{ + /* XXX: could be an atomic */ + boost::mutex::scoped_lock lm (_state_mutex); + _paused = p; +} + +float +Player::position () const +{ + boost::mutex::scoped_lock lm (_state_mutex); + return _position; +} + +bool +Player::paused () const +{ + boost::mutex::scoped_lock lm (_state_mutex); + return _paused; +} diff --git a/src/lib/player.h b/src/lib/player.h new file mode 100644 index 000000000..fc08deb9f --- /dev/null +++ b/src/lib/player.h @@ -0,0 +1,70 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef DVDOMATIC_PLAYER_H +#define DVDOMATIC_PLAYER_H + +#include +#include +#include + +class FilmState; +class Screen; + +class Player +{ +public: + enum Split { + SPLIT_NONE, + SPLIT_LEFT, + SPLIT_RIGHT + }; + + Player (boost::shared_ptr, boost::shared_ptr, Split); + ~Player (); + + void command (std::string); + + float position () const; + bool paused () const; + + pid_t mplayer_pid () const { + return _mplayer_pid; + } + +private: + void stdout_reader (); + void set_position (float); + void set_paused (bool); + + int _mplayer_stdin[2]; + int _mplayer_stdout[2]; + int _mplayer_stderr[2]; + pid_t _mplayer_pid; + + boost::thread* _stdout_reader; + /* XXX: should probably be atomically accessed */ + bool _stdout_reader_should_run; + + mutable boost::mutex _state_mutex; + float _position; + bool _paused; +}; + +#endif diff --git a/src/lib/player_manager.cc b/src/lib/player_manager.cc new file mode 100644 index 000000000..74fd2383b --- /dev/null +++ b/src/lib/player_manager.cc @@ -0,0 +1,137 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 "player_manager.h" +#include "player.h" +#include "film_state.h" +#include "screen.h" + +using namespace std; +using namespace boost; + +PlayerManager* PlayerManager::_instance = 0; + +PlayerManager::PlayerManager () +{ + +} + +PlayerManager * +PlayerManager::instance () +{ + if (_instance == 0) { + _instance = new PlayerManager (); + } + + return _instance; +} + +void +PlayerManager::setup (shared_ptr fs, shared_ptr sc) +{ + boost::mutex::scoped_lock lm (_players_mutex); + + _players.clear (); + _players.push_back (shared_ptr (new Player (fs, sc, Player::SPLIT_NONE))); +} + +void +PlayerManager::setup (shared_ptr fs_a, shared_ptr fs_b, shared_ptr sc) +{ + boost::mutex::scoped_lock lm (_players_mutex); + + _players.clear (); + + _players.push_back (shared_ptr (new Player (fs_a, sc, Player::SPLIT_LEFT))); + _players.push_back (shared_ptr (new Player (fs_b, sc, Player::SPLIT_RIGHT))); +} + +void +PlayerManager::pause_or_unpause () +{ + boost::mutex::scoped_lock lm (_players_mutex); + + for (list >::iterator i = _players.begin(); i != _players.end(); ++i) { + (*i)->command ("pause"); + } +} + +void +PlayerManager::set_position (float p) +{ + boost::mutex::scoped_lock lm (_players_mutex); + + stringstream s; + s << "pausing_keep_force seek " << p << " 2"; + for (list >::iterator i = _players.begin(); i != _players.end(); ++i) { + (*i)->command (s.str ()); + } +} + +float +PlayerManager::position () const +{ + boost::mutex::scoped_lock lm (_players_mutex); + + if (_players.empty ()) { + return 0; + } + + return _players.front()->position (); +} + +void +PlayerManager::child_exited (pid_t pid) +{ + boost::mutex::scoped_lock lm (_players_mutex); + + list >::iterator i = _players.begin(); + while (i != _players.end() && (*i)->mplayer_pid() != pid) { + ++i; + } + + if (i == _players.end()) { + return; + } + + _players.erase (i); +} + +PlayerManager::State +PlayerManager::state () const +{ + boost::mutex::scoped_lock lm (_players_mutex); + + if (_players.empty ()) { + return QUIESCENT; + } + + if (_players.front()->paused ()) { + return PAUSED; + } + + return PLAYING; +} + +void +PlayerManager::stop () +{ + boost::mutex::scoped_lock lm (_players_mutex); + _players.clear (); +} diff --git a/src/lib/player_manager.h b/src/lib/player_manager.h new file mode 100644 index 000000000..70a31b229 --- /dev/null +++ b/src/lib/player_manager.h @@ -0,0 +1,59 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include "player.h" + +class Player; +class FilmState; +class Screen; + +class PlayerManager +{ +public: + + void setup (boost::shared_ptr, boost::shared_ptr); + void setup (boost::shared_ptr, boost::shared_ptr, boost::shared_ptr); + void pause_or_unpause (); + void stop (); + + float position () const; + void set_position (float); + + enum State { + QUIESCENT, + PLAYING, + PAUSED + }; + + State state () const; + + void child_exited (pid_t); + + static PlayerManager* instance (); + +private: + PlayerManager (); + + mutable boost::mutex _players_mutex; + std::list > _players; + + static PlayerManager* _instance; +}; diff --git a/src/lib/scaler.cc b/src/lib/scaler.cc new file mode 100644 index 000000000..1e63d66b3 --- /dev/null +++ b/src/lib/scaler.cc @@ -0,0 +1,117 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/scaler.cc + * @brief A class to describe one of FFmpeg's software scalers. + */ + +#include +#include +extern "C" { +#include +} +#include "scaler.h" + +using namespace std; + +vector Scaler::_scalers; + +/** @param f FFmpeg id. + * @param m mplayer command line id. + * @param i Our id. + * @param n User-visible name. + */ +Scaler::Scaler (int f, int m, string i, string n) + : _ffmpeg_id (f) + , _mplayer_id (m) + , _id (i) + , _name (n) +{ + +} + +/** @return All available scalers */ +vector +Scaler::all () +{ + return _scalers; +} + +/** Set up the static _scalers vector; must be called before from_* + * methods are used. + */ +void +Scaler::setup_scalers () +{ + _scalers.push_back (new Scaler (SWS_BICUBIC, 2, "bicubic", "Bicubic")); + _scalers.push_back (new Scaler (SWS_X, 3, "x", "X")); + _scalers.push_back (new Scaler (SWS_AREA, 5, "area", "Area")); + _scalers.push_back (new Scaler (SWS_GAUSS, 7, "gauss", "Gaussian")); + _scalers.push_back (new Scaler (SWS_LANCZOS, 9, "lanczos", "Lanczos")); + _scalers.push_back (new Scaler (SWS_SINC, 8, "sinc", "Sinc")); + _scalers.push_back (new Scaler (SWS_SPLINE, 10, "spline", "Spline")); + _scalers.push_back (new Scaler (SWS_BILINEAR, 1, "bilinear", "Bilinear")); + _scalers.push_back (new Scaler (SWS_FAST_BILINEAR, 0, "fastbilinear", "Fast Bilinear")); +} + +/** @param id One of our ids. + * @return Corresponding scaler, or 0. + */ +Scaler const * +Scaler::from_id (string id) +{ + vector::iterator i = _scalers.begin (); + while (i != _scalers.end() && (*i)->id() != id) { + ++i; + } + + if (i == _scalers.end ()) { + return 0; + } + + return *i; +} + +/** @param s A scaler from our static list. + * @return Index of the scaler with the list, or -1. + */ +int +Scaler::as_index (Scaler const * s) +{ + vector::size_type i = 0; + while (i < _scalers.size() && _scalers[i] != s) { + ++i; + } + + if (i == _scalers.size ()) { + return -1; + } + + return i; +} + +/** @param i An index returned from as_index(). + * @return Corresponding scaler. + */ +Scaler const * +Scaler::from_index (int i) +{ + assert (i <= int(_scalers.size ())); + return _scalers[i]; +} diff --git a/src/lib/scaler.h b/src/lib/scaler.h new file mode 100644 index 000000000..8ededfe2a --- /dev/null +++ b/src/lib/scaler.h @@ -0,0 +1,78 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/scaler.h + * @brief A class to describe one of FFmpeg's software scalers. + */ + +#ifndef DVDOMATIC_SCALER_H +#define DVDOMATIC_SCALER_H + +#include +#include + +/** @class Scaler + * @brief Class to describe one of FFmpeg's software scalers + */ +class Scaler +{ +public: + Scaler (int f, int m, std::string i, std::string n); + + /** @return id used for calls to FFmpeg's pp_postprocess */ + int ffmpeg_id () const { + return _ffmpeg_id; + } + + /** @return number to use on an mplayer command line */ + int mplayer_id () const { + return _mplayer_id; + } + + /** @return id for our use */ + std::string id () const { + return _id; + } + + /** @return user-visible name for this scaler */ + std::string name () const { + return _name; + } + + static std::vector all (); + static void setup_scalers (); + static Scaler const * from_id (std::string id); + static Scaler const * from_index (int); + static int as_index (Scaler const *); + +private: + + /** id used for calls to FFmpeg's pp_postprocess */ + int _ffmpeg_id; + int _mplayer_id; + /** id for our use */ + std::string _id; + /** user-visible name for this scaler */ + std::string _name; + + /** sll available scalers */ + static std::vector _scalers; +}; + +#endif diff --git a/src/lib/scp_dcp_job.cc b/src/lib/scp_dcp_job.cc new file mode 100644 index 000000000..94e403816 --- /dev/null +++ b/src/lib/scp_dcp_job.cc @@ -0,0 +1,242 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/scp_dcp_job.cc + * @brief A job to copy DCPs to a SCP-enabled server. + */ + +#include +#include +#include +#include +#include +#include +#include "scp_dcp_job.h" +#include "exceptions.h" +#include "config.h" +#include "log.h" +#include "film_state.h" + +using namespace std; +using namespace boost; + +class SSHSession +{ +public: + SSHSession () + : _connected (false) + { + session = ssh_new (); + if (session == 0) { + throw NetworkError ("Could not start SSH session"); + } + } + + int connect () + { + int r = ssh_connect (session); + if (r == 0) { + _connected = true; + } + return r; + } + + ~SSHSession () + { + if (_connected) { + ssh_disconnect (session); + } + ssh_free (session); + } + + ssh_session session; + +private: + bool _connected; +}; + +class SSHSCP +{ +public: + SSHSCP (ssh_session s) + { + scp = ssh_scp_new (s, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, Config::instance()->tms_path().c_str ()); + if (!scp) { + stringstream s; + s << "Could not start SCP session (" << ssh_get_error (s) << ")"; + throw NetworkError (s.str ()); + } + } + + ~SSHSCP () + { + ssh_scp_free (scp); + } + + ssh_scp scp; +}; + + +SCPDCPJob::SCPDCPJob (shared_ptr s, Log* l) + : Job (s, shared_ptr (), l) + , _status ("Waiting") +{ + +} + +string +SCPDCPJob::name () const +{ + stringstream s; + s << "Copy DCP to TMS"; + return s.str (); +} + +void +SCPDCPJob::run () +{ + try { + _log->log ("SCP DCP job starting"); + + SSHSession ss; + + set_status ("Connecting"); + + ssh_options_set (ss.session, SSH_OPTIONS_HOST, Config::instance()->tms_ip().c_str ()); + ssh_options_set (ss.session, SSH_OPTIONS_USER, Config::instance()->tms_user().c_str ()); + int const port = 22; + ssh_options_set (ss.session, SSH_OPTIONS_PORT, &port); + + int r = ss.connect (); + if (r != SSH_OK) { + stringstream s; + s << "Could not connect to server " << Config::instance()->tms_ip() << " (" << ssh_get_error (ss.session) << ")"; + throw NetworkError (s.str ()); + } + + int const state = ssh_is_server_known (ss.session); + if (state == SSH_SERVER_ERROR) { + stringstream s; + s << "SSH error (" << ssh_get_error (ss.session) << ")"; + throw NetworkError (s.str ()); + } + + r = ssh_userauth_password (ss.session, 0, Config::instance()->tms_password().c_str ()); + if (r != SSH_AUTH_SUCCESS) { + stringstream s; + s << "Failed to authenticate with server (" << ssh_get_error (ss.session) << ")"; + throw NetworkError (s.str ()); + } + + SSHSCP sc (ss.session); + + r = ssh_scp_init (sc.scp); + if (r != SSH_OK) { + stringstream s; + s << "Could not start SCP session (" << ssh_get_error (ss.session) << ")"; + throw NetworkError (s.str ()); + } + + r = ssh_scp_push_directory (sc.scp, _fs->name.c_str(), S_IRWXU); + if (r != SSH_OK) { + stringstream s; + s << "Could not create remote directory " << _fs->name << "(" << ssh_get_error (ss.session) << ")"; + throw NetworkError (s.str ()); + } + + string const dcp_dir = _fs->dir (_fs->name); + + int bytes_to_transfer = 0; + for (filesystem::directory_iterator i = filesystem::directory_iterator (dcp_dir); i != filesystem::directory_iterator(); ++i) { + bytes_to_transfer += filesystem::file_size (*i); + } + + int buffer_size = 64 * 1024; + char buffer[buffer_size]; + int bytes_transferred = 0; + + for (filesystem::directory_iterator i = filesystem::directory_iterator (dcp_dir); i != filesystem::directory_iterator(); ++i) { + + /* Aah, the sweet smell of progress */ +#if BOOST_FILESYSTEM_VERSION == 3 + string const leaf = filesystem::path(*i).leaf().generic_string (); +#else + string const leaf = i->leaf (); +#endif + + set_status ("Copying " + leaf); + + int to_do = filesystem::file_size (*i); + ssh_scp_push_file (sc.scp, leaf.c_str(), to_do, S_IRUSR | S_IWUSR); + + int fd = open (filesystem::path (*i).string().c_str(), O_RDONLY); + if (fd == 0) { + stringstream s; + s << "Could not open " << *i << " to send"; + throw NetworkError (s.str ()); + } + + while (to_do > 0) { + int const t = min (to_do, buffer_size); + read (fd, buffer, t); + r = ssh_scp_write (sc.scp, buffer, t); + if (r != SSH_OK) { + stringstream s; + s << "Could not write to remote file (" << ssh_get_error (ss.session) << ")"; + throw NetworkError (s.str ()); + } + to_do -= t; + bytes_transferred += t; + + set_progress ((double) bytes_transferred / bytes_to_transfer); + } + } + + set_progress (1); + set_status ("OK"); + set_state (FINISHED_OK); + + } catch (std::exception& e) { + + stringstream s; + set_progress (1); + set_state (FINISHED_ERROR); + set_status (e.what ()); + + s << "SCP DCP job failed (" << e.what() << ")"; + _log->log (s.str ()); + + throw; + } +} + +string +SCPDCPJob::status () const +{ + boost::mutex::scoped_lock lm (_status_mutex); + return _status; +} + +void +SCPDCPJob::set_status (string s) +{ + boost::mutex::scoped_lock lm (_status_mutex); + _status = s; +} + diff --git a/src/lib/scp_dcp_job.h b/src/lib/scp_dcp_job.h new file mode 100644 index 000000000..1c795be47 --- /dev/null +++ b/src/lib/scp_dcp_job.h @@ -0,0 +1,40 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/scp_dcp_job.h + * @brief A job to copy DCPs to a SCP-enabled server. + */ + +#include "job.h" + +class SCPDCPJob : public Job +{ +public: + SCPDCPJob (boost::shared_ptr, Log *); + + std::string name () const; + void run (); + std::string status () const; + +private: + void set_status (std::string); + + mutable boost::mutex _status_mutex; + std::string _status; +}; diff --git a/src/lib/screen.cc b/src/lib/screen.cc new file mode 100644 index 000000000..25e44f77d --- /dev/null +++ b/src/lib/screen.cc @@ -0,0 +1,104 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include "screen.h" +#include "format.h" +#include "exceptions.h" + +using namespace std; +using namespace boost; + +Screen::Screen (string n) + : _name (n) +{ + vector f = Format::all (); + for (vector::iterator i = f.begin(); i != f.end(); ++i) { + set_geometry (*i, Position (0, 0), Size (2048, 1080)); + } +} + +void +Screen::set_geometry (Format const * f, Position p, Size s) +{ + _geometries[f] = Geometry (p, s); +} + +Position +Screen::position (Format const * f) const +{ + GeometryMap::const_iterator i = _geometries.find (f); + if (i == _geometries.end ()) { + throw PlayError ("format not found for screen"); + } + + return i->second.position; +} + +Size +Screen::size (Format const * f) const +{ + GeometryMap::const_iterator i = _geometries.find (f); + if (i == _geometries.end ()) { + throw PlayError ("format not found for screen"); + } + + return i->second.size; +} + +string +Screen::as_metadata () const +{ + stringstream s; + s << "\"" << _name << "\""; + + for (GeometryMap::const_iterator i = _geometries.begin(); i != _geometries.end(); ++i) { + s << " " << i->first->as_metadata() + << " " << i->second.position.x << " " << i->second.position.y + << " " << i->second.size.width << " " << i->second.size.height; + } + + return s.str (); +} + +shared_ptr +Screen::create_from_metadata (string v) +{ + vector b = split_at_spaces_considering_quotes (v); + + if (b.size() < 1) { + return shared_ptr (); + } + + shared_ptr s (new Screen (b[0])); + + vector::size_type i = 1; + while (b.size() > i) { + if (b.size() >= (i + 5)) { + s->set_geometry ( + Format::from_metadata (b[i].c_str()), + Position (atoi (b[i+1].c_str()), atoi (b[i+2].c_str())), + Size (atoi (b[i+3].c_str()), atoi (b[i+4].c_str())) + ); + } + i += 5; + } + + return s; +} diff --git a/src/lib/screen.h b/src/lib/screen.h new file mode 100644 index 000000000..663b3c3c4 --- /dev/null +++ b/src/lib/screen.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include +#include "util.h" + +class Format; + +class Screen +{ +public: + Screen (std::string); + + void set_geometry (Format const *, Position, Size); + + std::string name () const { + return _name; + } + + void set_name (std::string n) { + _name = n; + } + + struct Geometry { + Geometry () {} + + Geometry (Position p, Size s) + : position (p) + , size (s) + {} + + Position position; + Size size; + }; + + typedef std::map GeometryMap; + GeometryMap geometries () const { + return _geometries; + } + + Position position (Format const *) const; + Size size (Format const *) const; + + std::string as_metadata () const; + static boost::shared_ptr create_from_metadata (std::string); + +private: + std::string _name; + GeometryMap _geometries; +}; diff --git a/src/lib/server.cc b/src/lib/server.cc new file mode 100644 index 000000000..8a5b5cfca --- /dev/null +++ b/src/lib/server.cc @@ -0,0 +1,58 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/server.cc + * @brief Class to describe a server to which we can send + * encoding work. + */ + +#include +#include +#include +#include +#include "server.h" + +using namespace std; +using namespace boost; + +/** Create a server from a string of metadata returned from as_metadata(). + * @param v Metadata. + * @return Server, or 0. + */ +Server * +Server::create_from_metadata (string v) +{ + vector b; + split (b, v, is_any_of (" ")); + + if (b.size() != 2) { + return 0; + } + + return new Server (b[0], atoi (b[1].c_str ())); +} + +/** @return Description of this server as text */ +string +Server::as_metadata () const +{ + stringstream s; + s << _host_name << " " << _threads; + return s.str (); +} diff --git a/src/lib/server.h b/src/lib/server.h new file mode 100644 index 000000000..f7a0abb80 --- /dev/null +++ b/src/lib/server.h @@ -0,0 +1,60 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/server.h + * @brief Class to describe a server to which we can send + * encoding work. + */ + +#include + +/** @class Server + * @brief Class to describe a server to which we can send encoding work. + */ +class Server +{ +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) + : _host_name (h) + , _threads (t) + {} + + /** @return server's host name or IP address in string form */ + std::string host_name () const { + return _host_name; + } + + /** @return number of threads to use on the server */ + int threads () const { + return _threads; + } + + std::string as_metadata () const; + + static Server * create_from_metadata (std::string v); + +private: + /** server's host name */ + std::string _host_name; + /** number of threads to use on the server */ + int _threads; +}; diff --git a/src/lib/shell_command_job.cc b/src/lib/shell_command_job.cc new file mode 100644 index 000000000..11805bdfe --- /dev/null +++ b/src/lib/shell_command_job.cc @@ -0,0 +1,71 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/shell_command_job.cc + * @brief A job which calls a command via a shell. + */ + +#include +#include "shell_command_job.h" +#include "log.h" + +using namespace std; +using namespace boost; + +/** @param s Our FilmState. + * @param o Options. + * @param l Log. + */ +ShellCommandJob::ShellCommandJob (shared_ptr s, shared_ptr o, Log* l) + : Job (s, o, l) +{ + +} + +/** Run a command via a shell. + * @param c Command to run. + */ +void +ShellCommandJob::command (string c) +{ + _log->log ("Command: " + c, Log::VERBOSE); + + int const r = system (c.c_str()); + if (r < 0) { + set_state (FINISHED_ERROR); + return; + } else if (WEXITSTATUS (r) != 0) { + set_error ("command failed"); + set_state (FINISHED_ERROR); + } else { + set_state (FINISHED_OK); + } +} + +string +ShellCommandJob::status () const +{ + if (!running () && !finished()) { + return "Waiting"; + } else if (running ()) { + return ""; + } + + return Job::status (); +} diff --git a/src/lib/shell_command_job.h b/src/lib/shell_command_job.h new file mode 100644 index 000000000..e5dd58867 --- /dev/null +++ b/src/lib/shell_command_job.h @@ -0,0 +1,46 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/shell_command_job.h + * @brief A job which calls a command via a shell. + */ + +#ifndef DVDOMATIC_SHELL_COMMAND_JOB_H +#define DVDOMATIC_SHELL_COMMAND_JOB_H + +#include "job.h" + +class FilmState; +class Log; + +/** @class ShellCommandJob + * @brief A job which calls a command via a shell. + */ +class ShellCommandJob : public Job +{ +public: + ShellCommandJob (boost::shared_ptr s, boost::shared_ptr o, Log* l); + + std::string status () const; + +protected: + void command (std::string c); +}; + +#endif diff --git a/src/lib/thumbs_job.cc b/src/lib/thumbs_job.cc new file mode 100644 index 000000000..0eb116fd1 --- /dev/null +++ b/src/lib/thumbs_job.cc @@ -0,0 +1,68 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/thumbs_job.cc + * @brief A job to create thumbnails. + */ + +#include +#include "thumbs_job.h" +#include "film_state.h" +#include "tiff_encoder.h" +#include "transcoder.h" +#include "options.h" + +using namespace std; +using namespace boost; + +/** @param s FilmState to use. + * @param o Options. + * @param l A log that we can write to. + */ +ThumbsJob::ThumbsJob (shared_ptr s, shared_ptr o, Log* l) + : Job (s, o, l) +{ + +} + +string +ThumbsJob::name () const +{ + stringstream s; + s << "Update thumbs for " << _fs->name; + return s.str (); +} + +void +ThumbsJob::run () +{ + try { + shared_ptr e (new TIFFEncoder (_fs, _opt, _log)); + Transcoder w (_fs, _opt, this, _log, e); + w.go (); + set_progress (1); + set_state (FINISHED_OK); + + } catch (std::exception& e) { + + set_progress (1); + set_error (e.what ()); + set_state (FINISHED_ERROR); + } +} diff --git a/src/lib/thumbs_job.h b/src/lib/thumbs_job.h new file mode 100644 index 000000000..1dd69a0f9 --- /dev/null +++ b/src/lib/thumbs_job.h @@ -0,0 +1,37 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/thumbs_job.h + * @brief A job to create thumbnails. + */ + +#include "job.h" + +class FilmState; + +/** @class ThumbsJob + * @brief A job to create thumbnails (single frames of the film spaced out throughout the film). + */ +class ThumbsJob : public Job +{ +public: + ThumbsJob (boost::shared_ptr s, boost::shared_ptr o, Log* l); + std::string name () const; + void run (); +}; diff --git a/src/lib/tiff_decoder.cc b/src/lib/tiff_decoder.cc new file mode 100644 index 000000000..6738de49b --- /dev/null +++ b/src/lib/tiff_decoder.cc @@ -0,0 +1,224 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/tiff_decoder.cc + * @brief A decoder which reads a numbered set of TIFF files, one per frame. + */ + +#include +#include +#include +#include +#include +#include +#include +extern "C" { +#include +} +#include "util.h" +#include "tiff_decoder.h" +#include "film_state.h" +#include "exceptions.h" +#include "image.h" +#include "options.h" + +using namespace std; +using namespace boost; + +/** @param fs FilmState of our Film. + * @param o Options. + * @param j Job that we are associated with, or 0. + * @param l Log that we can write to. + * @param minimal true to do the bare minimum of work; just run through the content. Useful for acquiring + * accurate frame counts as quickly as possible. This generates no video or audio output. + * @param ignore_length Ignore the content's claimed length when computing progress. + */ +TIFFDecoder::TIFFDecoder (boost::shared_ptr fs, boost::shared_ptr o, Job* j, Log* l, bool minimal, bool ignore_length) + : Decoder (fs, o, j, l, minimal, ignore_length) +{ + string const dir = _fs->content_path (); + + if (!filesystem::is_directory (dir)) { + throw DecodeError ("TIFF content must be in a directory"); + } + + for (filesystem::directory_iterator i = filesystem::directory_iterator (dir); i != filesystem::directory_iterator(); ++i) { + /* Aah, the sweet smell of progress */ +#if BOOST_FILESYSTEM_VERSION == 3 + string const ext = filesystem::path(*i).extension().string(); + string const l = filesystem::path(*i).leaf().generic_string(); +#else + string const ext = filesystem::path(*i).extension(); + string const l = i->leaf (); +#endif + if (ext == ".tif" || ext == ".tiff") { + _files.push_back (l); + } + } + + _files.sort (); + + _iter = _files.begin (); + +} + +int +TIFFDecoder::length_in_frames () const +{ + return _files.size (); +} + +float +TIFFDecoder::frames_per_second () const +{ + /* We don't know */ + return 0; +} + +Size +TIFFDecoder::native_size () const +{ + if (_files.empty ()) { + throw DecodeError ("no TIFF files found"); + } + + TIFF* t = TIFFOpen (file_path (_files.front()).c_str (), "r"); + if (t == 0) { + throw DecodeError ("could not open TIFF file"); + } + + uint32_t width; + TIFFGetField (t, TIFFTAG_IMAGEWIDTH, &width); + uint32_t height; + TIFFGetField (t, TIFFTAG_IMAGELENGTH, &height); + + return Size (width, height); +} + +int +TIFFDecoder::audio_channels () const +{ + /* No audio */ + return 0; +} + +int +TIFFDecoder::audio_sample_rate () const +{ + return 0; +} + +AVSampleFormat +TIFFDecoder::audio_sample_format () const +{ + return AV_SAMPLE_FMT_NONE; +} + +bool +TIFFDecoder::do_pass () +{ + if (_iter == _files.end ()) { + return true; + } + + TIFF* t = TIFFOpen (file_path (*_iter).c_str (), "r"); + if (t == 0) { + throw DecodeError ("could not open TIFF file"); + } + + uint32_t width; + TIFFGetField (t, TIFFTAG_IMAGEWIDTH, &width); + uint32_t height; + TIFFGetField (t, TIFFTAG_IMAGELENGTH, &height); + + int const num_pixels = width * height; + uint32_t * raster = (uint32_t *) _TIFFmalloc (num_pixels * sizeof (uint32_t)); + if (raster == 0) { + throw DecodeError ("could not allocate memory to decode TIFF file"); + } + + if (TIFFReadRGBAImage (t, width, height, raster, 0) < 0) { + throw DecodeError ("could not read TIFF data"); + } + + RGBFrameImage image (Size (width, height)); + + uint8_t* p = image.data()[0]; + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + uint32_t const i = (height - y - 1) * width + x; + *p++ = raster[i] & 0xff; + *p++ = (raster[i] & 0xff00) >> 8; + *p++ = (raster[i] & 0xff0000) >> 16; + } + } + + _TIFFfree (raster); + TIFFClose (t); + + process_video (image.frame ()); + + ++_iter; + return false; +} + +/** @param file name within our content directory + * @return full path to the file + */ +string +TIFFDecoder::file_path (string f) const +{ + stringstream s; + s << _fs->content_path() << "/" << f; + return _fs->file (s.str ()); +} + +PixelFormat +TIFFDecoder::pixel_format () const +{ + return PIX_FMT_RGB24; +} + +int +TIFFDecoder::time_base_numerator () const +{ + return rint (_fs->frames_per_second);; +} + + +int +TIFFDecoder::time_base_denominator () const +{ + return 1; +} + +int +TIFFDecoder::sample_aspect_ratio_numerator () const +{ + /* XXX */ + return 1; +} + +int +TIFFDecoder::sample_aspect_ratio_denominator () const +{ + /* XXX */ + return 1; +} + diff --git a/src/lib/tiff_decoder.h b/src/lib/tiff_decoder.h new file mode 100644 index 000000000..dbd76f36d --- /dev/null +++ b/src/lib/tiff_decoder.h @@ -0,0 +1,69 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/tiff_decoder.h + * @brief A decoder which reads a numbered set of TIFF files, one per frame. + */ + +#ifndef DVDOMATIC_TIFF_DECODER_H +#define DVDOMATIC_TIFF_DECODER_H + +#include +#include +#include +#include +#include "util.h" +#include "decoder.h" + +class Job; +class FilmState; +class Options; +class Image; +class Log; + +/** @class TIFFDecoder. + * @brief A decoder which reads a numbered set of TIFF files, one per frame. + */ +class TIFFDecoder : public Decoder +{ +public: + TIFFDecoder (boost::shared_ptr, boost::shared_ptr, Job *, Log *, bool, bool); + + /* Methods to query our input video */ + int length_in_frames () const; + float frames_per_second () const; + Size native_size () const; + int audio_channels () const; + int audio_sample_rate () const; + AVSampleFormat audio_sample_format () const; + +private: + bool do_pass (); + PixelFormat pixel_format () const; + int time_base_numerator () const; + int time_base_denominator () const; + int sample_aspect_ratio_numerator () const; + int sample_aspect_ratio_denominator () const; + + std::string file_path (std::string) const; + std::list _files; + std::list::iterator _iter; +}; + +#endif diff --git a/src/lib/tiff_encoder.cc b/src/lib/tiff_encoder.cc new file mode 100644 index 000000000..2cf238006 --- /dev/null +++ b/src/lib/tiff_encoder.cc @@ -0,0 +1,77 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/tiff_encoder.h + * @brief An encoder that writes TIFF files (and does nothing with audio). + */ + +#include +#include +#include +#include +#include +#include +#include +#include "tiff_encoder.h" +#include "film.h" +#include "film_state.h" +#include "options.h" +#include "exceptions.h" +#include "image.h" + +using namespace std; +using namespace boost; + +/** @param s FilmState of the film that we are encoding. + * @param o Options. + * @param l Log. + */ +TIFFEncoder::TIFFEncoder (shared_ptr s, shared_ptr o, Log* l) + : Encoder (s, o, l) +{ + +} + +void +TIFFEncoder::process_video (shared_ptr image, int frame) +{ + shared_ptr scaled = image->scale_and_convert_to_rgb (_opt->out_size, _opt->padding, _fs->scaler); + string tmp_file = _opt->frame_out_path (frame, true); + TIFF* output = TIFFOpen (tmp_file.c_str (), "w"); + if (output == 0) { + throw CreateFileError (tmp_file); + } + + TIFFSetField (output, TIFFTAG_IMAGEWIDTH, _opt->out_size.width); + TIFFSetField (output, TIFFTAG_IMAGELENGTH, _opt->out_size.height); + TIFFSetField (output, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + TIFFSetField (output, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField (output, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField (output, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField (output, TIFFTAG_SAMPLESPERPIXEL, 3); + + if (TIFFWriteEncodedStrip (output, 0, scaled->data()[0], _opt->out_size.width * _opt->out_size.height * 3) == 0) { + throw WriteFileError (tmp_file, 0); + } + + TIFFClose (output); + + boost::filesystem::rename (tmp_file, _opt->frame_out_path (frame, false)); + frame_done (); +} diff --git a/src/lib/tiff_encoder.h b/src/lib/tiff_encoder.h new file mode 100644 index 000000000..ec8e38011 --- /dev/null +++ b/src/lib/tiff_encoder.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/tiff_encoder.h + * @brief An encoder that writes TIFF files (and does nothing with audio). + */ + +#include +#include +#include "encoder.h" + +class FilmState; +class Log; + +/** @class TIFFEncoder + * @brief An encoder that writes TIFF files (and does nothing with audio). + */ +class TIFFEncoder : public Encoder +{ +public: + TIFFEncoder (boost::shared_ptr s, boost::shared_ptr o, Log* l); + + void process_begin () {} + void process_video (boost::shared_ptr, int); + void process_audio (uint8_t *, int) {} + void process_end () {} +}; diff --git a/src/lib/timer.cc b/src/lib/timer.cc new file mode 100644 index 000000000..a45e80dcb --- /dev/null +++ b/src/lib/timer.cc @@ -0,0 +1,89 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/timer.cc + * @brief Some timing classes for debugging and profiling. + */ + +#include +#include +#include "timer.h" +#include "util.h" + +using namespace std; + +/** @param n Name to use when giving output */ +PeriodTimer::PeriodTimer (string n) + : _name (n) +{ + gettimeofday (&_start, 0); +} + +/** Destroy PeriodTimer and output the time elapsed since its construction */ +PeriodTimer::~PeriodTimer () +{ + struct timeval stop; + gettimeofday (&stop, 0); + cout << "T: " << _name << ": " << (seconds (stop) - seconds (_start)) << "\n"; +} + +/** @param n Name to use when giving output. + * @param s Initial state. + */ +StateTimer::StateTimer (string n, string s) + : _name (n) +{ + struct timeval t; + gettimeofday (&t, 0); + _time = seconds (t); + _state = s; +} + +/** @param s New state that the caller is in */ +void +StateTimer::set_state (string s) +{ + double const last = _time; + struct timeval t; + gettimeofday (&t, 0); + _time = seconds (t); + + if (_totals.find (s) == _totals.end ()) { + _totals[s] = 0; + } + + _totals[_state] += _time - last; + _state = s; +} + +/** Destroy StateTimer and generate a summary of the state timings on cout */ +StateTimer::~StateTimer () +{ + if (_state.empty ()) { + return; + } + + + set_state (""); + + cout << _name << ":\n"; + for (map::iterator i = _totals.begin(); i != _totals.end(); ++i) { + cout << "\t" << i->first << " " << i->second << "\n"; + } +} diff --git a/src/lib/timer.h b/src/lib/timer.h new file mode 100644 index 000000000..f509a7492 --- /dev/null +++ b/src/lib/timer.h @@ -0,0 +1,78 @@ +/* + Copyright (C) 2012 Carl Hetherington + Copyright (C) 2000-2007 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/timer.h + * @brief Some timing classes for debugging and profiling. + */ + +#ifndef DVDOMATIC_TIMER_H +#define DVDOMATIC_TIMER_H + +#include +#include +#include + +/** @class PeriodTimer + * @brief A class to allow timing of a period within the caller. + * + * On destruction, it will output the time since its construction. + */ +class PeriodTimer +{ +public: + PeriodTimer (std::string n); + ~PeriodTimer (); + +private: + + /** name to use when giving output */ + std::string _name; + /** time that this class was constructed */ + struct timeval _start; +}; + +/** @class StateTimer + * @brief A class to allow measurement of the amount of time a program + * spends in one of a set of states. + * + * Once constructed, the caller can call set_state() whenever + * its state changes. When StateTimer is destroyed, it will + * output (to cout) a summary of the time spent in each state. + */ +class StateTimer +{ +public: + StateTimer (std::string n, std::string s); + ~StateTimer (); + + void set_state (std::string s); + +private: + /** name to add to the output */ + std::string _name; + /** current state */ + std::string _state; + /** time that _state was entered */ + double _time; + /** time that has been spent in each state so far */ + std::map _totals; +}; + +#endif diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc new file mode 100644 index 000000000..652a18441 --- /dev/null +++ b/src/lib/transcode_job.cc @@ -0,0 +1,100 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/transcode_job.cc + * @brief A job which transcodes from one format to another. + */ + +#include +#include +#include "transcode_job.h" +#include "j2k_wav_encoder.h" +#include "film.h" +#include "format.h" +#include "transcoder.h" +#include "film_state.h" +#include "log.h" +#include "encoder_factory.h" + +using namespace std; +using namespace boost; + +/** @param s FilmState to use. + * @param o Options. + * @param l A log that we can write to. + */ +TranscodeJob::TranscodeJob (shared_ptr s, shared_ptr o, Log* l) + : Job (s, o, l) +{ + +} + +string +TranscodeJob::name () const +{ + stringstream s; + s << "Transcode " << _fs->name; + return s.str (); +} + +void +TranscodeJob::run () +{ + try { + + _log->log ("Transcode job starting"); + + _encoder = encoder_factory (_fs, _opt, _log); + Transcoder w (_fs, _opt, this, _log, _encoder); + w.go (); + set_progress (1); + set_state (FINISHED_OK); + + _log->log ("Transcode job completed successfully"); + + } catch (std::exception& e) { + + stringstream s; + set_progress (1); + set_state (FINISHED_ERROR); + + s << "Transcode job failed (" << e.what() << ")"; + _log->log (s.str ()); + + throw; + + } +} + +string +TranscodeJob::status () const +{ + if (!_encoder) { + return "0%"; + } + + 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."; + return s.str (); +} diff --git a/src/lib/transcode_job.h b/src/lib/transcode_job.h new file mode 100644 index 000000000..aa640f697 --- /dev/null +++ b/src/lib/transcode_job.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/transcode_job.h + * @brief A job which transcodes from one format to another. + */ + +#include +#include "job.h" + +class Encoder; + +/** @class TranscodeJob + * @brief A job which transcodes from one format to another. + */ +class TranscodeJob : public Job +{ +public: + TranscodeJob (boost::shared_ptr s, boost::shared_ptr o, Log* l); + + std::string name () const; + void run (); + std::string status () const; + +private: + boost::shared_ptr _encoder; +}; diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc new file mode 100644 index 000000000..3d71b68f5 --- /dev/null +++ b/src/lib/transcoder.cc @@ -0,0 +1,72 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/transcoder.cc + * @brief A class which takes a FilmState and some Options, then uses those to transcode a Film. + * + * A decoder is selected according to the content type, and the encoder can be specified + * as a parameter to the constructor. + */ + +#include +#include +#include "transcoder.h" +#include "encoder.h" +#include "decoder_factory.h" + +using namespace std; +using namespace boost; + +/** Construct a transcoder using a Decoder that we create and a supplied Encoder. + * @param s FilmState of Film that we are transcoding. + * @param o Options. + * @param j Job that we are running under, or 0. + * @param l Log that we can write to. + * @param e Encoder to use. + */ +Transcoder::Transcoder (shared_ptr s, shared_ptr o, Job* j, Log* l, shared_ptr e) + : _job (j) + , _encoder (e) + , _decoder (decoder_factory (s, o, j, l)) +{ + assert (_encoder); + + _decoder->Video.connect (sigc::mem_fun (*e, &Encoder::process_video)); + _decoder->Audio.connect (sigc::mem_fun (*e, &Encoder::process_audio)); +} + +/** Run the decoder, passing its output to the encoder, until the decoder + * has no more data to present. + */ +void +Transcoder::go () +{ + _encoder->process_begin (); + try { + _decoder->go (); + } catch (...) { + /* process_end() is important as the decoder may have worker + threads that need to be cleaned up. + */ + _encoder->process_end (); + throw; + } + + _encoder->process_end (); +} diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h new file mode 100644 index 000000000..ad6530914 --- /dev/null +++ b/src/lib/transcoder.h @@ -0,0 +1,59 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 "decoder.h" + +/** @file src/transcoder.h + * @brief A class which takes a FilmState and some Options, then uses those to transcode a Film. + * + * A decoder is selected according to the content type, and the encoder can be specified + * as a parameter to the constructor. + */ + +class Film; +class Job; +class Encoder; +class FilmState; + +/** @class Transcoder + * @brief A class which takes a FilmState and some Options, then uses those to transcode a Film. + * + * A decoder is selected according to the content type, and the encoder can be specified + * as a parameter to the constructor. + */ +class Transcoder +{ +public: + Transcoder (boost::shared_ptr s, boost::shared_ptr o, Job* j, Log* l, boost::shared_ptr e); + + void go (); + + /** @return Our decoder */ + boost::shared_ptr decoder () { + return _decoder; + } + +protected: + /** A Job that is running this Transcoder, or 0 */ + Job* _job; + /** The encoder that we will use */ + boost::shared_ptr _encoder; + /** The decoder that we will use */ + boost::shared_ptr _decoder; +}; diff --git a/src/lib/trim_action.h b/src/lib/trim_action.h new file mode 100644 index 000000000..405d31bbc --- /dev/null +++ b/src/lib/trim_action.h @@ -0,0 +1,28 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef DVDOMATIC_TRIM_ACTION_H +#define DVDOMATIC_TRIM_ACTION_H + +enum TrimAction { + CUT, ///< cut everything out after dcp_frames + BLACK_OUT ///< black out after dcp_frames so that the film stays the same length (and audio continues) +}; + +#endif diff --git a/src/lib/util.cc b/src/lib/util.cc new file mode 100644 index 000000000..b8531e26b --- /dev/null +++ b/src/lib/util.cc @@ -0,0 +1,496 @@ +/* + Copyright (C) 2012 Carl Hetherington + Copyright (C) 2000-2007 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/lib/util.cc + * @brief Some utility functions and classes. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +} +#include "util.h" +#include "exceptions.h" +#include "scaler.h" +#include "format.h" +#include "dcp_content_type.h" +#include "filter.h" +#include "screen.h" +#include "film_state.h" +#include "player_manager.h" + +#ifdef DEBUG_HASH +#include +#endif + +using namespace std; +using namespace boost; + +/** Convert some number of seconds to a string representation + * in hours, minutes and seconds. + * + * @param s Seconds. + * @return String of the form H:M:S (where H is hours, M + * is minutes and S is seconds). + */ +string +seconds_to_hms (int s) +{ + int m = s / 60; + s -= (m * 60); + int h = m / 60; + m -= (h * 60); + + stringstream hms; + hms << h << ":"; + hms.width (2); + hms << setfill ('0') << m << ":"; + hms.width (2); + hms << setfill ('0') << s; + + return hms.str (); +} + +/** @param s Number of seconds. + * @return String containing an approximate description of s (e.g. "about 2 hours") + */ +string +seconds_to_approximate_hms (int s) +{ + int m = s / 60; + s -= (m * 60); + int h = m / 60; + m -= (h * 60); + + stringstream ap; + + if (h > 0) { + if (m > 30) { + ap << (h + 1) << " hours"; + } else { + if (h == 1) { + ap << "1 hour"; + } else { + ap << h << " hours"; + } + } + } else if (m > 0) { + if (m == 1) { + ap << "1 minute"; + } else { + ap << m << " minutes"; + } + } else { + ap << s << " seconds"; + } + + return ap.str (); +} + +/** @param l Mangled C++ identifier. + * @return Demangled version. + */ +static string +demangle (string l) +{ + string::size_type const b = l.find_first_of ("("); + if (b == string::npos) { + return l; + } + + string::size_type const p = l.find_last_of ("+"); + if (p == string::npos) { + return l; + } + + if ((p - b) <= 1) { + return l; + } + + string const fn = l.substr (b + 1, p - b - 1); + + int status; + try { + + char* realname = abi::__cxa_demangle (fn.c_str(), 0, 0, &status); + string d (realname); + free (realname); + return d; + + } catch (std::exception) { + + } + + return l; +} + +/** Write a stacktrace to an ostream. + * @param out Stream to write to. + * @param levels Number of levels to go up the call stack. + */ +void +stacktrace (ostream& out, int levels) +{ + void *array[200]; + size_t size; + char **strings; + size_t i; + + size = backtrace (array, 200); + strings = backtrace_symbols (array, size); + + if (strings) { + for (i = 0; i < size && (levels == 0 || i < size_t(levels)); i++) { + out << " " << demangle (strings[i]) << endl; + } + + free (strings); + } +} + +/** @param s Sample format. + * @return String representation. + */ +string +audio_sample_format_to_string (AVSampleFormat s) +{ + /* Our sample format handling is not exactly complete */ + + switch (s) { + case AV_SAMPLE_FMT_S16: + return "S16"; + default: + break; + } + + return "Unknown"; +} + +/** @param s String representation of a sample format, as returned from audio_sample_format_to_string(). + * @return Sample format. + */ +AVSampleFormat +audio_sample_format_from_string (string s) +{ + if (s == "S16") { + return AV_SAMPLE_FMT_S16; + } + + return AV_SAMPLE_FMT_NONE; +} + +/** @return Version of OpenDCP that is on the path (and hence that we will use) */ +static string +opendcp_version () +{ + FILE* f = popen ("opendcp_xml", "r"); + if (f == 0) { + throw EncodeError ("could not run opendcp_xml to check version"); + } + + string version = "unknown"; + + while (!feof (f)) { + char* buf = 0; + size_t n = 0; + ssize_t const r = getline (&buf, &n, f); + if (r > 0) { + string s (buf); + vector b; + split (b, s, is_any_of (" ")); + if (b.size() >= 3 && b[0] == "OpenDCP" && b[1] == "version") { + version = b[2]; + } + free (buf); + } + } + + pclose (f); + + return version; +} + +/** @return Version of vobcopy that is on the path (and hence that we will use) */ +static string +vobcopy_version () +{ + FILE* f = popen ("vobcopy -V 2>&1", "r"); + if (f == 0) { + throw EncodeError ("could not run vobcopy to check version"); + } + + string version = "unknown"; + + while (!feof (f)) { + char* buf = 0; + size_t n = 0; + ssize_t const r = getline (&buf, &n, f); + if (r > 0) { + string s (buf); + vector b; + split (b, s, is_any_of (" ")); + if (b.size() >= 2 && b[0] == "Vobcopy") { + version = b[1]; + } + free (buf); + } + } + + pclose (f); + + return version; +} + +/** @param v Version as used by FFmpeg. + * @return A string representation of v. + */ +static string +ffmpeg_version_to_string (int v) +{ + stringstream s; + s << ((v & 0xff0000) >> 16) << "." << ((v & 0xff00) >> 8) << "." << (v & 0xff); + return s.str (); +} + +/** Return a user-readable string summarising the versions of our dependencies */ +string +dependency_version_summary () +{ + stringstream s; + s << "libopenjpeg " << opj_version () << ", " + << "opendcp " << opendcp_version () << ", " + << "vobcopy " << vobcopy_version() << ", " + << "libswresample " << ffmpeg_version_to_string (swresample_version()) << ", " + << "libavcodec " << ffmpeg_version_to_string (avcodec_version()) << ", " + << "libavfilter " << ffmpeg_version_to_string (avfilter_version()) << ", " + << "libavformat " << ffmpeg_version_to_string (avformat_version()) << ", " + << "libavutil " << ffmpeg_version_to_string (avutil_version()) << ", " + << "libpostproc " << ffmpeg_version_to_string (postproc_version()) << ", " + << "libswscale " << ffmpeg_version_to_string (swscale_version()) << ", " + << MagickVersion << ", " + << "libssh " << ssh_version (0); + + return s.str (); +} + +/** Write some data to a socket. + * @param fd Socket file descriptor. + * @param data Data. + * @param size Amount to write, in bytes. + */ +void +socket_write (int fd, uint8_t const * data, int size) +{ + uint8_t const * p = data; + while (size) { + int const n = send (fd, p, size, MSG_NOSIGNAL); + if (n < 0) { + stringstream s; + s << "could not write (" << strerror (errno) << ")"; + throw NetworkError (s.str ()); + } + + size -= n; + p += n; + } +} + +double +seconds (struct timeval t) +{ + return t.tv_sec + (double (t.tv_usec) / 1e6); +} + +/** @param fd File descriptor to read from */ +SocketReader::SocketReader (int fd) + : _fd (fd) + , _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 = ::read (_fd, 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 = ::read (_fd, _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); +} + +void +sigchld_handler (int, siginfo_t* info, void *) +{ + PlayerManager::instance()->child_exited (info->si_pid); +} + +/** Call the required functions to set up DVD-o-matic's static arrays, etc. */ +void +dvdomatic_setup () +{ + Format::setup_formats (); + DCPContentType::setup_dcp_content_types (); + Scaler::setup_scalers (); + Filter::setup_filters (); + + struct sigaction sa; + sa.sa_flags = SA_SIGINFO; + sigemptyset (&sa.sa_mask); + sa.sa_sigaction = sigchld_handler; + sigaction (SIGCHLD, &sa, 0); +} + +string +crop_string (Position start, Size size) +{ + stringstream s; + s << "crop=" << size.width << ":" << size.height << ":" << start.x << ":" << start.y; + return s.str (); +} + +vector +split_at_spaces_considering_quotes (string s) +{ + vector out; + bool in_quotes = false; + string c; + for (string::size_type i = 0; i < s.length(); ++i) { + if (s[i] == ' ' && !in_quotes) { + out.push_back (c); + c = ""; + } else if (s[i] == '"') { + in_quotes = !in_quotes; + } else { + c += s[i]; + } + } + + out.push_back (c); + return out; +} + +#ifdef DEBUG_HASH +void +md5_data (string title, 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); + + printf ("%s [%d]: ", title.c_str (), size); + for (int i = 0; i < int (mhash_get_block_size (MHASH_MD5)); ++i) { + printf ("%.2x", hash[i]); + } + printf ("\n"); +} +#endif + diff --git a/src/lib/util.h b/src/lib/util.h new file mode 100644 index 000000000..3901e7ec6 --- /dev/null +++ b/src/lib/util.h @@ -0,0 +1,121 @@ +/* + Copyright (C) 2012 Carl Hetherington + Copyright (C) 2000-2007 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file src/util.h + * @brief Some utility functions and classes. + */ + +#ifndef DVDOMATIC_UTIL_H +#define DVDOMATIC_UTIL_H + +#include +#include +#include +extern "C" { +#include +#include +} + +class Scaler; + +extern std::string seconds_to_hms (int); +extern std::string seconds_to_approximate_hms (int); +extern void stacktrace (std::ostream &, int); +extern std::string audio_sample_format_to_string (AVSampleFormat); +extern AVSampleFormat audio_sample_format_from_string (std::string); +extern std::string dependency_version_summary (); +extern void socket_write (int, uint8_t const *, int); +extern double seconds (struct timeval); +extern void dvdomatic_setup (); +extern std::vector split_at_spaces_considering_quotes (std::string); + +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. + */ +class SocketReader +{ +public: + SocketReader (int); + + void read_definite_and_consume (uint8_t *, int); + void read_indefinite (uint8_t *, int); + void consume (int); + +private: + /** file descriptor we are reading from */ + int _fd; + /** 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 +{ + /** Construct a zero Size */ + Size () + : width (0) + , height (0) + {} + + /** @param w Width. + * @param h Height. + */ + Size (int w, int h) + : width (w) + , height (h) + {} + + /** width */ + int width; + /** height */ + int height; +}; + +struct Position +{ + Position () + : x (0) + , y (0) + {} + + Position (int x_, int y_) + : x (x_) + , y (y_) + {} + + int x; + int y; +}; + +extern std::string crop_string (Position, Size); + +#endif diff --git a/src/lib/wscript b/src/lib/wscript new file mode 100644 index 000000000..ec5a723e4 --- /dev/null +++ b/src/lib/wscript @@ -0,0 +1,52 @@ +def build(bld): + obj = bld(features = 'cxx cxxshlib') + obj.name = 'libdvdomatic' + obj.export_includes = ['.'] + obj.uselib = 'AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE SNDFILE BOOST_FILESYSTEM BOOST_THREAD OPENJPEG POSTPROC TIFF SIGC++ MAGICK SSH' + if bld.env.DEBUG_HASH: + obj.uselib += ' MHASH' + obj.source = """ + ab_transcode_job.cc + ab_transcoder.cc + config.cc + copy_from_dvd_job.cc + dcp_content_type.cc + dcp_video_frame.cc + decoder.cc + decoder_factory.cc + delay_line.cc + dvd.cc + encoder.cc + encoder_factory.cc + examine_content_job.cc + ffmpeg_decoder.cc + film.cc + film_state.cc + filter.cc + format.cc + image.cc + imagemagick_decoder.cc + j2k_still_encoder.cc + j2k_wav_encoder.cc + job.cc + job_manager.cc + log.cc + lut.cc + make_dcp_job.cc + make_mxf_job.cc + player.cc + player_manager.cc + scaler.cc + screen.cc + server.cc + scp_dcp_job.cc + shell_command_job.cc + thumbs_job.cc + tiff_decoder.cc + tiff_encoder.cc + timer.cc + transcode_job.cc + transcoder.cc + util.cc + """ + obj.target = 'dvdomatic' diff --git a/src/tools/alignomatic.cc b/src/tools/alignomatic.cc new file mode 100644 index 000000000..9cab6c430 --- /dev/null +++ b/src/tools/alignomatic.cc @@ -0,0 +1,317 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include "lib/util.h" +#include "lib/config.h" +#include "lib/screen.h" +#include "lib/format.h" +#include "gtk/gtk_util.h" +#include "gtk/alignment.h" + +using namespace std; +using namespace boost; + +static Alignment* alignment = 0; +static Gtk::ComboBoxText* format_combo = 0; +static Format const * format = 0; +static Gtk::ComboBoxText* screen_combo = 0; +static shared_ptr screen; +static Gtk::Button* add_screen = 0; +static Gtk::Entry* screen_name = 0; +static Gtk::SpinButton* x_position = 0; +static Gtk::SpinButton* y_position = 0; +static Gtk::SpinButton* width = 0; +static Gtk::Button* calculate_width = 0; +static Gtk::SpinButton* height = 0; +static Gtk::Button* calculate_height = 0; +static Gtk::Button* save = 0; +static bool screen_dirty = false; + +enum GeometryPart { + GEOMETRY_PART_X, + GEOMETRY_PART_Y, + GEOMETRY_PART_WIDTH, + GEOMETRY_PART_HEIGHT +}; + +void +update_sensitivity () +{ + bool const dims = format && screen; + + x_position->set_sensitive (dims); + y_position->set_sensitive (dims); + width->set_sensitive (dims); + calculate_width->set_sensitive (dims); + height->set_sensitive (dims); + calculate_height->set_sensitive (dims); + + screen_name->set_sensitive (screen); + save->set_sensitive (screen_dirty); +} + +void +update_alignment () +{ + if (!screen || !format) { + return; + } + + delete alignment; + alignment = new Alignment (screen->position (format), screen->size (format)); + alignment->set_text_line (0, screen->name ()); + alignment->set_text_line (1, format->name ()); +} + +void +update_entries () +{ + if (!screen || !format) { + return; + } + + Position p = screen->position (format); + x_position->set_value (p.x); + y_position->set_value (p.y); + Size s = screen->size (format); + width->set_value (s.width); + height->set_value (s.height); + + update_sensitivity (); +} + +void +screen_changed () +{ + if (screen_combo->get_active_row_number() < 0) { + return; + } + + vector > screens = Config::instance()->screens (); + + if (screens[screen_combo->get_active_row_number()] == screen) { + return; + } + + screen = screens[screen_combo->get_active_row_number()]; + + update_entries (); + update_alignment (); + + screen_name->set_text (screen->name ()); + + screen_dirty = false; + update_sensitivity (); +} + +void +format_changed () +{ + vector formats = Format::all (); + + if (formats[format_combo->get_active_row_number()] == format) { + return; + } + + format = formats[format_combo->get_active_row_number()]; + + update_entries (); + update_alignment (); + update_sensitivity (); +} + +void +geometry_changed (GeometryPart p) +{ + if (p == GEOMETRY_PART_X && screen->position(format).x == x_position->get_value_as_int()) { + return; + } + + if (p == GEOMETRY_PART_Y && screen->position(format).y == y_position->get_value_as_int()) { + return; + } + + if (p == GEOMETRY_PART_WIDTH && screen->size(format).width == width->get_value_as_int()) { + return; + } + + if (p == GEOMETRY_PART_HEIGHT && screen->size(format).height == height->get_value_as_int()) { + return; + } + + screen->set_geometry ( + format, + Position (x_position->get_value_as_int(), y_position->get_value_as_int()), + Size (width->get_value_as_int(), height->get_value_as_int()) + ); + + update_alignment (); + + screen_dirty = true; + update_sensitivity (); +} + +void +save_clicked () +{ + Config::instance()->write (); + screen_dirty = false; + update_sensitivity (); +} + +void +calculate_width_clicked () +{ + width->set_value (height->get_value_as_int() * format->ratio_as_float ()); +} + +void +calculate_height_clicked () +{ + height->set_value (width->get_value_as_int() / format->ratio_as_float ()); +} + +void +update_screen_combo () +{ + screen_combo->clear (); + + vector > screens = Config::instance()->screens (); + for (vector >::iterator i = screens.begin(); i != screens.end(); ++i) { + screen_combo->append_text ((*i)->name ()); + } +} + +void +screen_name_changed () +{ + screen->set_name (screen_name->get_text ()); + + int const r = screen_combo->get_active_row_number (); + update_screen_combo (); + screen_combo->set_active (r); + + screen_dirty = true; + update_sensitivity (); +} + +void +add_screen_clicked () +{ + shared_ptr s (new Screen ("New Screen")); + vector > screens = Config::instance()->screens (); + screens.push_back (s); + Config::instance()->set_screens (screens); + update_screen_combo (); + screen_combo->set_active (screens.size() - 1); +} + +int +main (int argc, char* argv[]) +{ + dvdomatic_setup (); + + Gtk::Main kit (argc, argv); + + Gtk::Dialog dialog ("Align-o-matic"); + + screen_combo = Gtk::manage (new Gtk::ComboBoxText); + update_screen_combo (); + screen_combo->signal_changed().connect (sigc::ptr_fun (&screen_changed)); + + add_screen = Gtk::manage (new Gtk::Button ("Add")); + add_screen->signal_clicked().connect (sigc::ptr_fun (&add_screen_clicked)); + + screen_name = Gtk::manage (new Gtk::Entry ()); + screen_name->signal_changed().connect (sigc::ptr_fun (&screen_name_changed)); + + format_combo = Gtk::manage (new Gtk::ComboBoxText); + vector formats = Format::all (); + for (vector::iterator i = formats.begin(); i != formats.end(); ++i) { + format_combo->append_text ((*i)->name ()); + } + + format_combo->signal_changed().connect (sigc::ptr_fun (&format_changed)); + + save = Gtk::manage (new Gtk::Button ("Save")); + save->signal_clicked().connect (sigc::ptr_fun (&save_clicked)); + + x_position = Gtk::manage (new Gtk::SpinButton ()); + x_position->signal_value_changed().connect (sigc::bind (ptr_fun (&geometry_changed), GEOMETRY_PART_X)); + x_position->set_range (0, 2048); + x_position->set_increments (1, 16); + y_position = Gtk::manage (new Gtk::SpinButton ()); + y_position->signal_value_changed().connect (sigc::bind (sigc::ptr_fun (&geometry_changed), GEOMETRY_PART_Y)); + y_position->set_range (0, 1080); + y_position->set_increments (1, 16); + width = Gtk::manage (new Gtk::SpinButton ()); + width->signal_value_changed().connect (sigc::bind (sigc::ptr_fun (&geometry_changed), GEOMETRY_PART_WIDTH)); + width->set_range (0, 2048); + width->set_increments (1, 16); + height = Gtk::manage (new Gtk::SpinButton ()); + height->signal_value_changed().connect (sigc::bind (sigc::ptr_fun (&geometry_changed), GEOMETRY_PART_HEIGHT)); + height->set_range (0, 1080); + height->set_increments (1, 16); + + calculate_width = Gtk::manage (new Gtk::Button ("Calculate")); + calculate_width->signal_clicked().connect (sigc::ptr_fun (&calculate_width_clicked)); + calculate_height = Gtk::manage (new Gtk::Button ("Calculate")); + calculate_height->signal_clicked().connect (sigc::ptr_fun (&calculate_height_clicked)); + + Gtk::Table table; + table.set_row_spacings (12); + table.set_col_spacings (12); + table.set_border_width (12); + + int n = 0; + table.attach (left_aligned_label ("Screen"), 0, 1, n, n + 1); + table.attach (*screen_combo, 1, 2, n, n + 1); + table.attach (*add_screen, 2, 3, n, n + 1); + ++n; + table.attach (left_aligned_label ("Screen Name"), 0, 1, n, n + 1); + table.attach (*screen_name, 1, 2, n, n + 1); + ++n; + table.attach (left_aligned_label ("Format"), 0, 1, n, n + 1); + table.attach (*format_combo, 1, 2, n, n + 1); + ++n; + table.attach (left_aligned_label ("x"), 0, 1, n, n + 1); + table.attach (*x_position, 1, 2, n, n + 1); + ++n; + table.attach (left_aligned_label ("y"), 0, 1, n, n + 1); + table.attach (*y_position, 1, 2, n, n + 1); + ++n; + table.attach (left_aligned_label ("Width"), 0, 1, n, n + 1); + table.attach (*width, 1, 2, n, n + 1); + table.attach (*calculate_width, 2, 3, n, n + 1); + ++n; + table.attach (left_aligned_label ("Height"), 0, 1, n, n + 1); + table.attach (*height, 1, 2, n, n + 1); + table.attach (*calculate_height, 2, 3, n, n + 1); + ++n; + + dialog.get_vbox()->pack_start (table, false, false); + dialog.add_action_widget (*save, 0); + update_sensitivity (); + dialog.show_all (); + + Gtk::Main::run (dialog); + + return 0; +} diff --git a/src/tools/dvdomatic.cc b/src/tools/dvdomatic.cc new file mode 100644 index 000000000..803eec3c4 --- /dev/null +++ b/src/tools/dvdomatic.cc @@ -0,0 +1,328 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include "gtk/film_viewer.h" +#include "gtk/film_editor.h" +#include "gtk/film_player.h" +#include "gtk/job_manager_view.h" +#include "gtk/config_dialog.h" +#include "gtk/gpl.h" +#include "gtk/job_wrapper.h" +#include "lib/film.h" +#include "lib/format.h" +#include "lib/config.h" +#include "lib/filter.h" +#include "lib/util.h" +#include "lib/scaler.h" + +using namespace std; +using namespace boost; + +static Gtk::Window* window = 0; +static FilmViewer* film_viewer = 0; +static FilmEditor* film_editor = 0; +static FilmPlayer* film_player = 0; +static Film* film = 0; + +class FilmChangedDialog : public Gtk::MessageDialog +{ +public: + FilmChangedDialog () + : Gtk::MessageDialog ("", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE) + { + stringstream s; + s << "Save changes to film \"" << film->name() << "\" before closing?"; + set_message (s.str ()); + add_button ("Close _without saving", Gtk::RESPONSE_NO); + add_button ("_Cancel", Gtk::RESPONSE_CANCEL); + add_button ("_Save", Gtk::RESPONSE_YES); + } +}; + +bool +maybe_save_then_delete_film () +{ + if (!film) { + return false; + } + + if (film->dirty ()) { + FilmChangedDialog d; + switch (d.run ()) { + case Gtk::RESPONSE_CANCEL: + return true; + case Gtk::RESPONSE_YES: + film->write_metadata (); + break; + case Gtk::RESPONSE_NO: + return false; + } + } + + delete film; + film = 0; + return false; +} + +void +file_new () +{ + Gtk::FileChooserDialog c (*window, "New Film", Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER); + c.add_button ("_Cancel", Gtk::RESPONSE_CANCEL); + c.add_button ("C_reate", Gtk::RESPONSE_ACCEPT); + + int const r = c.run (); + if (r == Gtk::RESPONSE_ACCEPT) { + if (maybe_save_then_delete_film ()) { + return; + } + film = new Film (c.get_filename ()); +#if BOOST_FILESYSTEM_VERSION == 3 + film->set_name (filesystem::path (c.get_filename().c_str()).filename().generic_string()); +#else + film->set_name (filesystem::path (c.get_filename().c_str()).filename()); +#endif + film_viewer->set_film (film); + film_editor->set_film (film); + } +} + +void +file_open () +{ + Gtk::FileChooserDialog c (*window, "Open Film", Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER); + c.add_button ("_Cancel", Gtk::RESPONSE_CANCEL); + c.add_button ("_Open", Gtk::RESPONSE_ACCEPT); + + int const r = c.run (); + if (r == Gtk::RESPONSE_ACCEPT) { + if (maybe_save_then_delete_film ()) { + return; + } + film = new Film (c.get_filename ()); + film_viewer->set_film (film); + film_editor->set_film (film); + } +} + +void +file_save () +{ + film->write_metadata (); +} + +void +file_quit () +{ + if (maybe_save_then_delete_film ()) { + return; + } + + Gtk::Main::quit (); +} + +void +edit_preferences () +{ + ConfigDialog d; + d.run (); + Config::instance()->write (); +} + +void +jobs_make_dcp () +{ + JobWrapper::make_dcp (film, true); +} + +void +jobs_make_dcp_from_existing_transcode () +{ + JobWrapper::make_dcp (film, false); +} + +void +jobs_copy_from_dvd () +{ + film->copy_from_dvd (); +} + +void +jobs_send_dcp_to_tms () +{ + film->send_dcp_to_tms (); +} + +void +jobs_examine_content () +{ + film->examine_content (); +} + +void +help_about () +{ + Gtk::AboutDialog d; + d.set_name ("DVD-o-matic"); + d.set_version (DVDOMATIC_VERSION); + + stringstream s; + s << "DCP generation from arbitrary formats\n\n" + << "Using " << dependency_version_summary() << "\n"; + d.set_comments (s.str ()); + + vector authors; + authors.push_back ("Carl Hetherington"); + authors.push_back ("Terrence Meiczinger"); + authors.push_back ("Paul Davis"); + d.set_authors (authors); + + d.set_website ("http://carlh.net/software/dvdomatic"); + d.set_license (gpl); + + d.run (); +} + +void +setup_menu (Gtk::MenuBar& m) +{ + using namespace Gtk::Menu_Helpers; + + Gtk::Menu* file = manage (new Gtk::Menu); + MenuList& file_items (file->items ()); + file_items.push_back (MenuElem ("_New...", sigc::ptr_fun (file_new))); + file_items.push_back (MenuElem ("_Open...", sigc::ptr_fun (file_open))); + file_items.push_back (SeparatorElem ()); + file_items.push_back (MenuElem ("_Save", sigc::ptr_fun (file_save))); + file_items.push_back (SeparatorElem ()); + file_items.push_back (MenuElem ("_Quit", sigc::ptr_fun (file_quit))); + + Gtk::Menu* edit = manage (new Gtk::Menu); + MenuList& edit_items (edit->items ()); + edit_items.push_back (MenuElem ("_Preferences...", sigc::ptr_fun (edit_preferences))); + + Gtk::Menu* jobs = manage (new Gtk::Menu); + MenuList& jobs_items (jobs->items ()); + jobs_items.push_back (MenuElem ("_Make DCP", sigc::ptr_fun (jobs_make_dcp))); + jobs_items.push_back (MenuElem ("_Send DCP to TMS", sigc::ptr_fun (jobs_send_dcp_to_tms))); + jobs_items.push_back (MenuElem ("Copy from _DVD", sigc::ptr_fun (jobs_copy_from_dvd))); + jobs_items.push_back (MenuElem ("_Examine content", sigc::ptr_fun (jobs_examine_content))); + jobs_items.push_back (MenuElem ("Make DCP from _existing transcode", sigc::ptr_fun (jobs_make_dcp_from_existing_transcode))); + + Gtk::Menu* help = manage (new Gtk::Menu); + MenuList& help_items (help->items ()); + help_items.push_back (MenuElem ("_About", sigc::ptr_fun (help_about))); + + MenuList& items (m.items ()); + items.push_back (MenuElem ("_File", *file)); + items.push_back (MenuElem ("_Edit", *edit)); + items.push_back (MenuElem ("_Jobs", *jobs)); + items.push_back (MenuElem ("_Help", *help)); +} + +bool +window_closed (GdkEventAny *) +{ + if (maybe_save_then_delete_film ()) { + return true; + } + + return false; +} + +void +file_changed (string f) +{ + stringstream s; + s << "DVD-o-matic"; + if (!f.empty ()) { + s << " — " << f; + } + + window->set_title (s.str ()); +} + +int +main (int argc, char* argv[]) +{ + dvdomatic_setup (); + + Gtk::Main kit (argc, argv); + + if (argc == 2 && boost::filesystem::is_directory (argv[1])) { + film = new Film (argv[1]); + } + + window = new Gtk::Window (); + window->signal_delete_event().connect (sigc::ptr_fun (window_closed)); + + film_viewer = new FilmViewer (film); + film_editor = new FilmEditor (film); + film_player = new FilmPlayer (film); + JobManagerView jobs_view; + + window->set_title ("DVD-o-matic"); + + Gtk::VBox vbox; + + Gtk::MenuBar menu_bar; + vbox.pack_start (menu_bar, false, false); + setup_menu (menu_bar); + + Gtk::HBox hbox; + hbox.set_spacing (12); + + Gtk::VBox left_vbox; + left_vbox.set_spacing (12); + left_vbox.pack_start (film_editor->widget (), false, false); +// left_vbox.pack_start (film_player->widget (), false, false); + hbox.pack_start (left_vbox, false, false); + + Gtk::VBox right_vbox; + right_vbox.pack_start (film_viewer->widget (), true, true); + right_vbox.pack_start (jobs_view.widget(), false, false); + hbox.pack_start (right_vbox, true, true); + + vbox.pack_start (hbox, true, true); + + window->add (vbox); + window->show_all (); + + /* XXX: calling these here is a bit of a hack */ + film_editor->setup_visibility (); + film_player->setup_visibility (); + film_viewer->setup_visibility (); + + film_editor->FileChanged.connect (ptr_fun (file_changed)); + if (film) { + file_changed (film->directory ()); + } else { + file_changed (""); + } + + /* XXX this should be in JobManagerView, shouldn't it? */ + Glib::signal_timeout().connect (sigc::bind_return (sigc::mem_fun (jobs_view, &JobManagerView::update), true), 1000); + + window->maximize (); + Gtk::Main::run (*window); + + return 0; +} diff --git a/src/tools/fixlengths.cc b/src/tools/fixlengths.cc new file mode 100644 index 000000000..52696cd8b --- /dev/null +++ b/src/tools/fixlengths.cc @@ -0,0 +1,209 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include +#include +#include +#include +#include "lib/film.h" + +using namespace std; +using namespace boost; + +void +help (string n) +{ + cerr << "Syntax: " << n << " [--help] [--chop-audio-start] [--chop-audio-end] --film \n"; +} + +void +sox (vector const & audio_files, string const & process) +{ + for (vector::const_iterator i = audio_files.begin(); i != audio_files.end(); ++i) { + stringstream cmd; + cmd << "sox \"" << *i << "\" -t wav \"" << *i << ".tmp\" " << process; + cout << "> " << cmd.str() << "\n"; + int r = ::system (cmd.str().c_str()); + if (r == -1 || WEXITSTATUS (r) != 0) { + cerr << "fixlengths: call to sox failed.\n"; + exit (EXIT_FAILURE); + } + filesystem::rename (*i + ".tmp", *i); + } +} + +int main (int argc, char* argv[]) +{ + string film_dir; + bool chop_audio_start = false; + bool chop_audio_end = false; + bool pad_audio_end = false; + + while (1) { + static struct option long_options[] = { + { "help", no_argument, 0, 'h' }, + { "chop-audio-start", no_argument, 0, 'c' }, + { "chop-audio-end", no_argument, 0, 'd' }, + { "pad-audio-end", no_argument, 0, 'p' }, + { "film", required_argument, 0, 'f' }, + { 0, 0, 0, 0 } + }; + + int option_index = 0; + int c = getopt_long (argc, argv, "hcf:", long_options, &option_index); + + if (c == -1) { + break; + } + + switch (c) { + case 'h': + help (argv[0]); + exit (EXIT_SUCCESS); + case 'c': + chop_audio_start = true; + break; + case 'd': + chop_audio_end = true; + break; + case 'p': + pad_audio_end = true; + break; + case 'f': + film_dir = optarg; + break; + } + } + + if (film_dir.empty ()) { + help (argv[0]); + exit (EXIT_FAILURE); + } + + dvdomatic_setup (); + + Film* film = 0; + try { + film = new Film (film_dir, true); + } catch (std::exception& e) { + cerr << argv[0] << ": error reading film `" << film_dir << "' (" << e.what() << ")\n"; + exit (EXIT_FAILURE); + } + + /* XXX: hack */ + int video_frames = 0; + for (filesystem::directory_iterator i = filesystem::directory_iterator (film->j2k_dir()); i != filesystem::directory_iterator(); ++i) { + ++video_frames; + } + + float const video_length = video_frames / film->frames_per_second(); + cout << "Video length: " << video_length << " (" << video_frames << " frames at " << film->frames_per_second() << " frames per second).\n"; + + vector audio_files = film->audio_files (); + if (audio_files.empty ()) { + cerr << argv[0] << ": film has no audio files.\n"; + exit (EXIT_FAILURE); + } + + sf_count_t audio_frames = 0; + int audio_sample_rate = 0; + + for (vector::iterator i = audio_files.begin(); i != audio_files.end(); ++i) { + SF_INFO info; + info.format = 0; + SNDFILE* sf = sf_open (i->c_str(), SFM_READ, &info); + if (sf == 0) { + cerr << argv[0] << ": could not open WAV file for reading.\n"; + exit (EXIT_FAILURE); + } + + if (audio_frames == 0) { + audio_frames = info.frames; + } + + if (audio_sample_rate == 0) { + audio_sample_rate = info.samplerate; + } + + if (audio_frames != info.frames) { + cerr << argv[0] << ": audio files have differing lengths.\n"; + exit (EXIT_FAILURE); + } + + if (audio_sample_rate != info.samplerate) { + cerr << argv[0] << ": audio files have differing sample rates.\n"; + exit (EXIT_FAILURE); + } + + sf_close (sf); + } + + float const audio_length = audio_frames / float (audio_sample_rate); + + cout << "Audio length: " << audio_length << " (" << audio_frames << " frames at " << audio_sample_rate << " frames per second).\n"; + + cout << "\n"; + + if (audio_length > video_length) { + cout << setprecision (3); + cout << "Audio " << (audio_length - video_length) << "s longer than video.\n"; + + float const delta = audio_length - video_length; + int const delta_samples = delta * audio_sample_rate; + + if (chop_audio_start) { + cout << "Chopping difference off the start of the audio.\n"; + + stringstream s; + s << "trim " << delta_samples << "s"; + sox (audio_files, s.str ()); + + } else if (chop_audio_end) { + cout << "Chopping difference off the end of the audio.\n"; + + stringstream s; + s << "reverse trim " << delta_samples << "s reverse"; + sox (audio_files, s.str ()); + + } else { + cout << "Re-run with --chop-audio-start or --chop-audio-end, perhaps.\n"; + } + + } else if (audio_length < video_length) { + cout << setprecision (3); + cout << "Audio " << (video_length - audio_length) << "s shorter than video.\n"; + + if (pad_audio_end) { + + float const delta = video_length - audio_length; + int const delta_samples = delta * audio_sample_rate; + stringstream s; + s << "pad 0 " << delta_samples << "s"; + sox (audio_files, s.str ()); + + } else { + cout << "Re-run with --pad-audio-end, perhaps.\n"; + } + } + + + return 0; +} diff --git a/src/tools/makedcp.cc b/src/tools/makedcp.cc new file mode 100644 index 000000000..76cda8202 --- /dev/null +++ b/src/tools/makedcp.cc @@ -0,0 +1,138 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include +#include "format.h" +#include "film.h" +#include "filter.h" +#include "transcode_job.h" +#include "make_mxf_job.h" +#include "make_dcp_job.h" +#include "job_manager.h" +#include "ab_transcode_job.h" +#include "util.h" +#include "scaler.h" + +using namespace std; +using namespace boost; + +static void +help (string n) +{ + cerr << "Syntax: " << n << " [--help] [--deps] [--film ]\n"; +} + +int +main (int argc, char* argv[]) +{ + string film_dir; + + while (1) { + static struct option long_options[] = { + { "help", no_argument, 0, 'h'}, + { "deps", no_argument, 0, 'd'}, + { "film", required_argument, 0, 'f'}, + { 0, 0, 0, 0 } + }; + + int option_index = 0; + int c = getopt_long (argc, argv, "hdf:", long_options, &option_index); + + if (c == -1) { + break; + } + + switch (c) { + case 'h': + help (argv[0]); + exit (EXIT_SUCCESS); + case 'd': + cout << dependency_version_summary () << "\n"; + exit (EXIT_SUCCESS); + case 'f': + film_dir = optarg; + break; + } + } + + if (film_dir.empty ()) { + help (argv[0]); + exit (EXIT_FAILURE); + } + + dvdomatic_setup (); + + Film* film = 0; + try { + film = new Film (film_dir, true); + } catch (std::exception& e) { + cerr << argv[0] << ": error reading film `" << film_dir << "' (" << e.what() << ")\n"; + exit (EXIT_FAILURE); + } + + cout << "\nMaking "; + if (film->dcp_ab ()) { + cout << "A/B "; + } + cout << "DCP for " << film->name() << "\n"; + cout << "Content: " << film->content() << "\n"; + pair const f = Filter::ffmpeg_strings (film->filters ()); + cout << "Filters: " << f.first << " " << f.second << "\n"; + + film->make_dcp (true); + + list > jobs = JobManager::instance()->get (); + + bool all_done = false; + bool first = true; + while (!all_done) { + + sleep (5); + + if (!first) { + cout << "\033[" << jobs.size() << "A"; + cout.flush (); + } + + first = false; + + all_done = true; + for (list >::iterator i = jobs.begin(); i != jobs.end(); ++i) { + cout << (*i)->name() << ": "; + + float const p = (*i)->overall_progress (); + + if (p >= 0) { + cout << (*i)->status() << " \n"; + } else { + cout << ": Running \n"; + } + + if (!(*i)->finished ()) { + all_done = false; + } + } + } + + return 0; +} + + diff --git a/src/tools/playomatic.cc b/src/tools/playomatic.cc new file mode 100644 index 000000000..b6fcd43cd --- /dev/null +++ b/src/tools/playomatic.cc @@ -0,0 +1,67 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include "lib/util.h" +#include "gtk/film_player.h" +#include "gtk/film_list.h" + +using namespace std; + +static FilmPlayer* film_player = 0; + +void +film_changed (Film const * f) +{ + film_player->set_film (f); +} + +int +main (int argc, char* argv[]) +{ + dvdomatic_setup (); + + Gtk::Main kit (argc, argv); + + if (argc != 2) { + cerr << "Syntax: " << argv[0] << " \n"; + exit (EXIT_FAILURE); + } + + Gtk::Window* window = new Gtk::Window (); + + FilmList* film_list = new FilmList (argv[1]); + film_player = new FilmPlayer (); + + Gtk::HBox hbox; + hbox.pack_start (film_list->widget(), true, true); + hbox.pack_start (film_player->widget(), true, true); + + film_list->SelectionChanged.connect (sigc::ptr_fun (&film_changed)); + + window->set_title ("Play-o-matic"); + window->add (hbox); + window->show_all (); + + window->maximize (); + Gtk::Main::run (*window); + + return 0; +} + diff --git a/src/tools/run_film_editor b/src/tools/run_film_editor new file mode 100755 index 000000000..3a3079e92 --- /dev/null +++ b/src/tools/run_film_editor @@ -0,0 +1,4 @@ +#!/bin/sh + +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:build/src +build/tools/film_editor $* diff --git a/src/tools/servomatic.cc b/src/tools/servomatic.cc new file mode 100644 index 000000000..b312af352 --- /dev/null +++ b/src/tools/servomatic.cc @@ -0,0 +1,238 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 worker_threads; + +static std::list queue; +static mutex worker_mutex; +static condition worker_condition; +static Log log_ ("servomatic.log"); + +int +process (int fd) +{ + SocketReader reader (fd); + + 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") { + close (fd); + 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 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 encoded = dcp_video_frame.encode_locally (); + encoded->send (fd); + +#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); + } + + int fd = queue.front (); + queue.pop_front (); + + lock.unlock (); + + int frame = -1; + + struct timeval start; + gettimeofday (&start, 0); + + try { + frame = process (fd); + } catch (std::exception& e) { + cerr << "Error: " << e.what() << "\n"; + } + + close (fd); + + 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)); + } + + int fd = socket (AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + throw NetworkError ("could not open socket"); + } + + int const o = 1; + setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &o, sizeof (o)); + + struct timeval tv; + tv.tv_sec = 20; + tv.tv_usec = 0; + setsockopt (fd, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv, sizeof (tv)); + setsockopt (fd, SOL_SOCKET, SO_SNDTIMEO, (void *) &tv, sizeof (tv)); + + struct sockaddr_in server_address; + memset (&server_address, 0, sizeof (server_address)); + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = INADDR_ANY; + server_address.sin_port = htons (Config::instance()->server_port ()); + if (::bind (fd, (struct sockaddr *) &server_address, sizeof (server_address)) < 0) { + stringstream s; + s << "could not bind to port " << Config::instance()->server_port() << " (" << strerror (errno) << ")"; + throw NetworkError (s.str()); + } + + listen (fd, BACKLOG); + + while (1) { + struct sockaddr_in client_address; + socklen_t client_length = sizeof (client_address); + int new_fd = accept (fd, (struct sockaddr *) &client_address, &client_length); + if (new_fd < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + throw NetworkError ("accept failed"); + } + + continue; + } + + 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); + } + + struct timeval tv; + tv.tv_sec = 20; + tv.tv_usec = 0; + setsockopt (new_fd, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv, sizeof (tv)); + setsockopt (new_fd, SOL_SOCKET, SO_SNDTIMEO, (void *) &tv, sizeof (tv)); + + queue.push_back (new_fd); + worker_condition.notify_all (); + } + + close (fd); + + return 0; +} diff --git a/src/tools/servomatictest.cc b/src/tools/servomatictest.cc new file mode 100644 index 000000000..0f37e73a5 --- /dev/null +++ b/src/tools/servomatictest.cc @@ -0,0 +1,159 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include +#include +#include "format.h" +#include "film.h" +#include "filter.h" +#include "util.h" +#include "scaler.h" +#include "server.h" +#include "dcp_video_frame.h" +#include "options.h" +#include "decoder.h" +#include "exceptions.h" +#include "scaler.h" +#include "log.h" +#include "decoder_factory.h" + +using namespace std; +using namespace boost; + +static Server* server; +static Log log_ ("servomatictest.log"); + +void +process_video (shared_ptr image, int frame) +{ + shared_ptr local (new DCPVideoFrame (image, Size (1024, 1024), 0, Scaler::from_id ("bicubic"), frame, 24, "", 0, 250000000, &log_)); + shared_ptr 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 local_encoded = local->encode_locally (); + shared_ptr remote_encoded; + + string remote_error; + try { + remote_encoded = remote->encode_remotely (server); + } catch (NetworkError& e) { + 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; + } + + if (local_encoded->size() != remote_encoded->size()) { + cout << "\033[0;31msizes differ\033[0m\n"; + return; + } + + uint8_t* p = local_encoded->data(); + uint8_t* q = remote_encoded->data(); + for (int i = 0; i < local_encoded->size(); ++i) { + if (*p++ != *q++) { + cout << "\033[0;31mdata differ\033[0m at byte " << i << "\n"; + return; + } + } + + cout << "\033[0;32mgood\033[0m\n"; +} + +static void +help (string n) +{ + cerr << "Syntax: " << n << " [--help] --film --server \n"; + exit (EXIT_FAILURE); +} + +int +main (int argc, char* argv[]) +{ + string film_dir; + string server_host; + + while (1) { + static struct option long_options[] = { + { "help", no_argument, 0, 'h'}, + { "server", required_argument, 0, 's'}, + { "film", required_argument, 0, 'f'}, + { 0, 0, 0, 0 } + }; + + int option_index = 0; + int c = getopt_long (argc, argv, "hs:f:", long_options, &option_index); + + if (c == -1) { + break; + } + + switch (c) { + case 'h': + help (argv[0]); + exit (EXIT_SUCCESS); + case 's': + server_host = optarg; + break; + case 'f': + film_dir = optarg; + break; + } + } + + if (server_host.empty() || film_dir.empty()) { + help (argv[0]); + exit (EXIT_FAILURE); + } + + dvdomatic_setup (); + + server = new Server (server_host, 1); + Film film (film_dir, true); + + shared_ptr opt (new Options ("fred", "jim", "sheila")); + opt->out_size = Size (1024, 1024); + opt->apply_crop = false; + opt->decode_audio = false; + + shared_ptr decoder = decoder_factory (film.state_copy(), opt, 0, &log_); + try { + decoder->Video.connect (sigc::ptr_fun (process_video)); + decoder->go (); + } catch (std::exception& e) { + cerr << "Error: " << e.what() << "\n"; + } + + return 0; +} diff --git a/src/tools/test.cc b/src/tools/test.cc new file mode 100644 index 000000000..f81814160 --- /dev/null +++ b/src/tools/test.cc @@ -0,0 +1,15 @@ +#include +#include +#include "image.h" +#include "server.h" + +using namespace boost; + +int main () +{ + uint8_t* rgb = new uint8_t[256]; + shared_ptr image (new Image (rgb, 0, 32, 32, 24)); + Server* s = new Server ("localhost", 2); + image->encode_remotely (s); + return 0; +} diff --git a/src/tools/wscript b/src/tools/wscript new file mode 100644 index 000000000..919c98e3f --- /dev/null +++ b/src/tools/wscript @@ -0,0 +1,17 @@ +def build(bld): + for t in ['makedcp', 'servomatic', 'servomatictest', 'fixlengths']: + obj = bld(features = 'cxx cxxprogram') + obj.uselib = 'BOOST_THREAD' + obj.includes = ['..'] + obj.use = ['libdvdomatic'] + obj.source = '%s.cc' % t + obj.target = t + + if not bld.env.DISABLE_GUI: + for t in ['dvdomatic', 'playomatic', 'alignomatic']: + obj = bld(features = 'cxx cxxprogram') + obj.uselib = 'BOOST_THREAD GTKMM' + obj.includes = ['..'] + obj.use = ['libdvdomatic', 'libdvdomatic-gtk'] + obj.source = '%s.cc' % t + obj.target = t diff --git a/src/wscript b/src/wscript new file mode 100644 index 000000000..9ae35d507 --- /dev/null +++ b/src/wscript @@ -0,0 +1,5 @@ +def build(bld): + bld.recurse('lib') + bld.recurse('tools') + if not bld.env.DISABLE_GUI: + bld.recurse('gtk') diff --git a/test/dvd/VIDEO_TS/VIDEO_TS.BUP b/test/dvd/VIDEO_TS/VIDEO_TS.BUP new file mode 100644 index 000000000..e69de29bb diff --git a/test/dvd/VIDEO_TS/VIDEO_TS.IFO b/test/dvd/VIDEO_TS/VIDEO_TS.IFO new file mode 100644 index 000000000..e69de29bb diff --git a/test/dvd/VIDEO_TS/VIDEO_TS.VOB b/test/dvd/VIDEO_TS/VIDEO_TS.VOB new file mode 100644 index 000000000..e69de29bb diff --git a/test/dvd/VIDEO_TS/VST_01_1.VOB b/test/dvd/VIDEO_TS/VST_01_1.VOB new file mode 100644 index 000000000..190a18037 --- /dev/null +++ b/test/dvd/VIDEO_TS/VST_01_1.VOB @@ -0,0 +1 @@ +123 diff --git a/test/dvd/VIDEO_TS/VTS_01_0.IFO b/test/dvd/VIDEO_TS/VTS_01_0.IFO new file mode 100644 index 000000000..e69de29bb diff --git a/test/dvd/VIDEO_TS/VTS_01_0.VOB b/test/dvd/VIDEO_TS/VTS_01_0.VOB new file mode 100644 index 000000000..e69de29bb diff --git a/test/dvd/VIDEO_TS/VTS_01_1.VOB b/test/dvd/VIDEO_TS/VTS_01_1.VOB new file mode 100644 index 000000000..e69de29bb diff --git a/test/dvd/VIDEO_TS/VTS_02_0.VOB b/test/dvd/VIDEO_TS/VTS_02_0.VOB new file mode 100644 index 000000000..8d38505c1 --- /dev/null +++ b/test/dvd/VIDEO_TS/VTS_02_0.VOB @@ -0,0 +1 @@ +456 diff --git a/test/dvd/VIDEO_TS/VTS_02_1.VOB b/test/dvd/VIDEO_TS/VTS_02_1.VOB new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/dvd/VIDEO_TS/VTS_02_1.VOB @@ -0,0 +1 @@ +1 diff --git a/test/dvd/VIDEO_TS/VTS_02_2.VOB b/test/dvd/VIDEO_TS/VTS_02_2.VOB new file mode 100644 index 000000000..48082f72f --- /dev/null +++ b/test/dvd/VIDEO_TS/VTS_02_2.VOB @@ -0,0 +1 @@ +12 diff --git a/test/dvd/VIDEO_TS/VTS_02_3.VOB b/test/dvd/VIDEO_TS/VTS_02_3.VOB new file mode 100644 index 000000000..190a18037 --- /dev/null +++ b/test/dvd/VIDEO_TS/VTS_02_3.VOB @@ -0,0 +1 @@ +123 diff --git a/test/dvd/VIDEO_TS/VTS_02_4.VOB b/test/dvd/VIDEO_TS/VTS_02_4.VOB new file mode 100644 index 000000000..81c545efe --- /dev/null +++ b/test/dvd/VIDEO_TS/VTS_02_4.VOB @@ -0,0 +1 @@ +1234 diff --git a/test/dvd/VIDEO_TS/VTS_03_0.IFO b/test/dvd/VIDEO_TS/VTS_03_0.IFO new file mode 100644 index 000000000..e56e15bb7 --- /dev/null +++ b/test/dvd/VIDEO_TS/VTS_03_0.IFO @@ -0,0 +1 @@ +12345 diff --git a/test/dvd/VIDEO_TS/VTS_03_0.VOB b/test/dvd/VIDEO_TS/VTS_03_0.VOB new file mode 100644 index 000000000..e56e15bb7 --- /dev/null +++ b/test/dvd/VIDEO_TS/VTS_03_0.VOB @@ -0,0 +1 @@ +12345 diff --git a/test/dvd/VIDEO_TS/VTS_03_1.VOB b/test/dvd/VIDEO_TS/VTS_03_1.VOB new file mode 100644 index 000000000..9f358a4ad --- /dev/null +++ b/test/dvd/VIDEO_TS/VTS_03_1.VOB @@ -0,0 +1 @@ +123456 diff --git a/test/film/log b/test/film/log new file mode 100644 index 000000000..c88741037 --- /dev/null +++ b/test/film/log @@ -0,0 +1,3 @@ +Fri Feb 17 18:54:32 2012: Starting to make a DCP on shankly +Fri Feb 17 18:54:32 2012: Transcode job starting +Fri Feb 17 18:54:37 2012: Transcode job completed successfully diff --git a/test/film/metadata b/test/film/metadata new file mode 100644 index 000000000..6c5afd6c9 --- /dev/null +++ b/test/film/metadata @@ -0,0 +1,18 @@ +name +content +dcp_long_name +guess_dcp_long_name 0 +frames_per_second 0 +left_crop 0 +right_crop 0 +top_crop 0 +bottom_crop 0 +scaler bicubic +dcp_frames 0 +dcp_ab 0 +width 0 +height 0 +length 0 +audio_channels 0 +audio_sample_rate 0 +audio_sample_format Unknown diff --git a/test/long.cc b/test/long.cc new file mode 100644 index 000000000..6be1ef2ff --- /dev/null +++ b/test/long.cc @@ -0,0 +1,156 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include +#include +#include "format.h" +#include "film.h" +#include "filter.h" +#include "job_manager.h" +#include "util.h" +#include "exceptions.h" +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE dvdomatic_test +#include + +using namespace std; +using namespace boost; + +bool +compare (string ref, string test, list exclude) +{ + ifstream r (ref.c_str ()); + ifstream t (test.c_str ()); + + while (r.good ()) { + string rl; + getline (r, rl); + string tl; + getline (t, tl); + + bool ex = false; + for (list::iterator i = exclude.begin(); i != exclude.end(); ++i) { + if (rl.find (*i) != string::npos && tl.find (*i) != string::npos) { + ex = true; + } + } + + if (!ex && rl != tl) { + cerr << "Fail:\n" << rl << "\n" << tl << "\n"; + return true; + } + } + + return false; +} + + +BOOST_AUTO_TEST_CASE (make_dcp_test) +{ + dvdomatic_setup (); + + string const dcp_name = "FOO-BAR-BAZ"; + + string const ref_film = "test/film"; + string const ref_dcp = ref_film + "/" + dcp_name; + string const ref_pkl = ref_dcp + "/bdb4ae0a-0d09-4554-8557-0b4260f4c359_pkl.xml"; + string const ref_cpl = ref_dcp + "/08dd6e45-83b5-41dc-9179-d7c59f597a12_cpl.xml"; + string const test_film = "build/test/film"; + string const test_dcp = test_film + "/" + dcp_name; + + if (boost::filesystem::exists (test_film)) { + boost::filesystem::remove_all (test_film); + } + + Film f (test_film, false); + f.write_metadata (); + boost::filesystem::copy_file ("test/zombie.mpeg", "build/test/film/zombie.mpeg"); + f.set_content ("zombie.mpeg"); + f.set_dcp_content_type (DCPContentType::from_pretty_name ("Test")); + + BOOST_CHECK_EQUAL (f.audio_channels(), 2); + BOOST_CHECK_EQUAL (f.audio_sample_rate(), 48000); + BOOST_CHECK_EQUAL (audio_sample_format_to_string (f.audio_sample_format()), "S16"); + + f.set_format (Format::from_nickname ("Flat")); + + f.make_dcp (true, 5); + + while (JobManager::instance()->work_to_do ()) { + sleep (1); + } + + { + stringstream s; + s << "diff -ur test/film/j2c " << test_film << "/j2c"; + int const r = ::system (s.str().c_str ()); + BOOST_CHECK_EQUAL (r, 0); + } + + { + stringstream s; + s << "diff -ur test/film/wavs " << test_film << "/wavs"; + int const r = ::system (s.str().c_str ()); + BOOST_CHECK_EQUAL (r, 0); + } + + { + stringstream s; + s << "diff -u test/film/metadata " << test_film << "/metadata"; + int const r = ::system (s.str().c_str ()); + BOOST_CHECK_EQUAL (r, 0); + } + + /* Find the test pkl and cpl */ + string test_pkl; + string test_cpl; + + for (filesystem::directory_iterator i = filesystem::directory_iterator (test_dcp); i != filesystem::directory_iterator(); ++i) { +#if BOOST_FILESYSTEM_VERSION == 3 + string const t = filesystem::path(*i).generic_string (); +#else + string const t = i->string (); +#endif + if (algorithm::ends_with (t, "cpl.xml")) { + test_cpl = t; + } else if (algorithm::ends_with (t, "pkl.xml")) { + test_pkl = t; + } + } + + { + list exclude; + exclude.push_back ("urn:uuid"); + exclude.push_back ("urn:uri"); + exclude.push_back (""); + exclude.push_back (""); + exclude.push_back (""); + BOOST_CHECK_EQUAL (compare (ref_cpl, test_cpl, exclude), false); + } + + { + list exclude; + exclude.push_back ("urn:uuid"); + exclude.push_back (""); + exclude.push_back (""); + BOOST_CHECK_EQUAL (compare (ref_pkl, test_pkl, exclude), false); + } +} diff --git a/test/metadata.ref b/test/metadata.ref new file mode 100644 index 000000000..61b6df751 --- /dev/null +++ b/test/metadata.ref @@ -0,0 +1,24 @@ +name fred +content +dcp_long_name sheila +guess_dcp_long_name 0 +dcp_content_type Short +frames_per_second 0 +format 185 +left_crop 1 +right_crop 2 +top_crop 3 +bottom_crop 4 +filter pphb +filter unsharp +scaler bicubic +dcp_frames 42 +dcp_ab 1 +audio_gain 0 +audio_delay 0 +width 0 +height 0 +length 0 +audio_channels 0 +audio_sample_rate 0 +audio_sample_format Unknown diff --git a/test/short.cc b/test/short.cc new file mode 100644 index 000000000..3f010379c --- /dev/null +++ b/test/short.cc @@ -0,0 +1,220 @@ +/* + Copyright (C) 2012 Carl Hetherington + + This 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 +#include +#include +#include +#include "format.h" +#include "film.h" +#include "filter.h" +#include "job_manager.h" +#include "util.h" +#include "exceptions.h" +#include "dvd.h" +#include "delay_line.h" +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE dvdomatic_test +#include + +using namespace std; +using namespace boost; + +BOOST_AUTO_TEST_CASE (film_metadata_test) +{ + dvdomatic_setup (); + + string const test_film = "build/test/film"; + + if (boost::filesystem::exists (test_film)) { + boost::filesystem::remove_all (test_film); + } + + BOOST_CHECK_THROW (new Film ("build/test/film", true), OpenFileError); + + Film f (test_film, false); + BOOST_CHECK (f.format() == 0); + BOOST_CHECK (f.dcp_content_type() == 0); + BOOST_CHECK (f.filters ().empty()); + + f.set_name ("fred"); + BOOST_CHECK_THROW (f.set_content ("jim"), OpenFileError); + f.set_dcp_content_type (DCPContentType::from_pretty_name ("Short")); + f.set_format (Format::from_nickname ("Flat")); + f.set_left_crop (1); + f.set_right_crop (2); + f.set_top_crop (3); + f.set_bottom_crop (4); + vector f_filters; + f_filters.push_back (Filter::from_id ("pphb")); + f_filters.push_back (Filter::from_id ("unsharp")); + f.set_filters (f_filters); + f.set_dcp_frames (42); + f.set_dcp_ab (true); + f.write_metadata (); + + stringstream s; + s << "diff -u test/metadata.ref " << test_film << "/metadata"; + BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0); + + Film g (test_film, true); + + BOOST_CHECK_EQUAL (g.name(), "fred"); + BOOST_CHECK_EQUAL (g.dcp_content_type(), DCPContentType::from_pretty_name ("Short")); + BOOST_CHECK_EQUAL (g.format(), Format::from_nickname ("Flat")); + BOOST_CHECK_EQUAL (g.left_crop(), 1); + BOOST_CHECK_EQUAL (g.right_crop(), 2); + BOOST_CHECK_EQUAL (g.top_crop(), 3); + BOOST_CHECK_EQUAL (g.bottom_crop(), 4); + vector g_filters = g.filters (); + BOOST_CHECK_EQUAL (g_filters.size(), 2); + BOOST_CHECK_EQUAL (g_filters.front(), Filter::from_id ("pphb")); + BOOST_CHECK_EQUAL (g_filters.back(), Filter::from_id ("unsharp")); + BOOST_CHECK_EQUAL (g.dcp_frames(), 42); + BOOST_CHECK_EQUAL (g.dcp_ab(), true); + + g.write_metadata (); + BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0); +} + +BOOST_AUTO_TEST_CASE (format_test) +{ + Format::setup_formats (); + + Format const * f = Format::from_nickname ("Flat"); + BOOST_CHECK (f); + BOOST_CHECK_EQUAL (f->ratio_as_integer(), 185); + + f = Format::from_nickname ("Scope"); + BOOST_CHECK (f); + BOOST_CHECK_EQUAL (f->ratio_as_integer(), 239); +} + +BOOST_AUTO_TEST_CASE (util_test) +{ + string t = "Hello this is a string \"with quotes\" and indeed without them"; + vector b = split_at_spaces_considering_quotes (t); + vector::iterator i = b.begin (); + BOOST_CHECK_EQUAL (*i++, "Hello"); + BOOST_CHECK_EQUAL (*i++, "this"); + BOOST_CHECK_EQUAL (*i++, "is"); + BOOST_CHECK_EQUAL (*i++, "a"); + BOOST_CHECK_EQUAL (*i++, "string"); + BOOST_CHECK_EQUAL (*i++, "with quotes"); + BOOST_CHECK_EQUAL (*i++, "and"); + BOOST_CHECK_EQUAL (*i++, "indeed"); + BOOST_CHECK_EQUAL (*i++, "without"); + BOOST_CHECK_EQUAL (*i++, "them"); +} + +BOOST_AUTO_TEST_CASE (dvd_test) +{ + vector const t = dvd_titles ("test/dvd"); + BOOST_CHECK_EQUAL (t.size(), 4); + BOOST_CHECK_EQUAL (t[1], 0); + BOOST_CHECK_EQUAL (t[2], 14); + BOOST_CHECK_EQUAL (t[3], 7); +} + +void +do_positive_delay_line_test (int delay_length, int block_length) +{ + DelayLine d (delay_length); + uint8_t data[block_length]; + + int in = 0; + int out = 0; + int returned = 0; + int zeros = 0; + + for (int i = 0; i < 64; ++i) { + for (int j = 0; j < block_length; ++j) { + data[j] = in; + ++in; + } + + int const a = d.feed (data, block_length); + returned += a; + + for (int j = 0; j < a; ++j) { + if (zeros < delay_length) { + BOOST_CHECK_EQUAL (data[j], 0); + ++zeros; + } else { + BOOST_CHECK_EQUAL (data[j], out & 0xff); + ++out; + } + } + } + + BOOST_CHECK_EQUAL (returned, 64 * block_length); +} + +void +do_negative_delay_line_test (int delay_length, int block_length) +{ + DelayLine d (delay_length); + uint8_t data[block_length]; + + int in = 0; + int out = -delay_length; + int returned = 0; + + for (int i = 0; i < 256; ++i) { + for (int j = 0; j < block_length; ++j) { + data[j] = in; + ++in; + } + + int const a = d.feed (data, block_length); + returned += a; + + for (int j = 0; j < a; ++j) { + BOOST_CHECK_EQUAL (data[j], out & 0xff); + ++out; + } + } + + uint8_t remainder[-delay_length]; + d.get_remaining (remainder); + returned += -delay_length; + + for (int i = 0; i < -delay_length; ++i) { + BOOST_CHECK_EQUAL (remainder[i], 0); + ++out; + } + + BOOST_CHECK_EQUAL (returned, 256 * block_length); + +} + +BOOST_AUTO_TEST_CASE (delay_line_test) +{ + do_positive_delay_line_test (64, 128); + do_positive_delay_line_test (128, 64); + do_positive_delay_line_test (3, 512); + do_positive_delay_line_test (512, 3); + + do_positive_delay_line_test (0, 64); + + do_negative_delay_line_test (-64, 128); + do_negative_delay_line_test (-128, 64); + do_negative_delay_line_test (-3, 512); + do_negative_delay_line_test (-512, 3); +} diff --git a/test/wscript b/test/wscript new file mode 100644 index 000000000..7ea02a804 --- /dev/null +++ b/test/wscript @@ -0,0 +1,16 @@ +def build(bld): + obj = bld(features = 'cxx cxxprogram') + obj.name = 'short-unit-tests' + obj.uselib = 'BOOST_TEST' + obj.use = 'libdvdomatic' + obj.source = 'short.cc' + obj.target = 'short-unit-tests' + obj.install_path = '' + + obj = bld(features = 'cxx cxxprogram') + obj.name = 'long-unit-tests' + obj.uselib = 'BOOST_TEST' + obj.use = 'libdvdomatic' + obj.source = 'long.cc' + obj.target = 'long-unit-tests' + obj.install_path = '' diff --git a/waf b/waf new file mode 100755 index 000000000..178461f11 Binary files /dev/null and b/waf differ diff --git a/wscript b/wscript new file mode 100644 index 000000000..98fa0f6f7 --- /dev/null +++ b/wscript @@ -0,0 +1,91 @@ +APPNAME = 'dvdomatic' +VERSION = '0.26pre' + +def options(opt): + opt.load('compiler_cxx') + 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') + +def configure(conf): + conf.load('compiler_cxx') + + conf.env.append_value('CXXFLAGS', ['-D__STDC_CONSTANT_MACROS', '-D__STDC_LIMIT_MACROS', '-msse', '-mfpmath=sse', '-ffast-math', '-Wall']) + conf.env.append_value('CXXFLAGS', ['-DDVDOMATIC_VERSION="%s"' % VERSION]) + + conf.env.DEBUG_HASH = conf.options.debug_hash + 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') + + if conf.options.enable_debug: + conf.env.append_value('CXXFLAGS', '-g') + else: + conf.env.append_value('CXXFLAGS', '-O3') + + conf.env.DISABLE_GUI = conf.options.disable_gui + if conf.options.disable_gui is False: + conf.check_cfg(package = 'glib-2.0', args = '--cflags --libs', uselib_store = 'GLIB', mandatory = True) + conf.check_cfg(package = 'gtkmm-2.4', args = '--cflags --libs', uselib_store = 'GTKMM', mandatory = True) + conf.check_cfg(package = 'cairomm-1.0', args = '--cflags --libs', uselib_store = 'CAIROMM', mandatory = True) + + conf.check_cfg(package = 'sigc++-2.0', args = '--cflags --libs', uselib_store = 'SIGC++', mandatory = True) + conf.check_cfg(package = 'libavformat', args = '--cflags --libs', uselib_store = 'AVFORMAT', mandatory = True) + conf.check_cfg(package = 'libavfilter', args = '--cflags --libs', uselib_store = 'AVFILTER', mandatory = True) + 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 = '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 = '', 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') + conf.check_cc(fragment = """ + #include \n + #include \n + int main () {\n + void* p = (void *) opj_image_create;\n + return 0;\n + } + """, msg = 'Checking for library openjpeg', lib = 'openjpeg', uselib_store = 'OPENJPEG') + + conf.check_cc(fragment = """ + #include \n + int main () {\n + ssh_session s = ssh_new ();\n + return 0;\n + } + """, msg = 'Checking for library libssh', lib = 'ssh', uselib_store = 'SSH') + + conf.check_cxx(fragment = """ + #include \n + int main() { boost::thread t (); }\n + """, msg = 'Checking for boost threading library', lib = 'boost_thread', uselib_store = 'BOOST_THREAD') + conf.check_cxx(fragment = """ + #include \n + int main() { boost::filesystem::copy_file ("a", "b"); }\n + """, msg = 'Checking for boost filesystem library', libpath = '/usr/local/lib', lib = ['boost_filesystem', 'boost_system'], uselib_store = 'BOOST_FILESYSTEM') + conf.check_cxx(fragment = """ + #define BOOST_TEST_MODULE Config test\n + #include \n + int main() {} + """, msg = 'Checking for boost unit testing library', lib = 'boost_unit_test_framework', uselib_store = 'BOOST_TEST') + +def build(bld): + bld.recurse('src') + bld.recurse('test') + + d = { 'PREFIX' : '${PREFIX' } + + obj = bld(features = 'subst') + obj.source = 'dvdomatic.desktop.in' + obj.target = 'dvdomatic.desktop' + obj.dict = d + + bld.install_files('${PREFIX}/share/applications', 'dvdomatic.desktop') + for r in ['22x22', '32x32', '48x48', '64x64', '128x128']: + bld.install_files('${PREFIX}/share/icons/hicolor/%s/apps' % r, 'icons/%s/dvdomatic.png' % r) + + +def dist(ctx): + ctx.excl = 'TODO core *~ src/gtk/*~ src/lib/*~ .waf* build'