commit 40f82866427ea9b56fadbd688f0e20b32239f6bf Author: tpearson Date: Wed Feb 24 18:16:21 2010 +0000 Added KDE3 version of MLT git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/libraries/mlt@1095634 283d02a7-25f6-0310-bc7c-ecb5cbfe19da diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..ec47efc --- /dev/null +++ b/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. 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 library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..c5c26e1 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,58 @@ +$Id: ChangeLog 1008 2007-07-15 01:19:30Z ddennedy $ + +USING svn log NOW + +2007-04-09 Dan Dennedy + Cleanup copyrights and attributions, and move Jean-Baptiste's services + to a new kdenlive module. + +2007-03-30 Dan Dennedy + Add support for sox 13.0.0. + +2007-03-30 Jean-Baptiste Mardelle + Fix boxblur and wave filters license. + +2007-03-29 Dan Dennedy + Cleanup license declarations and remove dv1394d references. + Change registration of vmfx/mono to threshold to disambiguate with + core/mono. + +2007-03-27 Dan Dennedy + Fix ffmpeg swscale code enabled with mmx flags and fix --enable-swscale + in conjunction with --avformat-static. + +2007-03-16 Dan Dennedy + Added docs/policies.txt. + +2007-02-19 Jean-Baptiste Mardelle + Blur and wave filters: fix typos and make functions static (patch from Stephane Fillod) + +2007-02-18 Jean-Baptiste Mardelle + Add blur and wave filters from Leny Grisel + +2007-02-07 Dan Dennedy + Added ffmpeg libswscale support to avformat module (requires configure + option --avformat-swscale) + +2006-12-07 Dan Dennedy + applied audio frequency and audio channels initialization patch from Jean-Baptiste + +2006-09-27 Zachary Drew + applied amd64 patch from gentoo folks to fix compilation of motion_est + on amd64 (thanks for the heads-up Jean-Michel) + + +2006-09-25 Dan Dennedy + - src/modules/sdl/Makefile: fix compilation on some systems using + modular x.org. + +2006-08-08 Dan Dennedy + enhance producer_westley to parse Kino 0.9.1 SMIL (clock) time values. + +2006-08-08 Dan Dennedy + convert --avformat-cvs to svn and rename option as --avformat-svn (--avformat-cvs is an undocumented alias). diff --git a/GPL b/GPL new file mode 100644 index 0000000..623b625 --- /dev/null +++ b/GPL @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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/Makefile b/Makefile new file mode 100644 index 0000000..b4d5ffa --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +SUBDIRS = src/framework \ + src/inigo \ + src/valerie \ + src/miracle \ + src/humperdink \ + src/albino \ + src/modules \ + profiles + +all clean: + list='$(SUBDIRS)'; \ + for subdir in $$list; do \ + $(MAKE) -s -C $$subdir depend || exit 1; \ + $(MAKE) -C $$subdir $@ || exit 1; \ + done + +distclean: + rm mlt-config packages.dat; \ + list='$(SUBDIRS)'; \ + for subdir in $$list; do \ + $(MAKE) -C $$subdir $@ || exit 1; \ + done; \ + rm config.mak; + +dist-clean: distclean + +include config.mak + +install: + install -d "$(DESTDIR)$(prefix)/bin" + install -d "$(DESTDIR)$(prefix)/include" + install -d "$(DESTDIR)$(libdir)" + install -d "$(DESTDIR)$(libdir)/pkgconfig" + install -d "$(DESTDIR)$(prefix)/lib/mlt/modules" + install -d "$(DESTDIR)$(prefix)/share/mlt/modules" + install -c -m 755 mlt-config "$(DESTDIR)$(bindir)" + install -c -m 644 *.pc "$(DESTDIR)$(libdir)/pkgconfig" + install -m 644 packages.dat "$(DESTDIR)$(prefix)/share/mlt/" + list='$(SUBDIRS)'; \ + for subdir in $$list; do \ + $(MAKE) DESTDIR=$(DESTDIR) -C $$subdir $@ || exit 1; \ + done; \ +# if test -z "$(DESTDIR)"; then \ +# /sbin/ldconfig || true; \ +# fi + +uninstall: + rm -f "$(DESTDIR)$(bindir)"/mlt-config + rm -f "$(DESTDIR)$(libdir)/pkgconfig/mlt-*.pc" + list='$(SUBDIRS)'; \ + for subdir in $$list; do \ + $(MAKE) DESTDIR=$(DESTDIR) -C $$subdir $@ || exit 1; \ + done + rm -rf "$(DESTDIR)$(prefix)/include/mlt" + rm -rf "$(DESTDIR)$(prefix)/share/mlt" + +dist: + [ -d "mlt-$(version)" ] && rm -rf "mlt-$(version)" || echo + svn export . "mlt-$(version)" + svn log > "mlt-$(version)/ChangeLog" + tar -cvzf "mlt-$(version).tar.gz" "mlt-$(version)" diff --git a/README b/README new file mode 100644 index 0000000..aaac6e2 --- /dev/null +++ b/README @@ -0,0 +1,65 @@ +MLT/Miracle README +------------------ + + Sponsored by Ushodaya Enterprises Limited + Written by Charles Yates + and Dan Dennedy + + MLT is a LGPL multimedia framework designed for television broadcasting, + and Miracle is a GPL multi-unit video playout server with realtime + effects. + + This document provides a quick reference for the minimal configuration, + build and installation of MLT. See the docs directory for usage and + development details. + + +Configuration +------------- + + Configuration is triggered by running: + + ./configure + + More information on usage is found by running: + + ./configure --help + + NB: This script must be run to register new services after a CVS checkout + or subsequent update. + + +Compilation +----------- + + Once configured, it should be sufficient to run: + + make + + to compile the system. + + +Testing +------- + + To execute the mlt tools without installation, or to test a new version + on a system with an already installed mlt version, you should run: + + . setenv + + NB: This applies to your current shell only and it assumes a bash or + regular bourne shell is in use. + + +Installation +------------ + + The install is triggered by running: + + make install + + +More Information +---------------- + + For more detailed information, please refer to docs/install.txt. diff --git a/config.log b/config.log new file mode 100644 index 0000000..c6b91b1 --- /dev/null +++ b/config.log @@ -0,0 +1,2 @@ +Wed Feb 24 12:10:50 CST 2010 +./configure --enable-gpl --luma-compress --disable-mmx --enable-motion-est --avformat-shared=/usr --avformat-swscale --prefix=/opt/kde3 --with-extra-libs=/opt/kde3/lib --with-extra-includes=/opt/kde3/include/kde diff --git a/configure-stamp b/configure-stamp new file mode 100644 index 0000000..e69de29 diff --git a/demo/README b/demo/README new file mode 100644 index 0000000..a9a5a08 --- /dev/null +++ b/demo/README @@ -0,0 +1,217 @@ +MLT Demo Notes + +Before running the demo script, make sure you '. setenv' from the parent +directory. Also, please create clips clip1.dv, clip2.dv, clip3.dv, clip1.mpeg, +clip2.mpeg, clip3.mpeg, and music1.ogg. Please make sure clips are at least 500 +frames duration. + +These notes explain the the concepts presented in each demonstration and +what details to look for. + +First, a note on consumers. When you start the script, the main menu asks +you to choose a consumer. A consumer is like a viewer, but it could also +write to a stream/file. The "SDL" consumer is the popular Simple DirectMedia +Layer audio and video output. The "Westley" consumer generates an XML +representation of the service network. That can be played directly due to the +westley producer plugin. See docs/westley.txt for more information. The +"MainConcept DV" consumer refers to the proprietary MLT plugin required to +use MLT with MainConcept DV, DVCPro, and MPEG codecs. "/dev/dv1394/0" refers +to a device file for transmitting DV over FireWire using the Linux dv1394 kernel +module. The "BlueFish444" consumer is another proprietary plugin to use +the BlueFish444 manufactured SDI video/audio output cards with MLT. + +And now the demos... + +All clips + + Simply builds a playlist containing each video clip, and you can transport + between them using j and k keys. + +Filter in/out + + A video filter can be applied to a portion of a producer (clip, playlist, + or multitrack). This examples shows the greyscale filter. + +Watermark + + A graphic can overlay video in realtime with support for alpha channel. + This example uses a PNG file with an alpha channel. Distortion is explicitly + enabled here so the otherwise circular graphic is scaled to fill the + compositing region. By default, compositing honours the aspect ratio of the + overlay. + +My name is... + + Titles are very easy to composite in realtime. The titler uses Pango + with the FreeType2 rendering backend. This means it supports high + quality scalable font rendering with anti-aliasing, unicode (UTF-8), + and Pango markup capabilities. The compsiting here respects the aspect + ratio of the rendered title in the first two title pieces but distorts + the final one. This demo also shows the motion and scaling capabilities + of the compositor in conjunction with honouring aspect. The compositor + is doing field-based rendering. So, when displayed non-progressively + with SDL, you can see motion artifacts during animation. + +A composite transition + + The compositor also handles video over video as demonstrated in this + usage of the compositor to create a special transition. This demonstration + also crossfades the audio during the transition! Progressive rendering + is explicitly enabled on the compositor due to the poor results that + would otherwise occur due to scaling an interleaved video frame and moving + the video in a reverse direction horizontally. + +Fade in and out + + A simple series of transitions betwen 3 clips using dissolves and audio + crossfades. This is easy :-). + +Clock in and out + + Wipe transitions are very easy and highly extensible as they are generated + using a very convenient lookup table based upon the luma of an image. + This image can be a 16 bit PGM (grayscale bitmap) or the luma channel of + any video producer. A number of high quality wipes can be downloaded from + http://mlt.sf.net/. It also performs field rendering. + The second wipe demonstrates the ability to control the direction of the + wipe as well. + +Obscure + + A popular requirement in news production is to obscure a face, obscenity, + or trademarked logo. This demonstrates using a simple rectangular + obscure filter applied to a region of the image. The second example is more + advanced and shows using the "region" filter to select the image area and a + property of the region filter to "shape" the region using the alpha channel + of another image (circle.png) and another property to "filter" the region + using the obscure filter. + +Audio Stuff + + A music bed sound track can be mixed with a video. The sound track of the + video clip has a "floating" amplitude normalisation filter applied. + Typically, audio normalisation applies a constant gain factor across the + entire duration of an audio segment from a single source where the + gain factor is automatically determined by anaylsing the maximum "power" + or peak levels. However, in news production, a popular requirement is to + to dynamically boost the amplitude in soft areas and reduce the amplitude + in louder areas. Thus, the gain analysis is performed using a "sliding + window" approach. This example also applies a constant gain factor of + 0.5 (50%) to the normalised audio of the video clip (to get a nicer + mix level). + +Audio and Video Levels + + Audio can be normalised by setting a target amplitude level in decibels. + A gamma curve can be applied to the luma channel of video. + +Shadowed Title and Watermark + + Two instances of the titler are used to create a shadow effect. + The aspect ratio of the watermark in this example is not distorted. Since + the original image is a circle with square pixels--a computer-generated + image--and ITU BT.601 video is not composed of square samples. Therefore, + the compositor normalises the pixel aspect ratio of the overlay to the + destination image, and the circular image remains circular on the analog + video output. Finally, a greyscale filter is applied to the watermark + while its opacity is set at 30%. + +Station Promo into Story? + + Here is fun demo that might show using a still graphic with some music + to introduce a show. A luma wipe with an audio crossfade transitions from + the show title or station promotional material. + +Voiceover 2 clips with title + + A common news production requirement to have a "voiceover" audio track + to a clip or even multiple clips as demonstrated here. Likewise, it is + common to place a title caption on the video at the same time! This + demo has a little fun with the titler at the sake of practicality :-) + The foreground of the title is transparent while the opacity of the + background is reduced to blend with the video. Meanwhile, the compositor + stretches the image to fill the bottom slice of the video--not suitable + for overscan displays ;-) + + Also, pay close attention to the mixing levels of the audio tracks. + The audio of the video fades out as the voiceover track (just music + in this demo) fades in. Then, the voiceover remains mixed with the + ambient audio at a 60% level. Finally, the voiceover fades out smoothly + from the 60% level to nothing. + +GJ-TTAvantika title + + This demo requires a special TrueType font called Avantika. If you have the + font, register it with fontconfig using the fc-cache utility. This + demonstrates i18n capabilities of the titler and the alignment capabilities + of both the titler and the compositor. The titler centre aligns + the two lines of text, and the compositor centre aligns the title + horizontally on the frame. + +Title over graphic + + You can superimpose a title over a graphic over video! Also, + you can apply a luma wipe to the compositor! + +Slideshow + + This demo requires any number of JPEG images with the extension ".jpg" + in a subdirectory named "Scotland." + +Bouncy, Bouncy + + The "watermark" filter encapsulates the compositor, and you have full + control over the compositor properties. Who says a watermark can not + also be a video?! + +Bouncy, Bouncy Ball + + A variation on the above Bouncy, Bouncy demo that applies a shape, or + alpha producer, to the the compositing region. + +Breaking News + + This demonstrates layout capabilities of the compositor. + +Squeeze Transitions + + This demonstrates a distorting barndoor-like wipe. + + +J Cut + + A J cut is an edit where the audio cuts before the video. + It gets its name from the way it looks on a NLE timeline user interface. + When the audio cuts over, it does an audio crossfade over the duration of + one frame. This makes the audio cut slightly less abrupt and avoids any + "click" due to mismatched sample levels at the edit point. The video edit + is a hard cut. + +L Cut + + An L cut is an edit where the video cuts before the audio. + It gets its name from the way it looks on a NLE timeline user interface. + This demo shows a very quick dissolve over 5 frames for a soft video cut. + Like the J Cut demo, an audio crossfade for the duration of one frame makes + an audio edit nearly instantaneous while being slightly softened and + avoiding aberrations. + +Fade from/to black/silence + + Of course, it is possible using MLT to fade from black on video and silence + on audio as well fade to black and silence. + +Push wipe + + A push wipe is a somewhat fancier transition than most standard wipes + because it involves motion. The new video clip "pushes" the old video + clip off one edge. If you can preview on an analog monitor you will notice + how smooth the motion is due to field-based rendering. + +Ticker tape + + A very minimal reverse crawling title neard the bottom of the screen. + The goal of the demo is show fluid motion of the field-based rendering of + the compositor when viewed on an analog monitor using a DV or BlueFish444 + consumer. The demo also shows the potientional for using and extending the + existing set of services for a full blown news ticker implementation. diff --git a/demo/circle.png b/demo/circle.png new file mode 100644 index 0000000..968b74a Binary files /dev/null and b/demo/circle.png differ diff --git a/demo/circle.svg b/demo/circle.svg new file mode 100644 index 0000000..476ef3c --- /dev/null +++ b/demo/circle.svg @@ -0,0 +1 @@ + diff --git a/demo/consumers.ini b/demo/consumers.ini new file mode 100644 index 0000000..b02695f --- /dev/null +++ b/demo/consumers.ini @@ -0,0 +1,12 @@ +SDL Default sdl +SDL Half D1 sdl:360x288 rescale=nearest resize=1 +SDL High Latency sdl buffer=12 rescale=none +SDL Progressive sdl progressive=1 +Westley to Terminal westley +Westley to File westley: +MainConcept DV to /dev/dv1394/0 mcdv:/dev/dv1394/0 rescale=nearest buffer=25 +libdv to /dev/dv1394/0 libdv:/dev/dv1394/0 rescale=nearest buffer=25 +BlueFish444 PAL bluefish:1 +BlueFish444 NTSC bluefish:1 standard=NTSC +BlueFish444 PAL Prog LL bluefish:1 progressive=1 buffer=1 frames=4 +BlueFish444 NTSC Prog LL bluefish:1 standard=NTSC progressive=1 buffer=1 frames=4 diff --git a/demo/demo b/demo/demo new file mode 100755 index 0000000..98777ca --- /dev/null +++ b/demo/demo @@ -0,0 +1,106 @@ +#!/bin/bash + +function show_consumers( ) +{ + awk -F '\t' '{ printf( "%d. %s\n", ++ i, $1 ); }' < consumers.ini +} + +function get_consumer( ) +{ + option=$1 + [ "$option" != "" ] && [ $option -gt 0 ] && sed 's/\t\+/\t/g' < consumers.ini | cut -f 2 | head -n $option | tail -n -1 +} + +function show_menu( ) +{ + sed 's/\t\+/\t/g' < demo.ini | + awk -F '\t' '{ printf( "%2d. %-30.30s", ++ i, $2 ); if ( i % 2 == 0 ) printf( "\n" ); } END { if ( i % 2 == 1 ) printf( "\n" ); }' +} + +function check_dependencies( ) +{ + option=$1 + if [ $option -gt 0 ] + then + deps=`sed 's/\t\+/\t/g' < demo.ini | cut -f 3 | head -n $option | tail -n -1` + if [ "$deps" != "" ] + then + echo "$deps" | + tr ',' '\n' | + while read dep + do + ls $dep > /dev/null 2>&1 + val=$? + [ $val != 0 ] && echo Failed to find $dep >&2 && echo $val + done + fi + echo 0 + fi +} + +function get_demo( ) +{ + option=$1 + if [ $option -gt 0 ] + then + cut -f 1 demo.ini | head -n $option | tail -n -1 + fi +} + +while [ 1 ] +do + + echo Select Consumer + echo + + show_consumers + + echo + echo 0. Exit + echo + echo -n "Option: " + read option + echo + + [ "$option" == "0" ] && break + + export MLT_CONSUMER=`get_consumer $option` + + while [ "$option" != "0" -a "$MLT_CONSUMER" != "" ] + do + echo Choose Demo + echo + + show_menu + + echo + echo -n "Option: " + read option + echo + + [ "$option" == "" ] && break + + demo=`get_demo $option` + usable=`check_dependencies $option` + + if [ "$usable" = "0" -a "$demo" != "" ] + then + if [ "$MLT_CONSUMER" == "westley:" ] + then export WESTLEY_CONSUMER="westley:$demo.westley" + bash $demo -consumer $WESTLEY_CONSUMER + inigo +$demo.txt out=100 $demo.westley $demo.westley -filter watermark:watermark1.png composite.fill=1 composite.geometry=85%,5%:10%x10% + elif [ "$MLT_CONSUMER" == "westley" ] + then bash $demo -consumer $MLT_CONSUMER | less + else bash $demo -consumer $MLT_CONSUMER + fi + elif [ "$usable" != "" ] + then + echo + echo Unable to locate suitable files for the demo - please provide them. + read pause + fi + + stty sane + done + +done diff --git a/demo/demo.ini b/demo/demo.ini new file mode 100644 index 0000000..b01b049 --- /dev/null +++ b/demo/demo.ini @@ -0,0 +1,28 @@ +mlt_all All clips clip* +mlt_effect_in_middle Filter in/out clip1.mpeg +mlt_watermark Watermark clip2.dv,watermark1.png +mlt_my_name_is My name is... clip3.dv +mlt_composite_transition A composite transition clip1.dv,clip2.mpeg +mlt_fade_in_and_out Fade in and out clip1.dv,clip2.mpeg,clip3.dv +mlt_clock_in_and_out Clock in and out clip2.dv,clip1.dv,clip3.mpeg +mlt_obscure Obscure clip2.mpeg,circle.png +mlt_audio_stuff Audio Stuff clip*.dv,music1.ogg +mlt_levels Audio and Video Levels clip*.dv +mlt_titleshadow_watermark Shadowed Title and Watermark clip3.dv +mlt_intro Station Promo into Story? watermark1.png,clip3.mpeg,music1.ogg +mlt_voiceover Voiceover 2 clips with title clip1.dv,clip2.mpeg,music1.ogg +mlt_avantika_title GJ-TTAvantika title pango.westley +mlt_title_over_gfx Title over graphic watermark1.png,clip1.dv +mlt_slideshow Slideshow Scotland +mlt_bouncy Bouncy, Bouncy clip1.dv,clip3.dv +mlt_bouncy_ball Bouncy, Bouncy Ball clip1.mpeg,clip3.mpeg,circle.png +mlt_news Breaking News clip1.dv,clip2.dv +mlt_squeeze Squeeze Transitions clip1.dv,clip2.dv,clip3.dv +mlt_squeeze_box Squeeze Box clip1.dv,clip2.dv,clip3.dv +mlt_jcut J Cut clip1.dv,clip2.dv +mlt_lcut L Cut clip1.dv,clip2.dv +mlt_fade_black Fade from/to black/silence clip3.mpeg +mlt_push Push wipe clip1.mpeg, clip2.mpeg +mlt_ticker Ticker tape clip1.dv +mlt_attributes Attributes clip1.dv +mlt_slideshow_black Composite slideshow Scotland diff --git a/demo/demo.kino b/demo/demo.kino new file mode 100644 index 0000000..99e8767 --- /dev/null +++ b/demo/demo.kino @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/demo/entity.westley b/demo/entity.westley new file mode 100644 index 0000000..0f6a6b9 --- /dev/null +++ b/demo/entity.westley @@ -0,0 +1,11 @@ + + +]> + + + pango + Hello &name;, +My name is Inigo Montoya. + + diff --git a/demo/luma1.pgm b/demo/luma1.pgm new file mode 100644 index 0000000..ac689e5 Binary files /dev/null and b/demo/luma1.pgm differ diff --git a/demo/mlt_all b/demo/mlt_all new file mode 100644 index 0000000..d563da8 --- /dev/null +++ b/demo/mlt_all @@ -0,0 +1,3 @@ +inigo \ +clip* \ +$* diff --git a/demo/mlt_attributes b/demo/mlt_attributes new file mode 100644 index 0000000..e4fffb5 --- /dev/null +++ b/demo/mlt_attributes @@ -0,0 +1,7 @@ +inigo clip1.dv \ +meta.attr.location=1 meta.attr.location.markup="Location" \ +meta.attr.exclusive=1 meta.attr.exclusive.markup="Exclusive" \ +meta.attr.special=1 meta.attr.special.markup="Special" \ +meta.attr.super=1 meta.attr.super.0="Line 1" meta.attr.super.1="Line 2" \ +-filter data_show \ +$* diff --git a/demo/mlt_audio_stuff b/demo/mlt_audio_stuff new file mode 100644 index 0000000..c0074c1 --- /dev/null +++ b/demo/mlt_audio_stuff @@ -0,0 +1,6 @@ +inigo \ +clip*.dv \ +-track music1.ogg \ +-filter volume:0.5 normalise= track=0 \ +-transition mix out=9999 a_track=0 b_track=1 \ +$* diff --git a/demo/mlt_avantika_title b/demo/mlt_avantika_title new file mode 100644 index 0000000..89d0242 --- /dev/null +++ b/demo/mlt_avantika_title @@ -0,0 +1,3 @@ +inigo \ +pango.westley \ +$* diff --git a/demo/mlt_bouncy b/demo/mlt_bouncy new file mode 100644 index 0000000..5315ec9 --- /dev/null +++ b/demo/mlt_bouncy @@ -0,0 +1,10 @@ +inigo \ +clip3.dv \ +-filter \ +watermark:clip1.dv \ +composite.start=10%,10%:20%x20% \ +composite.key[33]=30%,70%:25%x25% \ +composite.key[66]=70%,30%:15%x15% \ +composite.end=70%,70%:20%x20% \ +composite.out=100 \ +$* diff --git a/demo/mlt_bouncy_ball b/demo/mlt_bouncy_ball new file mode 100644 index 0000000..1aedb5c --- /dev/null +++ b/demo/mlt_bouncy_ball @@ -0,0 +1,14 @@ +inigo \ +clip3.dv \ +-track \ +clip1.dv \ +-transition \ +region:circle \ +composite.geometry="10%,10%:20%x20%;33=30%,70%:25%x25%;66=70%,30%:15%x15%;-1=70%,70%:20%x20%" \ +composite.out=100 \ +composite.softness=0.1 \ +a_track=0 \ +b_track=1 \ +in=0 \ +out=5000 \ +$* diff --git a/demo/mlt_clock_in_and_out b/demo/mlt_clock_in_and_out new file mode 100644 index 0000000..f19dd5e --- /dev/null +++ b/demo/mlt_clock_in_and_out @@ -0,0 +1,7 @@ +inigo \ +clip2.dv in=100 out=174 -blank 99 clip3.dv in=100 \ +-track \ +-blank 49 clip3.mpeg in=100 out=249 \ +-transition luma:luma1.pgm softness=0.5 in=50 out=74 a_track=0 b_track=1 \ +-transition luma:luma1.pgm softness=0.2 in=175 out=199 a_track=0 b_track=1 reverse=1 \ +$* diff --git a/demo/mlt_composite_transition b/demo/mlt_composite_transition new file mode 100644 index 0000000..cc38ec8 --- /dev/null +++ b/demo/mlt_composite_transition @@ -0,0 +1,7 @@ +inigo \ +clip1.dv out=74 \ +-track \ +-blank 49 clip2.mpeg \ +-transition composite:57%,10%:33%x33% end=0%,0%:100%x100% progressive=1 distort=true in=50 out=74 a_track=0 b_track=1 \ +-transition mix:-1 in=50 out=74 a_track=0 b_track=1 \ +$* diff --git a/demo/mlt_effect_in_middle b/demo/mlt_effect_in_middle new file mode 100644 index 0000000..0fc3974 --- /dev/null +++ b/demo/mlt_effect_in_middle @@ -0,0 +1,4 @@ +inigo \ +clip1.mpeg in=100 out=500 \ +-filter greyscale in=100 out=199 \ +$* diff --git a/demo/mlt_fade_black b/demo/mlt_fade_black new file mode 100644 index 0000000..376fe86 --- /dev/null +++ b/demo/mlt_fade_black @@ -0,0 +1,9 @@ +inigo \ +colour:black out=199 \ +-track \ +clip3.mpeg in=100 out=299 \ +-transition luma in=0 out=49 a_track=0 b_track=1 \ +-transition luma in=150 out=199 a_track=0 b_track=1 reverse=1 \ +-filter volume in=0 out=49 track=1 gain=0 end=1.0 \ +-filter volume in=150 out=199 track=1 gain=1.0 end=0 \ +$* diff --git a/demo/mlt_fade_in_and_out b/demo/mlt_fade_in_and_out new file mode 100644 index 0000000..fbe1fa3 --- /dev/null +++ b/demo/mlt_fade_in_and_out @@ -0,0 +1,9 @@ +inigo \ +clip1.dv out=74 -blank 99 clip3.dv in=25 \ +-track \ +-blank 49 clip2.mpeg out=149 \ +-transition luma in=50 out=74 a_track=0 b_track=1 \ +-transition luma in=175 out=199 a_track=0 b_track=1 reverse=1 \ +-transition mix:-1 in=50 out=74 a_track=0 b_track=1 \ +-transition mix:-1 in=175 out=199 a_track=0 b_track=1 reverse=1 \ +$* diff --git a/demo/mlt_intro b/demo/mlt_intro new file mode 100644 index 0000000..4b00848 --- /dev/null +++ b/demo/mlt_intro @@ -0,0 +1,9 @@ +inigo \ +music1.ogg in=100 out=224 \ +-track \ +watermark1.png out=124 \ +clip3.mpeg \ +-mix 25 \ +-mixer luma resource=luma1.pgm softness=0.2 \ +-transition mix:-1 in=100 out=124 \ +$* diff --git a/demo/mlt_jcut b/demo/mlt_jcut new file mode 100644 index 0000000..a5d844e --- /dev/null +++ b/demo/mlt_jcut @@ -0,0 +1,10 @@ +inigo \ +-blank 49 \ +clip2.dv in=100 \ +-track \ +clip1.dv out=99 \ +-transition \ +mix start=0 end=1 in=49 out=50 a_track=1 b_track=0 \ +-transition \ +mix:1 in=51 out=99 a_track=1 b_track=0 \ +$* diff --git a/demo/mlt_lcut b/demo/mlt_lcut new file mode 100644 index 0000000..45f81dd --- /dev/null +++ b/demo/mlt_lcut @@ -0,0 +1,12 @@ +inigo \ +clip1.dv out=100 \ +-track \ +-blank 49 \ +clip2.dv in=100 \ +-transition \ +luma in=50 out=55 a_track=0 b_track=1 \ +-transition \ +mix:1 in=50 out=98 a_track=1 b_track=0 \ +-transition \ +mix start=1 end=0 in=99 out=100 a_track=1 b_track=0 \ +$* diff --git a/demo/mlt_levels b/demo/mlt_levels new file mode 100644 index 0000000..16bf9f5 --- /dev/null +++ b/demo/mlt_levels @@ -0,0 +1,5 @@ +inigo \ +*.dv \ +-filter gamma:1.5 \ +-filter volume normalise=-20db \ +$* diff --git a/demo/mlt_my_name_is b/demo/mlt_my_name_is new file mode 100644 index 0000000..bd6bc3f --- /dev/null +++ b/demo/mlt_my_name_is @@ -0,0 +1,10 @@ +inigo \ +clip3.dv \ +-track \ +"+My name is Inigo Montoya.txt" out=99 -blank 49 "+Prepare to die!.txt" out=99 \ +-track \ +-blank 74 "+You killed my father.txt" out=74 \ +-transition composite:50%,20%:5%x4% end=10%,20%:80%x12% distort=1 halign=centre valign=centre in=0 out=99 a_track=0 b_track=1 \ +-transition composite:0%,70%:100%x10% end=100%,70%:100%x10% in=75 out=149 a_track=0 b_track=2 \ +-transition composite:25%,25%:50%x50%! in=150 out=249 a_track=0 b_track=1 \ +$* diff --git a/demo/mlt_news b/demo/mlt_news new file mode 100644 index 0000000..37ccf6f --- /dev/null +++ b/demo/mlt_news @@ -0,0 +1,18 @@ +inigo \ +colour:black out=199 \ +-track \ +clip1.dv in=0 out=0 -repeat 99 clip1.dv \ +-track \ +clip2.dv out=199 \ +-track \ +pango: text=" Breaking News + MLT Rocks India" bgcolour=0xff000080 out=149 \ +pango: text=" Breaking News + MLT Rocks the World" bgcolour=0xff000080 out=349 \ +-transition mix:0.5 always_active=1 a_track=0 b_track=2 \ +-transition composite geometry=50%,15%:37.5%x40% a_track=0 b_track=1 in=0 out=174 \ +-transition composite geometry=10%,15%:37.5%x40% a_track=0 b_track=2 in=0 out=199 \ +-transition composite geometry="50%,15%:37.540%x40%;-1=0%,0%:100%x100%" a_track=0 b_track=1 in=174 out=199 distort=1 \ +-transition composite geometry=10%,65%:90%x20% a_track=0 b_track=3 in=0 out=199 \ +-transition composite geometry=10%,65%:90%x20% a_track=1 b_track=3 in=200 out=499 \ +$* diff --git a/demo/mlt_obscure b/demo/mlt_obscure new file mode 100644 index 0000000..0c0f838 --- /dev/null +++ b/demo/mlt_obscure @@ -0,0 +1,5 @@ +inigo \ +clip2.mpeg \ +-filter obscure:25%,25%:25%x25%:10x10 in=0 out=68 \ +-filter region:circle.png filter=obscure composite.start=55%,25%:12%x50% in=68 out=200 \ +$* diff --git a/demo/mlt_push b/demo/mlt_push new file mode 100644 index 0000000..bcbc8bf --- /dev/null +++ b/demo/mlt_push @@ -0,0 +1,18 @@ +inigo \ +-blank 49 colour:black out=25 -blank 999 \ +-track \ +clip3.dv in=200 out=275 \ +-track \ +-blank 49 \ +clip2.dv in=200 \ +-transition \ +composite in=50 out=75 a_track=0 b_track=1 \ +start=0,0:100%x100%:100 \ +end=100%,0:100%x100%:100 \ +-transition \ +composite in=50 out=75 a_track=0 b_track=2 \ +start=-100%,0:100%x100%:100 \ +end=0,0:100%x100%:100 \ +-transition \ +mix:-1 in=50 out=75 a_track=1 b_track=2 \ +$* diff --git a/demo/mlt_slideshow b/demo/mlt_slideshow new file mode 100644 index 0000000..efe0d06 --- /dev/null +++ b/demo/mlt_slideshow @@ -0,0 +1,4 @@ +inigo \ +Scotland/.all.jpg ttl=75 \ +-filter luma:luma1.pgm luma.softness=0.1 luma.invert=0 \ +$* diff --git a/demo/mlt_slideshow_black b/demo/mlt_slideshow_black new file mode 100644 index 0000000..ddbf595 --- /dev/null +++ b/demo/mlt_slideshow_black @@ -0,0 +1,3 @@ +inigo Scotland/.all.jpg ttl=100 \ +-filter watermark:colour:black reverse=1 composite.geometry="15%,15%:10%,10%;0.1625=0,0:100%x100%;-.1625=;-1=70%,70%:10%x10%" composite.mirror_off=1 composite.cycle=100 composite.fill=1 composite.valign=c composite.halign=c \ +$* diff --git a/demo/mlt_squeeze b/demo/mlt_squeeze new file mode 100644 index 0000000..a56780c --- /dev/null +++ b/demo/mlt_squeeze @@ -0,0 +1,9 @@ +inigo \ +clip1.dv out=124 clip2.dv out=149 clip3.dv in=75 out=224 clip1.dv \ +-track \ +-blank 99 colour:black out=49 -blank 99 colour:black out=49 -blank 99 colour:black out=49 \ +-group progressive=1 distort=1 \ +-transition composite geometry="0%,0%:100%x100%;25=50%,0%:5%x100%;-1=0%,0%:100%x100%" a_track=1 b_track=0 in=100 out=149 \ +-transition composite geometry="0%,0%:100%x100%;25=0%,50%:100%x5%;-1=0%,0%:100%x100%" a_track=1 b_track=0 in=250 out=299 \ +-transition composite geometry="0%,0%:100%x100%;25=100%,0%:5%x100%;-1=0%,0%:100%x100%" a_track=1 b_track=0 in=400 out=449 \ +$* diff --git a/demo/mlt_squeeze_box b/demo/mlt_squeeze_box new file mode 100644 index 0000000..dfa0ced --- /dev/null +++ b/demo/mlt_squeeze_box @@ -0,0 +1,9 @@ +inigo \ +clip1.dv out=124 clip2.dv out=149 clip3.dv in=75 out=224 clip1.dv \ +-track \ +-blank 99 colour:black out=49 -blank 99 colour:black out=49 -blank 99 colour:black out=49 \ +-group progressive=1 \ +-transition composite:0%,0%:100%x100% key[25]=50%,0%:5%x100% end=0%,0%:100%x100% a_track=1 b_track=0 in=100 out=149 \ +-transition composite:0%,0%:100%x100% key[25]=0%,50%:100%x5% end=0%,0%:100%x100% a_track=1 b_track=0 in=250 out=299 \ +-transition composite:0%,0%:100%x100% key[25]=100%,0%:5%x100% end=0%,0%:100%x100% a_track=1 b_track=0 in=400 out=449 \ +$* diff --git a/demo/mlt_ticker b/demo/mlt_ticker new file mode 100644 index 0000000..f6a8329 --- /dev/null +++ b/demo/mlt_ticker @@ -0,0 +1,15 @@ +inigo \ +clip1.dv out=299 \ +-track \ +colour:black out=299 \ +-track \ +"+The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog..txt" \ +out=299 \ +-transition \ +composite a_track=0 b_track=1 out=299 distort=1 \ +start=0,70%:100%x64:100 \ +-transition \ +composite a_track=0 b_track=2 out=299 titles=1 \ +start=100%,70%:999%x20% \ +end=-299%,70%:999%x20% \ +$* diff --git a/demo/mlt_title_over_gfx b/demo/mlt_title_over_gfx new file mode 100644 index 0000000..e9b7f66 --- /dev/null +++ b/demo/mlt_title_over_gfx @@ -0,0 +1,25 @@ +inigo \ + watermark1.png out=9999 \ +-track \ + "+title over gfx.txt" fgcolour=0x000000ff \ +-track \ + clip1.dv \ +-transition \ + composite start=30%,20%:40%x60% \ + in=50 \ + out=199 \ + a_track=0 \ + b_track=1 \ + distort=1 \ +-transition \ + composite:0%,75%:100%x20%:0 \ + in=50 \ + out=199 \ + a_track=2 \ + b_track=0 \ + key[24]=0%,75%:100%x20%:100 \ + key[-25]=0%,75%:100%x20%:100 \ + luma=luma1.pgm \ + end=0%,75%:100%x20%:0 \ + distort=1 \ +$* diff --git a/demo/mlt_titleshadow_watermark b/demo/mlt_titleshadow_watermark new file mode 100644 index 0000000..79ef8ae --- /dev/null +++ b/demo/mlt_titleshadow_watermark @@ -0,0 +1,10 @@ +inigo \ +"+hello~world.txt" align=1 out=1000 \ +-track "+hello~world.txt" align=1 out=1000 fgcolour=0x000000ff \ +-track watermark1.png out=1000 \ +-track clip3.dv \ +-filter greyscale track=2 \ +-transition composite:21%,11%:100%x100%:50 end=61%,41%:100%x100% out=99 a_track=3 b_track=1 \ +-transition composite:20%,10%:100%x100% end=60%,40%:100%x100% out=99 a_track=3 b_track=0 \ +-transition composite:85%,80%:10%x10%:30 out=1000 a_track=3 b_track=2 \ +$* diff --git a/demo/mlt_voiceover b/demo/mlt_voiceover new file mode 100644 index 0000000..338f4d8 --- /dev/null +++ b/demo/mlt_voiceover @@ -0,0 +1,36 @@ +inigo \ +"+voice over demo.txt" \ + font="Sans Bold 72" \ + fgcolour=0x00000000 \ + bgcolour=0xff9933aa \ + pad=10 \ +-track music1.ogg \ +-track clip1.dv out=149 clip2.mpeg \ +-transition \ + mix:0.0 \ + end=0.6 \ + in=75 \ + out=99 \ + a_track=2 \ + b_track=1 \ +-transition \ + mix:0.6 \ + in=100 \ + out=299 \ + a_track=2 \ + b_track=1 \ +-transition \ + mix:0.6 \ + end=0.0 \ + in=300 \ + out=324 \ + a_track=2 \ + b_track=1 \ +-transition \ + composite:0%,80%:100%x20% \ + distort=1 \ + in=100 \ + out=299 \ + a_track=2 \ + b_track=0 \ +$* diff --git a/demo/mlt_watermark b/demo/mlt_watermark new file mode 100644 index 0000000..b832d5b --- /dev/null +++ b/demo/mlt_watermark @@ -0,0 +1,6 @@ +inigo \ +clip2.dv out=1000 \ +-track \ +watermark1.png out=1000 \ +-transition composite fill=1 in=0 out=1000 a_track=0 b_track=1 geometry=85%,5%:10%x10% \ +$* diff --git a/demo/new.westley b/demo/new.westley new file mode 100644 index 0000000..c623b3e --- /dev/null +++ b/demo/new.westley @@ -0,0 +1,51 @@ + + + + + + clip2.mpeg + + + + + + + + clip3.mpeg + + + + + + greyscale + + + luma + + + + + + + + + + + + + + + + + + + + + luma + + + 1 + + + + diff --git a/demo/pango.westley b/demo/pango.westley new file mode 100644 index 0000000..4684d48 --- /dev/null +++ b/demo/pango.westley @@ -0,0 +1,34 @@ + + + + clip1.dv + + + pango + +.txt + GJ-TTAvantika 36 + 1 + 0xffffddff + 0x8c101080 + 8 + + + + + + + + + composite + 1 + 0 + -70%,65%:100%x35%:0 + 0,65%:100%x35%:100 + 0,65%:100%x35%:100 + 0,65%:100%x35%:0 + centre + centre + + + diff --git a/demo/svg.westley b/demo/svg.westley new file mode 100644 index 0000000..a41ff4a --- /dev/null +++ b/demo/svg.westley @@ -0,0 +1,50 @@ + + + + + + + + +]> + + + + + pixbuf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/watermark1.png b/demo/watermark1.png new file mode 100644 index 0000000..5a961f8 Binary files /dev/null and b/demo/watermark1.png differ diff --git a/docs/TODO b/docs/TODO new file mode 100644 index 0000000..290f729 --- /dev/null +++ b/docs/TODO @@ -0,0 +1 @@ +Review and documentation update. The review will yield a TODO with more tasks. diff --git a/docs/dvcp.txt b/docs/dvcp.txt new file mode 100644 index 0000000..18da20f --- /dev/null +++ b/docs/dvcp.txt @@ -0,0 +1,339 @@ +Miracle Control Protocol (DVCP) Reference Documentation + +Copyright (C) 2004 Ushodaya Enterprised Limited +Author: Dan Dennedy +Last Revision: 2004-03-20 + + +General Format +-------------- + DVCP is an ASCII-based request/response TCP protocol much like FTP and + inspired by the SGI MVCP (Multiport Video Computer Protocol). Each + command is three to eight characters long followed by zero or more + arguments. Every item (command or argument) in the request is delimited + by a space and terminated with a new line. Arguments that contain spaces + must be surrounded by double quotation marks. The new line must contain + a line feed optionally preceeded by a carriage return. There are no + request header lines or body. + + +Response Codes +-------------- + Responses consist of a numeric result code followed by a space folowed + by a brief textual description of the result. No quoting is applied to + descriptions regardless if it contains spaces. The result codes are + grouped by the hundreds into general categories of responses. Anything + in the 200-299 range is considered a success and anything 300 and above + is an error or exception. Most responses do not contain a body except + some of the success results that report information and sometimes the + 500 Server Error returns specific information. + + A 200 result code contains no body. + A 201 result code contains one or more lines in the body, and an empty + line terminates the response. + A 202 result code contains only a single response line in the body. + + Errors in the 400 range indicate a normally handled error where the + command could not perform its action due to protocol syntax errors or + problems with validation of one or more of the arguments. This usually + indicates that the client is responsible for performing an illegal + request. + + Errors in the 500 range indicate a server error or exception. + + The following is a list of response codes and their descriptions: + 200 OK + 201 OK + 202 OK + 400 Unknown command + 401 Operation timed out + 402 Argument missing + 403 Unit not found + 404 Failed to locate or open clip + 405 Argument value out of range + 500 Server Error + + +Establishing a Connection +------------------------- + One can connect to the miracle server using telnet or a custom client, + preferrably one developed using the valerie client API. The default port + is 5250. Connections can be broken at will or use the BYE command to + request the server to terminate the connection. + + +General Command Information +--------------------------- + + All commands are case insensitive. Arguments may or may not be case + sensitive. There are two categories of commands: global and unit. Global + commands operate at the server level. Unit commands address a specific + unit. miracle is a multi-unit system. Units are named as U? where ? + is the unit number, for example, U0. As units are added to the server, + the unit number increases; the first unit is U0. + + The command HELP lists all commands known to the server with a brief + description of their purpose and arguments. Most commands take zero or + one argument outside of the unit name. Sometimes an argument is + optional, and an optional argument always follows required arguments. + All units command required a unit name argument. + + {} = required argument + [] = optional argument + () = one of a set of pre-defined values + + +Global Commands +--------------- + +HELP + List the commands and their brief description. + +BYE + Close the connection. + +SHUTDOWN + Shutdown the server and all client connections. + +SET {key=value} + Set a global server configuration property. + Currently, the only planned key is "root" to set the base directory + path for the CLS and LOAD commands. The default root value is /. + +GET {key} + Get the current value of a configuration property. + The value is returned by itself in the body of the response. + +CLS {path} + List the clips and subdirectories at {path} on the server. + Only subdirectories, non-hidden regular files, symbolic links, and NFS + shares are supported. + The response body contains one line per item. + The name of the subdirectory/file is always surrounded by double + quotation marks in case it contains spaces. + Subdirectories are listed before files and have a trailing / in their + name. + File entries have a size value in bytes in the second column position. + +RUN {file} + Process the commands in a file located on the server. + Commands are executed one after the other with no delay until the end + of file is reached or a command returns a response code not in the 200 + range. + The response body contains each command sent along with its arguments, + followed by each command's response status code and response body. + + +STATUS + Responds with the output of USTA for each unit and accepts no further + input. Each time the state of the unit changes, a new row is returned by + the server containing the state of the unit. + +Unit Management + + The following global commands manage the DV units within the server. + Currently there is a maximum of four units, and units can not be + removed. Each unit may be in an online or offline state. Offline units + can not be used, and any unit commands issued against an offline unit + results in a 403 response. + +NLS + * NOT IMPLEMENTED IN MIRACLE YET * + + +UADD mlt-consumer[:argument] + Add a unit based upon the mlt-consumer id and optional constructor + argument. + If the consumer is not found, then it still added but in an + offline manner. Later, by adding the device to the bus, the unit will + automatically become online. + The response body contains the name of the new unit: U0, U1, U2, or U3. + Channel is an optional setting. + +ULS + List the units. + The response body contains a space-delimited row for each unit in the + server containing the following columns: + - unit name (one of U0, U1, U2, or U3) + - mlt-consumer[:argument] from uadd + - 1394 node GUID (defunt - always 0 with miracle for now) + - online flag (1 = online, 0 = offline) + +SHUTDOWN + Shutdown the server. + + +Unit Commands +------------- + + The first argument of any unit command is the unit name (U0 - U3). A + unit must be loaded with a file before it can play anything. A "clip" + refers to the presence of a file loaded into the unit. A clip can + contain an in and out point to set the playback region. The default in + point is 0, and the default out point is the number of frames in the + file minus one. Therefore, all frame positions are zero-based. + +USET {unit} {key=value} + Set a unit's configuration property. + Key is one of the following: eof, points. + + Property "eof" determines what the playback engine does when it reaches + the end of a clip. The eof property takes one of the following values: + stop, loop, continue or pause. The default is pause. + + Property "points" determines whether the playback engine restricts the + playback region to the in and out points. It takes one of the following + values: use, ignore. + +UGET {unit} {key} + Get a unit's configuration property. + Key is one of the following: eof, points. + The response body contains only the key's value. See USET for information + about each property. + +LIST {unit} + List the clips associated to the unit. + The response body consists of two sections - the first section is a single row + containing the generation number of the playlist associated to the unit (an + integer starting from 0 which is incremented on each action which changes the + playlist). The second sections contais a space-delimited row for each clip in the + units playlistcontaining the following columns: + - clip index (starts from 0) + - file name + - in point + - out point + - real length of the files + - calculated length of file + When USET points=use is specified (default), the calculated size is (out-in)+1. + When points are ignored, the real length of the file is returned. + +LOAD {unit} {filename} [in out] + Load a clip into the unit. + Optionally set the in and out points to the specified absolute frame numbers. + Sets the current position to the first frame in the clip. + Preface the filename with '!' to tell the disk reader thread to remove only + duplicate frames from the tail of its buffer queue (from a previously loaded + and playing clip). Otherwise, miracle flushes all of its buffers upon LOAD + to make the effect of LOAD instantaneous. The LOAD !, USET eof=pause, and + extended USTA information can be used for client-side playlists (see the + demo programs). + +APND {unit} {filename} [in out] + Append a clip onto the unit's playlist. + Optionally set the in and out points to the specified absolute frame numbers. + +INSERT {unit} {filename} [ [+|-]clip [ in out ] ] + Insert a clip into the units playlist at the specified clip index or relative + to the currently playing clip index. + +REMOVE {unit} [ [+|-]clip ] + Removes a clip from the specified clip index or position relative to the + currently playing clip index. + +CLEAN {unit} + Removes all by the playing clip. + +WIPE {unit} + Removes all clips before the playing clip. + +MOVE {unit} [+|-]clip [ [+|-]clip ] + Move a clip in the playlist to position specified or position relative to the + currently playing clip. + +PLAY {unit} [speed] + Commence unit playback from the current position. + The default speed is 100% if not specified. + Speed is represented as a percentage value multiplied by 10. Therefore + the default playback speed is 1000 (1X or 100%), 2X is 2000. + Negative speed values play in reverse. + +STOP {unit} + Terminate the unit playback resulting in no video being sent. + +PAUSE {unit} + Pause the unit playback causing the current frame position to he held + indefinitely. + +REW {unit} + Rewind the unit. + If the unit it playing, then REW sets the playback speed to -2000 + (200%). + If the unit is stopped, then the frame position is reset to the first + frame. First frame depends upon the "points" unit configuration property + and whether an in point has been established for the clip using the SIN + command. + Set the currently loaded clip's in point. + Frame is zero-based and absolute. It is not dependent upon the clip's + current in point. + A frame-number of -1, resets the in point to 0. + +FF {unit} + Fast forward the unit. + If the unit it playing, then FF sets the playback speed to 2000 (200% + in reverse). + If the unit is stopped, then the frame position is reset to the first + frame. First frame depends upon the "points" unit configuration property + and whether an in point has been established for the clip using the SIN + command. + +STEP {unit} {number-of-frames} + Adjust the current frame position by the number of frames specified. + Number-of-frames can accept positive or negative values. + +GOTO {unit} {frame-number} [ [+|-]clip ] + Set the current frame position to frame-number. + Frame-number is zero-based and absolute within the clip, which means it is + relative to the file beginning and not the clip in point. + It does not alter the playback status of the unit. + +SIN {unit} {frame-number} [ [+|-]clip ] + Set the currently loaded clip's in point. + The in point is the logical starting frame of the clip. + Frame is zero-based and absolute. It is not dependent upon the clip's + current in point. + A frame-number of -1, resets the in point to 0. + +SOUT {unit} {frame-number} [ [+|-]clip ] + Set the currently loaded clip's out point. + The out point is the logical last frame of the clip. + Frame is zero-based and absolute. It is not dependent upon the clip's + current out point. + A frame-number of -1, resets the out point to the number of frames in + the file minus 1. + +USTA {unit} + Get the unit status report. + The response body contains the following fields delimited by spaces: + - unit number: U0, U1, U2, or U3 without the "U" prefix + - mode: (offline|not_loaded|playing|stopped|paused|disconnected|unknown) + "unknown" means the unit has not been added + "disconnected" means the server has closed the connection to the client. + - current clip name: filename + - current position: in absolute frame number units + - speed: playback rate in (percent * 10) + - fps: frames-per-second of loaded clip + - current in-point: starting frame number + - current out-point: ending frame number + - length of the clip + - buffer tail clip name: filename + - buffer tail position: in absolute frame number units + - buffer tail in-point: starting frame number + - buffer tail out-point: ending frame number + - buffer tail length: length of clip in buffer tail + - seekable flag: indicates if the current clip is seekable (relates to head) + - playlist generation number + - current clip index (relates to head) + + The status contains information based not only on the current frame being + output (current above) but also based upon the most recent frame read by + the disk reader thread and added to the tail of the input buffer queue + (buffer tail above). + +XFER {unit} {target-unit} + Transfer the unit's clip to the target unit. + The clip inherently includes the in- and out-point information. + The target unit's "points" configuration property is set to "use." + + + + diff --git a/docs/framework.txt b/docs/framework.txt new file mode 100644 index 0000000..1c2bafb --- /dev/null +++ b/docs/framework.txt @@ -0,0 +1,1341 @@ +Framework Documentation + +Copyright (C) 2004 Ushodaya Enterprises Limited +Author: Charles Yates +Last Revision: 2004-10-08 + + +MLT FRAMEWORK +------------- + +Preamble: + + MLT is a multimedia framework designed for television broadcasting. As such, + it provides a pluggable architecture for the inclusion of new audio/video + sources, filters, transitions and playback devices. + + The framework provides the structure and utility functionality on which + all of the MLT applications and services are defined. + + On its own, the framework provides little more than 'abstract classes' and + utilities for managing resources, such as memory, properties, dynamic object + loading and service instantiation. + + This document is split roughly into 3 sections. The first section provides a + basic overview of MLT, the second section shows how it's used and the final + section shows structure and design, with an emphasis on how the system is + extended. + + +Target Audience: + + This document is provided as a 'road map' for the framework and should be + considered mandatory reading for anyone wishing to develop code at the MLT + level. + + This includes: + + 1. framework maintainers; + 2. module developers; + 3. application developers; + 4. anyone interested in MLT. + + The emphasis of the document is in explaining the public interfaces, as + opposed to the implementation details. + + It is not required reading for the MLT client/server integration - please + refer to valerie.txt and dvcp.txt for more details on this area. + + +SECTION 1 - BASIC OVERVIEW +-------------------------- + +Basic Design Information: + + MLT is written in C. + + The framework has no dependencies other than the standard C99 and POSIX + libraries. + + It follows a basic Object Oriented design paradigm, and as such, much of the + design is loosely based on the Producer/Consumer design pattern. + + It employs Reverse Polish Notation for the application of audio and video FX. + + The framework is designed to be colour space neutral - the currently + implemented modules, however, are very much 8bit YUV422 oriented. In theory, + the modules could be entirely replaced. + + A vague understanding of these terms is assumed throughout the remainder of + this document. + + +Structure and Flow: + + The general structure of an MLT 'network' is simply the connection of a + 'producer' to a 'consumer': + + +--------+ +--------+ + |Producer|-->|Consumer| + +--------+ +--------+ + + A typical consumer requests MLT Frame objects from the producer, does + something with them and when finished with a frame, closes it. + + /\ A common confusion with the producer/consumer terminology used here is + /!!\ that a consumer may 'produce' something. For example, the libdv consumer + \!!/ produces DV and the libdv producer seems to consume DV. However, the + \/ naming conventions refer only to producers and consumers of MLT Frames. + + To put it another way - a producer produces MLT Frame objects and a consumer + consumes MLT Frame objects. + + An MLT Frame essentially provides an uncompressed image and its associated + audio samples. + + Filters may also be placed between the producer and the consumer: + + +--------+ +------+ +--------+ + |Producer|-->|Filter|-->|Consumer| + +--------+ +------+ +--------+ + + A service is the collective name for producers, filters, transitions and + consumers. + + The communications between a connected consumer and producer or service are + carried out in 3 phases: + + * get the frame + * get the image + * get the audio + + MLT employs 'lazy evaluation' - the image and audio need not be extracted + from the source until the get image and audio methods are invoked. + + In essence, the consumer pulls from what it's connected to - this means that + threading is typically in the domain of the consumer implementation and some + basic functionality is provided on the consumer class to ensure realtime + throughput. + + +SECTION 2 - USAGE +----------------- + +Hello World: + + Before we go in to the specifics of the framework architecture, a working + example of usage is provided. + + The following simply provides a media player: + + #include + #include + #include + + int main( int argc, char *argv[] ) + { + // Initialise the factory + if ( mlt_factory_init( NULL ) == 0 ) + { + // Create the default consumer + mlt_consumer hello = mlt_factory_consumer( NULL, NULL ); + + // Create via the default producer + mlt_producer world = mlt_factory_producer( NULL, argv[ 1 ] ); + + // Connect the producer to the consumer + mlt_consumer_connect( hello, mlt_producer_service( world ) ); + + // Start the consumer + mlt_consumer_start( hello ); + + // Wait for the consumer to terminate + while( !mlt_consumer_is_stopped( hello ) ) + sleep( 1 ); + + // Close the consumer + mlt_consumer_close( hello ); + + // Close the producer + mlt_producer_close( world ); + + // Close the factory + mlt_factory_close( ); + } + else + { + // Report an error during initialisation + fprintf( stderr, "Unable to locate factory modules\n" ); + } + + // End of program + return 0; + } + + This is a simple example - it doesn't provide any seeking capabilities or + runtime configuration options. + + The first step of any MLT application is the factory initialisation - this + ensures that the environment is configured and MLT can function. The factory + is covered in more detail below. + + All services are instantiated via the factories, as shown by the + mlt_factory_consumer and mlt_factory_producer calls above. There are similar + factories for filters and transitions. There are details on all the standard + services in services.txt. + + The defaults requested here are a special case - the NULL usage requests + that we use the default producers and consumers. + + The default producer is "fezzik". This producer matches file names to + locate a service to use and attaches 'normalising filters' (such as scalers, + deinterlacers, resamplers and field normalisers) to the loaded content - + these filters ensure that the consumer gets what it asks for. + + The default consumer is "sdl". The combination of fezzik and sdl will + provide a media player. + + In this example, we connect the producer and then start the consumer. We + then wait until the consumer is stopped (in this case, by the action of the + user closing the SDL window) and finally close the consumer, producer and + factory before exiting the application. + + Note that the consumer is threaded - waiting for an event of some sort is + always required after starting and before stopping or closing the consumer. + + Also note, you can override the defaults as follows: + + $ MLT_CONSUMER=westley ./hello file.avi + + This will create a westley xml document on stdout. + + $ MLT_CONSUMER=westley MLT_PRODUCER=avformat ./hello file.avi + + This will play the video using the avformat producer directly, thus it will + bypass the normalising functions. + + $ MLT_CONSUMER=libdv ./hello file.avi > /dev/dv1394 + + This might, if you're lucky, do on the fly, realtime conversions of file.avi + to DV and broadcast it to your DV device. + + +Factories: + + As shown in the 'Hello World' example, factories create service objects. + + The framework itself provides no services - they are provided in the form of + a plugin structure. A plugin is organised in the form of a 'module' and a + module can provide many services of different types. + + Once the factory is initialised, all the configured services are available + for use. + + The complete set of methods associated to the factory are as follows: + + int mlt_factory_init( char *prefix ); + const char *mlt_factory_prefix( ); + char *mlt_environment( char *name ); + mlt_producer mlt_factory_producer( char *name, void *input ); + mlt_filter mlt_factory_filter( char *name, void *input ); + mlt_transition mlt_factory_transition( char *name, void *input ); + mlt_consumer mlt_factory_consumer( char *name, void *input ); + void mlt_factory_close( ); + + The mlt_factory_prefix returns the path to the location of the installed + modules directory. This can be specified in the mlt_factory_init call + itself, or it can be specified via the MLT_REPOSITORY environment variable, + or in the absence of either of those, it will default to the install + prefix/shared/mlt/modules. + + The mlt_environment provides read only access to a collection of name=value + pairs as shown in the following table: + + +------------------+------------------------------------+------------------+ + |Name |Description |Values | + +------------------+------------------------------------+------------------+ + |MLT_NORMALISATION |The normalisation of the system |PAL or NTSC | + +------------------+------------------------------------+------------------+ + |MLT_PRODUCER |The default producer |"fezzik" or other | + +------------------+------------------------------------+------------------+ + |MLT_CONSUMER |The default consumer |"sdl" or other | + +------------------+------------------------------------+------------------+ + |MLT_TEST_CARD |The default test card producer |any producer | + +------------------+------------------------------------+------------------+ + + These values are initialised from the environment variables of the same + name. + + As shown above, a producer can be created using the 'default normalising' + producer, and they can also be requested by name. Filters and transitions + are always requested by name - there is no concept of a 'default' for these. + + +Service Properties: + + As shown in the services.txt document, all services have their own set of + properties than can be manipulated to affect their behaviour. + + In order to set properties on a service, we need to retrieve the properties + object associated to it. For producers, this is done by invoking: + + mlt_properties properties = mlt_producer_properties( producer ); + + All services have a similar method associated to them. + + Once retrieved, setting and getting properties can be done directly on this + object, for example: + + mlt_properties_set( properties, "name", "value" ); + + A more complete description of the properties object is found below. + + +Playlists: + + So far, we've shown a simple producer/consumer configuration - the next + phase is to organise producers in playlists. + + Let's assume that we're adapting the Hello World example, and wish to queue + a number of files for playout, ie: + + hello *.avi + + Instead of invoking mlt_factory_producer directly, we'll create a new + function called create_playlist. This function is responsible for creating + the playlist, creating each producer and appending to the playlist. + + mlt_producer create_playlist( int argc, char **argv ) + { + // We're creating a playlist here + mlt_playlist playlist = mlt_playlist_init( ); + + // We need the playlist properties to ensure clean up + mlt_properties properties = mlt_playlist_properties( playlist ); + + // Loop through each of the arguments + int i = 0; + for ( i = 1; i < argc; i ++ ) + { + // Create the producer + mlt_producer producer = mlt_factory_producer( NULL, argv[ i ] ); + + // Add it to the playlist + mlt_playlist_append( playlist, producer ); + + // Close the producer (see below) + mlt_producer_close( producer ); + } + + // Return the playlist as a producer + return mlt_playlist_producer( playlist ); + } + + Notice that we close the producer after the append. Actually, what we're + doing is closing our reference to it - the playlist creates its own reference + to the producer on append and insert, and it will close its reference + when the playlist is destroyed[*]. + + Note also that if you append multiple instances of the same producer, it + will create multiple references to it. + + Now all we need do is to replace these lines in the main function: + + // Create a normalised producer + mlt_producer world = mlt_factory_producer( NULL, argv[ 1 ] ); + + with: + + // Create a playlist + mlt_producer world = create_playlist( argc, argv ); + + and we have a means to play multiple clips. + + [*] This reference functionality was introduced in mlt 0.1.2 - it is 100% + compatable with the early mechanism of registering the reference and + destructor with the properties of the playlist object. + + +Filters: + + Inserting filters between the producer and consumer is just a case of + instantiating the filters, connecting the first to the producer, the next + to the previous filter and the last filter to the consumer. + + For example: + + // Create a producer from something + mlt_producer producer = mlt_factory_producer( ... ); + + // Create a consumer from something + mlt_consumer consumer = mlt_factory_consumer( ... ); + + // Create a greyscale filter + mlt_filter filter = mlt_factory_filter( "greyscale", NULL ); + + // Connect the filter to the producer + mlt_filter_connect( filter, mlt_producer_service( producer ), 0 ); + + // Connect the consumer to filter + mlt_consumer_connect( consumer, mlt_filter_service( filter ) ); + + As with producers and consumers, filters can be manipulated via their + properties object - the mlt_filter_properties method can be invoked and + properties can be set as needed. + + The additional argument in the filter connection is an important one as it + dictates the 'track' on which the filter operates. For basic producers and + playlists, there's only one track (0), and as you will see in the next + section, even multiple tracks have a single track output. + + +Attached Filters: + + All services can have attached filters. + + Consider the following example: + + // Create a producer + mlt_producer producer = mlt_factory_producer( NULL, clip ); + + // Get the service object of the producer + mlt_producer service = mlt_producer_service( producer ); + + // Create a filter + mlt_filter filter = mlt_factory_filter( "greyscale" ); + + // Create a playlist + mlt_playlist playlist = mlt_playlist_init( ); + + // Attach the filter to the producer + mlt_service_attach( producer, filter ); + + // Construct a playlist with various cuts from the producer + mlt_playlist_append_io( producer, 0, 99 ); + mlt_playlist_append_io( producer, 450, 499 ); + mlt_playlist_append_io( producer, 200, 399 ); + + // We can close the producer and filter now + mlt_producer_close( producer ); + mlt_filter_close( filter ); + + When this is played out, the greyscale filter will be executed for each frame + in the playlist which comes from that producer. + + Further, each cut can have their own filters attached which are executed after + the producer's filters. As an example: + + // Create a new filter + filter = mlt_factory_filter( "invert", NULL ); + + // Get the second 'clip' in the playlist + producer = mlt_playlist_get_clip( 1 ); + + // Get the service object of the clip + service = mlt_producer_service( producer ); + + // Attach the filter + mlt_service_attach( producer, filter ); + + // Close the filter + mlt_filter_close( filter ); + + Even the playlist itself can have an attached filter: + + // Create a new filter + filter = mlt_factory_filter( "watermark", "+Hello.txt" ); + + // Get the service object of the playlist + service = mlt_playlist_service( playlist ); + + // Attach the filter + mlt_service_attach( service, filter ); + + // Close the filter + mlt_filter_close( filter ); + + And, of course, the playlist, being a producer, can be cut up and placed on + another playlist, and filters can be attached to those cuts or on the new + playlist itself and so on ad nauseum. + + The main advantage of attached filters is that they remain attached and don't + suffer from the maintenance problems associated with items being inserted and + displacing calculated in/out points - this being a major issue if you + exclusively use the connect or insert detached filters in a multitrack field + (described below). + + +Introducing the Mix: + + The mix is the simplest way to introduce transitions between adjacent clips + on a playlist. + + Consider the following playlist: + + +-+----------------------+----------------------------+-+ + |X|A |B |X| + +-+----------------------+----------------------------+-+ + + Let's assume that the 'X' is a 'black clip' of 50 frames long. + + When you play this out, you'll get a 50 frames of black, abrupt cut into + A, followed by an abrupt cut into B, and finally into black again. + + The intention is to convert this playlist into something like: + + +-+---------------------+-+------------------------+-+ + |X|A |A|B |B| + |A| |B| |X| + +-+---------------------+-+------------------------+-+ + + Where the clips which refer to 2 clips represent a transition. Notice that + the representation of the second playlist is shorter than the first - this is + to be expected - a single transition of 50 frames between two clips will + reduce the playtime of the result by 50 frames. + + This is done via the use of the mlt_playlist_mix method. So, assuming you get + a playlist as shown in the original diagram, to do the first mix, you could do + something like: + + // Create a transition + mlt_transition transition = mlt_factor_transition( "luma", NULL ); + + // Mix the first and second clips for 50 + mlt_playlist_mix( playlist, 0, 50, transition ); + + // Close the transition + mlt_transition_close( transition ); + + This would give you the first transition, subsequently, you would apply a similar + technique to mix clips 1 and 2. Note that this would create a new clip on the + playlist, so the next mix would be between 3 and 4. + + As a general hint, to simplify the requirement to know the next clip index, + you might find the following simpler: + + // Get the number of clips on the playlist + int i = mlt_playlist_count( ); + + // Iterate through them in reverse order + while ( i -- ) + { + // Create a transition + mlt_transition transition = mlt_factor_transition( "luma", NULL ); + + // Mix the first and second clips for 50 + mlt_playlist_mix( playlist, i, 50, transition ); + + // Close the transition + mlt_transition_close( transition ); + } + + There are other techniques, like using the mlt_playlist_join between the + current clip and the newly created one (you can determine if a new clip was + created by comparing the playlist length before and after the mix call). + + Internally, the mlt_playlist_mix call generates a tractor and multitrack as + described below. Like the attached filters, the mix makes life very simple + when you're inserting items into the playlist. + + Also note that it allows a simpler user interface - instead of enforcing the + use of a complex multitrack object, you can do many operations on a single + track. Thus, additional tracks can be used to introduce audio dubs, mixes + or composites which are independently positioned and aren't affected by + manipulations on other tracks. But hey, if you want a bombastic, confusing + and ultimately frustrating traditional NLE experience, that functionality + is provided too ;-). + + +Practicalities and Optimisations: + + In the previous two sections I've introduced some powerful functionality + designed to simplify MLT usage. However, a general issue comes into this - + what happens when you introduce a transition between two cuts from the same + bit of video footage? + + Anyone who is familiar with video compression will be aware that seeking + isn't always without consequence from a performance point of view. So if + you happen to require two frames from the same clip for a transition, the + processing is going to be excessive and the result will undoubtedly be very + unpleasant, especially if you're rendering in realtime... + + So how do we get round this? + + Actually, it's very simple - you invoke mlt_producer_optimise on the top + level object after a modification and MLT will determine how to handle it. + Internally, it determines the maximum number of overlapping instances + throughout the object and creates clones and assigns clone indexes as + required. + + In the mix example above, you can simply call: + + // Optimise the playlist + mlt_producer_optimise( mlt_playlist_producer( playlist ) ); + + after the mix calls have be done. Note that this is automatically applied + to deserialised westleys. + + +Multiple Tracks and Transitions: + + MLT's approach to multiple tracks is governed by two requirements: + + 1) The need for a consumer and producer to communicate with one another via + a single frame; + 2) The desire to be able to serialise and manipulate a 'network' (or filter + graph if you prefer). + + We can visualise a multitrack in the way that an NLE presents it: + + +-----------------+ +-----------------------+ + 0: |a1 | |a2 | + +---------------+-+--------------------------+-+---------------------+ + 1: |b1 | + +------------------------------+ + + The overlapping areas of track 0 and 1 would (presumably) have some kind of + transition - without a transition, the frames from b1 and b2 would be shown + during the areas of overlap (ie: by default, the higher numbered track takes + precedence over the lower numbered track). + + MLT has a multitrack object, but it is not a producer in the sense that it + can be connected directly to a consumer and everything will work correctly. + A consumer would treat it precisely as it would a normal producer, and, in + the case of the multitrack above, you would never see anything from track 1 + other than the transitions between the clips - the gap between a1 and a2 + would show test frames. + + This happens because a consumer pulls one frame from the producer it's + connected to while a multitrack will provide one frame per track. + Something, somewhere, must ensure that all frames are pulled from the + multitrack and elect the correct frame to pass on. + + Hence, MLT provides a wrapper for the multitrack, which is called a + 'tractor', and its the tractors task to ensure that all tracks are pulled + evenly, the correct frame is output and that we have 'producer like' + behaviour. + + Thus, a multitrack is conceptually 'pulled' by a tractor as shown here: + + +----------+ + |multitrack| + | +------+ | +-------+ + | |track0|-|--->|tractor| + | +------+ | |\ | + | | | \ | + | +------+ | | \ | + | |track1|-|--->|---o---|---> + | +------+ | | / | + | | | / | + | +------+ | |/ | + | |track2|-|--->| | + | +------+ | +-------+ + +----------+ + + With a combination of the two, we can now connect multitracks to consumers. + The last non-test card will be retrieved and passed on. + + The tracks can be producers, playlists, or even other tractors. + + Now we wish to insert filters and transitions between the multitrack and the + tractor. We can do this directly by inserting filters directly between the + tractor and the multitrack, but this involves a lot of connecting and + reconnecting left and right producers and consumers, and it seemed only fair + that we should be able to automate that process. + + So in keeping with our agricultural theme, the concept of the 'field' was + born. We 'plant' filters and transitions in the field and the tractor pulls + the multitrack (think of a combine harvester :-)) over the field and + produces a 'bail' (sorry - kidding - frame :-)). + + Conceptually, we can see it like this: + + +----------+ + |multitrack| + | +------+ | +-------------+ +-------+ + | |track0|-|--->|field |--->|tractor| + | +------+ | | | |\ | + | | | filters | | \ | + | +------+ | | and | | \ | + | |track1|-|--->| transitions |--->|---o---|---> + | +------+ | | | | / | + | | | | | / | + | +------+ | | | |/ | + | |track2|-|--->| |--->| | + | +------+ | +-------------+ +-------+ + +----------+ + + So, we need to create the tractor first, and from that we obtain the + multitrack and field objects. We can populate these and finally + connect the tractor to a consumer. + + In essence, this is how it looks to the consumer: + + +-----------------------------------------------+ + |tractor +--------------------------+ | + | +----------+ | +-+ +-+ +-+ +-+ | | + | |multitrack| | |f| |f| |t| |t| | | + | | +------+ | | |i| |i| |r| |r| | | + | | |track0|-|--->| |l|- ->|l|- ->|a|--->|a|\| | + | | +------+ | | |t| |t| |n| |n| | | + | | | | |e| |e| |s| |s| |\ | + | | +------+ | | |r| |r| |i| |i| | \| + | | |track1|-|- ->| |0|--->|1|--->|t|--->|t|-|--o---> + | | +------+ | | | | | | |i| |i| | /| + | | | | | | | | |o| |o| |/ | + | | +------+ | | | | | | |n| |n| | | + | | |track2|-|- ->| | |- ->| |--->|0|- ->|1|/| | + | | +------+ | | | | | | | | | | | | + | +----------+ | +-+ +-+ +-+ +-+ | | + | +--------------------------+ | + +-----------------------------------------------+ + + An example will hopefully clarify this. + + Let's assume that we want to provide a 'watermark' to our hello world + example. We have already extended the example to play multiple clips, + and now we will place a text based watermark, reading 'Hello World' in + the top left hand corner: + + mlt_producer create_tracks( int argc, char **argv ) + { + // Create the tractor + mlt_tractor tractor = mlt_tractor_new( ); + + // Obtain the field + mlt_field field = mlt_tractor_field( tractor ); + + // Obtain the multitrack + mlt_multitrack multitrack = mlt_tractor_multitrack( tractor ); + + // Create a composite transition + mlt_transition transition = mlt_factory_transition( "composite", "10%,10%:15%x15%" ); + + // Create track 0 + mlt_producer track0 = create_playlist( argc, argv ); + + // Create the watermark track - note we NEED fezzik for scaling here + mlt_producer track1 = mlt_factory_producer( "fezzik", "pango" ); + + // Get the length of track0 + mlt_position length = mlt_producer_get_playtime( track0 ); + + // Set the properties of track1 + mlt_properties properties = mlt_producer_properties( track1 ); + mlt_properties_set( properties, "text", "Hello\nWorld" ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", length - 1 ); + mlt_properties_set_position( properties, "length", length ); + mlt_properties_set_int( properties, "a_track", 0 ); + mlt_properties_set_int( properties, "b_track", 1 ); + + // Now set the properties on the transition + properties = mlt_transition_properties( transition ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", length - 1 ); + + // Add our tracks to the multitrack + mlt_multitrack_connect( multitrack, track0, 0 ); + mlt_multitrack_connect( multitrack, track1, 1 ); + + // Now plant the transition + mlt_field_plant_transition( field, transition, 0, 1 ); + + // Close our references + mlt_producer_close( track0 ); + mlt_producer_close( track1 ); + mlt_transition_close( transition ); + + // Return the tractor + return mlt_tractor_producer( tractor ); + } + + Now all we need do is to replace these lines in the main function: + + // Create a playlist + mlt_producer world = create_playlist( argc, argv ); + + with: + + // Create a watermarked playlist + mlt_producer world = create_tracks( argc, argv ); + + and we have a means to play multiple clips with a horribly obtrusive + watermark - just what the world needed, right? ;-) + + Incidentally, the same thing could be achieved with the more trivial + watermark filter inserted between the producer and the consumer. + + +SECTION 3 - STRUCTURE AND DESIGN +-------------------------------- + +Class Hierarchy: + + The mlt framework consists of an OO class hierarchy which consists of the + following public classes and abstractions: + + mlt_properties + mlt_frame + mlt_service + mlt_producer + mlt_playlist + mlt_tractor + mlt_filter + mlt_transition + mlt_consumer + mlt_deque + mlt_pool + mlt_factory + + Each class defined above can be read as extending the classes above and to + the left. + + The following sections describe the properties, stacking/queuing and memory + pooling functionality provided by the framework - these are key components + and a basic understanding of these is required for the remainder of the + documentation. + + +mlt_properties: + + The properties class is the base class for the frame and service classes. + + It is designed to provide an efficient lookup table for various types of + information, such as strings, integers, floating points values and pointers + to data and data structures. + + All properties are indexed by a unique string. + + The most basic use of properties is as follows: + + // 1. Create a new, empty properties set; + mlt_properties properties = mlt_properties_new( ); + + // 2. Assign the value "world" to the property "hello"; + mlt_properties_set( properties, "hello", "world" ); + + // 3. Retrieve and print the value of "hello"; + printf( "%s\n", mlt_properties_get( properties, "hello" ) ); + + // 4. Reassign "hello" to "world!"; + mlt_properties_set( properties, "hello", "world!" ); + + // 5. Retrieve and print the value of "hello"; + printf( "%s\n", mlt_properties_get( properties, "hello" ) ); + + // 6. Assign the value "0" to "int"; + mlt_properties_set( properties, "int", "0" ); + + // 7. Retrieve and print the integer value of "int"; + printf( "%d\n", mlt_properties_get_int( properties, "int" ) ); + + // 8. Assign the integer value 50 to "int2"; + mlt_properties_set_int( properties, "int2", 50 ); + + // 9. Retrieve and print the double value of "int2"; + printf( "%s\n", mlt_properties_get( properties, "int2" ) ); + + Steps 2 through 5 demonstrate that the "name" is unique - set operations on + an existing "name" change the value. They also free up memory associated to + the previous value. Note that it also possible to change type in this way + too. + + Steps 6 and 7 demonstrate that the properties object handles deserialisation + from strings. The string value of "0" is set, the integer value of 0 is + retrieved. + + Steps 8 and 9 demonstrate that the properties object handles serialisation + to strings. + + To show all the name/value pairs in a properties, it is possible to iterate + through them: + + for ( i = 0; i < mlt_properties_count( properties ); i ++ ) + printf( "%s = %s\n", mlt_properties_get_name( properties, i ), + mlt_properties_get_value( properties, i ) ); + + Note that properties are retrieved in the order in which they are set. + + Properties are also used to hold pointers to memory. This is done via the + set_data call: + + uint8_t *image = malloc( size ); + mlt_properties_set_data( properties, "image", image, size, NULL, NULL ); + + In this example, we specify that the pointer can be retrieved from + properties by a subsequent request to get_data: + + image = mlt_properties_get_data( properties, "image", &size ); + + or: + + image = mlt_properties_get_data( properties, "image", NULL ); + + if we don't wish to retrieve the size. + + Two points here: + + 1) The allocated memory remains after the properties object is closed unless + you specify a destructor. In the case above, this can be done with: + + mlt_properties_set_data( properties, "image", image, size, free, NULL ); + + When the properties are closed, or the value of "image" is changed, the + destructor is invoked. + + 2) The string value returned by mlt_properties_get is NULL. Typically, you + wouldn't wish to serialise an image as a string, but other structures + might need such functionality - you can specify a serialiser as the last + argument if required (declaration is char *serialise( void * )). + + Properties also provides some more advanced usage capabilities. + + It has the ability to inherit all serialisable values from another properties + object: + + mlt_properties_inherit( this, that ); + + It has the ability to mirror properties set on this on another set of + properties: + + mlt_properties_mirror( this, that ); + + After this call, all serialisable values set on this are passed on to that. + + +mlt_deque: + + Stacks and queues are essential components in the MLT framework. Being of a + lazy disposition, we elected to implement a 'Double Ended Queue' (deque) - + this encapsulates the functionality of both. + + The API of the deque is defined as follows: + + mlt_deque mlt_deque_init( ); + int mlt_deque_count( mlt_deque this ); + int mlt_deque_push_back( mlt_deque this, void *item ); + void *mlt_deque_pop_back( mlt_deque this ); + int mlt_deque_push_front( mlt_deque this, void *item ); + void *mlt_deque_pop_front( mlt_deque this ); + void *mlt_deque_peek_back( mlt_deque this ); + void *mlt_deque_peek_front( mlt_deque this ); + void mlt_deque_close( mlt_deque this ); + + The stacking operations are used in a number of places: + + * Reverse Polish Notation (RPN) image and audio operations + * memory pooling + + The queuing operations are used in: + + * the consumer base class; + * consumer implementations may require further queues. + + +mlt_pool: + + The MLT framework provides memory pooling capabilities through the mlt_pool + API. Once initilialised, these can be seen as a straightforward drop in + replacement for malloc/realloc/free functionality. + + The background behind this API is that malloc/free operations are + notoriously inefficient, especially when dealing with large blocks of memory + (such as an image). On linux, malloc is optimised for memory allocations + less than 128k - memory blocks allocated of these sizes or less are retained + in the process heap for subsequent reuse, thus bypassing the kernel calls + for repeated allocation/frees for small blocks of memory. However, blocks of + memory larger than that require kernel calls and this has a detrimental + impact on performance. + + The mlt_pool design is simply to hold a list of stacks - there is one stack + per 2^n bytes (where n is between 8 and 31). When an alloc is called, the + requested size is rounded to the next 2^n, the stack is retrieved for that + size, and an item is popped or created if the stack is empty. + + Each item has a 'header', situated immediately before the returned address - + this holds the 'stack' to which the item belongs. + + When an item is released, we retrieve the header, obtain the stack and push + it back. + + Thus, from the programmers point of view, the API is the same as the + traditional malloc/realloc/free calls: + + void *mlt_pool_alloc( int size ); + void *mlt_pool_realloc( void *ptr, int size ); + void mlt_pool_release( void *release ); + + +mlt_frame: + + A frame object is essentially defined as: + + +------------+ + |frame | + +------------+ + | properties | + | image stack| + | audio stack| + +------------+ + + The life cycle of a frame can be represented as follows: + + +-----+----------------------+-----------------------+---------------------+ + |Stage|Producer |Filter |Consumer | + +-----+----------------------+-----------------------+---------------------+ + | 0.0 | | |Request frame | + +-----+----------------------+-----------------------+---------------------+ + | 0.1 | |Receives request | | + | | |Request frame | | + +-----+----------------------+-----------------------+---------------------+ + | 0.2 |Receives request | | | + | |Generates frame for | | | + | |current position | | | + | |Increments position | | | + +-----+----------------------+-----------------------+---------------------+ + | 0.3 | |Receives frame | | + | | |Updates frame | | + +-----+----------------------+-----------------------+---------------------+ + | 0.4 | | |Receives frame | + +-----+----------------------+-----------------------+---------------------+ + + Note that neither the filter nor the consumer have any conception of + 'position' until they receive a frame. Speed and position are properties of + the producer, and they are assigned to the frame object when the producer + creates it. + + Step 0.3 is a critical one here - if the filter determines that the frame is + of interest to it, then it should manipulate the image and/or audio stacks + and properties as required. + + Assuming that the filter deals with both image and audio, then it should + push data and methods on to the stacks which will deal with the processing. + This can be done with the mlt_frame_push_image and audio methods. In order for + the filter to register interest in the frame, the stacks should hold: + + image stack: + [ producer_get_image ] [ data1 ] [ data2 ] [ filter_get_image ] + + audio stack: + [ producer_get_audio ] [ data ] [ filter_get_audio ] + + The filter_get methods are invoked automatically when the consumer invokes a + get_image on the frame. + + +-----+----------------------+-----------------------+---------------------+ + |Stage|Producer |Filter |Consumer | + +-----+----------------------+-----------------------+---------------------+ + | 1.0 | | |frame_get_image | + +-----+----------------------+-----------------------+---------------------+ + | 1.1 | |filter_get_image: | | + | | | pop data2 and data1 | | + | | | frame_get_image | | + +-----+----------------------+-----------------------+---------------------+ + | 1.2 |producer_get_image | | | + | | Generates image | | | + +-----+----------------------+-----------------------+---------------------+ + | 1.3 | |Receives image | | + | | |Updates image | | + +-----+----------------------+-----------------------+---------------------+ + | 1.4 | | |Receives image | + +-----+----------------------+-----------------------+---------------------+ + + Obviously, if the filter isn't interested in the image, then it should leave + the stack alone, and then the consumer will retrieve its image directly from + the producer. + + Similarly, audio is handled as follows: + + +-----+----------------------+-----------------------+---------------------+ + |Stage|Producer |Filter |Consumer | + +-----+----------------------+-----------------------+---------------------+ + | 2.0 | | |frame_get_audio | + +-----+----------------------+-----------------------+---------------------+ + | 2.1 | |filter_get_audio: | | + | | | pop data | | + | | | frame_get_audio | | + +-----+----------------------+-----------------------+---------------------+ + | 2.2 |producer_get_audio | | | + | | Generates audio | | | + +-----+----------------------+-----------------------+---------------------+ + | 2.3 | |Receives audio | | + | | |Updates audio | | + +-----+----------------------+-----------------------+---------------------+ + | 2.4 | | |Receives audio | + +-----+----------------------+-----------------------+---------------------+ + + And finally, when the consumer is done with the frame, it should close it. + + Note that a consumer may not evaluate both image and audio for any given + frame, especially in a realtime environment. See 'Realtime Considerations' + below. + + By default, a frame has the following properties: + + +------------------+------------------------------------+------------------+ + |Name |Description |Values | + +------------------+------------------------------------+------------------+ + |_position |The producers frame position |0 to n | + +------------------+------------------------------------+------------------+ + |_speed |The producers speed |double | + +------------------+------------------------------------+------------------+ + |image |The generated image |NULL or pointer | + +------------------+------------------------------------+------------------+ + |alpha |The generated alpha mask |NULL or pointer | + +------------------+------------------------------------+------------------+ + |width |The width of the image | | + +------------------+------------------------------------+------------------+ + |height |The height of the image | | + +------------------+------------------------------------+------------------+ + |normalised_width |The normalised width of the image |720 | + +------------------+------------------------------------+------------------+ + |normalised_height |The normalised height of the image |576 or 480 | + +------------------+------------------------------------+------------------+ + |progressive |Indicates progressive/interlaced |0 or 1 | + +------------------+------------------------------------+------------------+ + |top_field_first |Indicates top field first |0 or 1 | + +------------------+------------------------------------+------------------+ + |audio |The generated audio |NULL or pointer | + +------------------+------------------------------------+------------------+ + |frequency |The frequency of the audio | | + +------------------+------------------------------------+------------------+ + |channels |The channels of the audio | | + +------------------+------------------------------------+------------------+ + |samples |The samples of the audio | | + +------------------+------------------------------------+------------------+ + |aspect_ratio |The sample aspect ratio of the image|double | + +------------------+------------------------------------+------------------+ + |test_image |Used to indicate no image available |0 or 1 | + +------------------+------------------------------------+------------------+ + |test_audio |Used to indicate no audio available |0 or 1 | + +------------------+------------------------------------+------------------+ + + The consumer can attach the following properties which affect the default + behaviour of a frame: + + +------------------+------------------------------------+------------------+ + |test_card_producer|Synthesise test images from here |NULL or pointer | + +------------------+------------------------------------+------------------+ + |consumer_aspect_ |Apply this aspect ratio to the test |double | + |ratio |card producer | | + +------------------+------------------------------------+------------------+ + |rescale.interp |Use this scale method for test image|"string" | + +------------------+------------------------------------+------------------+ + + While most of these are mainly self explanatory, the normalised_width and + normalised_height values require a little explanation. These are required + to ensure that effects are consistently handled as PAL or NTSC, regardless + of the consumers or producers width/height image request. + + The test_image and audio flags are used to determine when images and audio + should be synthesised. + + Additional properties may be provided by the producer implementation, and + filters, transitions and consumers may add additional properties to + communicate specific requests. These are documented in modules.txt. + + The complete API for the mlt frame is as follows: + + mlt_frame mlt_frame_init( ); + mlt_properties mlt_frame_properties( mlt_frame this ); + int mlt_frame_is_test_card( mlt_frame this ); + int mlt_frame_is_test_audio( mlt_frame this ); + double mlt_frame_get_aspect_ratio( mlt_frame this ); + int mlt_frame_set_aspect_ratio( mlt_frame this, double value ); + mlt_position mlt_frame_get_position( mlt_frame this ); + int mlt_frame_set_position( mlt_frame this, mlt_position value ); + int mlt_frame_get_image( mlt_frame this, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ); + uint8_t *mlt_frame_get_alpha_mask( mlt_frame this ); + int mlt_frame_get_audio( mlt_frame this, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ); + int mlt_frame_push_get_image( mlt_frame this, mlt_get_image get_image ); + mlt_get_image mlt_frame_pop_get_image( mlt_frame this ); + int mlt_frame_push_frame( mlt_frame this, mlt_frame that ); + mlt_frame mlt_frame_pop_frame( mlt_frame this ); + int mlt_frame_push_service( mlt_frame this, void *that ); + void *mlt_frame_pop_service( mlt_frame this ); + int mlt_frame_push_audio( mlt_frame this, void *that ); + void *mlt_frame_pop_audio( mlt_frame this ); + void mlt_frame_close( mlt_frame this ); + + +mlt_service: + + The service base class extends properties and allows 0 to m inputs and 0 to + n outputs and is represented as follows: + + +-----------+ + - ->| |- -> + - ->| Service |- -> + - ->| | + +-----------+ + | properties| + +-----------+ + + Descendents of service impose restrictions on how inputs and outputs can be + connected and will provide a basic set of properties. Typically, the service + instance is encapsulated by the descendent in order for it to ensure that + its connection rules are followed. + + A service does not define any properties when constructed. It should be + noted that producers, filters and transitions my be serialised (say, via the + westley consumer), and care should be taken to distinguish between + serialisable and transient properties. The convention used is to prefix + transient properties with an underscore. + + The public interface is defined by the following functions: + + int mlt_service_init( mlt_service this, void *child ); + mlt_properties mlt_service_properties( mlt_service this ); + int mlt_service_connect_producer( mlt_service this, mlt_service producer, int index ); + int mlt_service_get_frame( mlt_service this, mlt_frame_ptr frame, int index ); + void mlt_service_close( mlt_service this ); + + Typically, only direct descendents of services need invoke these methods and + developers are encouraged to use those extensions when defining new services. + + +mlt_producer: + + A producer has 0 inputs and 1 output: + + +-----------+ + | | + | Producer |---> + | | + +-----------+ + | service | + +-----------+ + + A producer provides an abstraction for file readers, pipes, streams or any + other image or audio input. + + When instantiated, a producer has the following properties: + + +------------------+------------------------------------+------------------+ + |Name |Description |Values | + +------------------+------------------------------------+------------------+ + |mlt_type |The producers type |mlt_producer | + +------------------+------------------------------------+------------------+ + |_position |The producers frame position |0 to n | + +------------------+------------------------------------+------------------+ + |_speed |The producers speed |double | + +------------------+------------------------------------+------------------+ + |fps |The output frames per second |25 or 29.97 | + +------------------+------------------------------------+------------------+ + |in |The in point in frames |0 to length - 1 | + +------------------+------------------------------------+------------------+ + |out |The out point in frames |in to length - 1 | + +------------------+------------------------------------+------------------+ + |length |The length of the input in frames |0 to n | + +------------------+------------------------------------+------------------+ + |aspect_ratio |aspect_ratio of the source |0 to n | + +------------------+------------------------------------+------------------+ + |eof |end of clip behaviour |"pause" or "loop" | + +------------------+------------------------------------+------------------+ + |resource |Constructor argument (ie: file name)|"" | + +------------------+------------------------------------+------------------+ + + Additional properties may be provided by the producer implementation. + + The public interface is defined by the following functions: + + mlt_producer mlt_producer_new( ); + int mlt_producer_init( mlt_producer this, void *child ); + mlt_service mlt_producer_service( mlt_producer this ); + mlt_properties mlt_producer_properties( mlt_producer this ); + int mlt_producer_seek( mlt_producer this, mlt_position position ); + mlt_position mlt_producer_position( mlt_producer this ); + mlt_position mlt_producer_frame( mlt_producer this ); + int mlt_producer_set_speed( mlt_producer this, double speed ); + double mlt_producer_get_speed( mlt_producer this ); + double mlt_producer_get_fps( mlt_producer this ); + int mlt_producer_set_in_and_out( mlt_producer this, mlt_position in, mlt_position out ); + mlt_position mlt_producer_get_in( mlt_producer this ); + mlt_position mlt_producer_get_out( mlt_producer this ); + mlt_position mlt_producer_get_playtime( mlt_producer this ); + mlt_position mlt_producer_get_length( mlt_producer this ); + void mlt_producer_prepare_next( mlt_producer this ); + void mlt_producer_close( mlt_producer this ); + + +mlt_filter: + + The public interface is defined by the following functions: + + int mlt_filter_init( mlt_filter this, void *child ); + mlt_filter mlt_filter_new( ); + mlt_service mlt_filter_service( mlt_filter this ); + mlt_properties mlt_filter_properties( mlt_filter this ); + mlt_frame mlt_filter_process( mlt_filter this, mlt_frame that ); + int mlt_filter_connect( mlt_filter this, mlt_service producer, int index ); + void mlt_filter_set_in_and_out( mlt_filter this, mlt_position in, mlt_position out ); + int mlt_filter_get_track( mlt_filter this ); + mlt_position mlt_filter_get_in( mlt_filter this ); + mlt_position mlt_filter_get_out( mlt_filter this ); + void mlt_filter_close( mlt_filter ); + + +mlt_transition: + + The public interface is defined by the following functions: + + int mlt_transition_init( mlt_transition this, void *child ); + mlt_transition mlt_transition_new( ); + mlt_service mlt_transition_service( mlt_transition this ); + mlt_properties mlt_transition_properties( mlt_transition this ); + int mlt_transition_connect( mlt_transition this, mlt_service producer, int a_track, int b_track ); + void mlt_transition_set_in_and_out( mlt_transition this, mlt_position in, mlt_position out ); + int mlt_transition_get_a_track( mlt_transition this ); + int mlt_transition_get_b_track( mlt_transition this ); + mlt_position mlt_transition_get_in( mlt_transition this ); + mlt_position mlt_transition_get_out( mlt_transition this ); + mlt_frame mlt_transition_process( mlt_transition this, mlt_frame a_frame, mlt_frame b_frame ); + void mlt_transition_close( mlt_transition this ); + + +mlt_consumer: + + The public interface is defined by the following functions: + + int mlt_consumer_init( mlt_consumer this, void *child ); + mlt_service mlt_consumer_service( mlt_consumer this ); + mlt_properties mlt_consumer_properties( mlt_consumer this ); + int mlt_consumer_connect( mlt_consumer this, mlt_service producer ); + int mlt_consumer_start( mlt_consumer this ); + mlt_frame mlt_consumer_get_frame( mlt_consumer this ); + mlt_frame mlt_consumer_rt_frame( mlt_consumer this ); + int mlt_consumer_stop( mlt_consumer this ); + int mlt_consumer_is_stopped( mlt_consumer this ); + void mlt_consumer_close( mlt_consumer ); + + +Specialised Producers: + + There are two major types of specialised producers - playlists and tractors. + + The following sections describe these. + + +mlt_playlist: + + mlt_playlist mlt_playlist_init( ); + mlt_producer mlt_playlist_producer( mlt_playlist this ); + mlt_service mlt_playlist_service( mlt_playlist this ); + mlt_properties mlt_playlist_properties( mlt_playlist this ); + int mlt_playlist_count( mlt_playlist this ); + int mlt_playlist_clear( mlt_playlist this ); + int mlt_playlist_append( mlt_playlist this, mlt_producer producer ); + int mlt_playlist_append_io( mlt_playlist this, mlt_producer producer, mlt_position in, mlt_position out ); + int mlt_playlist_blank( mlt_playlist this, mlt_position length ); + mlt_position mlt_playlist_clip( mlt_playlist this, mlt_whence whence, int index ); + int mlt_playlist_current_clip( mlt_playlist this ); + mlt_producer mlt_playlist_current( mlt_playlist this ); + int mlt_playlist_get_clip_info( mlt_playlist this, mlt_playlist_clip_info *info, int index ); + int mlt_playlist_insert( mlt_playlist this, mlt_producer producer, int where, mlt_position in, mlt_position out ); + int mlt_playlist_remove( mlt_playlist this, int where ); + int mlt_playlist_move( mlt_playlist this, int from, int to ); + int mlt_playlist_resize_clip( mlt_playlist this, int clip, mlt_position in, mlt_position out ); + void mlt_playlist_close( mlt_playlist this ); + +mlt_tractor: diff --git a/docs/inigo.txt b/docs/inigo.txt new file mode 100644 index 0000000..b18af6c --- /dev/null +++ b/docs/inigo.txt @@ -0,0 +1,378 @@ +Inigo Documentation + +Copyright (C) 2004 Ushodaya Enterprised Limited +Author: Charles Yates +Last Revision: 2004-03-20 + + +INIGO +----- + +Preamble: + + inigo was developed as a test tool for the MLT framework. It can be thought + of as a powerful, if somewhat obscure, multitrack command line oriented + video editor. + + The following details the usage of the tool and as a result, provides a lot + of insight into the workings of the MLT framework. + + +Usage: + + inigo [ -group [ name=value ]* ] + [ -consumer id[:arg] [ name=value ]* ] + [ -filter filter[:arg] [ name=value ] * ] + [ -attach filter[:arg] [ name=value ] * ] + [ -mix length [ -mixer transition ]* ] + [ -transition id[:arg] [ name=value ] * ] + [ -blank frames ] + [ -track ] + [ -split relative-frame ] + [ -join clips ] + [ -repeat times ] + [ producer [ name=value ] * ]+ + + +General rules: + + 1. Order is incredibly important; + + 2. Error checking on command line parsing is weak; + + 3. Please refer to services.txt for details on services available; + + 4. The MLT framework, from which inigo has inherited its naming convention, + is very mlt-centric. Producers produce MLT frame objects and consumers + consume MLT frame objects. The distinction is important - a DV producer + does not produce DV, it produces MLT frames from a DV source, and similarly + a DV consumer does not consume DV, it consumes MLT frames and produces DV + frames. + + +Terminology: + + 'Producers' typically refer to files but may also indicate devices (such as + dv1394 input or video4linux). Hence, the more generic term is used [the more + generic usage is out of scope for now...]. + + 'Filters' are frame modifiers - they always guarantee that for every frame + they receive, they output *precisely* one frame. Never more, never less, + ever. Nothing says that a filter cannot generate frames though + + 'Transitions' collect frames from two tracks (a and b) and output 1 + modified frame on their 'a track', and 1 unmodified frame on their 'b track'. + Never more, never less, ever. + + 'Consumers' collect frames from a producer, do something with them and + destroy them. + + Collectively, these are known as 'services'. + + All services have 'properties' associated to them. These are typically + defaulted or evaluated and may be overriden on a case by case basis. + + All services except consumers obey in and out properties. + + Consumers have no say in the flow of frames [though they may give the + illusion that they do]. They get frames from a connected producer, use them, + destroy them and get more. + + +Basics: + + To play a file with the default SDL PAL consumer, usage is: + + $ inigo file + + Note that 'file' can be anything that inigo has a known 'producer' mapping + for (so this can be anything from .dv to .txt). + + You can also specify the producer directly, for example: + + $ inigo avformat:file.mpeg + + Would force the direct use of avformat for loading the file. + + +Properties: + + Properties can be assigned to the producer by adding additional name=value + pairs after the producer: + + $ inigo file in=50 out=100 something="something else" + + Note that while some properties have meaning to all producers (for example: + in, out and length are guaranteed to be valid for all, though typically, + length is determined automatically), the validity of others are dependent on + the producer - however, properties will always be assigned and silently + ignored if they won't be used. + + +Multiple Files: + + Multiple files of different types can be used: + + $ inigo a.dv b.mpg c.png + + Properties can be assigned to each file: + + $ inigo a.dv in=50 out=100 b.mpg out=500 c.png out=500 + + MLT will take care of 'normalising' the output of a producer to ensure + that the consumer gets what it needs. So, in the case above, the mlt + framework will ensure that images are rescaled and audio resampled to meet + the requirements of your configuration (which, by default, will be PAL). + See 'Appendix A: Normalisation Rules' below. + + +Filters: + + Filters are frame modifiers - they can change the contents of the audio or + the images associated to a frame. + + $ inigo a.dv -filter greyscale + + As with producers, properties may be specified on filters too. + + Again, in and out properties are common to all, so to apply a filter to a + range of frames, you would use something like: + + $ inigo a.dv -filter greyscale in=0 out=50 + + Again, filters have their own set of rules about properties and will + silently ignore properties that do not apply. + + +Groups: + + The -group switch is provided to force default properties on the following + 'services'. For example: + + $ inigo -group in=0 out=49 clip* + + would play the first 50 frames of all clips that match the wild card + pattern. + + Note that the last -group settings also apply to the following filters, + transitions and consumers, so: + + $ inigo -group in=0 out=49 clip* -filter greyscale + + is *probably not* what you want (ie: the greyscale filter would only be + applied to the first 50 frames). + + To shed the group properties, you can use any empty group: + + $ inigo -group in=0 out=49 clip* -group -filter greyscale + + +Attached Filters: + + As described above, the -filter switch applies filters to an entire track. To + localise filters to a specific clip on a track, you have to know information + about the lengths of the clip and all clips leading up to it. In practise, + this is horrifically impractical, especially at a command line level (and not + even that practical from a programing point of view...). + + The -attach family of switches simplify things enormously. By default, -attach + will attach a filter to the last service created, so: + + $ inigo clip1.dv clip2.dv -attach greyscale clip3.dv + + would only apply the filter to clip2.dv. You can further narrow down the area of + the effect by specifying in/out points on the attached filter. + + This might seem simple so far, but there is a catch... consider the following: + + $ ingo clip1.dv -attach watermark:+hello.txt -attach invert + + The second attached filter is actually attached to the watermark. You might + think, yay, nice (and it is :-)), but, it might not be what you want. For example + you might want to attach both to clip1.dv. To do that, you can use: + + $ ingo clip1.dv -attach-cut watermark:+hello.txt -attach-cut invert + + As you shall see below, there are still another couple of gotchas associated to + -attach, and even another variant :-). + + +Mixes: + + The -mix switch provides the simplest means to introduce transitions between + adjacent clips. + + For example: + + $ inigo clip1.dv clip2.dv -mix 25 -mixer luma -mixer mix:-1 + + would provide both an audio and video transition between clip1 and clip2. + + This functionality supercedes the enforced use of the -track and -transition + switches from earlier versions of inigo and makes life a lot easier :-). + + These can be used in combination, so you can for example do a fade from black + and to black using the following: + + $ inigo colour:black out=24 clip1.dv -mix 25 -mixer luma \ + colour:black out=24 -mix 25 -mixer luma + + while this may not be immediately obvious, consider what's happening as the + command line is being parsed from left to right: + + Input: Track + ----------------------- ----------------------------------------------------- + colour:black out=24 [black] + clip1.dv [black][clip1.dv] + -mix 25 [black+clip1.dv][clip1.dv] + -mixer luma [luma:black+clip1.dv][clip1.dv] + colour:black out=24 [luma:black+clip1.dv][clip1.dv][black] + -mix 25 [luma:black+clip1.dv][clip1.dv][clip1.dv+black] + -mixer luma [luma:black+clip1.dv][clip1.dv][luma:clip1.dv+black] + + Obviously, the clip1.dv instances refer to different parts of the clip, but + hopefully that will demonstrate what happens as we construct the track. + + You will find more details on the mix in the framework.txt. + + +Mix and Attach: + + As noted, -attach normally applies to the last created service - so, you can + attach a filter to the transition region using: + + $ inigo clip1.dv clip2.dv -mix 25 -mixer luma -attach watermark:+Transition.txt + + Again, nice, but take care - if you want the attached filter to be associated + to the region following the transition, use -attach-cut instead. + + +Splits, Joins, Removes and Swaps: + + COMPLEX - needs simplification.... + + +Introducing Tracks and Blanks: + + So far, all of the examples have shown the definition of a single + playlist, or more accurately, track. + + When multiple tracks exist, the consumer will receive a frame + from the 'highest numbered' track that is generating a non-blank + frame. + + It is best to visualise a track arrangement, so we'll start with + an example: + + $ inigo a.dv -track b.dv in=0 out=49 + + This can be visualised as follows: + + +------------------+ + |a | + +-------+----------+ + |b | + +-------+ + + Playout will show the first 50 frames of b and the 51st frame shown will be + the 51st frame of a. + + This rule also applies to audio only producers on the second track, for + example, the following would show the video from the a track, but the audio + would come from the second track: + + $ inigo a.dv -track b.mp3 in=0 out=49 + + To have the 51st frame be the first frame of b, we can use the -blank switch: + + $ inigo a.dv out=49 -track -blank 49 b.dv + + Which we can visualise as: + + +-------+ + |a | + +-------+-------------------+ + |b | + +-------------------+ + + Now playout will continue as though a and b clips are on the + same track (which on its own, is about as useful as reversing the + process of slicing bread). + + +Transitions: + + Where tracks become useful is in the placing of transitions. + + Here we need tracks to overlap, so a useful multitrack + definition could be given as: + + $ inigo a.dv out=49 \ + -track \ + -blank 24 b.dv \ + -transition luma in=25 out=49 a_track=0 b_track=1 + + Now we're cooking - our visualisation would be something like: + + +-------+ + |a | + +---+---+--------------+ + |b | + +------------------+ + + Playout will now show the first 25 frames of a and then a fade + transition for 25 frames between a and b, and will finally + playout the remainder of b. + + +Reversing a Transition: + + When we visualise a track definition, we also see situations + like: + + +-------+ +----------+ + |a1 | |a2 | + +---+---+--------------+----+-----+ + |b | + +-----------------------+ + + In this case, we have two transitions, a1 to b and b to a2. + + In this scenario, we define a command line as follows: + + $ inigo a.dv out=49 -blank 49 a2.dv \ + -track \ + -blank 24 b.dv out=99 \ + -transition luma in=25 out=49 a_track=0 b_track=1 \ + -transition luma in=100 out=124 reverse=1 a_track=0 b_track=1 + + +Serialisation: + + Inigo has a built in serialisation mechanism - you can build up + your command, test it via any consumer and then add a -serialise + file.inigo switch to save it. + + The saved file can be subsequently used as a clip by either + miracle or inigo. Take care though - paths to files are saved as + provided on the command line.... + + A more expressive serialisation can be obtained with the westley consumer + - this will provide an xml document which can be used freely in inigo and + miracle. + + See westley.txt for more information. + + +Missing Features: + + Some filters/transitions should be applied on the output frame regardless + of which track it comes from - for example, you might have a 3rd text + track or a watermark which you want composited on every frame, and of + course, there's the obscure filter.... + + inigo only supports this in two invocations - as a simple example: + + $ inigo a.dv -track -blank 100 b.dv -consumer westley:basic.westley + $ inigo basic.westley -filter watermark:watermark.png + diff --git a/docs/install.txt b/docs/install.txt new file mode 100644 index 0000000..d71d849 --- /dev/null +++ b/docs/install.txt @@ -0,0 +1,187 @@ +Installation Documentation + +Copyright (C) 2004 Ushodaya Enterprises Limited +Author: Charles Yates +Last Revision: 2004-04-13 + + +INSTALL +------- + + This document provides a description of the MLT project installation and + organisation. + + +Directories +----------- + + The directory heirarchy is defined as follows: + + + demo - A selection of samples to show off capabilities. + + docs - Location of all documentation + + src - All project source is provided here + + framework - The mlt media framework + + modules - All services are defined here + + avformat - libavformat dependent services + + bluefish - Bluefish dependent services (*) + + core - Independent MLT services + + dv - libdv dependent services + + fezzik - A giant (meta) service to load and normalise media + + gtk2 - pango and pixbuf dependent services + + mainconcept - mainconcept dependent services (*) + + normalize - audio normalisation functions (**) + + plus - throwaway silliness + + resample - libresample dependent services (**) + + sdl - SDL dependent services + + vorbis - vorbis dependenent services + + westley - Nice and clever XML services + + xine - Xine-derived sources (**) + + albino - A simple console (protocol level) example (**) + + inigo - A media playing test application (**) + + humperdink - A terminal-based example client + + miracle - The server implementation (**) + + tests - Reserved for regression and unit tests + + valerie - Client API to access the server (**) + + Additional subdirectories may be nested below those shown and should be + documented in their parent. + + (*) Not posted to CVS due to licensing issues. + (**) Contains GPL dependencies or code. + + +Dependencies +------------ + + The MLT core is dependent on: + + * a C99 compliant C compiler + * posix threading + * standard posix libraries + + The MLT applications and libraries provided are all dependent on the core. + + The modules have the following dependencies: + + ----------- ---------------------------------------------------------- + MODULE DESCRIPTION + ----------- ---------------------------------------------------------- + avformat Provided from ffmpeg CVS and compiled as a shared library. + URL: http://ffmpeg.sf.net + ----------- ---------------------------------------------------------- + bluefish Bluefish hardware and software development kit + URL: http://www.bluefish444.com + ----------- ---------------------------------------------------------- + dv libdv 0.102 or later. + URL: http://libdv.sf.net + ----------- ---------------------------------------------------------- + gtk2 GTK2 and associated dependencies. + URL: http://www.gtk.org + ----------- ---------------------------------------------------------- + mainconcept Mainconcept MPEG and DVCPRO Release SDKs. + URL: http://www.mainconcept.com + ----------- ---------------------------------------------------------- + resample libsamplerate 0.15 or later + URL: http://www.mega-nerd.com/SRC/ (GPL) + ----------- ---------------------------------------------------------- + sdl SDL 1.2 or later. + URL: http://www.libsdl.org + ----------- ---------------------------------------------------------- + vorbis libvorbis 1.0.1 or later. + URL: http://www.vorbis.com/ + ----------- ---------------------------------------------------------- + westley libxml2 2.5 or later. + URL: http://www.xmlsoft.org/ + ----------- ---------------------------------------------------------- + + +Configuration +------------- + + Configuration is triggered from the top level directory via a + ./configure script. + + Each source bearing subdirectory shown above have their own configure + script which are called automatically from the top level. + + Typically, new modules can be introduced without modification to the + configure script and arguments are accepted and passed through to all + subdirectories. + + More information on usage is found by running: + + ./configure --help + + NB: This script must be run to register new services after a CVS checkout + or subsequent update. + + +Compilation +----------- + + Makefiles are generated during configuration and these are based on + a per directory template which must be provided by the developer. + + +Testing +------- + + To execute the mlt tools without installation, or to test a new version + on a system with an already installed mlt version, you should run: + + . setenv + + NB: This applies to your current shell only and it assumes sh or bash. + + +Installation +------------ + + The install is triggered by running make install from the top level + directory. + + The framework produces a single shared object which is installed in + $prefix/lib/ and public header files which are installed in + $prefix/include/mlt/framework. + + Valerie produces a single shared object which is installed in + $prefix/lib/ and public header which are installed in + $prefix/include/mlt/valerie. + + Miracle produces a single exectuable which is installed in + $prefix/bin/, a library in $prefix/lib and associated header files in + $prefix/include. + + The modules produce a shared object per module and update text files + containing a list of modules provided by this build. These are installed + in $prefix/share/mlt/modules. It is at the discretion of the module to + install additional support files. + + To allow the development of external components, mlt-config and scripts + are generated and installed in $prefix/bin. + + After install, only those modules listed are usable by the server. No + module is loaded unless explicitly requested via server configuration + or usage. + + External modules are also placed in this $prefix/share/mlt/modules, and the + installation of those must modify the text file accordingly before they + will be considered at runtime. + + +Development +----------- + + All compilation in the project has {top-level-dir}/src on the include path. + All headers are included as: + + #include + + All external modules have {prefix}/include/mlt on the include path. All + headers should also be included as: + + #include + + This allows migration of source between external and internal modules. + The configuration and Makefile template requirements will require + attention though. diff --git a/docs/policies.txt b/docs/policies.txt new file mode 100644 index 0000000..5a5d2ce --- /dev/null +++ b/docs/policies.txt @@ -0,0 +1,52 @@ +Open Source Development Policies and Procedures for MLT +by Dan Dennedy + +Policies +-------- + +Any contribution to the "core" module must assign copyright to Ushodaya +Enterprises Limited because they need license control over that module. + +The framework and valerie client libraries are covered under LGPL. Miracle, +inigo, albino, and humperdink applications are covered under GPL. Modules +should strive to be LGPL to make them available through the framework as LGPL. + +Comments in the framework and valerie header files must be C-style, not C++. + +Coding Style: +There are not a lot of rules, but we prefer brackets on their own line, +indents using tabs, liberal usage of spaces in statements and expressions, and +no hard line length. The code in src/framework serves as a good example. + +Commit messages must be prefaced with the name of the changed files. This makes +the Subversion log more useful as a ChangeLog. For example, + docs/policies.txt: added policy about commit message + +Increment the version number in ./configure on the first commit after a release +as well as just prior to a new release. This way we can track if someone is +using an unreleased version from the source code repository. + +Procedures +---------- + +Update services.txt when you add or update a service. + +Setting Copyright on Appropriated Code: +You do not want to be accused of copying someone's code and changing copyright +or license without permission. The license is straightforward: you must retain +the original author's license unless you receive explicit permission. There are +a few ways to approach the copyright declaration depending upon the +intermingling and changes. If you heavily comingle original and new code or +lightly modifiy the original code, you can retain the original's copyright +including the years, and then add your copyright for the current year. If you +can separate the MLT integration from the core subroutines, then you can put +the core subroutines into a separate file with the original copyright and just +copyright the MLT integration code as your own. However, if you have heavily +modified the original code beyond nearly all recognition, you can copyright it +as your own and attribute the original author as inspiration. + +Bug Reporting: +First preference is to use the SourceForge tracker: +http://sourceforge.net/tracker/?group_id=96039&atid=613414 +Second preference is in the mailing list: +mlt-devel@lists.sourceforge.net diff --git a/docs/services.txt b/docs/services.txt new file mode 100644 index 0000000..23e6b5c --- /dev/null +++ b/docs/services.txt @@ -0,0 +1,1588 @@ +Service Documentation + +Authors: Charles Yates + Dan Dennedy +Last Revision: $Date: 2007-03-30 08:55:55 +0200 (ven, 30 mar 2007) $ + + +SERVICES +-------- + + Services marked as "(Proprietary)" are not distributed with the LGPL + version of mlt. + +Producers +--------- + + avformat + + Description + + ffmpeg libavformat based producer for video and audio. + + Constructor Argument + + 'file' - a filename specification or URL in the form: + [{protocol}|{format}]:{resource}[?{format-parameter}[&{format-parameter}...]] + For example, video4linux:/dev/video1?width:320&height:240 + Note: on the bash command line, & must be escaped as '\&'. + Also, note the use of ':' instead of '=' for parameters. + Use 'ffmpeg -formats' to see a list of supported protocols + and formats. + + Details + + Format parameters only appear to be useful with 'video4linux' or + 'audio_device' formats. For 'video4linux' the parameters are + width, height, frame_rate, frame_rate_base, and standard (ntsc|pal). + For 'audio_device' the parameters are channels and sample_rate. + + Initialisation Properties + + int video_index - index of video stream to use (-1 is off) + int audio_index - index of audio stream to use (-1 is off) + int in - in point + int out - out point + + Read Only Properties + + string resource - file location + double fps - this is fixed at 25 for PAL currently + double source_fps - the framerate of the resource + double aspect_ratio - sample aspect ratio of the resource + - this is determined on every frame read + + Dependencies + + ffmpeg must be configured as --enable-shared and installed prior + to compilation of mlt. + + Known Bugs + + Audio sync discrepancy with some content. + Not all libavformat supported formats are seekable. + Ogg Vorbis is currently broken. + MPEG seeking is inaccurate - doesn't seek to i-frames so you may + get junk for a few frames. + RAW DV seeking not supported. + Fails to play beyond first frame of video of sources with PTS not + starting at 0 (video4linux). + + fezzik + + Description + + A friendly giant that likes to rhyme and throw rocks + + Constructor Argument + + 'file' - a filename specification: + [{mlt-service}:]{resource} | {mlt-service} + - can also be the name of a producer service that can + accept the resource specified post construction. + + Initialisation Properties + + int in - in point + int out - out point + + all producer initialising properties + + Read Only Properties + + string resource - file location + + all producer read only properties + + Details + + This producer has two roles: + + 1. it handles the mappings of all file names to the other + producers; + 2. it attaches normalising filters (rescale, resize and resample) + to the producers (when necessary). + + This producer simplifies many aspects of use. Essentially, it + ensures that a consumer will receive images and audio precisely as + they request them. + + Dependencies + + all. + + Known Bugs + + None. + + + colour + + Description + + A simple colour generator. + + Constructor Argument + + colour - A colour value is a hexadecimal representation of RGB plus + alpha channel as 0xrrggbbaa. + - Also colours can be the words: white, black, red, green, + or blue. + - The default colour is black. + + Initialisation Properties + + none + + Read Only Properties + + none + + Dependencies + + none + + Known Bugs + + none + + + libdv + + Description + + libdv based decoder for video and audio. + + Constructor Argument + + 'file' - produce a/v from file + + Initialisation Properties + + int in - in point + int out - out point + + Read Only Properties + + string resource - file location + double fps - output frames per second + int length - duration of resource (in frames) + + Mutable Properties + + string quality - one of "best," "fast" or anything else chooses + medium. + + Dependencies + + libdv. + + Known Bugs + + DVCPRO is incorrectly identified as 16:9 aspect ratio. You must use + libdv from CVS or a post 0.101 release. + + mcdv (Proprietary) + + Description + + MainConcept based dv decoder for video and audio. + + Constructor Argument + + 'file' - produce a/v from file + + Initialisation Properties + + int in - in point + int out - out point + + Read Only Properties + + string resource - file location + double fps - output frames per second + int length - duration of resource (in frames) + + Dependencies + + MainConcept DV or DVCPRO SDK, libdv. + "dv_sdk" installed parallel to mlt. + + Known Bugs + + None + + mcmpeg (Proprietary) + + Description + + MainConcept based mpeg decoder for video and audio. + + Constructor Argument + + 'file' - produce a/v from file + + Initialisation Properties + + int in - in point + int out - out point + + Read Only Properties + + string resource - file location + double fps - output frames per second + double aspect_ratio - sample aspect ratio of video + int length - duration of resource (in frames) + + Dependencies + + MainConcept MPEG SDK. + "mpeg_sdk_release" installed parallel to mlt. + + Known Bugs + + None. + + noise + + Description + + White noise producer + + Constructor Argument + + none + + Initialisation Properties + + int in - in point + int out - out point + + Read Only Properties + + string resource - file location + double fps - output frames per second + double aspect_ratio - sample aspect ratio of video + int length - duration of resource (in frames) + + Dependencies + + none + + Known Bugs + + none + + pango + + Description + + A title generator that uses the Pango international text layout + and Freetype2 font renderer. + + Constructor Argument + + string file - a text file containing Pango markup, see: + http://developer.gnome.org/doc/API/2.0/pango/PangoMarkupFormat.html + - requires xml-like encoding special chars from: + <, >, & -to- <, >, & + + Details + + Supplying a filename with extension ".txt" causes the Fezzik + producer to load with pango. If the filename begins with "+" the + pango producer interprets the filename as pango text. This is a + shortcut to embed titles in inigo commands. For westley, it is + recommended that you embed the title text in the property value. + + Pango has builtin scaling. It will rescale the originally rendered + title to whatever the consumer requests. Therefore, it will lose + its aspect ratio if so requested, and it is up to the consumer to + request a proper width and height that maintains the image aspect. + + Initialisation Properties + + int in - in point + int out - out point + + Mutable Properties + + string markup - a string containing Pango markup see: + http://developer.gnome.org/doc/API/2.0/pango/PangoMarkupFormat.html + - requires xml-like encoding special chars from: + <, >, & -to- <, >, & + string fgcolour - an RGBA colour specification of the text + (i.e. 0xrrggbbaa) + string bgcolour - an RGBA colour of the background rectangle + string align - paragraph alignment: left, centre, right + - also, numbers 0, 1 and 2 can be used respectively. + int pad - the number of pixels to pad the background rectangle + beyond edges of text. default 0. + string markup - see constructor argument + string text - non-markup string in UTF-8 encoding (can contain + markup chars un-encoded) + string font - the default typeface to use when not using markup. + default "Sans 48". FreeType2 renders at 72 dpi. + string encoding - the text encoding type of the input if not UTF-8. + - see 'iconv --list' for a list of possible inputs. + int weight - the weight of the font (default is 400) + + Read Only Properties + + string resource - the text/markup file or "pango" if no file. + int real_width - the original, unscaled width of the rendered title. + int real_height - the original, unscaled height of the title. + int width - the last requested scaled image width. + int height - the last requested scaled image height. + + Dependencies + + libpango-1.0, libpangoft2-1.0, libfreetype, libgdk_pixbuf-2.0, + libglib-2.0, libgobject-2.0, libgmodule-2.0, libfontconfig. + + Known Bugs + + The foreground and background Pango markup span attributes are not + supported. + Word wrapping is not supported. + + pixbuf + + Description + + A still graphics to video generator using gdk-pixbuf + + Constructor Argument + + 'file' - The name of a graphics file loadable by + a gdk-pixbuf loader. see /usr/lib/gdk-pixbuf/loaders + definitely png, jpeg, tiff, pnm, and xpm + - If "%" in filename, the filename is used with sprintf + generate a filename from a counter for multi-file/flipbook + animation. The file sequence ends when numeric + discontinuity >100. + - If filename contains "/.all.", suffix with an extension to + load all pictures with matching extension from a directory. + - If filename contains the string "2 channels. + + rescale + + Description + + Scale the producer video frame size to match the consumer. + This filter is designed for use as a Fezzik normaliser. + + Constructor Argument + + None. + + Initialisation Properties + + int in - in point + int out - out point + + Mutable Properties + + If a property "consumer_aspect_ratio" exists on the frame, then + rescaler normalises the producer's aspect ratio and maximises the + size of the frame, but may not produce the consumer's requested + dimension. Therefore, this option works best in conjunction with the + resize filter. This behavior can be disabled by another service by + either removing the property, setting it to zero, or setting + frame property "distort" to 1. + + Dependencies + + none + + Known Bugs + + none but... it only implements a nearest neighbour scaling - it is + used as the base class for the gtkrescale and mcrescale filters. + + resize + + Description + + Image scaling and padding and field order adjustment. + + Details + + Normally resize is used to pad the producer's + output to what the consumer has requested after an upstream rescale + filter first scales the image to maximise usage of the image area. + This filter also adjusts the field order to lower field first + if the frame property "top_field_first" has been set to 1. + Therefore, when done, it sets the top_field_first to 0. + This filter is automatically invoked by Fezzik as part of image + sample aspect ratio normalisation. + + Constructor Argument + + scale - "affine" to use affine transform scaling, otherwise + center and pad. + + Initialisation Properties + + int in - in point + int out - out point + + Read Only Properties + + none + + Dependencies + + none + + Known Bugs + + Assumes lower field first output. + + volume + + Description + + Adjust an audio stream's volume level + - based on the 'normalize' utility + + Constructor Argument + + gain - a string containing one of: + - a floating point value of the gain adjustment + - a numeric value with the suffix "dB" to adjust in terms of decibels + - "normalise" to normalise the volume to the target amplitude -12dBFS + + Initialisation Properties + + int in - in point + int out - out point + int window - the number of video frames over which to smooth normalisation. + defaults to 75. + + Mutable Properties + + string gain - same as constructor argument above + + string normalise - normalise the volume to the amplitude: + - a numeric value with the suffix "dB" to set amplitude in decibels + - a floating point value of the relative volume + - an unspecified value uses the default -12dBFS + + string limiter - limit all samples above: + - a numeric value with the suffix "dB" + - a floating point value ( dB = 20 * log10(x) ) + - an unspecified value uses the default -6dBFS + + double max_gain - a floating point or decibel value of the maximum gain that + can be applied during normalisation. + - an unspecified value uses the default 20dB + + string end - a gain value just like the gain property above. + This causes the gain to be interpolated from 'gain' to 'end' + over the duration. + + int window - the size of the normalising smoothing buffer in video frame units. + - the smoothing buffer prevents erratic gain changes. + - the default value is 75 video frames. + + gain can be applied as a factor to the normalise amplitude! + + Dependencies + + none + + Known Bugs + + none + + watermark + + Description + + Add a watermark to the frames. + + Constructor Argument + + resource - the producer to use (ie: a .png) + + Initialisation Properties + + string resource - the producer to use + string factory - producer required for the resource ('fezzik') + string geometry - composite geometry + string distort - control scaling + int in - in point + int out - out point + + Mutable Properties + + none + + Dependencies + + mlt core modules and optionally, fezzik + + Known Bugs + + none + +Transitions +----------- + + composite + + Description + + A key-framable alpha-channel compositor for two frames. + + Details + + Performs dissolves and luma wipes in addition to alpha compositing. + + By default, the aspect ratio of the B frame is respected and the + size portion of the geometry specification simply defines a + bounding rectangle. + + This performs field-based rendering unless the A frame property + "progressive" or "consumer_progressive" or the transition property + "progressive" is set to 1. + + Constructor Argument + + none[*] + + Initialisation Properties + + int in - in point + int out - out point + string factory - The name of a factory service used as a non-PGM + producer loader. The default is fezzik. + + Read Only Properties + + none + + Mutable Properties + + + string geometry - key frame specification + - this is a ; delimited form of the deprecated start, + key[n], end properties + int progressive - set to 1 to disable field-based rendering. + string distort - when set, causes the B frame image to fill the WxH + completely with no regard to B's aspect ratio. + string halign - when not distorting, set the horizontal alignment + of B within the geometry rectangle. + - one of: left (0), centre (1), or right (2). + - the default is left. + string valign - when not distorting, set the vertical alignment of + B within the geometry rectangle. + - one of: top (0), middle (1), or bottom (2). + - the default is top. + string luma - the luma map file name. If not supplied, a dissolve. + double softness - only when using a luma map, how soft to make the + edges between A and B. 0.0 = no softness. 1.0 = + too soft. + Any property starting with "luma." is passed to the non-PGM luma + producer. + + Deprecated Properties + + string start - a geometry specification as X,Y:WxH[!][:mix] + - X, Y, W, H are assumed to pixel units unless they + have the suffix '%' + - '!' is a shortcut to specify distort, see below. + - mix is always a 2 digit percentage, defaults to 100. + - default is "85%,5%:10%x10%" + string end - the ending size and position. + string key[F] - X,Y:WxH[:mix] - set a key frame for geometry between + the in and out. F is a frame number and can be + negative to make it relative to the out point. + + Dependencies + + none + + Known Bugs + + Assumes lower field first during field rendering. + + luma + + Description + + A generic dissolve and wipe transition processor. + + Details + + luma gets its name + from how it uses a grayscale "map" file. As the luma value varies + over time, a threshold filter is applied to the map to determine + what parts of frame A vs. frame B to show. It reads PGM files + up to 16 bits! Alternatively, it can use the first frame from any + producer that outputs yuv, but it will be limited to the luma + gamut of 220 values. + This performs field-based rendering unless the A frame property + "progressive" or "consumer_progressive" or the transition property + "progressive" is set to 1. + + Constructor Argument + + string resource - the luma map file name - either PGM or any other + producable video. + - If not supplied, a dissolve. + + Initialisation Properties + + int in - in point + int out - out point + string factory - The name of a factory service used as a non-PGM + producer loader. The default is Fezzik. + + Mutable Properties + + string resource - same as above + double softness - only when using a luma map, how soft to make the + edges between A and B. 0.0 = no softness. 1.0 = + too soft. + int reverse - reverse the direction of the transition. + Any property starting with "producer." is passed to the non-PGM luma + producer. + + Read Only Properties + + none + + Dependencies + + none + + Known Bugs + + Assumes lower field first output. + + mix + + Description + + An two stream audio mixer. + + Constructor Argument + + start - see below + + Initalisation Properties + + int in - in point + int out - out point + + Mutable Properties + + double start - the mix level to apply to the second frame. + - any negative value causes an automatic crossfade from 0 to 1. + double end - the ending value of the mix level. mix level will be interpolated + from start to end over the in-out range. + int reverse - set to 1 to reverse the direction of the mix. + + Read Only Properties + + none + + Dependencies + + none + + Known Bugs + + Samples from the longer of the two frames are discarded. + + + region + + Description + + Apply zero or more filters to B frame as it is composited onto + a region of the A frame. The "shape" of the region can be defined + by the alpha channel of a third producer. + + Constructor Argument + + resource - a shape producer + + Initialisation Properties + + string resource - nothing is rectangle, "circle" is a pixbuf- + generated SVG circle, anything else is loaded by the factory. + string factory - the service that creates the shape producer. + - the default is fezzik. + string filter[N] - one or more filters to apply. All filter + properties are passed using the same filter "key" + Any property starting with "composite." is passed to the + encapsulated composite transition. + + Read Only Properties + + none + + Dependencies + + transition_composite + + Known Bugs + + none + + +Consumers +--------- + + avformat + + Description + + Multiformat transcoding consumer. + + Constructor Argument + + string target - the filename to write to, e.g. test.mpeg. + + Initialisation Properties + + int buffer - the number of frames to buffer, minimum 1, default 25. + string rescale - a rescale method, see the Filters/rescale. + int progressive - indicates whether to use progressive or field- + based rendering, default 0 (off). + + Read Only Properties + + none + + Dependencies + + libavformat + + Known Bugs + + Plenty. + + bluefish (Proprietary) + + Description + + BlueFish444 audio and video output module. + + Constructor Argument + + card - a numeric card id starting at 1, default is 1. + + Initialisation Properties + + string standard - "PAL" (default) or "NTSC" + - default is based upon MLT_NORMALISATION + environment variable, which defaults to PAL. + int frames - the number of DMA video frames. default is 8. + minimum is 2. maximum on my system is 11. + int buffer - the number of frames to buffer within MLT, minimum 1, + default 25. + string rescale - a rescale method, see the Filters/rescale. + + Read Only Properties + + none + + Dependencies + + BlueVelvet SDK installed parallel to mlt in "bluefish." + + Known Bugs + + Does not work with any service that uses pipes! + + If mlt crashes, you might need to reload the BlueDriver kernel + module due to unreleased DMA buffers. + + Only supports 2 channel audio at the moment. + + libdv + + Description + + libdv dv producer. + + Constructor Argument + + string target - the filename to write to, e.g. /dev/dv1394. + + Initialisation Properties + + int buffer - the number of frames to buffer, minimum 1, default 25. + string rescale - a rescale method, see the Filters/rescale. + + Mutable Properties + + int progressive - indicates whether to use progressive or field- + based rendering, default 0 (off). + + Read Only Properties + + none + + Dependencies + + libdv + + Known Bugs + + none + + mcmpeg + + Description + + Mainconcept MPEG encoder. + + Constructor Argument + + string target - the filename to write to. + + Initialisation Properties + + int buffer - the number of frames to buffer, minimum 1, default 25. + string rescale - a rescale method, see the Filters/rescale. + string format - vcd [default], svcd or dvd provide base settings + int motion_search_type - 0 to 16 - reduces quality/cpu usage + int gop - group of picture size (default: format dependent) + + Mutable Properties + + int progressive - indicates whether to use progressive or field- + based rendering, default 0 (off). + + Read Only Properties + + none + + Dependencies + + Mainconcept MPEG SDK + + Known Bugs + + none + + sdl + + Description + + Simple DirectMedia Layer audio and video output module. + + Constructor Argument + + string video_standard - "PAL" (default), "NTSC", or "WxH" + + Initialisation Properties + + int buffer - the number of frames to buffer, minimum 1, default 25. + string rescale - a rescale method, see the Filters/rescale. + - Hint: "none" works very good with SDL output. + + Mutable Properties + + double volume - audio level factor + int video_off - if 1, disable video output + int audio_off - if 1, disable audio output + int resize - TODO + int progressive - indicates whether to use progressive or field- + based rendering, default 0 (off). + int audio_buffer - size of the sdl audio buffer (default: 1024) + + Read Only Properties + + none + + Dependencies + + libSDL-1.2, libasound, libaudio, libXt, libX11, libXext, libSM, libICE + + Known Bugs + + none + + westley + + Description + + Serialise the service network to XML. + See docs/westley.txt for more information. + + Constructor Argument + + resource - the name of a file in which to store the XML. + - stdout is default. + + Initialisation Properties + + string resource - same as above. + + Dependencies + + libxml2 + + Known Bugs + + Untested arbitrary nesting of multitracks and playlists. + Property "id" is generated as service type followed by number if + no property named "id" exists, but it fails to guarantee uniqueness. diff --git a/docs/testing-20040110.txt b/docs/testing-20040110.txt new file mode 100644 index 0000000..14c8dcd --- /dev/null +++ b/docs/testing-20040110.txt @@ -0,0 +1,35 @@ +On 1/10/2004, Dan Dennedy ran the testing.txt against mlt albino and miracle. + + +NOTE: Discrepancies cited here may have impact on related functionality. + + +General +------------------------------------------------------------------------------ +Server side error checks and related response error codes are not stringently enforced. + + +Not Implemented +------------------------------------------------------------------------------ +NLS +USET points=ignore +USET eof=terminate + + +Incorrect Behaviour +------------------------------------------------------------------------------ + + +Different Intentional Behaviour +------------------------------------------------------------------------------ + +Different forced Behaviour +------------------------------------------------------------------------------ +killall miracle does not work when the SDL consumer is in use. requires killall -HUP + +MLT Bugs +------------------------------------------------------------------------------ +Please check the services.txt doc for known bugs related to MLT components. + + + diff --git a/docs/testing.txt b/docs/testing.txt new file mode 100644 index 0000000..2886989 --- /dev/null +++ b/docs/testing.txt @@ -0,0 +1,599 @@ +Miracle Test Procedure + +Copyright (C) 2003 Ushodaya Enterprised Limited +Author: Dan Dennedy +Last Revision: 2004-03-20 + + +NOTE: THIS DOCUMENT REQUIRES REVISION TO NEW, EXPECTED BEHAVIOR FROM MIRACLE. + +Tests are divided into 9 sections: + + 1. Command Line Usage + 2. Unit Management + 3. Server Configuration + 4. Simple Playback + 5. Multi-unit Playback + 6. Unit Configuration + 7. Advanced Playback + 8. Bus Reset + 9. Server Side Queuing + +Each section contains many tests which I've divided into a minimum of two lines: + +n.m action to carry out +--> expected result + +Further lines may appear to show the actual results when they deviate from what +I expected or if there are special cases to consider. + +Sequential tests are indicated as: + +n.m.o action to carry out +--> expected result + +It is suggested that you run top during the testing and note cpu hikes +or any excessive memory usage related to an operation. + + +0. Introduction +--------------- + +The tests following are by no means exhaustive, but they should cover typical +use cases - creativity is encouraged with more cases being added where necessary. +This document should also be maintained to dictate actual state, especially with +regard to a final release. Unit test cases are encouraged, but are excluded from +this document. + +It is important to carry out the full test cycle when preparing a final release. +In this situation, please resist the temptation to bug fix a given test case and +resume the tests from that point onward - it is better to repeat from the +beginning (but you can of course employ common sense in this situation). + +Before starting the final tests, please delete/backup your current +/etc/dv139d.conf file. This (more or less) ensures that tests are carried out +for a virgin install. + + +1. Command Line Usage +--------------------- + +Run these from the top level project directory + +1.1.0 Start miracle in interactive mode: src/miracle/miracle -test +--> miracle starts interactively and reports: +(5) Starting server on 5250. +(5) miracle version 0.0.1 listening on port 5250 + +1.1.1 Stop the server by pressing Ctrl-C +--> miracle returns the following and returns control to the console: +(5) miracle version 0.0.1 server terminated. + +1.2.2 Start miracle as a daemon: src/miracle/miracle +--> control returns to the console + +1.2.3 Verify miracle is running: ps ax +--> several miracle processes are running + +1.2.4 Verify successful miracle startup using syslog: sudo tail /var/log/syslog +--> miracle: miracle version 0.0.1 listening on port 5250 + +1.2.5 Verify connectivity on port 5250: telnet localhost 5250 +--> 100 VTR Ready + +1.2.6 Test clean disconnect: BYE +--> Connection closed by foreign host. + +1.2.7 Stop the daemon: killall miracle +--> no errors + +1.2.8 Verify a clean server shutdown: sudo tail /var/log/syslog +--> miracle: shutdown complete. + +1.3.0 Start miracle on a different port: src/miracle/miracle -port 5260 + +1.3.1 Verify successful miracle startup using syslog: sudo tail /var/log/syslog +--> miracle: miracle version 0.0.1 listening on port 5260 + +1.3.2 Verify connectivity on port 5260: telnet localhost 5260 +--> 100 VTR Ready + +1.3.3 Test clean disconnect: BYE +--> Connection closed by foreign host. + +1.3.4 Stop the daemon: killall miracle +--> no errors + + +2. Unit Management +------------------ + +Start the miracle server and connect to it with telnet or a protocol- +level client (albino). + +2.1 List the AV/C devices on the bus: NLS +--> 201 OK +--> one or more lines listing devices with GUID in second column + +2.2 Add a device as a miracle unit: UADD {sdl, bluefish} +--> 201 OK +--> U0 + +2.3 List the units: ULS +--> 201 OK +--> U0 ?? {sdl, bluefish} 1 +--> It is important that the last column is '1' to indicate it is online. + +2.4 List the units: ULS +--> 201 OK +--> U0 ?? {sdl, bluefish} 1 + +2.5 Attempt unit commands for a unit that does not exist: LOAD U6 foo +--> 403 Unit not found + +2.6 Attempt unit commands without specifying a unit: PLAY +--> 402 Argument missing + +2.7 Attempt unit commands for a unit: PLAY U0 +--> 200 OK + +2.8.0 Load a clip into an unit: LOAD U0 test.dv +--> 200 OK + +2.7.1 Verify the status of the unit: USTA U0 +--> 202 OK +--> 0 online "test.dv" 0 1000 25.00 0 ... +--> only the first 3 columns are relevant in this test + + +3. Server Configuration +----------------------- + +Start miracle if not already started from a previous test. + +3.1 Get the hard-coded default root property value: GET root +--> 202 OK +--> / + +3.2 List the files and subdirectories at the root: CLS / +--> 201 OK +--> "bin/" +--> ... + +3.3 Change the server root to a place where clips are stored: e.g., + SET root=/tmp +--> 200 OK + +3.4 Get the new value of the root property value: GET root +--> 202 OK +--> /tmp/ +--> Notice that if you did not use a trailing slash in step 2.3, one is + added for you and appears in this step. This is normal and correct. + +3.5 List the files and subdirectories at the root: CLS / +--> 201 OK +--> zero or more lines listing subdirectories followed by files. + +3.6 Try to set a property that does not exist: SET foo=bar +--> 405 Argument value out of range + +3.7 Try to set no property or value: SET +--> 402 Argument missing + +3.8 Attempt a bogus command: FOO +--> 400 Unknown command + +XXX 3.9 Attempt the incorrect case for a command: get root +XXX --> 400 Unknown command + +3.10 Attempt case insensitivity of property key: GET Root +--> 202 OK +--> /tmp/ + + +4. Simple Playback +------------------- + +Start miracle or restart if already started. +Add an online unit. +Set the server root property if desired. + +4.1.0 Load a clip into the unit: LOAD U0 test.dv +--> 200 OK + +4.1.1 Check the unit status: USTA U0 +--> 202 OK +--> 0 stopped "test.dv" 0 1000 25.00 0 ... +--> Only the first 3 columns are relevant in this test. +--> The remaining columns are only relevant to the tester. + +4.2.0 Play the clip: PLAY U0 +--> 200 OK +--> Verify audio and video output + +4.2.1 Check the unit status: USTA U0 +--> 202 OK +--> 0 playing "test.dv" 1739 1000 25.00 0 ... +--> Only the first 3 columns are relevant in this test. +--> The remaining columns are only relevant to the tester. + +4.3.0 Pause playback: PAUSE U0 +--> 200 OK +--> Verify video continues, but audio is muted. + +4.3.1 Check the unit status: USTA U0 +--> 202 OK +--> 0 paused "test.dv" 1739 0 25.00 0 ... +--> The fifth column --------^ should be 0; it indicates speed. + +4.3.2 Stop playback: STOP U0 +--> 200 OK +--> The analog video output stops + +4.3.3 Pause playback: PAUSE U0 +--> 200 OK +--> Analog video starts again, but it is held on the same frame + paused in 4.3.0. + +4.3.4 Stop playback: STOP U0 +--> 200 OK +--> The analog video signal ceases. + +4.3.5 Rewind the unit: REW U0 +--> 200 OK + +4.3.6 Play the unit: PLAY U0 +--> 200 OK +--> Analog audio and video are produced from the beginning of the file. + +4.4 Stop the server during playback and ensure clean shutdown. + + +5. Multi-unit Playback +----------------------- + +Start miracle or restart if already started. +Add *2* online units. +Set the server root property if desired. + +5.1.0 Load a clip into one unit: LOAD U0 test.dv +--> 200 OK + +5.1.1 Load a clip into the other unit: LOAD U1 test.dv +--> 200 OK + +5.1.2 Start playing one unit: PLAY U0 +--> 200 OK +--> Verify audio and video output + +5.1.3 Start playing the other unit: PLAY U1 +--> 200 OK +--> Verify audio and video output of both units + +5.2 Verify independence of units by pausing one of them: PAUSE U0 +--> 200 OK +--> Verify video continues, but audio is muted on the first unit only. + +5.3 Stop the server during multi-unit playback and ensure clean shutdown. + + +6. Advanced Playback +-------------------- + +Start miracle or restart if already started. +Add *2* online units. +Set the server root property if desired. + +Trick play modes: + +6.1.0 Load a clip: LOAD U0 test.dv +--> 200 OK + +6.1.1 Start playback by pausing on the first frame: PAUSE U0 +--> 200 OK +--> analog video starts, but audio is muted. + +6.1.2 Play fast forward: FF U0 +--> 200 OK +--> verify video is playing fast in the forward direction. + +6.1.3 Get unit status: USTA U0 +--> 202 OK +--> 0 playing "test.dv" 219 2000 25.00 0 ... +--> The important column is --^, indicates speed + +6.1.4 Play fast reverse: REW U0 +--> 200 OK +--> verify analog video output is fast in the reverse direction. + +6.1.5 Get unit status: USTA U0 +--> 202 OK +--> 0 playing "test.dv" 4621 -2000 25.00 0 ... +--> The important column is ---^, negative mean reverse + +6.1.6 Play slow forward: PLAY U0 500 +--> 200 OK +--> Verify the analog video output is slow in the forward direction. + +6.1.7 Play reverse normal speed: PLAY U0 -1000 +--> 200 OK +--> Verify the analog video output is at a normal speed in the reverse direction. +--> Audio output is reverse, but not the field order of video + +Loading while playing: + +6.2.0 Stop the unit (might be playing): STOP U0 +--> 200 OK + +6.2.1 Rewing the unit: REW U0 +--> 200 OK + +6.2.2 Start playing: PLAY U0 +--> 200 OK +--> verify analog audio and video output + +6.2.3 Load another clip: LOAD test002.dv +--> 200 OK +--> playback seamlessly switches to the new clip +--> verify the analog appearance of the video makes a clean switch + +6.2.4 Load another clip, this time with in and out points: + LOAD test.dv 100 500 (whatever works for your test footage) +--> 200 OK +--> verify the analog appearance of the video makes a clean switch + +6.2.5 Get unit status: USTA U0 +--> 202 OK +--> 0 playing "test.dv" 403 1000 25.00 100 ... +--> verify position -----^ is beyond --^ in point, last column is the out + point specified in the previous step. + +Edit points: + +6.3.0 Load a clip: LOAD U0 test.dv +--> 200 OK + +6.3.1 Pause the playback unit: PAUSE U0 +--> 200 OK + +6.3.2 Set the in point: SIN U0 100 +--> 200 OK +--> verify the frame displayed in analog video out changes + +6.3.4 Get the unit status: USTA U0 +--> 202 OK +--> 0 paused "test.dv" 100 0 25.00 100 ... +--> verify position ---^ and in ---^ + +6.3.5 Change the mode of the unit to not restrict playback to the edit + region: USET U0 points=ignore +--> 200 OK + +6.3.6 Jump to a frame before the in frame: GOTO U0 50 +--> 200 OK + +6.3.7 Get the unit status: USTA U0 +--> 202 OK +--> 0 paused "test.dv" 50 0 25.00 100 ... +--> position ----------^ preceeds -^ (in) + +6.3.8 Set the unit mode to restrict playback to the edit region: USET U0 points=use +--> 200 OK +--> verify frame on analog video output changes + +6.3.9 Get the unit status: USTA U0 +--> 202 OK +--> 0 paused "test.dv" 100 0 25.00 100 ... +--> verify position ---^ and in ---^ + +6.3.10 Clear the in point: SIN U0 -1 +--> 200 OK + +6.3.11 Get the unit status: USTA U0 +--> 202 OK +--> 0 paused "test.dv" 100 0 25.00 0 ... +--> verify the in point is reset --^ + +The above sequence should be repeated in a similar manner for the out point +using the SOUT command. + +Transfer: + +6.4.0 Load a clip into the first unit: LOAD U0 test.dv +--> 200 OK + +6.4.1 Load a clip into the second unit: LOAD U1 test002.dv +--> 200 OK + +6.4.2 Start playing the first unit: PLAY U0 +--> 200 OK +--> verify audio and video analog output + +6.4.3 Set an in point on the clip in the second unit: SIN U1 100 +--> 200 OK + +6.4.4 Play the second unit: PLAY U1 +--> 200 OK +--> note the beginning footage + +6.4.5 Transfer the clip from the second to the first unit: XFER U1 U0 +--> 200 OK +--> verify a clean switch on the analog audio and video output of the first unit. +--> upon transfer it should play the same footage previewed in step 6.4.4 + +6.4.5 Get the first unit's status: USTA U0 +--> 202 OK +--> 0 playing "test002.dv" 963 1000 29.97 100 2502 +--> note the in point set from U1 ---------^ + + +7. Unit Configuration +--------------------- + +Start miracle or restart if already started. +Add an online unit. +Set the server root property if desired. + +7.1.0 Load a short clip: LOAD U0 test.dv +--> 200 OK + +7.1.1 Play a clip: PLAY U0 +--> 200 OK +--> Wait until it gets to the end, and it should pause on the last frame. + +7.1.2 Make the clip start looping: USET U0 eof=loop +--> 200 OK +--> verify the clip starts playing from the beginning and loops + +7.2.0 Set the in point: SIN U0 10 +--> 200 OK +--> playback pauses at in point (verify with USTA U0) + +7.2.1 Set the out point: SOUT U0 200 +--> 200 OK +--> playback pauses at in point (verify with USTA U0) + +7.2.2 Start playing again: PLAY U0 +--> 200 OK +--> verify playback loops between in and out points + +7.3 Tell the unit to ignore the edit points: USET U0 points=ignore +--> 200 OK +--> verify playback loops over entire video file + +7.4 Get the current value of the points property: UGET U0 points +--> 202 OK +--> ignore + + +9. Server Side Queuing +---------------------- + +Only one unit is used for these test cases, and +users are encouraged to test with multiple units online. It is assumed that a +number of dv files are available for use in the servers ROOT directory - this +document assumes that they are named test001.dv and up. + +9.1.0 Start miracle in interactive mode and add a unit (all tests will assume U0) +--> server started with unit 0 available + +9.1.1 Obtain a miracle shell (via telnet or albino). +--> 100 VTR (if reported by the client) + +9.1.2 Load a clip with LOAD U0 test001.dv and PAUSE U0 +--> 200 OK + +9.1.3 List the clips with LIST U0 +--> 201 OK +--> 1 +--> 0 "test001.dv" 0 6999 7000 7000 25.00 +--> The 1 on the second line denotes the number of times the list has been changed + via user commands (known as the 'generation' number). +--> The third line and beyond reports the clip index (from 0 to n), file loaded, in point, + out point, real size of the file and the calculated size (out - in + 1 ). + +9.1.4 Check the unit status with USTA U0 +--> 202 OK +--> 0 paused "test001.dv" 0 0 25.00 0 6999 7000 "test001.dv" 0 0 6999 7000 1 1 0 +--> The last two fields indicate the generation number and current clip resp. + +9.1.5 Append a clip with APND U0 test002.dv followed by LIST U0 +--> 201 OK +--> 2 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 2 and current clip of 0 + +9.1.6 Move clip 1 to clip 0 with MOVE U0 1 0 followed by LIST U0 +--> 201 OK +--> 3 +--> 0 "test002.dv" 0 6999 7000 7000 +--> 1 "test001.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 3 and current clip of 1 + +9.1.7 Move clip 0 to clip 1 with MOVE U0 0 1 followed by LIST U0 +--> 201 OK +--> 4 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 4 and current clip of 0 +--> Note that the order in which you run 9.1.6 and 9.1.7 shouldn't matter as the + result will be identical + +9.1.8 Change the position to the next clip with GOTO U0 0 +1 +--> 200 OK +--> Check that USTA U0 reports a generation of 4 and current clip of 1 + +9.1.9 Remove all but the playing clip with CLEAN U0 followed by LIST U0 +--> 201 OK +--> 5 +--> 0 "test002.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 5 and current clip of 0 + +9.1.10 Insert test001.dv back into the list using INSERT U0 test001.dv and run LIST U0 +--> 201 OK +--> 6 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 6 and current clip of 1 + +9.1.11 Insert test003.dv at position 2 using INSERT U0 test001.dv 3 and run LIST U0 +--> 201 OK +--> 7 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 0 6999 7000 7000 +--> 2 "test003.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 7 and current clip of 1 + +9.1.12 Change the in point of the current clip using SIN U0 5000 and run LIST U0 +--> 201 OK +--> 8 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 5000 6999 7000 2000 +--> 2 "test003.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 8 and current clip of 1 + +9.1.13 Change the out point of the following clip using SOUT U0 5000 +1 and run LIST U0 +--> 201 OK +--> 9 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 5000 6999 7000 2000 +--> 2 "test003.dv" 0 5000 7000 5001 +--> Check that USTA U0 reports a generation of 9 and current clip of 2 + +9.1.14 Change the in point of the current clip to 1000 using SIN U0 1000 and run LIST U0 +--> 201 OK +--> 10 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 5000 6999 7000 2000 +--> 2 "test003.dv" 1000 5000 7000 4001 +--> Check that USTA U0 reports a generation of 10 and current clip of 2 + +9.1.15 Ignore the in/out points by running USET U0 points=ignore and run LIST U0 +--> 201 OK +--> 11 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 5000 6999 7000 7000 +--> 2 "test003.dv" 1000 5000 7000 7000 +--> Check that USTA U0 reports a generation of 11 and current clip of 2 + +9.1.16 Turn the in/out points on again by running USET U0 points=use and run LIST U0 +--> 201 OK +--> 12 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 5000 6999 7000 2000 +--> 2 "test003.dv" 1000 5000 7000 4001 +--> Check that USTA U0 reports a generation of 12 and current clip of 2 + +9.1.17 Remove the current clip using REMOVE U0 and run LIST U0 +--> 201 OK +--> 13 +--> 0 "test001.dv" 0 6999 7000 7000 +--> 1 "test002.dv" 5000 6999 7000 2000 +--> Check that USTA U0 reports a generation of 13 and current clip of 0 + +9.1.17 Remove the next clip using REMOVE U0 +1 and run LIST U0 +--> 201 OK +--> 14 +--> 0 "test001.dv" 0 6999 7000 7000 +--> Check that USTA U0 reports a generation of 14 and current clip of 0 diff --git a/docs/valerie.txt b/docs/valerie.txt new file mode 100644 index 0000000..9a90c86 --- /dev/null +++ b/docs/valerie.txt @@ -0,0 +1,861 @@ +Valerie API Documentation + +Copyright (C) 2004 Ushodaya Enterprised Limited +Author: Charles Yates +Last Revision: 2004-03-20 + + +TABLE OF CONTENTS +----------------- + + 0. Overview + 0.1. Intended Audience + 0.2. Terminology + 1. Definition of a Parser + 1.1. Construction of a Local Parser + 1.2. Construction of a Remote Parser + 1.3. Using the Parser + 1.4. Closing the Parser + 2. The High Level Parser Wrapper + 2.1. Connecting + 2.2. valerie_error_code + 2.3. Using the High Level Wrapper + 2.4. Obtaining Directory Contents + 2.5. Obtaining the Node List + 2.6. Obtaining the Unit List + 2.7. Unit Status Information + 2.8. Server Side Queuing APIs + 2.9. Accessing the Low Level Parser Directly + 2.10. Cleaning up + 2.11. Examples + 3. The Low Level Parser API + 3.1. Executing a Command + 3.2. Interpreting valerie_response + 3.3. Accessing Unit Status + APPENDIX A - COMPILATION AND LINKING + APPENDIX B - COMPLETE HIGH LEVEL PARSER WRAPPER API LISTING + APPENDIX C - COMPLETE LOW LEVEL PARSER API LISTING + APPENDIX D - REFERENCES + + +0. Overview +----------- + + This document details how applications interface to DVCP functionality. + + +0.1. Intended Audience +---------------------- + + This document draws heavily upon the DVCP design (1) and assumes a basic + knowledge of the functionality provided by the DVCP core. + + It is aimed at developers who wish to use or maintain the API. + + +0.2. Terminology +---------------- + + The API is designed to allow client applications the ability to communicate + to a standalone miracle server or entirely embed the DVCP core in an + instance of a client application. + + The distinction between the two is defined by the construction of the + 'parser'. + + This 'parser' can be used to issue DVCP commands and receive responses and + a 'high level parser wrapper' is provided to simplify the usage and + decouple the application from the DVCP command set. + + +1. Definition of a Parser +------------------------- + + The parser provides a low level API which allows text DVCP commands to be + executed with responses being returned to the caller. Commands and + responses are ASCII formatted text. + + Two parsers are provided - local and remote. + + The local parser is the physical implementation which takes commands and + executes them. + + The remote parser is a network abstraction that forwards commands to a + miracle instance that hosts a local parser. + + +1.1. Construction of a Local Parser +----------------------------------- + + To construct a local parser you must have: + + #include + + and code to initialise the parser is as follows: + + valerie_parser parser = miracle_parser_init_local( ); + + See Appendix A for compilation and linking details. + + +1.2. Construction of a Remote Parser +------------------------------------ + + To construct a remote parser you must have: + + #include + + and code to initialise the parser is as follows: + + valerie_parser parser = valerie_parser_init_remote( "server", port ); + + See Appendix A for compilation and linking details. + + +1.3. Using the Parser +--------------------- + + Although the parser can be used directly to send commands and receive + responses, this low level usage puts the onus on the developer to parse the + responses in a meaningful way. + + Although this usage is not strictly forbidden by applications, it is + discouraged as construction of commands and meaningful parsing of responses + leads to the clients being unnecessarily dependent on the servers input and + output. + + As a result, a higher level Parser Wrapper API is provided - this API + encapsulates the command construction and response parsing. + + The following 2 sections provide details on these modes of access. + + +1.4. Closing the Parser +----------------------- + + Regardless of use, it is the constructors responsibility to close the + parser before it goes out of scope. This is done via: + + valerie_parser_close( parser ); + + +2. The High Level Parser Wrapper +-------------------------------- + + The recommended way to access the parser, is via the valerie API. To use + this API, you must have: + + #include + + and code to construct the wrapper is: + + valerie dv = valerie_init( parser ); + + Note that either remote or local parsers can be used here and there is no + difference in usage, though some error returns will not be applicable to + both. + + It is recommended that applications honour and deal with the error returns + of both as this allows applications to interchange parsers. + + Also note that valerie is not threadsafe, so you should not use the same + structure in multiple threads. The correct solution to this is to create a + valerie per thread - you may safely use the same parser for each thread ie: + + /* valerie for the application */ + valerie dv = valerie_init( parser ); + /* valerie for the status handling thread. */ + valerie dv_status = valerie_init( parser ); + + For the purposes of simplification, the remainder of this section assumes + that a remote parser is in use. + + +2.1. Connecting +--------------- + + Once constructed, the next thing to do is 'connect': + + valerie_error_code error = valerie_connect( dv ); + + This function call initialises the parser (ie: if it's remote, it + establishes a connection to the server, or if it's local, it initialises + the state of the units and supporting objects). + + Note that if you have multiple valerie instances on the same parser you + should only connect one of the instances. + + +2.2. valerie_error_code +---------------------- + + All but a couple of the functions that make up the valerie API return a + valerie_error_code. + + These are defined as follows: + + valerie_ok = 0, + valerie_malloc_failed, + valerie_unknown_error, + valerie_no_response, + valerie_invalid_command, + valerie_server_timeout, + valerie_missing_argument, + valerie_server_unavailable, + valerie_unit_creation_failed, + valerie_unit_unavailable, + valerie_invalid_file, + valerie_invalid_position + + In most cases, it is sufficient to check on a return of valerie_ok. + + To obtain a textual description of a particular error, you can use: + + char *valerie_error_description( valerie_error_code ); + + +2.3. Using the High Level Wrapper +--------------------------------- + + The following code snippet assumes that dv is an initialised and connected + valerie structure: + + valerie_error_code error = valerie_unit_play( dv, 0 ); + if ( error == valerie_ok ) + fprintf( stderr, "Unit 0 is now playing\n" ); + else + fprintf( stderr, "Play on unit 0 failed: %s\n", + valerie_error_description( error ) ); + + The complete interface to valerie is listed in Appendix B of this document. + + +2.4. Obtaining Directory Contents +-------------------------------- + + To obtain a list of files and subdirectories in a given directory relative + to the ROOT property of the server, DVCP provides the CLS command. + + A valid execution of CLS would be something like: + + CLS "/Stuff" + + would provide a response formatted as follows: + + 201 OK + "More Stuff/" + "file0001.dv" 15552000 + "file0002.dv" 15552000 + + with a trailing empty line. + + The first line indicates the error value, the second line shows an example + of a subdirectory and the 3rd and 4th line lists two files that happen to + exist in the directory. + + valerie provides a high level view on this which automatically parses the + response from the server correctly via the valerie_dir structures and + related functions. + + An example of use is as follows: + + valerie_dir dir = valerie_dir_init( dv, "/Stuff" ); + valerie_error_code error = valerie_dir_get_error_code( dir ); + if ( error == valerie_ok ) + { + if ( valerie_dir_count( dir ) > 0 ) + { + valerie_dir_entry_t entry; + int index = 0; + for ( index = 0; index < valerie_dir_count( dir ); index ++ ) + { + valerie_dir_get( dir, index, &entry ); + if ( entry.dir ) + printf( "<%s>\n", entry.name ); + else + printf( "%30s %8d", entry.name, entry.size ); + } + } + else + { + fprintf( stderr, "Directory is empty\n" ); + } + } + else + { + fprintf( stderr, "Directory listing failed: %s\n", + valerie_error_description( error ) ); + } + valerie_dir_close( dir ); + + Note that entry.name provides the name of the file or directory without the + directory prefix. As a convenience, entry.full provides the prefixed name, + so you could subsequently use: + + error = valerie_unit_load( dv, 0, entry.full ); + + to load unit 0 with an entry. + + +2.5. Obtaining the Node List +---------------------------- + + Currently not defined by miracle. + +2.6. Obtaining the Unit List +---------------------------- + + To obtain a list of defined units, DVCP provides the ULS command. + + A valid execution of ULS would be: + + ULS + + and would provide a response formatted as follows: + + 201 OK + U0 00 sdl:360x288 1 + + with a trailing empty line. + + The fields of each record in the response dictate unit, node, mlt consumer and + online status respectively. + + valerie provides a high level view on this which automatically parses the + response from the server correctly via the valerie_units structures and + related functions. + + An example of use is as follows: + + valerie_units units = valerie_units_init( dv ); + valerie_error_code error = valerie_units_get_error_code( units ); + if ( error == valerie_ok ) + { + if ( valerie_units_count( units ) > 0 ) + { + valerie_unit_entry_t entry; + int index = 0; + for ( index = 0; index < valerie_units_count( units ); index ++ ) + { + valerie_units_get( units, index, &entry ); + printf( "U%d %02d %s %s\n", + entry.unit, + entry.node, + entry.guid, + entry.online ? "online" : "offline" ); + } + } + else + { + fprintf( stderr, "Unit list is empty\n" ); + } + } + else + { + fprintf( stderr, "Unit listing failed: %s\n", + valerie_error_description( error ) ); + } + valerie_units_close( units ); + + +2.7. Unit Status Information +---------------------------- + + There are two methods for a client to obtain unit status information. + + The first is via the DVCP USTA command, which would normally be accessed + via: + + USTA U0 + + and would provide a response formated as follows: + + 202 OK + 0 playing "a.dv" 58 1000 25.00 0 6999 7000 "a.dv" 157 0 6999 7000 1 4 0 + + with no trailing empty line. + + The entries in the record are: + + * Unit + * State (undefined, offline, not_loaded, stopped, playing, + paused, disconnected [when server dies]) + * Name of Clip + * Position in clip + * Speed * 1000 + * Frames per second + * Start of clip (in point) + * End of clip (out point) + * Length of clip + * Read ahead clip + * Read ahead position + * Read ahead clip in + * Read ahead clip out + * Read ahead clip length + * Seekable flag + * Playlist generation + * Clip index + + Again, valerie provides a high level means for obtaining this via the + valerie_unit_status function and valerie_status structures: + + valerie_status_t status; + valerie_error_code error = valerie_unit_status( dv, 0, &status ); + if ( error == valerie_ok ) + { + switch( status.status ) + { + case unit_offline: + printf( "offline " ); + break; + case unit_undefined: + printf( "undefined " ); + break; + case unit_not_loaded: + printf( "unloaded " ); + break; + case unit_stopped: + printf( "stopped " ); + break; + case unit_playing: + printf( "playing " ); + break; + default: + printf( "unknown " ); + break; + } + + printf( "%06lld %06lld %06lld %s\n", status.in, + status.position, + status.out, + status.clip ); + } + else + { + fprintf( stderr, "Unit status failed: %s\n", + valerie_error_description( error ) ); + } + + The second approach for obtaining a units status is via automatic + notification. + + This is done via the valerie_notifier API. To obtain the notifier from the + high level API, you can use: + + valerie_notifier notifier = valerie_get_notifier( dv ); + + To obtain the last status associated to a unit, you can use: + + int unit = 1; + valerie_status_t status; + valerie_notifier_get( notifier, &status, unit ); + + To wait for the next status from any unit, you can use: + + valerie_notifier_wait( notifier, &status ); + + If you wish to trigger the action associated to your applications wait + handling of a particular unit, you can use: + + valerie_notifier_get( notifier, &status, unit ); + valerie_notifier_put( notifier, &status ); + + See Examples below for details on this. + + The complete list of fields in the status structure are: + + int unit; + unit_status status; + char clip[ 2048 ]; + int64_t position; + int speed; + double fps; + int64_t in; + int64_t out; + int64_t length; + char tail_clip[ 2048 ]; + int64_t tail_position; + int64_t tail_in; + int64_t tail_out; + int64_t tail_length; + int seekable; + int generation; + int clip_index; + + You will always receive a status record for every frame output. + + The read ahead information is provided for client side queuing. Client side + queuing assumes that uset eof=pause is applied to the unit. A client can + detect when the previously scheduled clip is played out by using the read + ahead information and schedule the next clip. While this mode of operation + is still supported, it is recommended that new clients use the server side + queuing mechanism which is described in the following section. + + +2.8. Server Side Queueing APIs +------------------------------ + + This section describes the APIs available to provide server side queueing. + + The concept is that each unit maintains its own playlist, containing multiple + clips. Associated to the playlist is a generation number which is incremented + on each modification to the playlist. The current playlist generation is + provided in the status record in order for a client to know when to refresh + its presentation of the list. The status record also indicates which clip is + currently active. + + Actions that can be carried out on the playlist are summarised as: + + * list - list all the clips and associated in/out points and size + * loading a clip - a load will wipe the current list and replace it with the + specified clip + * appending a clip - append will always place the specified clip at the end + of the playlist + * inserting a clip - insert will place a new clip at the specified position + in the playlist + * moving a clip - move will allow clips can be moved in the playlist + * removing a clip - remove will remove the specified clip from the playlist + * clean - clean will remove all but the playing clip from the playlist + + Additionally, the following existing actions are clip aware: + + * goto allows you to move the current play position to a specific clip position + * set in/out points allows you to modify clip in and out points + + Backward compatability has been maintained by the addition of a clip-aware + family of APIs which have the naming convention of valerie_unit_clip_*. + + These are listed in Appendix B. + + The following shows an example of obtaining the clips queued on unit 0: + + valerie_list list = valerie_list_init( dv, 0 ); + valerie_list_entry_t entry; + int index; + + printf( "Generation = %d\n", list->generation ); + for ( index = 0; index < valerie_list_count( list ); index ++ ) + { + valerie_list_get( list, index, &entry ); + printf( "%d %s %d %d %d %d\n", + entry.clip, + entry.full, + entry.in, + entry.out, + entry.max, + entry.size ); + } + valerie_list_close( list ); + + To load a clip on unit 0: + + valerie_unit_load( dv, 0, "/path/clip.dv" ); + + To append a clip on unit 0: + + valerie_unit_append( dv, 0, "/path/clip.dv", -1, -1 ); + + Note that the last two arguments specify the in and out points of the clip + with -1 denoting dfaults of the entirety of the file. + + To insert a clip at position 0 on unit 0, we can use the following: + + valerie_unit_clip_insert( dv, 0, clip_absolute, 0, "/path/clip.dv", -1, -1 ); + + The 3rd and 4th arguments here are common to all the valerie_unit_clip functions. + They take the form of either [clip_absolute, n] to indicate an absolute clip + index, or [clip_relative, n] to indicate a clip index relative to the + currently playing clip. + + So, to insert a clip immediately before the currently playing clip, we can + use: + + valerie_unit_clip_insert( dv, 0, clip_relative, -1, "/path/clip.dv", -1, -1 ); + + To move the current clip to the next position in the list: + + valerie_unit_clip_move( dv, 0, clip_relative, 0, clip_relative, 1 ); + + To remove a specific clip: + + valerie_unit_clip_remove( dv, 0, clip_absolute, index ); + + To remove all but the currently playing clip: + + valerie_unit_clean( dv, 0 ); + + To goto the first frame in the first clip, you can use: + + valerie_unit_clip_goto( dv, 0, clip_absolute, 0, 0 ); + + To set the in and out points on the current clip: + + valerie_unit_clip_set_in( dv, 0, clip_relative, 0, 0 ); + valerie_unit_clip_set_out( dv, 0, clip_relative, 0, 1000 ); + + A more complete example of use of the server side can queuing can be found + at: + + http://users.pandora.be/acp/rugen + + The demo client provided with valerie is used for retaining backward + compatability with the client side queuing API. + + +2.9. Accessing the Low Level Parser Directly +-------------------------------------------- + + The low level parser and its associated structures can be accessed directly + from the high level API, but is very occasionally actually needed. + + The methods are provided via a pair of high level methods: + + valerie_error_code error = valerie_execute( dv, 1024, "USTA U%d", unit ); + valerie_response response = valerie_get_last_response( dv ); + int index = 0; + for ( index = 0; index < valerie_response_count( response ); index ++ ) + printf( "%d: %s\n", index, valerie_response_get_line( response,index ) ); + + More details on the valerie_response structure can be found in section 3 of this + document. + + +2.10. Cleaning up +----------------- + + Before the valerie and parser go out of scope, you need to run: + + valerie_close( dv ); + valerie_parser_close( parser ); + + Note that you should close all valerie instances before closing the parser. + + +2.11. Examples +-------------- + + Please refer to albino and humperdink source for examples provided with + the project. Additional examples can be found via google with gdv1394 and + poldo. + + +3. The Low Level Parser API +--------------------------- + + The low level parser API provides a very simple mechanism for constructing + commands and receiving responses. + + As described in section 2, a parser is constructed as local or remote and + this is sufficient for constructing the low level parser. + + +3.1. Executing a Command +------------------------ + + All commands can be executed via the single variable argument function + valerie_parser_executef and this function returns a valerie_response, ie: + + valerie_response response = valerie_parser_executef( parser, "CLS \"%s\"", dir ); + + Note that no carriage return/line feed is required (adding this is + erroneous). + + It is the receiver of the response who is responsible for closing it. + + valerie_response_close( response ); + + +3.2. Interpreting valerie_response +----------------------------- + + The response received can be NULL, but it is safe to call: + + int error = valerie_response_get_error_code( response ); + + which will return: + + * -1 if response is NULL, + * -2 if there is no content to the response, + * 0 if the responses first line does not correspond to a valid DVCP response + * or the DVCP protocol error code returned on the first line of the response + + A simple use of a valerie_response structure is as follows: + + valerie_response response = valerie_parser_executef( parser, "CLS \"%s\"", dir ); + int error = valerie_response_get_error_code( response ); + if ( error >= 0 ) + { + int index = 0; + for ( index = 0; index < valerie_response_count( response ); index ++ ) + printf( "%3d: %s\n", index, valerie_response_get_line( response, index ) ); + } + else + { + /* interpret error */ + } + valerie_response_close( response ); + + Note that it is safe to call valerie_response_close regardless of the error + condition indicated. + + +3.3. Accessing Unit Status +-------------------------- + + As with the high level parser, there are two alternatives to obtain unit + status information - either via the USTA DVCP command or via the + valerie1394_notifier. + + The latter is the recommended way for any applications which wish to extract + meaningful information from the status while avoiding the requirement to + duplicate the parsing process in a specific client. + + The notifier can be obtained by: + + valerie_notifier notifier = valerie_parser_get_notifier( parser ); + + The use of the notifier with the low level parser is identical to that + dictated in Section 2 - to obtain the last status associated to a unit, + you can use: + + int unit = 1; + valerie_status_t status; + valerie_notifier_get( notifier, &status, unit ); + + To wait for the next status from any unit, you can use: + + valerie_notifier_wait( notifier, &status ); + + +APPENDIX A - COMPILATION AND LINKING +------------------------------------ + + Compilation flags are: + + -I /include + + where prefix defaults to /usr/local. + + Linking flags for a client are: + + -L /lib/ -lvalerie + + Or for a local parser: + + -L /lib/ -lmiracle + + Note that you never need both libs. + + +APPENDIX B - COMPLETE HIGH LEVEL PARSER WRAPPER API LISTING +----------------------------------------------------------- + + valerie valerie_init( valerie_parser ); + + valerie_error_code valerie_connect( valerie ); + + valerie_error_code valerie_set( valerie, char *, char * ); + valerie_error_code valerie_get( valerie, char *, char *, int ); + + valerie_error_code valerie_unit_add( valerie, char * ); + valerie_error_code valerie_unit_load( valerie, int, char * ); + valerie_error_code valerie_unit_load_clipped( valerie,int,char *,long,long ); + valerie_error_code valerie_unit_load_back( valerie, int, char * ); + valerie_error_code valerie_unit_load_back_clipped(valerie,int,char *,long,long) + valerie_error_code valerie_unit_play( valerie, int ); + valerie_error_code valerie_unit_play_at_speed( valerie, int, int ); + valerie_error_code valerie_unit_stop( valerie, int ); + valerie_error_code valerie_unit_pause( valerie, int ); + valerie_error_code valerie_unit_rewind( valerie, int ); + valerie_error_code valerie_unit_fast_forward( valerie, int ); + valerie_error_code valerie_unit_step( valerie, int, int ); + valerie_error_code valerie_unit_goto( valerie, int, int ); + valerie_error_code valerie_unit_set_in( valerie, int, int ); + valerie_error_code valerie_unit_set_out( valerie, int, int ); + valerie_error_code valerie_unit_clear_in( valerie, int ); + valerie_error_code valerie_unit_clear_out( valerie, int ); + valerie_error_code valerie_unit_clear_in_out( valerie, int ); + valerie_error_code valerie_unit_set( valerie, int, char *, char * ); + valerie_error_code valerie_unit_get( valerie, int, char * ); + + valerie_error_code valerie_unit_status( valerie, int, valerie_status ); + valerie_notifier valerie_get_notifier( valerie ); + + valerie_dir valerie_dir_init( valerie, char * ); + valerie_error_code valerie_dir_get( valerie_dir, int, valerie_dir_entry ); + int valerie_dir_count( valerie_dir ); + void valerie_dir_close( valerie_dir ); + + valerie_nodes valerie_nodes_init( valerie ); + valerie_error_code valerie_nodes_get(valerie_nodes,int,valerie_node_entry); + int valerie_nodes_count( valerie_nodes ); + void valerie_nodes_close( valerie_nodes ); + + valerie_units valerie_units_init( valerie ); + valerie_error_code valerie_units_get(valerie_units,int,valerie_unit_entry); + int valerie_units_count( valerie_units ); + void valerie_units_close( valerie_units ); + + valerie_response valerie_get_last_response( valerie ); + + valerie_error_code valerie_execute( valerie, size_t, char *, ... ); + + void valerie_close( valerie ); + + Notifier Functions + ------------------ + + void valerie_notifier_get( valerie_notifier, valerie_status, int ); + void valerie_notifier_put( valerie_notifier, valerie_status ); + int valerie_notifier_wait( valerie_notifier, valerie_status ); + void valerie_notifier_close( valerie_notifier ); + + Server Side Queuing + ------------------- + + valerie_list valerie_list_init( valerie, int ) + valerie_error_code valerie_list_get_error_code( valerie_list ) + valerie_error_code valerie_list_get( valerie_list, int, valerie_list_entry ) + int valerie_list_count( valerie_list ) + void valerie_list_close( valerie_list ) + + valerie_error_code valerie_unit_clean( valerie dv, int unit ) + valerie_error_code valerie_unit_append( valerie dv, int unit, char *file, int in, int out ) + valerie_error_code valerie_unit_remove_current_clip( valerie dv, int unit ) + + valerie_error_code valerie_unit_clip_goto( valerie dv, int unit, valerie_clip_offset offset, int clip, int position ) + valerie_error_code valerie_unit_clip_set_in( valerie dv, int unit, valerie_clip_offset offset, int clip, int in ) + valerie_error_code valerie_unit_clip_set_out( valerie dv, int unit, valerie_clip_offset offset, int clip, int in ) + valerie_error_code valerie_unit_clip_move( valerie dv, int unit, valerie_clip_offset offset, int src, valerie_clip_offset offset, int dest ) + valerie_error_code valerie_unit_clip_remove( valerie dv, int unit, valerie_clip_offset offset, int clip ) + valerie_error_code valerie_unit_clip_insert( valerie dv, int unit, valerie_clip_offset offset, int clip, char *file, int in, int out ) + + + +APPENDIX C - COMPLETE LOW LEVEL PARSER API LISTING +-------------------------------------------------- + + valerie_response valerie_parser_connect( valerie_parser ); + valerie_response valerie_parser_execute( valerie_parser, char * ); + valerie_response valerie_parser_executef( valerie_parser, char *, ... ); + valerie_response valerie_parser_run( valerie_parser, char * ); + valerie_notifier valerie_parser_get_notifier( valerie_parser ); + void valerie_parser_close( valerie_parser ); + + valerie_response valerie_response_init( ); + valerie_response valerie_response_clone( valerie_response ); + int valerie_response_get_error_code( valerie_response ); + char *valerie_response_get_error_string( valerie_response ); + char *valerie_response_get_line( valerie_response, int ); + int valerie_response_count( valerie_response ); + void valerie_response_set_error( valerie_response, int, char * ); + int valerie_response_printf( valerie_response, size_t, char *, ... ); + int valerie_response_write( valerie_response, char *, int ); + void valerie_response_close( valerie_response ); + + +APPENDIX D - REFERENCES +----------------------- + + (1) doc/dvcp.txt - DVCP protocol + (2) doc/testing.txt - Test procedures diff --git a/docs/westley.txt b/docs/westley.txt new file mode 100644 index 0000000..1f42538 --- /dev/null +++ b/docs/westley.txt @@ -0,0 +1,574 @@ +Westley Documentation + +Copyright (C) 2004 Ushodaya Enterprised Limited +Authors: Charles Yates +Last Revision: 2004-03-20 + + +WESTLEY +------- + +Preamble: + + Westley is the MLT projects XML serialisation/deserialisation format - + as such, it closely mirrors the internal structure of the MLT API. + + If you just want to go straight to the DTD, then see + mlt/src/modules/westley/westley.dtd, which gets installed at + $(prefix)/share/mlt/modules/westley.dtd. Currently, the westley parser is + non-validating. + + +Introduction: + + A westley document is essentially a list of 'producers' - a producer is + an mlt object which generates mlt frames (images and associated audio + samples). + + There are 3 types of producer: + + * Basic Producers - these are typically file or device oriented feeds; + * Playlists - these are arrangements of multiple producers; + * Multitracks - these are the fx encapsulators. + + In the mlt model, producers are created and attached to 'consumers' - + consumers are software playback components (such as SDL), or wrappers for + hardware drivers (such as bluefish) or even the westley serialising + consumer itself (the latter doesn't receive frames - it merely + interrogates the connected producer for its configuration). + + Although westley was defined as a serialisation mechanism for instantiated + MLT components, this document will concentrate on the hand authoring of + westley documents. + + +Rules: + + As shall become apparent through the remainder of this document, the basic + tenet of westley authoring is to organise the document in the following + manner: + + 1) create producer elements for each unique media clip in the project; + 2) create playlists for each track; + 3) create a multitrack and specify filters and transitions; + 4) adding global filters. + + While other uses of westley exist, the approach taken here is to maximise + efficiency for complex projects. + + +Basic Producers: + + The simplest westley document is: + + + + clip1.dv + + + + The westley wrapping is of course superfluous here - loading this document + with MLT is identical to loading the clip directly. + + Of course, you can specify additional properties. For example, consider an + MPEG file with multiple soundtracks - you could define a westley document to + ensure that the second audio track is loaded: + + + + clip1.mpeg + 1 + + + + NB: This relies on the mpeg being handled by the avformat producer, rather + than the mcmpeg one. See services.txt for more details. + + A more useful example comes with the pango producer for a text producer. + + TODO: pango example... + + Notes: + + 1) It is better not to specify in/out points when defining basic producers + as these can be specified in the playlists. The reasoning is that in/out + restricts the amount of the clip available, and could lead to the same clip + being loaded multiple times if you need different regions of the clip + elsewhere; + 2) A westley can be specified as a resource, so westleys can naturally + encapsulate other westleys. + + +Playlists: + + Playlists provide a 'collection' structure for producers. These can be used + to define 'tracks' in the multitrack approach, or simple playlists for + sequential, single track playout. + + As an example, the following defines two basic producers and a playlist with 3 + items: + + + + clip1.dv + + + clip2.dv + + + + + + + + + Here we see how the playlist defines the in/out points of the basic + producers. + + Notes: + + 1) All in/out points are absolute frame positions relative to the producer + being appended to the playlist; + 2) Westley documents are currently authored for a specific normalisation; + 3) The last 'producer' in the document is the default for play out; + 4) Playlists can reference the same producer multiple times. In/out regions + do not need to be contiguous - duplication and skipping is acceptable. + + +Interlude - Introducing Multitracks: + + So far we've defined basic producers and playlists/tracks - the tractor is + the element that allows us to arrange our tracks and specify filters and + transitions. Similarly to a playlist, a tractor is a container. + + Note that MLT doesn't see a filter or a transition as a producer in the + normal sense - filters and transitions are passive when it comes to seeking. + Internally, seeks are carried out on the producers. This is an important + point - MLT does not follow a traditional graph oriented model. + + Visualising an MLT tractor and it's interaction with the consumer will + assist here: + + +----------------------------------------------+ + |tractor | + | +----------+ +-+ +-+ +-+ +-+ | + | |multitrack| |f| |f| |t| |t| | + | | +------+ | |i| |i| |r| |r| | + | | |track0|-|--->|l|- ->|l|- ->|a|--->|a|\ | + | | +------+ | |t| |t| |n| |n| \ | + | | | |e| |e| |s| |s| \ | + | | +------+ | |r| |r| |i| |i| \ | +--------+ + | | |track1|-|- ->|0|--->|1|--->|t|--->|t|-----|--->|consumer| + | | +------+ | | | | | |i| |i| / | +--------+ + | | | | | | | |o| |o| / | ^ + | | +------+ | | | | | |n| |n| / | | + | | |track2|-|- ->| |- ->| |--->|0|- ->|1|/ | | + | | +------+ | | | | | | | | | | | + | +----------+ +-+ +-+ +-+ +-+ | | + +----------------------------------------------+ | + ^ | + | | + +-----------+ | + |APPLICATION|--------------------------------------------+ + +-----------+ + + Internally, all frames from all tracks pass through all the filters and + transitions - these are told which tracks to deal and which regions of the + tracks to work on. + + Note that the application communicates with the producer - it can alter + playback speed, position, or even which producer is connected to which + consumer. + + The consumer receives the first non-blank frame (see below). It has no say + in the order in which gets them (the sdl consumer when used with inigo might + appear to be an exception - it isn't - it simply has a route back to the + application to allow the application to interpret key presses). + + +Tractors: + + To create a multitrack westley, we can use two playlists and introduce a + tractor. For the purposes of demonstration, I'll add a filter here too: + + + + clip1.dv + + + clip2.dv + + + + + + + + + + + + + + + + 0 + greyscale + + + + + Here we see that blank frames are inserted into the first playlist and a + blank is provided at the beginning of the second - this can be visualised in + the traditional timeline widget as follows: + + +-------+ +-------------+ + |a | |a | + +-------+---+-------------+ + |b | + +---+ + + Adding the filter on the top track, gives us: + + +-------+ +-------------+ + |a | |a | + +-------+---+-------------+ + |greyscale | + --------+---+-------------+ + |b | + +---+ + + Note that it's only applied to the visible parts of the top track. + + The requirement to apply a filter to the output, as opposed to a specific + track leads us to the final item in the Rules section above. As an example, + let's assume we wish to watermark all output, then we could use the + following: + + + + clip1.dv + + + clip2.dv + + + + + + + + + + + + + + + + 0 + greyscale + + + + + + + + watermark + watermark1.png + + + + + Here we employ another tractor and we define a single track (being the + tractor we previously defined) and apply a watermarking filter there. + + This is simply provided as an example - the watermarking functionality could + be better handled at the playout stage itself (ie: as a filter automatically + placed between all producers and the consumer). + + Tracks act like "layers" in an image processing program like the GIMP. The + bottom-most track takes highest priority and higher layers are overlays + and do not appear unless there are gaps in the lower layers or unless + a transition is applied that merges the tracks on the specifed region. + Practically speaking, for A/B video editing it does not mean too much, + and it will work as expected; however, as a general rule apply any CGI + (graphic overlays with pixbuf or titles with pango) on tracks higher than + your video tracks. Also, this means that any audio-only tracks that are + lower than your video tracks will play rather than the audio from the video + clip. Remember, nothing is affected like mixing or compositing until one + applies a transition or appropriate filter. + + + + clip1.dv + + + + + + clip2.mpeg + + + + + + + + + + + + 0 + 1 + luma + + + 0 + 1 + mix + 0.0 + 1.0 + + + + + A "luma" transition is a video wipe processor that takes a greyscale bitmap + for the wipe definition. When one does not specify a bitmap, luma performs + a dissolve. The "mix" transition does an audio mix, but it interpolates + between the gain scaling factors between the start and end properties - + in this example, from 0.0 (none of track B) to 1.0 (all of track B). + Because the bottom track starts out with a gap specified using the + element, the upper track appears during the blank segment. See the demos and + services.txt to get an idea of the capabilities of the included transitions. + +Flexibility: + + The information presented above is considered the MLT Westley "normal" + form. This is the output generated by the westley consumer, for example, + when used with inigo. It is the output generated when you use the + "Westley to File" consumer in the demo script, which beginners will find + most useful for learning to use westley XML. This section describes + alternative forms the westley producer accepts. + + First of all, the normal form is more of a linear format with producers + and playlists defined prior to their usage in a multitrack. Westley + also accepts a hierarchical format with producers as children of tracks + or playlist entries and with playlists as children of tracks: + + + + + + + + + clip1.dv + + + + + + + + + Obviously, this example is meant to demonstrate hierarchy and not effective + use of playlist or multitrack! + + Secondly, as part of error handling, westley is forgiving if you fail to + supply , , and where one can be understood. This + affords an abbreviated syntax that is less verbose and perhaps less + intimidating for a human to read and understand. One can simplify the + above example as: + + + + + + clip1.dv + + + + + + Yes, filters and transitions can be added to the above example after the + closing multitrack tag () because it is still enclosed within + the westley body tags. + + If you specify in and out on a producer and it has been enclosed within + an or , then the edit points apply to the playlist + entry and not to the producer itself. This facilitates re-use of media: + + + + clip1.dv + + + + + In the above example, the producer attribute of the entry element is + a reference to the preceding producer. All references must follow the + definition. The edit points supplied on the producer above will not affect + the entry that references it below because westley knows the clip is a + playlist entry and optimises this situation. The advantage is that one + does not need to determine every clip to be included ahead of time + and specify them outside the context of the mutlitrack timeline. + + This form of authoring will be easier for many to visualise as a non-linear + editor's timeline. Here is a more complex example: + + + + + + clip2.mpeg + + + + + + + + clip3.mpeg + + + + + + + + + + + + Did you notice something different in the last example? Properties can be + expressed using XML attributes on the element as well. However, only + non-service-specific properties are supported in this way. For example, + "mlt_service" is available to any producer, filter, or transition. However, + "resource" is actually service-specific. Notice the syntax of the last + property, on the last transition. Westley accepts property values using + the "value" attribute as well as using element text. + + We have seen a few different ways of expressing property values. There are + a couple more for properties that can accept XML data. For example, the + GDK pixbuf producer with librsvg can handle embedded SVG, and the Pango + producer can handle embedded Pango markup. You can enclose the embedded XML + using a CDATA section: + + ... ]]> + + Please ensure the opening CDATA tag immediately follows the opening + property tag and that the section closing tag immediately precedes the + closing property tag. + + However, westley can also accept inline embedded XML: + + + + + + + Currently, there is no namespace handling so a conflict will occur only on + any embedded XML that contains an element named "property" because + westley collects embedded XML until it reaches a closing property tag. + + +Entities and Parameterisation: + + The westley producer parser supports XML entities. An example: + + + + ]> + + + pango + &msg; + + + + If you are embedding another XML document into a property value not using + a CNODE section, then any DOCTYPE section must be relocated before any of + the xml elements to be well-formed. See demo/svg.westley for an example. + + Entities can be used to parameterise westley! Using the above example, the + entity declared serves as the default value for &msg;. The entity content + can be overridden from the resource property supplied to the westley + producer. The syntax is the familiar, url-encoded query string used with + HTTP, e.g.: file?name=value&name=value... + + There are a couple of rules of usage. The Miracle LOAD command and inigo + command line tool require you to preface the URL with "westley:" because + the query string destroys the filename extension matching peformed by + Fezzik. Also, inigo looks for '=' to tokenise property settings. Therefore, + one uses ':' between name and value instead of '='. Finally, since inigo + is run from the shell, one must enclose the URL within single quotes to + prevent shell filename expansion, or similar. + + Needless to say, the ability to parameterise westley XML compositions is + an extremely powerful tool. An example for you to play with is available in + demo/entity.westley. Try overriding the name from inigo: + inigo 'westley:entity.westley?name:Charlie' + + Technically, the entity declaration is not needed in the head of the XML + document if you always supply the parameter. However, you run the risk + of unpredictable behviour without one. Therefore, it is safest and a best + practice to always supply an entity declaration. It is improves the + readability as one does not need to search for the entity references to + see what parameters are available. + + +Tips and Technique: + + If one finds the above hierarchical, abbreviated format intuitive, + start with a simple template and fill and extend as needed: + + + + + ...add a playlist for each track... + + ...add filters and transitions... + + + By using a playlist for each track, it is easier to iteratively add new + clips and blank regions as you develop the project. You will not have to + use or later add when necessary. + + A more advanced template that allows sequencing multitracks is: + + + + + + ...add a playlist for each track... + + ...add filters and transitions... + + + + + + + ...add a playlist for each track... + + ...add filters and transitions... + + + + If you end up making a collection of templates for various situations, then + consider using XML Entities to make the template more effective by moving + anything that should parameterised into an entity. + + If you want to have a silent, black background for audio and video fades, + then make the top track simply . Then, + use composite and volume effects. See the "Fade from/to black/silence" + demo for an example (demo/mlt_fade_black). + + If you apply the reverse=1 property to a transition like "luma," then + be careful because it also inherently swaps the roles of A and B tracks. + Therefore, you need to might need to swap the a_track and b_track values + if it did not turn out the way you expected. See the "Clock in and out" + for an example (demo/mlt_clock_in_and_out). diff --git a/mlt-config-template b/mlt-config-template new file mode 100644 index 0000000..5559bf7 --- /dev/null +++ b/mlt-config-template @@ -0,0 +1,32 @@ +export package=framework +export field=0 + +while [ "$1" != "" ] +do + case $1 in + --help ) field=0 ;; + --version ) field=-1 ;; + --prefix ) field=-2 ;; + --prefix=* ) prefix="${i#--prefix=}" ;; + --cflags ) field=2 ;; + --libs ) field=3 ;; + --list ) field=1; package="" ;; + * ) package=$1 ;; + esac + shift +done + +if [ "$field" = "0" ] +then echo "Usage: mlt-config [ --version ] | [ --prefix=dir ] [ [ package ] [ --cflags ] [ --libs ] ]" +elif [ "$field" = "-1" ] +then echo $version +elif [ "$field" = "-2" ] +then config=`which mlt-config` + dir=`dirname $config` + dir=`dirname $dir` + echo $dir +elif [ -f "$prefix/share/mlt/packages.dat" ] +then grep "^$package" $prefix/share/mlt/packages.dat | cut -f $field +else echo mlt-config cannot find package $package. +fi + diff --git a/mlt-framework.pc b/mlt-framework.pc new file mode 100644 index 0000000..2c9cd9f --- /dev/null +++ b/mlt-framework.pc @@ -0,0 +1,14 @@ +prefix=/opt/kde3 +exec_prefix=/opt/kde3 +libdir=/opt/kde3/lib +includedir=/opt/kde3/include +version=0.2.5 +cflags=-I/opt/kde3/include -I/opt/kde3/include/mlt -D_REENTRANT +libs=-L/opt/kde3/lib -lmlt + +Name: mlt-framework +Description: MLT multimedia framework +Version: ${version} +Requires: +Libs: -L${libdir} ${libs} +Cflags: ${cflags} diff --git a/mlt-framework.pc.in b/mlt-framework.pc.in new file mode 100644 index 0000000..5748867 --- /dev/null +++ b/mlt-framework.pc.in @@ -0,0 +1,7 @@ + +Name: mlt-framework +Description: MLT multimedia framework +Version: ${version} +Requires: +Libs: -L${libdir} ${libs} +Cflags: ${cflags} diff --git a/mlt-miracle.pc b/mlt-miracle.pc new file mode 100644 index 0000000..94aabe2 --- /dev/null +++ b/mlt-miracle.pc @@ -0,0 +1,14 @@ +prefix=/opt/kde3 +exec_prefix=/opt/kde3 +libdir=/opt/kde3/lib +includedir=/opt/kde3/include +version=0.2.5 +cflags=-I/opt/kde3/include/mlt -D_REENTRANT +libs=-L/opt/kde3/lib -lmiracle + +Name: mlt-miracle +Description: MLT Miracle server API +Version: ${version} +Requires: +Libs: -L${libdir} ${libs} +Cflags: ${cflags} diff --git a/mlt-miracle.pc.in b/mlt-miracle.pc.in new file mode 100644 index 0000000..593055e --- /dev/null +++ b/mlt-miracle.pc.in @@ -0,0 +1,7 @@ + +Name: mlt-miracle +Description: MLT Miracle server API +Version: ${version} +Requires: +Libs: -L${libdir} ${libs} +Cflags: ${cflags} diff --git a/mlt-valerie.pc b/mlt-valerie.pc new file mode 100644 index 0000000..9cd6ac1 --- /dev/null +++ b/mlt-valerie.pc @@ -0,0 +1,14 @@ +prefix=/opt/kde3 +exec_prefix=/opt/kde3 +libdir=/opt/kde3/lib +includedir=/opt/kde3/include +version=0.2.5 +cflags=-I/opt/kde3/include/mlt -D_REENTRANT +libs=-L/opt/kde3/lib -lvalerie + +Name: mlt-valerie +Description: MLT Valerie client API +Version: ${version} +Requires: +Libs: -L${libdir} ${libs} +Cflags: ${cflags} diff --git a/mlt-valerie.pc.in b/mlt-valerie.pc.in new file mode 100644 index 0000000..7750ba3 --- /dev/null +++ b/mlt-valerie.pc.in @@ -0,0 +1,7 @@ + +Name: mlt-valerie +Description: MLT Valerie client API +Version: ${version} +Requires: +Libs: -L${libdir} ${libs} +Cflags: ${cflags} diff --git a/profiles/Makefile b/profiles/Makefile new file mode 100644 index 0000000..4da1114 --- /dev/null +++ b/profiles/Makefile @@ -0,0 +1,18 @@ +include ../config.mak + +all: + +depend: + +distclean: + +clean: + +install: all uninstall + install -d "$(DESTDIR)$(prefix)/share/mlt/profiles" + install -m 644 * "$(DESTDIR)$(prefix)/share/mlt/profiles" + rm -f "$(DESTDIR)$(prefix)/share/mlt/profiles/*~" + rm -f "$(DESTDIR)$(prefix)/share/mlt/profiles/Makefile" + +uninstall: + rm -rf "$(DESTDIR)$(prefix)/share/mlt/profiles" diff --git a/profiles/atsc_1080i_60 b/profiles/atsc_1080i_60 new file mode 100644 index 0000000..3c95886 --- /dev/null +++ b/profiles/atsc_1080i_60 @@ -0,0 +1,10 @@ +description=ATSC 1080i 60Hz +frame_rate_num=30000 +frame_rate_den=1001 +width=1920 +height=1080 +progressive=0 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/atsc_720p_30 b/profiles/atsc_720p_30 new file mode 100644 index 0000000..beb6cc9 --- /dev/null +++ b/profiles/atsc_720p_30 @@ -0,0 +1,10 @@ +description=ATSC 720p 30Hz +frame_rate_num=30000 +frame_rate_den=1001 +width=1280 +height=720 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/cif_ntsc b/profiles/cif_ntsc new file mode 100644 index 0000000..48279a5 --- /dev/null +++ b/profiles/cif_ntsc @@ -0,0 +1,10 @@ +description=CIF NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=352 +height=288 +progressive=1 +sample_aspect_num=10 +sample_aspect_den=11 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/cif_pal b/profiles/cif_pal new file mode 100644 index 0000000..a7f66d4 --- /dev/null +++ b/profiles/cif_pal @@ -0,0 +1,10 @@ +description=CIF PAL +frame_rate_num=25 +frame_rate_den=1 +width=352 +height=288 +progressive=1 +sample_aspect_num=59 +sample_aspect_den=54 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/cvd_ntsc b/profiles/cvd_ntsc new file mode 100644 index 0000000..508b3cc --- /dev/null +++ b/profiles/cvd_ntsc @@ -0,0 +1,10 @@ +description=CVD NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=352 +height=480 +progressive=0 +sample_aspect_num=20 +sample_aspect_den=11 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/cvd_pal b/profiles/cvd_pal new file mode 100644 index 0000000..2415c77 --- /dev/null +++ b/profiles/cvd_pal @@ -0,0 +1,10 @@ +description=CVD PAL +frame_rate_num=25 +frame_rate_den=1 +width=352 +height=576 +progressive=0 +sample_aspect_num=59 +sample_aspect_den=27 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/dv_ntsc b/profiles/dv_ntsc new file mode 100644 index 0000000..c5462dd --- /dev/null +++ b/profiles/dv_ntsc @@ -0,0 +1,10 @@ +description=DV NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=720 +height=480 +progressive=0 +sample_aspect_num=10 +sample_aspect_den=11 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/dv_ntsc_wide b/profiles/dv_ntsc_wide new file mode 100644 index 0000000..6c98e4f --- /dev/null +++ b/profiles/dv_ntsc_wide @@ -0,0 +1,10 @@ +description=DV NTSC Widescreen +frame_rate_num=30000 +frame_rate_den=1001 +width=720 +height=480 +progressive=0 +sample_aspect_num=40 +sample_aspect_den=33 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/dv_pal b/profiles/dv_pal new file mode 100644 index 0000000..33e82bc --- /dev/null +++ b/profiles/dv_pal @@ -0,0 +1,10 @@ +description=DV PAL +frame_rate_num=25 +frame_rate_den=1 +width=720 +height=576 +progressive=0 +sample_aspect_num=59 +sample_aspect_den=54 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/dv_pal_wide b/profiles/dv_pal_wide new file mode 100644 index 0000000..a4b669c --- /dev/null +++ b/profiles/dv_pal_wide @@ -0,0 +1,10 @@ +description=DV PAL Widescreen +frame_rate_num=25 +frame_rate_den=1 +width=720 +height=576 +progressive=0 +sample_aspect_num=118 +sample_aspect_den=81 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/hdv_1080_50i b/profiles/hdv_1080_50i new file mode 100644 index 0000000..6544470 --- /dev/null +++ b/profiles/hdv_1080_50i @@ -0,0 +1,10 @@ +description=HDV 1080 50i +frame_rate_num=25 +frame_rate_den=1 +width=1440 +height=1080 +progressive=0 +sample_aspect_num=4 +sample_aspect_den=3 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/hdv_1080_60i b/profiles/hdv_1080_60i new file mode 100644 index 0000000..9c3e6fc --- /dev/null +++ b/profiles/hdv_1080_60i @@ -0,0 +1,10 @@ +description=HDV 1080 60i +frame_rate_num=30000 +frame_rate_den=1001 +width=1440 +height=1080 +progressive=0 +sample_aspect_num=4 +sample_aspect_den=3 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/hdv_720_25p b/profiles/hdv_720_25p new file mode 100644 index 0000000..7bd6eed --- /dev/null +++ b/profiles/hdv_720_25p @@ -0,0 +1,10 @@ +description=HDV 720 25p +frame_rate_num=25 +frame_rate_den=1 +width=1280 +height=720 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/hdv_720_30p b/profiles/hdv_720_30p new file mode 100644 index 0000000..68caaf9 --- /dev/null +++ b/profiles/hdv_720_30p @@ -0,0 +1,10 @@ +description=HDV 720 30p +frame_rate_num=30000 +frame_rate_den=1001 +width=1280 +height=720 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/qcif_ntsc b/profiles/qcif_ntsc new file mode 100644 index 0000000..0e90157 --- /dev/null +++ b/profiles/qcif_ntsc @@ -0,0 +1,10 @@ +description=QCIF NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=176 +height=144 +progressive=1 +sample_aspect_num=10 +sample_aspect_den=11 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/qcif_pal b/profiles/qcif_pal new file mode 100644 index 0000000..21667ee --- /dev/null +++ b/profiles/qcif_pal @@ -0,0 +1,10 @@ +description=QCIF PAL +frame_rate_num=25 +frame_rate_den=1 +width=176 +height=144 +progressive=1 +sample_aspect_num=59 +sample_aspect_den=54 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/quarter_ntsc b/profiles/quarter_ntsc new file mode 100644 index 0000000..fb37cd7 --- /dev/null +++ b/profiles/quarter_ntsc @@ -0,0 +1,10 @@ +description=Quarter Square NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=320 +height=240 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/quarter_ntsc_wide b/profiles/quarter_ntsc_wide new file mode 100644 index 0000000..8c7be94 --- /dev/null +++ b/profiles/quarter_ntsc_wide @@ -0,0 +1,10 @@ +description=Quarter Square NTSC Widescreen +frame_rate_num=30000 +frame_rate_den=1001 +width=426 +height=240 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/quarter_pal b/profiles/quarter_pal new file mode 100644 index 0000000..beec207 --- /dev/null +++ b/profiles/quarter_pal @@ -0,0 +1,10 @@ +description=Quarter Square PAL +frame_rate_num=25 +frame_rate_den=1 +width=384 +height=288 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/quarter_pal_wide b/profiles/quarter_pal_wide new file mode 100644 index 0000000..195410a --- /dev/null +++ b/profiles/quarter_pal_wide @@ -0,0 +1,10 @@ +description=Quarter Square PAL Widescreen +frame_rate_num=25 +frame_rate_den=1 +width=512 +height=288 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/square_ntsc b/profiles/square_ntsc new file mode 100644 index 0000000..e139c3e --- /dev/null +++ b/profiles/square_ntsc @@ -0,0 +1,10 @@ +description=Square NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=640 +height=480 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/square_ntsc_wide b/profiles/square_ntsc_wide new file mode 100644 index 0000000..da2de3e --- /dev/null +++ b/profiles/square_ntsc_wide @@ -0,0 +1,10 @@ +description=Square NTSC Widescreen +frame_rate_num=30000 +frame_rate_den=1001 +width=854 +height=480 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/square_pal b/profiles/square_pal new file mode 100644 index 0000000..a69c8d6 --- /dev/null +++ b/profiles/square_pal @@ -0,0 +1,10 @@ +description=Square PAL +frame_rate_num=25 +frame_rate_den=1 +width=768 +height=576 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/square_pal_wide b/profiles/square_pal_wide new file mode 100644 index 0000000..316a80d --- /dev/null +++ b/profiles/square_pal_wide @@ -0,0 +1,10 @@ +description=Square PAL Widescreen +frame_rate_num=25 +frame_rate_den=1 +width=1024 +height=576 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/svcd_ntsc b/profiles/svcd_ntsc new file mode 100644 index 0000000..fd6f13a --- /dev/null +++ b/profiles/svcd_ntsc @@ -0,0 +1,10 @@ +description=SVCD NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=480 +height=480 +progressive=0 +sample_aspect_num=15 +sample_aspect_den=11 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/svcd_ntsc_wide b/profiles/svcd_ntsc_wide new file mode 100644 index 0000000..174960a --- /dev/null +++ b/profiles/svcd_ntsc_wide @@ -0,0 +1,10 @@ +description=SVCD NTSC Widescreen +frame_rate_num=30000 +frame_rate_den=1001 +width=480 +height=480 +progressive=0 +sample_aspect_num=20 +sample_aspect_den=11 +display_aspect_num=16 +display_aspect_den=9 diff --git a/profiles/svcd_pal b/profiles/svcd_pal new file mode 100644 index 0000000..2049270 --- /dev/null +++ b/profiles/svcd_pal @@ -0,0 +1,10 @@ +description=SVCD PAL +frame_rate_num=25 +frame_rate_den=1 +width=480 +height=576 +progressive=0 +sample_aspect_num=59 +sample_aspect_den=36 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/svcd_pal_wide b/profiles/svcd_pal_wide new file mode 100644 index 0000000..3aea87a --- /dev/null +++ b/profiles/svcd_pal_wide @@ -0,0 +1,10 @@ +description=SVCD PAL Widescreen +frame_rate_num=25 +frame_rate_den=1 +width=480 +height=576 +progressive=0 +sample_aspect_num=59 +sample_aspect_den=27 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/vcd_ntsc b/profiles/vcd_ntsc new file mode 100644 index 0000000..e58f4f2 --- /dev/null +++ b/profiles/vcd_ntsc @@ -0,0 +1,10 @@ +description=VCD NTSC +frame_rate_num=30000 +frame_rate_den=1001 +width=352 +height=240 +progressive=1 +sample_aspect_num=10 +sample_aspect_den=11 +display_aspect_num=4 +display_aspect_den=3 diff --git a/profiles/vcd_pal b/profiles/vcd_pal new file mode 100644 index 0000000..5f4e03f --- /dev/null +++ b/profiles/vcd_pal @@ -0,0 +1,10 @@ +description=VCD PAL +frame_rate_num=25 +frame_rate_den=1 +width=352 +height=288 +progressive=1 +sample_aspect_num=59 +sample_aspect_den=54 +display_aspect_num=4 +display_aspect_den=3 diff --git a/setenv b/setenv new file mode 100644 index 0000000..dd38115 --- /dev/null +++ b/setenv @@ -0,0 +1,25 @@ + +# Environment variable settings to allow execution without install + +export MLT_REPOSITORY=`pwd`/src/modules +export MLT_PROFILES_PATH=`pwd`/profiles + +export LD_LIBRARY_PATH=\ +`pwd`/src/framework:\ +`pwd`/src/valerie:\ +`pwd`/src/miracle:\ +`pwd`/src/modules/bluefish:\ +`pwd`/../BlueLinuxDriver/install/lib:\ +`pwd`/../mpeg_sdk_release/bin:\ +`pwd`/../dvcpro_sdk_release/lib:\ +`pwd`/../sr_sdk_release:\ +$LD_LIBRARY_PATH + +[ $(uname -s) = Darwin ] && export DYLD_LIBRARY_PATH=$LD_LIBRARY_PATH + +export PATH=\ +`pwd`/src/albino:\ +`pwd`/src/inigo:\ +`pwd`/src/humperdink:\ +`pwd`/src/miracle:\ +$PATH diff --git a/setenv_mc b/setenv_mc new file mode 100644 index 0000000..705d4e2 --- /dev/null +++ b/setenv_mc @@ -0,0 +1,9 @@ + +# Environment variable settings to allow execution without install + +export LD_LIBRARY_PATH=\ +`pwd`/../mpeg_sdk_release/bin:\ +`pwd`/../dvcpro_sdk_release/lib:\ +`pwd`/../sr_sdk_release/lib:\ +$LD_LIBRARY_PATH + diff --git a/src/albino/Makefile b/src/albino/Makefile new file mode 100644 index 0000000..45b7c80 --- /dev/null +++ b/src/albino/Makefile @@ -0,0 +1,36 @@ +include ../../config.mak + +TARGET = albino + +OBJS = albino.o + +CFLAGS += -I.. $(RDYNAMIC) + +LDFLAGS += -L../miracle -lmiracle -L../valerie -lvalerie -L../miracle -lmiracle -L../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -d "$(DESTDIR)$(bindir)" + install -c -s -m 755 $(TARGET) "$(DESTDIR)$(bindir)" + +uninstall: + rm -f "$(DESTDIR)$(bindir)/$(TARGET)" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/albino/albino.c b/src/albino/albino.c new file mode 100644 index 0000000..d4f0856 --- /dev/null +++ b/src/albino/albino.c @@ -0,0 +1,110 @@ +/* + * albino.c -- Local Valerie/Miracle Test Utility + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* System header files */ +#include +#include +#include +#include + +/* Application header files */ +#include +#include +#include + +char *prompt( char *command, int length ) +{ + printf( "> " ); + return fgets( command, length, stdin ); +} + +void report( valerie_response response ) +{ + int index = 0; + if ( response != NULL ) + for ( index = 0; index < valerie_response_count( response ); index ++ ) + printf( "%4d: %s\n", index, valerie_response_get_line( response, index ) ); +} + +int main( int argc, char **argv ) +{ + valerie_parser parser = NULL; + valerie_response response = NULL; + char temp[ 1024 ]; + int index = 1; + + if ( argc > 2 && !strcmp( argv[ 1 ], "-s" ) ) + { + printf( "Miracle Client Instance\n" ); + parser = valerie_parser_init_remote( argv[ 2 ], 5250 ); + response = valerie_parser_connect( parser ); + index = 3; + } + else + { + struct sched_param scp; + + // Use realtime scheduling if possible + memset( &scp, '\0', sizeof( scp ) ); + scp.sched_priority = sched_get_priority_max( SCHED_FIFO ) - 1; +#ifndef __DARWIN__ + sched_setscheduler( 0, SCHED_FIFO, &scp ); +#endif + + printf( "Miracle Standalone Instance\n" ); + parser = miracle_parser_init_local( ); + response = valerie_parser_connect( parser ); + } + + if ( response != NULL ) + { + /* process files on command lines before going into console mode */ + for ( ; index < argc; index ++ ) + { + valerie_response_close( response ); + response = valerie_parser_run( parser, argv[ index ] ); + report( response ); + } + + while ( response != NULL && prompt( temp, 1024 ) ) + { + valerie_util_trim( valerie_util_chomp( temp ) ); + if ( !strcasecmp( temp, "BYE" ) ) + { + break; + } + else if ( strcmp( temp, "" ) ) + { + valerie_response_close( response ); + response = valerie_parser_execute( parser, temp ); + report( response ); + } + } + } + else + { + fprintf( stderr, "Unable to connect to a Miracle instance.\n" ); + } + + printf( "\n" ); + valerie_parser_close( parser ); + + return 0; +} diff --git a/src/framework/Makefile b/src/framework/Makefile new file mode 100644 index 0000000..f5df34c --- /dev/null +++ b/src/framework/Makefile @@ -0,0 +1,97 @@ +include ../../config.mak + +NAME = libmlt$(LIBSUF) +TARGET = $(NAME).$(version) + +ifneq ($(targetos), Darwin) +NAME = libmlt$(LIBSUF) +TARGET = $(NAME).$(version) +SHFLAGS += -Wl,-soname,$(TARGET) +else +NAME = libmlt$(LIBSUF) +TARGET = libmlt.$(version)$(LIBSUF) +SHFLAGS += -install_name $(libdir)/$(TARGET) +endif + +OBJS = mlt_frame.o \ + mlt_geometry.o \ + mlt_deque.o \ + mlt_property.o \ + mlt_properties.o \ + mlt_events.o \ + mlt_parser.o \ + mlt_service.o \ + mlt_producer.o \ + mlt_multitrack.o \ + mlt_playlist.o \ + mlt_consumer.o \ + mlt_filter.o \ + mlt_transition.o \ + mlt_field.o \ + mlt_tractor.o \ + mlt_factory.o \ + mlt_repository.o \ + mlt_pool.o \ + mlt_tokeniser.o \ + mlt_profile.o + +INCS = mlt_consumer.h \ + mlt_factory.h \ + mlt_filter.h \ + mlt.h \ + mlt_multitrack.h \ + mlt_pool.h \ + mlt_properties.h \ + mlt_events.h \ + mlt_parser.h \ + mlt_repository.h \ + mlt_tractor.h \ + mlt_types.h \ + mlt_deque.h \ + mlt_field.h \ + mlt_frame.h \ + mlt_geometry.h \ + mlt_playlist.h \ + mlt_producer.h \ + mlt_property.h \ + mlt_service.h \ + mlt_transition.h \ + mlt_tokeniser.h \ + mlt_profile.h + +SRCS := $(OBJS:.o=.c) + +CFLAGS += $(RDYNAMIC) -DPREFIX="\"$(prefix)\"" + +LDFLAGS += -lm $(LIBDL) -lpthread + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + ln -sf $(TARGET) $(NAME) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) $(NAME) + +install: + install -d $(DESTDIR)$(libdir) + install -m 755 $(TARGET) $(DESTDIR)$(libdir) + ln -sf $(TARGET) $(DESTDIR)$(libdir)/$(NAME) + install -d "$(DESTDIR)$(prefix)/include/mlt/framework" + install -m 644 $(INCS) "$(DESTDIR)$(prefix)/include/mlt/framework" + +uninstall: + rm -f "$(DESTDIR)$(libdir)/$(TARGET)" + rm -f "$(DESTDIR)$(libdir)/$(NAME)" + rm -rf "$(DESTDIR)$(prefix)/include/mlt/framework" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/framework/config.h b/src/framework/config.h new file mode 100644 index 0000000..214b97b --- /dev/null +++ b/src/framework/config.h @@ -0,0 +1,9 @@ +/** GENERATED FILE - DON'T EDIT */ + +#ifndef _MLT_CONFIG_H_ +#define _MLT_CONFIG_H_ + +#define PREFIX_DATA PREFIX "/lib/mlt/modules" + +#endif + diff --git a/src/framework/configure b/src/framework/configure new file mode 100755 index 0000000..52655ae --- /dev/null +++ b/src/framework/configure @@ -0,0 +1,2 @@ +#!/bin/sh +echo "framework -I$prefix/include -I$prefix/include/mlt -D_REENTRANT -L$libdir -lmlt" >> ../../packages.dat diff --git a/src/framework/mlt.h b/src/framework/mlt.h new file mode 100644 index 0000000..9c99a2a --- /dev/null +++ b/src/framework/mlt.h @@ -0,0 +1,51 @@ +/* + * mlt.h -- header file for lazy client and implementation code :-) + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_H_ +#define _MLT_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "mlt_factory.h" +#include "mlt_frame.h" +#include "mlt_deque.h" +#include "mlt_multitrack.h" +#include "mlt_producer.h" +#include "mlt_transition.h" +#include "mlt_consumer.h" +#include "mlt_filter.h" +#include "mlt_playlist.h" +#include "mlt_properties.h" +#include "mlt_field.h" +#include "mlt_tractor.h" +#include "mlt_tokeniser.h" +#include "mlt_parser.h" +#include "mlt_geometry.h" +#include "mlt_profile.h" + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/framework/mlt_consumer.c b/src/framework/mlt_consumer.c new file mode 100644 index 0000000..4fc9d71 --- /dev/null +++ b/src/framework/mlt_consumer.c @@ -0,0 +1,797 @@ +/* + * mlt_consumer.c -- abstraction for all consumer services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "mlt_consumer.h" +#include "mlt_factory.h" +#include "mlt_producer.h" +#include "mlt_frame.h" +#include "mlt_profile.h" + +#include +#include +#include +#include + +static void mlt_consumer_frame_render( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ); +static void mlt_consumer_frame_show( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ); +static void mlt_consumer_property_changed( mlt_service owner, mlt_consumer this, char *name ); +static void apply_profile_properties( mlt_profile profile, mlt_properties properties ); + +static mlt_event g_event_listener = NULL; + +/** Public final methods +*/ + +int mlt_consumer_init( mlt_consumer this, void *child ) +{ + int error = 0; + memset( this, 0, sizeof( struct mlt_consumer_s ) ); + this->child = child; + error = mlt_service_init( &this->parent, this ); + if ( error == 0 ) + { + // Get the properties from the service + mlt_properties properties = MLT_SERVICE_PROPERTIES( &this->parent ); + + // Apply the profile to properties for legacy integration + apply_profile_properties( mlt_profile_get(), properties ); + + // Default rescaler for all consumers + mlt_properties_set( properties, "rescale", "bilinear" ); + + // Default read ahead buffer size + mlt_properties_set_int( properties, "buffer", 25 ); + + // Default audio frequency and channels + mlt_properties_set_int( properties, "frequency", 48000 ); + mlt_properties_set_int( properties, "channels", 2 ); + + // Default of all consumers is real time + mlt_properties_set_int( properties, "real_time", 1 ); + + // Default to environment test card + mlt_properties_set( properties, "test_card", mlt_environment( "MLT_TEST_CARD" ) ); + + // Hmm - default all consumers to yuv422 :-/ + this->format = mlt_image_yuv422; + + mlt_events_register( properties, "consumer-frame-show", ( mlt_transmitter )mlt_consumer_frame_show ); + mlt_events_register( properties, "consumer-frame-render", ( mlt_transmitter )mlt_consumer_frame_render ); + mlt_events_register( properties, "consumer-stopped", NULL ); + + // Register a property-changed listener to handle the profile property - + // subsequent properties can override the profile + g_event_listener = mlt_events_listen( properties, this, "property-changed", ( mlt_listener )mlt_consumer_property_changed ); + + // Create the push mutex and condition + pthread_mutex_init( &this->put_mutex, NULL ); + pthread_cond_init( &this->put_cond, NULL ); + + } + return error; +} + +static void apply_profile_properties( mlt_profile profile, mlt_properties properties ) +{ + mlt_event_block( g_event_listener ); + mlt_properties_set_double( properties, "fps", mlt_profile_fps( profile ) ); + mlt_properties_set_int( properties, "frame_rate_num", profile->frame_rate_num ); + mlt_properties_set_int( properties, "frame_rate_den", profile->frame_rate_den ); + mlt_properties_set_int( properties, "width", profile->width ); + mlt_properties_set_int( properties, "height", profile->height ); + mlt_properties_set_int( properties, "progressive", profile->progressive ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_profile_sar( profile ) ); + mlt_properties_set_int( properties, "sample_aspect_num", profile->sample_aspect_num ); + mlt_properties_set_int( properties, "sample_aspect_den", profile->sample_aspect_den ); + mlt_properties_set_double( properties, "display_ratio", mlt_profile_dar( profile ) ); + mlt_properties_set_int( properties, "display_aspect_num", profile->display_aspect_num ); + mlt_properties_set_int( properties, "display_aspect_num", profile->display_aspect_num ); + mlt_event_unblock( g_event_listener ); +} + +static void mlt_consumer_property_changed( mlt_service owner, mlt_consumer this, char *name ) +{ + if ( !strcmp( name, "profile" ) ) + { + // Get the properies + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Locate the profile + mlt_profile_select( mlt_properties_get( properties, "profile" ) ); + + // Apply to properties + apply_profile_properties( mlt_profile_get(), properties ); + } + else if ( !strcmp( name, "frame_rate_num" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->frame_rate_num = mlt_properties_get_int( properties, "frame_rate_num" ); + mlt_properties_set_double( properties, "fps", mlt_profile_fps( NULL ) ); + } + else if ( !strcmp( name, "frame_rate_den" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->frame_rate_den = mlt_properties_get_int( properties, "frame_rate_den" ); + mlt_properties_set_double( properties, "fps", mlt_profile_fps( NULL ) ); + } + else if ( !strcmp( name, "width" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->width = mlt_properties_get_int( properties, "width" ); + } + else if ( !strcmp( name, "height" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->height = mlt_properties_get_int( properties, "height" ); + } + else if ( !strcmp( name, "progressive" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->progressive = mlt_properties_get_int( properties, "progressive" ); + } + else if ( !strcmp( name, "sample_aspect_num" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->sample_aspect_num = mlt_properties_get_int( properties, "sample_aspect_num" ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_profile_sar( NULL ) ); + } + else if ( !strcmp( name, "sample_aspect_den" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->sample_aspect_den = mlt_properties_get_int( properties, "sample_aspect_den" ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_profile_sar( NULL ) ); + } + else if ( !strcmp( name, "display_aspect_num" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->display_aspect_num = mlt_properties_get_int( properties, "display_aspect_num" ); + mlt_properties_set_double( properties, "display_ratio", mlt_profile_dar( NULL ) ); + } + else if ( !strcmp( name, "display_aspect_den" ) ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + mlt_profile_get()->display_aspect_den = mlt_properties_get_int( properties, "display_aspect_den" ); + mlt_properties_set_double( properties, "display_ratio", mlt_profile_dar( NULL ) ); + } +} + +static void mlt_consumer_frame_show( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( mlt_frame )args[ 0 ] ); +} + +static void mlt_consumer_frame_render( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( mlt_frame )args[ 0 ] ); +} + +/** Create a new consumer. +*/ + +mlt_consumer mlt_consumer_new( ) +{ + // Create the memory for the structure + mlt_consumer this = malloc( sizeof( struct mlt_consumer_s ) ); + + // Initialise it + if ( this != NULL ) + mlt_consumer_init( this, NULL ); + + // Return it + return this; +} + +/** Get the parent service object. +*/ + +mlt_service mlt_consumer_service( mlt_consumer this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Get the consumer properties. +*/ + +mlt_properties mlt_consumer_properties( mlt_consumer this ) +{ + return this != NULL ? MLT_SERVICE_PROPERTIES( &this->parent ) : NULL; +} + +/** Connect the consumer to the producer. +*/ + +int mlt_consumer_connect( mlt_consumer this, mlt_service producer ) +{ + return mlt_service_connect_producer( &this->parent, producer, 0 ); +} + +/** Start the consumer. +*/ + +int mlt_consumer_start( mlt_consumer this ) +{ + // Stop listening to the property-changed event + mlt_event_block( g_event_listener ); + + // Get the properies + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Determine if there's a test card producer + char *test_card = mlt_properties_get( properties, "test_card" ); + + // Just to make sure nothing is hanging around... + mlt_frame_close( this->put ); + this->put = NULL; + this->put_active = 1; + + // Deal with it now. + if ( test_card != NULL ) + { + if ( mlt_properties_get_data( properties, "test_card_producer", NULL ) == NULL ) + { + // Create a test card producer + mlt_producer producer = mlt_factory_producer( NULL, test_card ); + + // Do we have a producer + if ( producer != NULL ) + { + // Test card should loop I guess... + mlt_properties_set( MLT_PRODUCER_PROPERTIES( producer ), "eof", "loop" ); + //mlt_producer_set_speed( producer, 0 ); + //mlt_producer_set_in_and_out( producer, 0, 0 ); + + // Set the test card on the consumer + mlt_properties_set_data( properties, "test_card_producer", producer, 0, ( mlt_destructor )mlt_producer_close, NULL ); + } + } + } + else + { + // Allow the hash table to speed things up + mlt_properties_set_data( properties, "test_card_producer", NULL, 0, NULL, NULL ); + } + + // Check and run an ante command + if ( mlt_properties_get( properties, "ante" ) ) + system( mlt_properties_get( properties, "ante" ) ); + + // Set the real_time preference + this->real_time = mlt_properties_get_int( properties, "real_time" ); + + // Start the service + if ( this->start != NULL ) + return this->start( this ); + + return 0; +} + +/** An alternative method to feed frames into the consumer - only valid if + the consumer itself is not connected. +*/ + +int mlt_consumer_put_frame( mlt_consumer this, mlt_frame frame ) +{ + int error = 1; + + // Get the service assoicated to the consumer + mlt_service service = MLT_CONSUMER_SERVICE( this ); + + if ( mlt_service_producer( service ) == NULL ) + { + struct timeval now; + struct timespec tm; + pthread_mutex_lock( &this->put_mutex ); + while ( this->put_active && this->put != NULL ) + { + gettimeofday( &now, NULL ); + tm.tv_sec = now.tv_sec + 1; + tm.tv_nsec = now.tv_usec * 1000; + pthread_cond_timedwait( &this->put_cond, &this->put_mutex, &tm ); + } + if ( this->put_active && this->put == NULL ) + this->put = frame; + else + mlt_frame_close( frame ); + pthread_cond_broadcast( &this->put_cond ); + pthread_mutex_unlock( &this->put_mutex ); + } + else + { + mlt_frame_close( frame ); + } + + return error; +} + +/** Protected method for consumer to get frames from connected service +*/ + +mlt_frame mlt_consumer_get_frame( mlt_consumer this ) +{ + // Frame to return + mlt_frame frame = NULL; + + // Get the service assoicated to the consumer + mlt_service service = MLT_CONSUMER_SERVICE( this ); + + // Get the consumer properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Get the frame + if ( mlt_service_producer( service ) == NULL && mlt_properties_get_int( properties, "put_mode" ) ) + { + struct timeval now; + struct timespec tm; + pthread_mutex_lock( &this->put_mutex ); + while ( this->put_active && this->put == NULL ) + { + gettimeofday( &now, NULL ); + tm.tv_sec = now.tv_sec + 1; + tm.tv_nsec = now.tv_usec * 1000; + pthread_cond_timedwait( &this->put_cond, &this->put_mutex, &tm ); + } + frame = this->put; + this->put = NULL; + pthread_cond_broadcast( &this->put_cond ); + pthread_mutex_unlock( &this->put_mutex ); + if ( frame != NULL ) + mlt_service_apply_filters( service, frame, 0 ); + } + else if ( mlt_service_producer( service ) != NULL ) + { + mlt_service_get_frame( service, &frame, 0 ); + } + else + { + frame = mlt_frame_init( ); + } + + if ( frame != NULL ) + { + // Get the frame properties + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Get the test card producer + mlt_producer test_card = mlt_properties_get_data( properties, "test_card_producer", NULL ); + + // Attach the test frame producer to it. + if ( test_card != NULL ) + mlt_properties_set_data( frame_properties, "test_card_producer", test_card, 0, NULL, NULL ); + + // Attach the rescale property + mlt_properties_set( frame_properties, "rescale.interp", mlt_properties_get( properties, "rescale" ) ); + + // Aspect ratio and other jiggery pokery + mlt_properties_set_double( frame_properties, "consumer_aspect_ratio", mlt_properties_get_double( properties, "aspect_ratio" ) ); + mlt_properties_set_int( frame_properties, "consumer_deinterlace", mlt_properties_get_int( properties, "progressive" ) | mlt_properties_get_int( properties, "deinterlace" ) ); + mlt_properties_set( frame_properties, "deinterlace_method", mlt_properties_get( properties, "deinterlace_method" ) ); + } + + // Return the frame + return frame; +} + +static inline long time_difference( struct timeval *time1 ) +{ + struct timeval time2; + time2.tv_sec = time1->tv_sec; + time2.tv_usec = time1->tv_usec; + gettimeofday( time1, NULL ); + return time1->tv_sec * 1000000 + time1->tv_usec - time2.tv_sec * 1000000 - time2.tv_usec; +} + +int mlt_consumer_profile( mlt_properties properties, char *profile ) +{ + mlt_profile p = mlt_profile_select( profile ); + if ( p ) + { + apply_profile_properties( p, properties ); + return 1; + } + else + { + return 0; + } +} + +static void *consumer_read_ahead_thread( void *arg ) +{ + // The argument is the consumer + mlt_consumer this = arg; + + // Get the properties of the consumer + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Get the width and height + int width = mlt_properties_get_int( properties, "width" ); + int height = mlt_properties_get_int( properties, "height" ); + + // See if video is turned off + int video_off = mlt_properties_get_int( properties, "video_off" ); + int preview_off = mlt_properties_get_int( properties, "preview_off" ); + int preview_format = mlt_properties_get_int( properties, "preview_format" ); + + // Get the audio settings + mlt_audio_format afmt = mlt_audio_pcm; + int counter = 0; + double fps = mlt_properties_get_double( properties, "fps" ); + int channels = mlt_properties_get_int( properties, "channels" ); + int frequency = mlt_properties_get_int( properties, "frequency" ); + int samples = 0; + int16_t *pcm = NULL; + + // See if audio is turned off + int audio_off = mlt_properties_get_int( properties, "audio_off" ); + + // Get the maximum size of the buffer + int buffer = mlt_properties_get_int( properties, "buffer" ) + 1; + + // General frame variable + mlt_frame frame = NULL; + uint8_t *image = NULL; + + // Time structures + struct timeval ante; + + // Average time for get_frame and get_image + int count = 1; + int skipped = 0; + int64_t time_wait = 0; + int64_t time_frame = 0; + int64_t time_process = 0; + int skip_next = 0; + mlt_service lock_object = NULL; + + if ( preview_off && preview_format != 0 ) + this->format = preview_format; + + // Get the first frame + frame = mlt_consumer_get_frame( this ); + + // Get the lock object + lock_object = mlt_properties_get_data( MLT_FRAME_PROPERTIES( frame ), "consumer_lock_service", NULL ); + + // Lock it + if ( lock_object ) mlt_service_lock( lock_object ); + + // Get the image of the first frame + if ( !video_off ) + { + mlt_events_fire( MLT_CONSUMER_PROPERTIES( this ), "consumer-frame-render", frame, NULL ); + mlt_frame_get_image( frame, &image, &this->format, &width, &height, 0 ); + } + + if ( !audio_off ) + { + samples = mlt_sample_calculator( fps, frequency, counter++ ); + mlt_frame_get_audio( frame, &pcm, &afmt, &frequency, &channels, &samples ); + } + + // Unlock the lock object + if ( lock_object ) mlt_service_unlock( lock_object ); + + // Mark as rendered + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "rendered", 1 ); + + // Get the starting time (can ignore the times above) + gettimeofday( &ante, NULL ); + + // Continue to read ahead + while ( this->ahead ) + { + // Fetch width/height again + width = mlt_properties_get_int( properties, "width" ); + height = mlt_properties_get_int( properties, "height" ); + + // Put the current frame into the queue + pthread_mutex_lock( &this->mutex ); + while( this->ahead && mlt_deque_count( this->queue ) >= buffer ) + pthread_cond_wait( &this->cond, &this->mutex ); + mlt_deque_push_back( this->queue, frame ); + pthread_cond_broadcast( &this->cond ); + pthread_mutex_unlock( &this->mutex ); + + time_wait += time_difference( &ante ); + + // Get the next frame + frame = mlt_consumer_get_frame( this ); + time_frame += time_difference( &ante ); + + // If there's no frame, we're probably stopped... + if ( frame == NULL ) + continue; + + // Attempt to fetch the lock object + lock_object = mlt_properties_get_data( MLT_FRAME_PROPERTIES( frame ), "consumer_lock_service", NULL ); + + // Increment the count + count ++; + + // Lock if there's a lock object + if ( lock_object ) mlt_service_lock( lock_object ); + + // All non normal playback frames should be shown + if ( mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "_speed" ) != 1 ) + { + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "consumer_deinterlace", 1 ); + skipped = 0; + time_frame = 0; + time_process = 0; + time_wait = 0; + count = 1; + skip_next = 0; + } + + // Get the image + if ( !skip_next ) + { + // Get the image, mark as rendered and time it + if ( !video_off ) + { + mlt_events_fire( MLT_CONSUMER_PROPERTIES( this ), "consumer-frame-render", frame, NULL ); + mlt_frame_get_image( frame, &image, &this->format, &width, &height, 0 ); + } + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "rendered", 1 ); + } + else + { + // Increment the number of sequentially skipped frames + skipped ++; + skip_next = 0; + + // If we've reached an unacceptable level, reset everything + if ( skipped > 5 ) + { + skipped = 0; + time_frame = 0; + time_process = 0; + time_wait = 0; + count = 1; + } + } + + // Always process audio + if ( !audio_off ) + { + samples = mlt_sample_calculator( fps, frequency, counter++ ); + mlt_frame_get_audio( frame, &pcm, &afmt, &frequency, &channels, &samples ); + } + + // Increment the time take for this frame + time_process += time_difference( &ante ); + + // Determine if the next frame should be skipped + if ( mlt_deque_count( this->queue ) <= 5 && ( ( time_wait + time_frame + time_process ) / count ) > 40000 ) + skip_next = 1; + + // Unlock if there's a lock object + if ( lock_object ) mlt_service_unlock( lock_object ); + } + + // Remove the last frame + mlt_frame_close( frame ); + + return NULL; +} + +static void consumer_read_ahead_start( mlt_consumer this ) +{ + // We're running now + this->ahead = 1; + + // Create the frame queue + this->queue = mlt_deque_init( ); + + // Create the mutex + pthread_mutex_init( &this->mutex, NULL ); + + // Create the condition + pthread_cond_init( &this->cond, NULL ); + + // Create the read ahead + pthread_create( &this->ahead_thread, NULL, consumer_read_ahead_thread, this ); +} + +static void consumer_read_ahead_stop( mlt_consumer this ) +{ + // Make sure we're running + if ( this->ahead ) + { + // Inform thread to stop + this->ahead = 0; + + // Broadcast to the condition in case it's waiting + pthread_mutex_lock( &this->mutex ); + pthread_cond_broadcast( &this->cond ); + pthread_mutex_unlock( &this->mutex ); + + // Broadcast to the put condition in case it's waiting + pthread_mutex_lock( &this->put_mutex ); + pthread_cond_broadcast( &this->put_cond ); + pthread_mutex_unlock( &this->put_mutex ); + + // Join the thread + pthread_join( this->ahead_thread, NULL ); + + // Destroy the mutex + pthread_mutex_destroy( &this->mutex ); + + // Destroy the condition + pthread_cond_destroy( &this->cond ); + + // Wipe the queue + while ( mlt_deque_count( this->queue ) ) + mlt_frame_close( mlt_deque_pop_back( this->queue ) ); + + // Close the queue + mlt_deque_close( this->queue ); + } +} + +void mlt_consumer_purge( mlt_consumer this ) +{ + if ( this->ahead ) + { + pthread_mutex_lock( &this->mutex ); + while ( mlt_deque_count( this->queue ) ) + mlt_frame_close( mlt_deque_pop_back( this->queue ) ); + pthread_cond_broadcast( &this->cond ); + pthread_mutex_unlock( &this->mutex ); + } +} + +mlt_frame mlt_consumer_rt_frame( mlt_consumer this ) +{ + // Frame to return + mlt_frame frame = NULL; + + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Check if the user has requested real time or not + if ( this->real_time ) + { + int size = 1; + + // Is the read ahead running? + if ( this->ahead == 0 ) + { + int buffer = mlt_properties_get_int( properties, "buffer" ); + int prefill = mlt_properties_get_int( properties, "prefill" ); + consumer_read_ahead_start( this ); + if ( buffer > 1 ) + size = prefill > 0 && prefill < buffer ? prefill : buffer; + } + + // Get frame from queue + pthread_mutex_lock( &this->mutex ); + while( this->ahead && mlt_deque_count( this->queue ) < size ) + pthread_cond_wait( &this->cond, &this->mutex ); + frame = mlt_deque_pop_front( this->queue ); + pthread_cond_broadcast( &this->cond ); + pthread_mutex_unlock( &this->mutex ); + } + else + { + // Get the frame in non real time + frame = mlt_consumer_get_frame( this ); + + // This isn't true, but from the consumers perspective it is + if ( frame != NULL ) + mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "rendered", 1 ); + } + + return frame; +} + +/** Callback for the implementation to indicate a stopped condition. +*/ + +void mlt_consumer_stopped( mlt_consumer this ) +{ + mlt_properties_set_int( MLT_CONSUMER_PROPERTIES( this ), "running", 0 ); + mlt_events_fire( MLT_CONSUMER_PROPERTIES( this ), "consumer-stopped", NULL ); + mlt_event_unblock( g_event_listener ); +} + +/** Stop the consumer. +*/ + +int mlt_consumer_stop( mlt_consumer this ) +{ + // Get the properies + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + char *debug = mlt_properties_get( MLT_CONSUMER_PROPERTIES( this ), "debug" ); + + // Just in case... + if ( debug ) fprintf( stderr, "%s: stopping put waiting\n", debug ); + pthread_mutex_lock( &this->put_mutex ); + this->put_active = 0; + pthread_cond_broadcast( &this->put_cond ); + pthread_mutex_unlock( &this->put_mutex ); + + // Stop the consumer + if ( debug ) fprintf( stderr, "%s: stopping consumer\n", debug ); + if ( this->stop != NULL ) + this->stop( this ); + + // Check if the user has requested real time or not and stop if necessary + if ( debug ) fprintf( stderr, "%s: stopping read_ahead\n", debug ); + if ( mlt_properties_get_int( properties, "real_time" ) ) + consumer_read_ahead_stop( this ); + + // Kill the test card + mlt_properties_set_data( properties, "test_card_producer", NULL, 0, NULL, NULL ); + + // Check and run a post command + if ( mlt_properties_get( properties, "post" ) ) + system( mlt_properties_get( properties, "post" ) ); + + if ( debug ) fprintf( stderr, "%s: stopped\n", debug ); + + return 0; +} + +/** Determine if the consumer is stopped. +*/ + +int mlt_consumer_is_stopped( mlt_consumer this ) +{ + // Check if the consumer is stopped + if ( this->is_stopped != NULL ) + return this->is_stopped( this ); + + return 0; +} + +/** Close the consumer. +*/ + +void mlt_consumer_close( mlt_consumer this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_CONSUMER_PROPERTIES( this ) ) <= 0 ) + { + // Get the childs close function + void ( *consumer_close )( ) = this->close; + + if ( consumer_close ) + { + // Just in case... + //mlt_consumer_stop( this ); + + this->close = NULL; + consumer_close( this ); + } + else + { + // Make sure it only gets called once + this->parent.close = NULL; + + // Destroy the push mutex and condition + pthread_mutex_destroy( &this->put_mutex ); + pthread_cond_destroy( &this->put_cond ); + + mlt_service_close( &this->parent ); + } + } +} diff --git a/src/framework/mlt_consumer.h b/src/framework/mlt_consumer.h new file mode 100644 index 0000000..8d0d66b --- /dev/null +++ b/src/framework/mlt_consumer.h @@ -0,0 +1,80 @@ +/* + * mlt_consumer.h -- abstraction for all consumer services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_CONSUMER_H_ +#define _MLT_CONSUMER_H_ + +#include "mlt_service.h" +#include + +/** The interface definition for all consumers. +*/ + +struct mlt_consumer_s +{ + /* We're implementing service here */ + struct mlt_service_s parent; + + /* public virtual */ + int ( *start )( mlt_consumer ); + int ( *stop )( mlt_consumer ); + int ( *is_stopped )( mlt_consumer ); + void ( *close )( mlt_consumer ); + + /* Private data */ + void *local; + void *child; + + int real_time; + int ahead; + mlt_image_format format; + mlt_deque queue; + pthread_t ahead_thread; + pthread_mutex_t mutex; + pthread_cond_t cond; + pthread_mutex_t put_mutex; + pthread_cond_t put_cond; + mlt_frame put; + int put_active; +}; + +/** Public final methods +*/ + +#define MLT_CONSUMER_SERVICE( consumer ) ( &( consumer )->parent ) +#define MLT_CONSUMER_PROPERTIES( consumer ) MLT_SERVICE_PROPERTIES( MLT_CONSUMER_SERVICE( consumer ) ) + +extern int mlt_consumer_init( mlt_consumer self, void *child ); +extern mlt_consumer mlt_consumer_new( ); +extern mlt_service mlt_consumer_service( mlt_consumer self ); +extern mlt_properties mlt_consumer_properties( mlt_consumer self ); +extern int mlt_consumer_connect( mlt_consumer self, mlt_service producer ); +extern int mlt_consumer_start( mlt_consumer self ); +extern void mlt_consumer_purge( mlt_consumer self ); +extern int mlt_consumer_put_frame( mlt_consumer self, mlt_frame frame ); +extern mlt_frame mlt_consumer_get_frame( mlt_consumer self ); +extern mlt_frame mlt_consumer_rt_frame( mlt_consumer self ); +extern int mlt_consumer_stop( mlt_consumer self ); +extern int mlt_consumer_is_stopped( mlt_consumer self ); +extern void mlt_consumer_stopped( mlt_consumer self ); +extern void mlt_consumer_close( mlt_consumer ); +extern int mlt_consumer_profile( mlt_properties properties, char *profile ); + +#endif diff --git a/src/framework/mlt_deque.c b/src/framework/mlt_deque.c new file mode 100644 index 0000000..75bb84b --- /dev/null +++ b/src/framework/mlt_deque.c @@ -0,0 +1,297 @@ +/* + * mlt_deque.c -- double ended queue + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// Local header files +#include "mlt_deque.h" + +// System header files +#include +#include + +typedef union +{ + void *addr; + int value; + double floating; +} +deque_entry; + +/** Private structure. +*/ + +struct mlt_deque_s +{ + deque_entry *list; + int size; + int count; +}; + +/** Create a deque. +*/ + +mlt_deque mlt_deque_init( ) +{ + mlt_deque this = malloc( sizeof( struct mlt_deque_s ) ); + if ( this != NULL ) + { + this->list = NULL; + this->size = 0; + this->count = 0; + } + return this; +} + +/** Return the number of items in the deque. +*/ + +int mlt_deque_count( mlt_deque this ) +{ + return this->count; +} + +/** Allocate space on the deque. +*/ + +static int mlt_deque_allocate( mlt_deque this ) +{ + if ( this->count == this->size ) + { + this->list = realloc( this->list, sizeof( deque_entry ) * ( this->size + 20 ) ); + this->size += 20; + } + return this->list == NULL; +} + +/** Push an item to the end. +*/ + +int mlt_deque_push_back( mlt_deque this, void *item ) +{ + int error = mlt_deque_allocate( this ); + + if ( error == 0 ) + this->list[ this->count ++ ].addr = item; + + return error; +} + +/** Pop an item. +*/ + +void *mlt_deque_pop_back( mlt_deque this ) +{ + return this->count > 0 ? this->list[ -- this->count ].addr : NULL; +} + +/** Queue an item at the start. +*/ + +int mlt_deque_push_front( mlt_deque this, void *item ) +{ + int error = mlt_deque_allocate( this ); + + if ( error == 0 ) + { + memmove( &this->list[ 1 ], this->list, ( this->count ++ ) * sizeof( deque_entry ) ); + this->list[ 0 ].addr = item; + } + + return error; +} + +/** Remove an item from the start. +*/ + +void *mlt_deque_pop_front( mlt_deque this ) +{ + void *item = NULL; + + if ( this->count > 0 ) + { + item = this->list[ 0 ].addr; + memmove( this->list, &this->list[ 1 ], ( -- this->count ) * sizeof( deque_entry ) ); + } + + return item; +} + +/** Inquire on item at back of deque but don't remove. +*/ + +void *mlt_deque_peek_back( mlt_deque this ) +{ + return this->count > 0 ? this->list[ this->count - 1 ].addr : NULL; +} + +/** Inquire on item at front of deque but don't remove. +*/ + +void *mlt_deque_peek_front( mlt_deque this ) +{ + return this->count > 0 ? this->list[ 0 ].addr : NULL; +} + +/** Push an item to the end. +*/ + +int mlt_deque_push_back_int( mlt_deque this, int item ) +{ + int error = mlt_deque_allocate( this ); + + if ( error == 0 ) + this->list[ this->count ++ ].value = item; + + return error; +} + +/** Pop an item. +*/ + +int mlt_deque_pop_back_int( mlt_deque this ) +{ + return this->count > 0 ? this->list[ -- this->count ].value : 0; +} + +/** Queue an item at the start. +*/ + +int mlt_deque_push_front_int( mlt_deque this, int item ) +{ + int error = mlt_deque_allocate( this ); + + if ( error == 0 ) + { + memmove( &this->list[ 1 ], this->list, ( this->count ++ ) * sizeof( deque_entry ) ); + this->list[ 0 ].value = item; + } + + return error; +} + +/** Remove an item from the start. +*/ + +int mlt_deque_pop_front_int( mlt_deque this ) +{ + int item = 0; + + if ( this->count > 0 ) + { + item = this->list[ 0 ].value; + memmove( this->list, &this->list[ 1 ], ( -- this->count ) * sizeof( deque_entry ) ); + } + + return item; +} + +/** Inquire on item at back of deque but don't remove. +*/ + +int mlt_deque_peek_back_int( mlt_deque this ) +{ + return this->count > 0 ? this->list[ this->count - 1 ].value : 0; +} + +/** Inquire on item at front of deque but don't remove. +*/ + +int mlt_deque_peek_front_int( mlt_deque this ) +{ + return this->count > 0 ? this->list[ 0 ].value : 0; +} + +/** Push an item to the end. +*/ + +int mlt_deque_push_back_double( mlt_deque this, double item ) +{ + int error = mlt_deque_allocate( this ); + + if ( error == 0 ) + this->list[ this->count ++ ].floating = item; + + return error; +} + +/** Pop an item. +*/ + +double mlt_deque_pop_back_double( mlt_deque this ) +{ + return this->count > 0 ? this->list[ -- this->count ].floating : 0; +} + +/** Queue an item at the start. +*/ + +int mlt_deque_push_front_double( mlt_deque this, double item ) +{ + int error = mlt_deque_allocate( this ); + + if ( error == 0 ) + { + memmove( &this->list[ 1 ], this->list, ( this->count ++ ) * sizeof( deque_entry ) ); + this->list[ 0 ].floating = item; + } + + return error; +} + +/** Remove an item from the start. +*/ + +double mlt_deque_pop_front_double( mlt_deque this ) +{ + double item = 0; + + if ( this->count > 0 ) + { + item = this->list[ 0 ].floating; + memmove( this->list, &this->list[ 1 ], ( -- this->count ) * sizeof( deque_entry ) ); + } + + return item; +} + +/** Inquire on item at back of deque but don't remove. +*/ + +double mlt_deque_peek_back_double( mlt_deque this ) +{ + return this->count > 0 ? this->list[ this->count - 1 ].floating : 0; +} + +/** Inquire on item at front of deque but don't remove. +*/ + +double mlt_deque_peek_front_double( mlt_deque this ) +{ + return this->count > 0 ? this->list[ 0 ].floating : 0; +} + +/** Close the queue. +*/ + +void mlt_deque_close( mlt_deque this ) +{ + free( this->list ); + free( this ); +} + diff --git a/src/framework/mlt_deque.h b/src/framework/mlt_deque.h new file mode 100644 index 0000000..5bc3d7d --- /dev/null +++ b/src/framework/mlt_deque.h @@ -0,0 +1,51 @@ +/* + * mlt_deque.h -- double ended queue + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_DEQUE_H_ +#define _MLT_DEQUE_H_ + +#include "mlt_types.h" + +extern mlt_deque mlt_deque_init( ); +extern int mlt_deque_count( mlt_deque self ); +extern int mlt_deque_push_back( mlt_deque self, void *item ); +extern void *mlt_deque_pop_back( mlt_deque self ); +extern int mlt_deque_push_front( mlt_deque self, void *item ); +extern void *mlt_deque_pop_front( mlt_deque self ); +extern void *mlt_deque_peek_back( mlt_deque self ); +extern void *mlt_deque_peek_front( mlt_deque self ); + +extern int mlt_deque_push_back_int( mlt_deque self, int item ); +extern int mlt_deque_pop_back_int( mlt_deque self ); +extern int mlt_deque_push_front_int( mlt_deque self, int item ); +extern int mlt_deque_pop_front_int( mlt_deque self ); +extern int mlt_deque_peek_back_int( mlt_deque self ); +extern int mlt_deque_peek_front_int( mlt_deque self ); + +extern int mlt_deque_push_back_double( mlt_deque self, double item ); +extern double mlt_deque_pop_back_double( mlt_deque self ); +extern int mlt_deque_push_front_double( mlt_deque self, double item ); +extern double mlt_deque_pop_front_double( mlt_deque self ); +extern double mlt_deque_peek_back_double( mlt_deque self ); +extern double mlt_deque_peek_front_double( mlt_deque self ); + +extern void mlt_deque_close( mlt_deque self ); + +#endif diff --git a/src/framework/mlt_events.c b/src/framework/mlt_events.c new file mode 100644 index 0000000..c74ab33 --- /dev/null +++ b/src/framework/mlt_events.c @@ -0,0 +1,403 @@ +/* + * mlt_events.h -- event handling + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include + +#include "mlt_properties.h" +#include "mlt_events.h" + +/** Memory leak checks. +*/ + +//#define _MLT_EVENT_CHECKS_ + +#ifdef _MLT_EVENT_CHECKS_ +static int events_created = 0; +static int events_destroyed = 0; +#endif + +struct mlt_events_struct +{ + mlt_properties owner; + mlt_properties list; +}; + +typedef struct mlt_events_struct *mlt_events; + +struct mlt_event_struct +{ + mlt_events owner; + int ref_count; + int block_count; + mlt_listener listener; + void *service; +}; + +/** Increment the reference count on this event. +*/ + +void mlt_event_inc_ref( mlt_event this ) +{ + if ( this != NULL ) + this->ref_count ++; +} + +/** Increment the block count on this event. +*/ + +void mlt_event_block( mlt_event this ) +{ + if ( this != NULL && this->owner != NULL ) + this->block_count ++; +} + +/** Decrement the block count on this event. +*/ + +void mlt_event_unblock( mlt_event this ) +{ + if ( this != NULL && this->owner != NULL ) + this->block_count --; +} + +/** Close this event. +*/ + +void mlt_event_close( mlt_event this ) +{ + if ( this != NULL ) + { + if ( -- this->ref_count == 1 ) + this->owner = NULL; + if ( this->ref_count <= 0 ) + { +#ifdef _MLT_EVENT_CHECKS_ + events_destroyed ++; + fprintf( stderr, "Events created %d, destroyed %d\n", events_created, events_destroyed ); +#endif + free( this ); + } + } +} + +/** Forward declaration to private functions. +*/ + +static mlt_events mlt_events_fetch( mlt_properties ); +static void mlt_events_store( mlt_properties, mlt_events ); +static void mlt_events_close( mlt_events ); + +/** Initialise the events structure. +*/ + +void mlt_events_init( mlt_properties this ) +{ + mlt_events events = mlt_events_fetch( this ); + if ( events == NULL ) + { + events = malloc( sizeof( struct mlt_events_struct ) ); + events->list = mlt_properties_new( ); + mlt_events_store( this, events ); + } +} + +/** Register an event and transmitter. +*/ + +int mlt_events_register( mlt_properties this, char *id, mlt_transmitter transmitter ) +{ + int error = 1; + mlt_events events = mlt_events_fetch( this ); + if ( events != NULL ) + { + mlt_properties list = events->list; + char temp[ 128 ]; + error = mlt_properties_set_data( list, id, transmitter, 0, NULL, NULL ); + sprintf( temp, "list:%s", id ); + if ( mlt_properties_get_data( list, temp, NULL ) == NULL ) + mlt_properties_set_data( list, temp, mlt_properties_new( ), 0, ( mlt_destructor )mlt_properties_close, NULL ); + } + return error; +} + +/** Fire an event. +*/ + +void mlt_events_fire( mlt_properties this, char *id, ... ) +{ + mlt_events events = mlt_events_fetch( this ); + if ( events != NULL ) + { + int i = 0; + va_list alist; + void *args[ 10 ]; + mlt_properties list = events->list; + mlt_properties listeners = NULL; + char temp[ 128 ]; + mlt_transmitter transmitter = mlt_properties_get_data( list, id, NULL ); + sprintf( temp, "list:%s", id ); + listeners = mlt_properties_get_data( list, temp, NULL ); + + va_start( alist, id ); + do + args[ i ] = va_arg( alist, void * ); + while( args[ i ++ ] != NULL ); + va_end( alist ); + + if ( listeners != NULL ) + { + for ( i = 0; i < mlt_properties_count( listeners ); i ++ ) + { + mlt_event event = mlt_properties_get_data_at( listeners, i, NULL ); + if ( event != NULL && event->owner != NULL && event->block_count == 0 ) + { + if ( transmitter != NULL ) + transmitter( event->listener, event->owner, event->service, args ); + else + event->listener( event->owner, event->service ); + } + } + } + } +} + +/** Register a listener. +*/ + +mlt_event mlt_events_listen( mlt_properties this, void *service, char *id, mlt_listener listener ) +{ + mlt_event event = NULL; + mlt_events events = mlt_events_fetch( this ); + if ( events != NULL ) + { + mlt_properties list = events->list; + mlt_properties listeners = NULL; + char temp[ 128 ]; + sprintf( temp, "list:%s", id ); + listeners = mlt_properties_get_data( list, temp, NULL ); + if ( listeners != NULL ) + { + int first_null = -1; + int i = 0; + for ( i = 0; event == NULL && i < mlt_properties_count( listeners ); i ++ ) + { + mlt_event entry = mlt_properties_get_data_at( listeners, i, NULL ); + if ( entry != NULL && entry->owner != NULL ) + { + if ( entry->service == service && entry->listener == listener ) + event = entry; + } + else if ( ( entry == NULL || entry->owner == NULL ) && first_null == -1 ) + { + first_null = i; + } + } + + if ( event == NULL ) + { + event = malloc( sizeof( struct mlt_event_struct ) ); + if ( event != NULL ) + { +#ifdef _MLT_EVENT_CHECKS_ + events_created ++; +#endif + sprintf( temp, "%d", first_null == -1 ? mlt_properties_count( listeners ) : first_null ); + event->owner = events; + event->ref_count = 0; + event->block_count = 0; + event->listener = listener; + event->service = service; + mlt_properties_set_data( listeners, temp, event, 0, ( mlt_destructor )mlt_event_close, NULL ); + mlt_event_inc_ref( event ); + } + } + + } + } + return event; +} + +/** Block all events for a given service. +*/ + +void mlt_events_block( mlt_properties this, void *service ) +{ + mlt_events events = mlt_events_fetch( this ); + if ( events != NULL ) + { + int i = 0, j = 0; + mlt_properties list = events->list; + for ( j = 0; j < mlt_properties_count( list ); j ++ ) + { + char *temp = mlt_properties_get_name( list, j ); + if ( !strncmp( temp, "list:", 5 ) ) + { + mlt_properties listeners = mlt_properties_get_data( list, temp, NULL ); + for ( i = 0; i < mlt_properties_count( listeners ); i ++ ) + { + mlt_event entry = mlt_properties_get_data_at( listeners, i, NULL ); + if ( entry != NULL && entry->service == service ) + mlt_event_block( entry ); + } + } + } + } +} + +/** Unblock all events for a given service. +*/ + +void mlt_events_unblock( mlt_properties this, void *service ) +{ + mlt_events events = mlt_events_fetch( this ); + if ( events != NULL ) + { + int i = 0, j = 0; + mlt_properties list = events->list; + for ( j = 0; j < mlt_properties_count( list ); j ++ ) + { + char *temp = mlt_properties_get_name( list, j ); + if ( !strncmp( temp, "list:", 5 ) ) + { + mlt_properties listeners = mlt_properties_get_data( list, temp, NULL ); + for ( i = 0; i < mlt_properties_count( listeners ); i ++ ) + { + mlt_event entry = mlt_properties_get_data_at( listeners, i, NULL ); + if ( entry != NULL && entry->service == service ) + mlt_event_unblock( entry ); + } + } + } + } +} + +/** Disconnect all events for a given service. +*/ + +void mlt_events_disconnect( mlt_properties this, void *service ) +{ + mlt_events events = mlt_events_fetch( this ); + if ( events != NULL ) + { + int i = 0, j = 0; + mlt_properties list = events->list; + for ( j = 0; j < mlt_properties_count( list ); j ++ ) + { + char *temp = mlt_properties_get_name( list, j ); + if ( !strncmp( temp, "list:", 5 ) ) + { + mlt_properties listeners = mlt_properties_get_data( list, temp, NULL ); + for ( i = 0; i < mlt_properties_count( listeners ); i ++ ) + { + mlt_event entry = mlt_properties_get_data_at( listeners, i, NULL ); + char *name = mlt_properties_get_name( listeners, i ); + if ( entry != NULL && entry->service == service ) + mlt_properties_set_data( listeners, name, NULL, 0, NULL, NULL ); + } + } + } + } +} + +typedef struct +{ + int done; + pthread_cond_t cond; + pthread_mutex_t mutex; +} +condition_pair; + +static void mlt_events_listen_for( mlt_properties this, condition_pair *pair ) +{ + pthread_mutex_lock( &pair->mutex ); + if ( pair->done == 0 ) + { + pthread_cond_signal( &pair->cond ); + pthread_mutex_unlock( &pair->mutex ); + } +} + +mlt_event mlt_events_setup_wait_for( mlt_properties this, char *id ) +{ + condition_pair *pair = malloc( sizeof( condition_pair ) ); + pair->done = 0; + pthread_cond_init( &pair->cond, NULL ); + pthread_mutex_init( &pair->mutex, NULL ); + pthread_mutex_lock( &pair->mutex ); + return mlt_events_listen( this, pair, id, ( mlt_listener )mlt_events_listen_for ); +} + +void mlt_events_wait_for( mlt_properties this, mlt_event event ) +{ + if ( event != NULL ) + { + condition_pair *pair = event->service; + pthread_cond_wait( &pair->cond, &pair->mutex ); + } +} + +void mlt_events_close_wait_for( mlt_properties this, mlt_event event ) +{ + if ( event != NULL ) + { + condition_pair *pair = event->service; + event->owner = NULL; + pair->done = 0; + pthread_mutex_unlock( &pair->mutex ); + pthread_mutex_destroy( &pair->mutex ); + pthread_cond_destroy( &pair->cond ); + } +} + +/** Fetch the events object. +*/ + +static mlt_events mlt_events_fetch( mlt_properties this ) +{ + mlt_events events = NULL; + if ( this != NULL ) + events = mlt_properties_get_data( this, "_events", NULL ); + return events; +} + +/** Store the events object. +*/ + +static void mlt_events_store( mlt_properties this, mlt_events events ) +{ + if ( this != NULL && events != NULL ) + mlt_properties_set_data( this, "_events", events, 0, ( mlt_destructor )mlt_events_close, NULL ); +} + +/** Close the events object. +*/ + +static void mlt_events_close( mlt_events events ) +{ + if ( events != NULL ) + { + mlt_properties_close( events->list ); + free( events ); + } +} + diff --git a/src/framework/mlt_events.h b/src/framework/mlt_events.h new file mode 100644 index 0000000..e04de61 --- /dev/null +++ b/src/framework/mlt_events.h @@ -0,0 +1,52 @@ +/* + * mlt_events.h -- event handling + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_EVENTS_H_ +#define _MLT_EVENTS_H_ + +#include "mlt_types.h" + +#if GCC_VERSION >= 40000 +typedef void ( *mlt_transmitter )( void *, ... ); +typedef void ( *mlt_listener )( void *, ... ); +#else +typedef void ( *mlt_transmitter )( ); +typedef void ( *mlt_listener )( ); +#endif + +extern void mlt_events_init( mlt_properties self ); +extern int mlt_events_register( mlt_properties self, char *id, mlt_transmitter transmitter ); +extern void mlt_events_fire( mlt_properties self, char *id, ... ); +extern mlt_event mlt_events_listen( mlt_properties self, void *service, char *id, mlt_listener listener ); +extern void mlt_events_block( mlt_properties self, void *service ); +extern void mlt_events_unblock( mlt_properties self, void *service ); +extern void mlt_events_disconnect( mlt_properties self, void *service ); + +extern mlt_event mlt_events_setup_wait_for( mlt_properties self, char *id ); +extern void mlt_events_wait_for( mlt_properties self, mlt_event event ); +extern void mlt_events_close_wait_for( mlt_properties self, mlt_event event ); + +extern void mlt_event_inc_ref( mlt_event self ); +extern void mlt_event_block( mlt_event self ); +extern void mlt_event_unblock( mlt_event self ); +extern void mlt_event_close( mlt_event self ); + +#endif + diff --git a/src/framework/mlt_factory.c b/src/framework/mlt_factory.c new file mode 100644 index 0000000..686020a --- /dev/null +++ b/src/framework/mlt_factory.c @@ -0,0 +1,304 @@ +/* + * mlt_factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "mlt.h" +#include "mlt_repository.h" + +#include +#include +#include + +/** Singleton repositories +*/ + +static char *mlt_prefix = NULL; +static mlt_properties global_properties = NULL; +static mlt_properties object_list = NULL; +static mlt_repository producers = NULL; +static mlt_repository filters = NULL; +static mlt_repository transitions = NULL; +static mlt_repository consumers = NULL; +static mlt_properties event_object = NULL; +static int unique_id = 0; + +/** Event transmitters. +*/ + +static void mlt_factory_create_request( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( char * )args[ 0 ], ( char * )args[ 1 ], ( mlt_service * )args[ 2 ] ); +} + +static void mlt_factory_create_done( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( char * )args[ 0 ], ( char * )args[ 1 ], ( mlt_service )args[ 2 ] ); +} + +/** Construct the factories. +*/ + +int mlt_factory_init( const char *prefix ) +{ + // Only initialise once + if ( mlt_prefix == NULL ) + { + // Allow user over rides + if ( prefix == NULL || !strcmp( prefix, "" ) ) + prefix = getenv( "MLT_REPOSITORY" ); + + // If no directory is specified, default to install directory + if ( prefix == NULL ) + prefix = PREFIX_DATA; + + // Store the prefix for later retrieval + mlt_prefix = strdup( prefix ); + + // Initialise the pool + mlt_pool_init( ); + + // Create and set up the events object + event_object = mlt_properties_new( ); + mlt_events_init( event_object ); + mlt_events_register( event_object, "producer-create-request", ( mlt_transmitter )mlt_factory_create_request ); + mlt_events_register( event_object, "producer-create-done", ( mlt_transmitter )mlt_factory_create_done ); + mlt_events_register( event_object, "filter-create-request", ( mlt_transmitter )mlt_factory_create_request ); + mlt_events_register( event_object, "filter-create-done", ( mlt_transmitter )mlt_factory_create_done ); + mlt_events_register( event_object, "transition-create-request", ( mlt_transmitter )mlt_factory_create_request ); + mlt_events_register( event_object, "transition-create-done", ( mlt_transmitter )mlt_factory_create_done ); + mlt_events_register( event_object, "consumer-create-request", ( mlt_transmitter )mlt_factory_create_request ); + mlt_events_register( event_object, "consumer-create-done", ( mlt_transmitter )mlt_factory_create_done ); + + // Create the global properties + global_properties = mlt_properties_new( ); + + // Create the object list. + object_list = mlt_properties_new( ); + + // Create a repository for each service type + producers = mlt_repository_init( object_list, prefix, "producers", "mlt_create_producer" ); + filters = mlt_repository_init( object_list, prefix, "filters", "mlt_create_filter" ); + transitions = mlt_repository_init( object_list, prefix, "transitions", "mlt_create_transition" ); + consumers = mlt_repository_init( object_list, prefix, "consumers", "mlt_create_consumer" ); + + // Force a clean up when app closes + atexit( mlt_factory_close ); + } + + // Allow property refresh on a subsequent initialisation + if ( global_properties != NULL ) + { + mlt_properties_set_or_default( global_properties, "MLT_NORMALISATION", getenv( "MLT_NORMALISATION" ), "PAL" ); + mlt_properties_set_or_default( global_properties, "MLT_PRODUCER", getenv( "MLT_PRODUCER" ), "fezzik" ); + mlt_properties_set_or_default( global_properties, "MLT_CONSUMER", getenv( "MLT_CONSUMER" ), "sdl" ); + mlt_properties_set( global_properties, "MLT_TEST_CARD", getenv( "MLT_TEST_CARD" ) ); + mlt_properties_set_or_default( global_properties, "MLT_PROFILE", getenv( "MLT_PROFILE" ), "dv_pal" ); + + // Load the most appropriate profile + // MLT_PROFILE preferred + if ( getenv( "MLT_PROFILE" ) ) + mlt_profile_select( mlt_environment( "MLT_PROFILE" ) ); + // MLT_NORMALISATION backwards compatibility + else if ( strcmp( mlt_environment( "MLT_NORMALISATION" ), "PAL" ) ) + mlt_profile_select( "dv_ntsc" ); + else + mlt_profile_select( "dv_pal" ); + } + + + return 0; +} + +/** Fetch the events object. +*/ + +mlt_properties mlt_factory_event_object( ) +{ + return event_object; +} + +/** Fetch the prefix used in this instance. +*/ + +const char *mlt_factory_prefix( ) +{ + return mlt_prefix; +} + +/** Get a value from the environment. +*/ + +char *mlt_environment( const char *name ) +{ + return mlt_properties_get( global_properties, name ); +} + +/** Set a value in the environment. +*/ + +int mlt_environment_set( const char *name, const char *value ) +{ + return mlt_properties_set( global_properties, name, value ); +} + +/** Fetch a producer from the repository. +*/ + +mlt_producer mlt_factory_producer( const char *service, void *input ) +{ + mlt_producer obj = NULL; + + // Pick up the default normalising producer if necessary + if ( service == NULL ) + service = mlt_environment( "MLT_PRODUCER" ); + + // Offer the application the chance to 'create' + mlt_events_fire( event_object, "producer-create-request", service, input, &obj, NULL ); + + // Try to instantiate via the specified service + if ( obj == NULL ) + { + obj = mlt_repository_fetch( producers, service, input ); + mlt_events_fire( event_object, "producer-create-done", service, input, obj, NULL ); + if ( obj != NULL ) + { + mlt_properties properties = MLT_PRODUCER_PROPERTIES( obj ); + mlt_properties_set_int( properties, "_unique_id", ++ unique_id ); + mlt_properties_set( properties, "mlt_type", "producer" ); + if ( mlt_properties_get_int( properties, "_mlt_service_hidden" ) == 0 ) + mlt_properties_set( properties, "mlt_service", service ); + } + } + return obj; +} + +/** Fetch a filter from the repository. +*/ + +mlt_filter mlt_factory_filter( const char *service, void *input ) +{ + mlt_filter obj = NULL; + + // Offer the application the chance to 'create' + mlt_events_fire( event_object, "filter-create-request", service, input, &obj, NULL ); + + if ( obj == NULL ) + { + obj = mlt_repository_fetch( filters, service, input ); + mlt_events_fire( event_object, "filter-create-done", service, input, obj, NULL ); + } + + if ( obj != NULL ) + { + mlt_properties properties = MLT_FILTER_PROPERTIES( obj ); + mlt_properties_set_int( properties, "_unique_id", ++ unique_id ); + mlt_properties_set( properties, "mlt_type", "filter" ); + mlt_properties_set( properties, "mlt_service", service ); + } + return obj; +} + +/** Fetch a transition from the repository. +*/ + +mlt_transition mlt_factory_transition( const char *service, void *input ) +{ + mlt_transition obj = NULL; + + // Offer the application the chance to 'create' + mlt_events_fire( event_object, "transition-create-request", service, input, &obj, NULL ); + + if ( obj == NULL ) + { + obj = mlt_repository_fetch( transitions, service, input ); + mlt_events_fire( event_object, "transition-create-done", service, input, obj, NULL ); + } + + if ( obj != NULL ) + { + mlt_properties properties = MLT_TRANSITION_PROPERTIES( obj ); + mlt_properties_set_int( properties, "_unique_id", ++ unique_id ); + mlt_properties_set( properties, "mlt_type", "transition" ); + mlt_properties_set( properties, "mlt_service", service ); + } + return obj; +} + +/** Fetch a consumer from the repository +*/ + +mlt_consumer mlt_factory_consumer( const char *service, void *input ) +{ + mlt_consumer obj = NULL; + + if ( service == NULL ) + service = mlt_environment( "MLT_CONSUMER" ); + + // Offer the application the chance to 'create' + mlt_events_fire( event_object, "consumer-create-request", service, input, &obj, NULL ); + + if ( obj == NULL ) + { + obj = mlt_repository_fetch( consumers, service, input ); + mlt_events_fire( event_object, "consumer-create-done", service, input, obj, NULL ); + } + + if ( obj != NULL ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( obj ); + mlt_properties_set_int( properties, "_unique_id", ++ unique_id ); + mlt_properties_set( properties, "mlt_type", "consumer" ); + mlt_properties_set( properties, "mlt_service", service ); + } + return obj; +} + +/** Register an object for clean up. +*/ + +void mlt_factory_register_for_clean_up( void *ptr, mlt_destructor destructor ) +{ + char unique[ 256 ]; + sprintf( unique, "%08d", mlt_properties_count( global_properties ) ); + mlt_properties_set_data( global_properties, unique, ptr, 0, destructor, NULL ); +} + +/** Close the factory. +*/ + +void mlt_factory_close( ) +{ + if ( mlt_prefix != NULL ) + { + mlt_properties_close( event_object ); + mlt_repository_close( producers ); + mlt_repository_close( filters ); + mlt_repository_close( transitions ); + mlt_repository_close( consumers ); + mlt_properties_close( global_properties ); + mlt_properties_close( object_list ); + free( mlt_prefix ); + mlt_prefix = NULL; + mlt_pool_close( ); + mlt_profile_close(); + } +} diff --git a/src/framework/mlt_factory.h b/src/framework/mlt_factory.h new file mode 100644 index 0000000..46a5307 --- /dev/null +++ b/src/framework/mlt_factory.h @@ -0,0 +1,38 @@ +/* + * mlt_factory.h -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_FACTORY_H +#define _MLT_FACTORY_H + +#include "mlt_types.h" + +extern int mlt_factory_init( const char *prefix ); +extern const char *mlt_factory_prefix( ); +extern char *mlt_environment( const char *name ); +extern int mlt_environment_set( const char *name, const char *value ); +extern mlt_properties mlt_factory_event_object( ); +extern mlt_producer mlt_factory_producer( const char *name, void *input ); +extern mlt_filter mlt_factory_filter( const char *name, void *input ); +extern mlt_transition mlt_factory_transition( const char *name, void *input ); +extern mlt_consumer mlt_factory_consumer( const char *name, void *input ); +extern void mlt_factory_register_for_clean_up( void *ptr, mlt_destructor destructor ); +extern void mlt_factory_close( ); + +#endif diff --git a/src/framework/mlt_field.c b/src/framework/mlt_field.c new file mode 100644 index 0000000..48e0470 --- /dev/null +++ b/src/framework/mlt_field.c @@ -0,0 +1,193 @@ +/* + * mlt_field.c -- A field for planting multiple transitions and filters + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "mlt_field.h" +#include "mlt_service.h" +#include "mlt_filter.h" +#include "mlt_transition.h" +#include "mlt_multitrack.h" +#include "mlt_tractor.h" + +#include +#include + +/** Private structures. +*/ + +struct mlt_field_s +{ + // This is the producer we're connected to + mlt_service producer; + + // Multitrack + mlt_multitrack multitrack; + + // Tractor + mlt_tractor tractor; +}; + +/** Constructor. + + We construct a multitrack and a tractor here. +*/ + +mlt_field mlt_field_init( ) +{ + // Initialise the field + mlt_field this = calloc( sizeof( struct mlt_field_s ), 1 ); + + // Initialise it + if ( this != NULL ) + { + // Construct a multitrack + this->multitrack = mlt_multitrack_init( ); + + // Construct a tractor + this->tractor = mlt_tractor_init( ); + + // The first plant will be connected to the mulitrack + this->producer = MLT_MULTITRACK_SERVICE( this->multitrack ); + + // Connect the tractor to the multitrack + mlt_tractor_connect( this->tractor, this->producer ); + } + + // Return this + return this; +} + +mlt_field mlt_field_new( mlt_multitrack multitrack, mlt_tractor tractor ) +{ + // Initialise the field + mlt_field this = calloc( sizeof( struct mlt_field_s ), 1 ); + + // Initialise it + if ( this != NULL ) + { + // Construct a multitrack + this->multitrack = multitrack; + + // Construct a tractor + this->tractor = tractor; + + // The first plant will be connected to the mulitrack + this->producer = MLT_MULTITRACK_SERVICE( this->multitrack ); + + // Connect the tractor to the multitrack + mlt_tractor_connect( this->tractor, this->producer ); + } + + // Return this + return this; +} + +/** Get the service associated to this field. +*/ + +mlt_service mlt_field_service( mlt_field this ) +{ + return MLT_TRACTOR_SERVICE( this->tractor ); +} + +/** Get the multi track. +*/ + +mlt_multitrack mlt_field_multitrack( mlt_field this ) +{ + return this != NULL ? this->multitrack : NULL; +} + +/** Get the tractor. +*/ + +mlt_tractor mlt_field_tractor( mlt_field this ) +{ + return this != NULL ? this->tractor : NULL; +} + +/** Get the properties associated to this field. +*/ + +mlt_properties mlt_field_properties( mlt_field this ) +{ + return MLT_SERVICE_PROPERTIES( mlt_field_service( this ) ); +} + +/** Plant a filter. +*/ + +int mlt_field_plant_filter( mlt_field this, mlt_filter that, int track ) +{ + // Connect the filter to the last producer + int result = mlt_filter_connect( that, this->producer, track ); + + // If sucessful, then we'll use this for connecting in the future + if ( result == 0 ) + { + // This is now the new producer + this->producer = MLT_FILTER_SERVICE( that ); + + // Reconnect tractor to new producer + mlt_tractor_connect( this->tractor, this->producer ); + + // Fire an event + mlt_events_fire( mlt_field_properties( this ), "service-changed", NULL ); + } + + return result; +} + +/** Plant a transition. +*/ + +int mlt_field_plant_transition( mlt_field this, mlt_transition that, int a_track, int b_track ) +{ + // Connect the transition to the last producer + int result = mlt_transition_connect( that, this->producer, a_track, b_track ); + + // If sucessful, then we'll use this for connecting in the future + if ( result == 0 ) + { + // This is now the new producer + this->producer = MLT_TRANSITION_SERVICE( that ); + + // Reconnect tractor to new producer + mlt_tractor_connect( this->tractor, this->producer ); + + // Fire an event + mlt_events_fire( mlt_field_properties( this ), "service-changed", NULL ); + } + + return 0; +} + +/** Close the field. +*/ + +void mlt_field_close( mlt_field this ) +{ + if ( this != NULL && mlt_properties_dec_ref( mlt_field_properties( this ) ) <= 0 ) + { + //mlt_tractor_close( this->tractor ); + //mlt_multitrack_close( this->multitrack ); + free( this ); + } +} + diff --git a/src/framework/mlt_field.h b/src/framework/mlt_field.h new file mode 100644 index 0000000..032b767 --- /dev/null +++ b/src/framework/mlt_field.h @@ -0,0 +1,37 @@ +/* + * mlt_field.h -- A field for planting multiple transitions and services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_FIELD_H_ +#define _MLT_FIELD_H_ + +#include "mlt_types.h" + +extern mlt_field mlt_field_init( ); +extern mlt_field mlt_field_new( mlt_multitrack multitrack, mlt_tractor tractor ); +extern mlt_service mlt_field_service( mlt_field self ); +extern mlt_tractor mlt_field_tractor( mlt_field self ); +extern mlt_multitrack mlt_field_multitrack( mlt_field self ); +extern mlt_properties mlt_field_properties( mlt_field self ); +extern int mlt_field_plant_filter( mlt_field self, mlt_filter that, int track ); +extern int mlt_field_plant_transition( mlt_field self, mlt_transition that, int a_track, int b_track ); +extern void mlt_field_close( mlt_field self ); + +#endif + diff --git a/src/framework/mlt_filter.c b/src/framework/mlt_filter.c new file mode 100644 index 0000000..3fc2229 --- /dev/null +++ b/src/framework/mlt_filter.c @@ -0,0 +1,213 @@ +/* + * mlt_filter.c -- abstraction for all filter services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "mlt_filter.h" +#include "mlt_frame.h" + +#include +#include +#include + +static int filter_get_frame( mlt_service this, mlt_frame_ptr frame, int index ); + +/** Constructor method. +*/ + +int mlt_filter_init( mlt_filter this, void *child ) +{ + mlt_service service = &this->parent; + memset( this, 0, sizeof( struct mlt_filter_s ) ); + this->child = child; + if ( mlt_service_init( service, this ) == 0 ) + { + mlt_properties properties = MLT_SERVICE_PROPERTIES( service ); + + // Override the get_frame method + service->get_frame = filter_get_frame; + + // Define the destructor + service->close = ( mlt_destructor )mlt_filter_close; + service->close_object = this; + + // Default in, out, track properties + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", 0 ); + mlt_properties_set_int( properties, "track", 0 ); + + return 0; + } + return 1; +} + +/** Create a new filter. +*/ + +mlt_filter mlt_filter_new( ) +{ + mlt_filter this = calloc( 1, sizeof( struct mlt_filter_s ) ); + if ( this != NULL ) + mlt_filter_init( this, NULL ); + return this; +} + +/** Get the service associated to this filter +*/ + +mlt_service mlt_filter_service( mlt_filter this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Get the properties associated to this filter. +*/ + +mlt_properties mlt_filter_properties( mlt_filter this ) +{ + return MLT_SERVICE_PROPERTIES( MLT_FILTER_SERVICE( this ) ); +} + +/** Connect this filter to a producers track. Note that a filter only operates + on a single track, and by default it operates on the entirety of that track. +*/ + +int mlt_filter_connect( mlt_filter this, mlt_service producer, int index ) +{ + int ret = mlt_service_connect_producer( &this->parent, producer, index ); + + // If the connection was successful, grab the producer, track and reset in/out + if ( ret == 0 ) + { + mlt_properties properties = MLT_SERVICE_PROPERTIES( &this->parent ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", 0 ); + mlt_properties_set_int( properties, "track", index ); + } + + return ret; +} + +/** Tune the in/out points. +*/ + +void mlt_filter_set_in_and_out( mlt_filter this, mlt_position in, mlt_position out ) +{ + mlt_properties properties = MLT_SERVICE_PROPERTIES( &this->parent ); + mlt_properties_set_position( properties, "in", in ); + mlt_properties_set_position( properties, "out", out ); +} + +/** Return the track that this filter is operating on. +*/ + +int mlt_filter_get_track( mlt_filter this ) +{ + mlt_properties properties = MLT_SERVICE_PROPERTIES( &this->parent ); + return mlt_properties_get_int( properties, "track" ); +} + +/** Get the in point. +*/ + +mlt_position mlt_filter_get_in( mlt_filter this ) +{ + mlt_properties properties = MLT_SERVICE_PROPERTIES( &this->parent ); + return mlt_properties_get_position( properties, "in" ); +} + +/** Get the out point. +*/ + +mlt_position mlt_filter_get_out( mlt_filter this ) +{ + mlt_properties properties = MLT_SERVICE_PROPERTIES( &this->parent ); + return mlt_properties_get_position( properties, "out" ); +} + +/** Process the frame. +*/ + +mlt_frame mlt_filter_process( mlt_filter this, mlt_frame frame ) +{ + int disable = mlt_properties_get_int( MLT_FILTER_PROPERTIES( this ), "disable" ); + if ( disable || this->process == NULL ) + return frame; + else + return this->process( this, frame ); +} + +/** Get a frame from this filter. +*/ + +static int filter_get_frame( mlt_service service, mlt_frame_ptr frame, int index ) +{ + mlt_filter this = service->child; + + // Get coords in/out/track + int track = mlt_filter_get_track( this ); + int in = mlt_filter_get_in( this ); + int out = mlt_filter_get_out( this ); + + // Get the producer this is connected to + mlt_service producer = mlt_service_producer( &this->parent ); + + // If the frame request is for this filters track, we need to process it + if ( index == track || track == -1 ) + { + int ret = mlt_service_get_frame( producer, frame, index ); + if ( ret == 0 ) + { + mlt_position position = mlt_frame_get_position( *frame ); + if ( position >= in && ( out == 0 || position <= out ) ) + *frame = mlt_filter_process( this, *frame ); + return 0; + } + else + { + *frame = mlt_frame_init( ); + return 0; + } + } + else + { + return mlt_service_get_frame( producer, frame, index ); + } +} + +/** Close the filter. +*/ + +void mlt_filter_close( mlt_filter this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_FILTER_PROPERTIES( this ) ) <= 0 ) + { + if ( this->close != NULL ) + { + this->close( this ); + } + else + { + this->parent.close = NULL; + mlt_service_close( &this->parent ); + } + free( this ); + } +} diff --git a/src/framework/mlt_filter.h b/src/framework/mlt_filter.h new file mode 100644 index 0000000..06866a1 --- /dev/null +++ b/src/framework/mlt_filter.h @@ -0,0 +1,62 @@ +/* + * mlt_filter.h -- abstraction for all filter services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_FILTER_H_ +#define _MLT_FILTER_H_ + +#include "mlt_service.h" + +/** The interface definition for all filters. +*/ + +struct mlt_filter_s +{ + /* We're implementing service here */ + struct mlt_service_s parent; + + /* public virtual */ + void ( *close )( mlt_filter ); + + /* protected filter method */ + mlt_frame ( *process )( mlt_filter, mlt_frame ); + + /* Protected */ + void *child; +}; + +/** Public final methods +*/ + +#define MLT_FILTER_SERVICE( filter ) ( &( filter )->parent ) +#define MLT_FILTER_PROPERTIES( filter ) MLT_SERVICE_PROPERTIES( MLT_FILTER_SERVICE( filter ) ) + +extern int mlt_filter_init( mlt_filter self, void *child ); +extern mlt_filter mlt_filter_new( ); +extern mlt_service mlt_filter_service( mlt_filter self ); +extern mlt_properties mlt_filter_properties( mlt_filter self ); +extern mlt_frame mlt_filter_process( mlt_filter self, mlt_frame that ); +extern int mlt_filter_connect( mlt_filter self, mlt_service producer, int index ); +extern void mlt_filter_set_in_and_out( mlt_filter self, mlt_position in, mlt_position out ); +extern int mlt_filter_get_track( mlt_filter self ); +extern mlt_position mlt_filter_get_in( mlt_filter self ); +extern mlt_position mlt_filter_get_out( mlt_filter self ); +extern void mlt_filter_close( mlt_filter ); + +#endif diff --git a/src/framework/mlt_frame.c b/src/framework/mlt_frame.c new file mode 100644 index 0000000..79fed6e --- /dev/null +++ b/src/framework/mlt_frame.c @@ -0,0 +1,1310 @@ +/* + * mlt_frame.c -- interface for all frame classes + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "mlt_frame.h" +#include "mlt_producer.h" +#include "mlt_factory.h" +#include "mlt_profile.h" + +#include +#include +#include +#include + +/** Constructor for a frame. +*/ + +mlt_frame mlt_frame_init( ) +{ + // Allocate a frame + mlt_frame this = calloc( sizeof( struct mlt_frame_s ), 1 ); + + if ( this != NULL ) + { + // Initialise the properties + mlt_properties properties = &this->parent; + mlt_properties_init( properties, this ); + + // Set default properties on the frame + mlt_properties_set_position( properties, "_position", 0.0 ); + mlt_properties_set_data( properties, "image", NULL, 0, NULL, NULL ); + mlt_properties_set_int( properties, "width", mlt_profile_get()->width ); + mlt_properties_set_int( properties, "height", mlt_profile_get()->height ); + mlt_properties_set_int( properties, "normalised_width", mlt_profile_get()->width ); + mlt_properties_set_int( properties, "normalised_height", mlt_profile_get()->height ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_profile_sar( NULL ) ); + mlt_properties_set_data( properties, "audio", NULL, 0, NULL, NULL ); + mlt_properties_set_data( properties, "alpha", NULL, 0, NULL, NULL ); + + // Construct stacks for frames and methods + this->stack_image = mlt_deque_init( ); + this->stack_audio = mlt_deque_init( ); + this->stack_service = mlt_deque_init( ); + } + + return this; +} + +/** Fetch the frames properties. +*/ + +mlt_properties mlt_frame_properties( mlt_frame this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Check if we have a way to derive something other than a test card. +*/ + +int mlt_frame_is_test_card( mlt_frame this ) +{ + return mlt_deque_count( this->stack_image ) == 0 || mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "test_image" ); +} + +/** Check if we have a way to derive something other than test audio. +*/ + +int mlt_frame_is_test_audio( mlt_frame this ) +{ + return mlt_deque_count( this->stack_audio ) == 0 || mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "test_audio" ); +} + +/** Get the aspect ratio of the frame. +*/ + +double mlt_frame_get_aspect_ratio( mlt_frame this ) +{ + return mlt_properties_get_double( MLT_FRAME_PROPERTIES( this ), "aspect_ratio" ); +} + +/** Set the aspect ratio of the frame. +*/ + +int mlt_frame_set_aspect_ratio( mlt_frame this, double value ) +{ + return mlt_properties_set_double( MLT_FRAME_PROPERTIES( this ), "aspect_ratio", value ); +} + +/** Get the position of this frame. +*/ + +mlt_position mlt_frame_get_position( mlt_frame this ) +{ + int pos = mlt_properties_get_position( MLT_FRAME_PROPERTIES( this ), "_position" ); + return pos < 0 ? 0 : pos; +} + +/** Set the position of this frame. +*/ + +int mlt_frame_set_position( mlt_frame this, mlt_position value ) +{ + return mlt_properties_set_position( MLT_FRAME_PROPERTIES( this ), "_position", value ); +} + +/** Stack a get_image callback. +*/ + +int mlt_frame_push_get_image( mlt_frame this, mlt_get_image get_image ) +{ + return mlt_deque_push_back( this->stack_image, get_image ); +} + +/** Pop a get_image callback. +*/ + +mlt_get_image mlt_frame_pop_get_image( mlt_frame this ) +{ + return mlt_deque_pop_back( this->stack_image ); +} + +/** Push a frame. +*/ + +int mlt_frame_push_frame( mlt_frame this, mlt_frame that ) +{ + return mlt_deque_push_back( this->stack_image, that ); +} + +/** Pop a frame. +*/ + +mlt_frame mlt_frame_pop_frame( mlt_frame this ) +{ + return mlt_deque_pop_back( this->stack_image ); +} + +/** Push a service. +*/ + +int mlt_frame_push_service( mlt_frame this, void *that ) +{ + return mlt_deque_push_back( this->stack_image, that ); +} + +/** Pop a service. +*/ + +void *mlt_frame_pop_service( mlt_frame this ) +{ + return mlt_deque_pop_back( this->stack_image ); +} + +/** Push a service. +*/ + +int mlt_frame_push_service_int( mlt_frame this, int that ) +{ + return mlt_deque_push_back_int( this->stack_image, that ); +} + +/** Pop a service. +*/ + +int mlt_frame_pop_service_int( mlt_frame this ) +{ + return mlt_deque_pop_back_int( this->stack_image ); +} + +/** Push an audio item on the stack. +*/ + +int mlt_frame_push_audio( mlt_frame this, void *that ) +{ + return mlt_deque_push_back( this->stack_audio, that ); +} + +/** Pop an audio item from the stack +*/ + +void *mlt_frame_pop_audio( mlt_frame this ) +{ + return mlt_deque_pop_back( this->stack_audio ); +} + +/** Return the service stack +*/ + +mlt_deque mlt_frame_service_stack( mlt_frame this ) +{ + return this->stack_service; +} + +/** Replace image stack with the information provided. + + This might prove to be unreliable and restrictive - the idea is that a transition + which normally uses two images may decide to only use the b frame (ie: in the case + of a composite where the b frame completely obscures the a frame). + + The image must be writable and the destructor for the image itself must be taken + care of on another frame and that frame cannot have a replace applied to it... + Further it assumes that no alpha mask is in use. + + For these reasons, it can only be used in a specific situation - when you have + multiple tracks each with their own transition and these transitions are applied + in a strictly reversed order (ie: highest numbered [lowest track] is processed + first). + + More reliable approach - the cases should be detected during the process phase + and the upper tracks should simply not be invited to stack... +*/ + +void mlt_frame_replace_image( mlt_frame this, uint8_t *image, mlt_image_format format, int width, int height ) +{ + // Remove all items from the stack + while( mlt_deque_pop_back( this->stack_image ) ) ; + + // Update the information + mlt_properties_set_data( MLT_FRAME_PROPERTIES( this ), "image", image, 0, NULL, NULL ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( this ), "width", width ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( this ), "height", height ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( this ), "format", format ); + this->get_alpha_mask = NULL; +} + +/** Get the image associated to the frame. +*/ + +int mlt_frame_get_image( mlt_frame this, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + mlt_get_image get_image = mlt_frame_pop_get_image( this ); + mlt_producer producer = mlt_properties_get_data( properties, "test_card_producer", NULL ); + int error = 0; + + if ( get_image != NULL ) + { + mlt_properties_set_int( properties, "image_count", mlt_properties_get_int( properties, "image_count" ) - 1 ); + mlt_position position = mlt_frame_get_position( this ); + error = get_image( this, buffer, format, width, height, writable ); + mlt_properties_set_int( properties, "width", *width ); + mlt_properties_set_int( properties, "height", *height ); + mlt_properties_set_int( properties, "format", *format ); + mlt_frame_set_position( this, position ); + } + else if ( mlt_properties_get_data( properties, "image", NULL ) != NULL ) + { + *format = mlt_properties_get_int( properties, "format" ); + *buffer = mlt_properties_get_data( properties, "image", NULL ); + *width = mlt_properties_get_int( properties, "width" ); + *height = mlt_properties_get_int( properties, "height" ); + } + else if ( producer != NULL ) + { + mlt_frame test_frame = NULL; + mlt_service_get_frame( MLT_PRODUCER_SERVICE( producer ), &test_frame, 0 ); + if ( test_frame != NULL ) + { + mlt_properties test_properties = MLT_FRAME_PROPERTIES( test_frame ); + mlt_properties_set_double( test_properties, "consumer_aspect_ratio", mlt_properties_get_double( properties, "consumer_aspect_ratio" ) ); + mlt_properties_set( test_properties, "rescale.interp", mlt_properties_get( properties, "rescale.interp" ) ); + mlt_frame_get_image( test_frame, buffer, format, width, height, writable ); + mlt_properties_set_data( properties, "test_card_frame", test_frame, 0, ( mlt_destructor )mlt_frame_close, NULL ); + mlt_properties_set_data( properties, "image", *buffer, *width * *height * 2, NULL, NULL ); + mlt_properties_set_int( properties, "width", *width ); + mlt_properties_set_int( properties, "height", *height ); + mlt_properties_set_int( properties, "format", *format ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_frame_get_aspect_ratio( test_frame ) ); + } + else + { + mlt_properties_set_data( properties, "test_card_producer", NULL, 0, NULL, NULL ); + mlt_frame_get_image( this, buffer, format, width, height, writable ); + } + } + else + { + register uint8_t *p; + register uint8_t *q; + int size = 0; + + *width = *width == 0 ? 720 : *width; + *height = *height == 0 ? 576 : *height; + size = *width * *height; + + mlt_properties_set_int( properties, "format", *format ); + mlt_properties_set_int( properties, "width", *width ); + mlt_properties_set_int( properties, "height", *height ); + mlt_properties_set_int( properties, "aspect_ratio", 0 ); + + switch( *format ) + { + case mlt_image_none: + size = 0; + *buffer = NULL; + break; + case mlt_image_rgb24: + size *= 3; + size += *width * 3; + *buffer = mlt_pool_alloc( size ); + if ( *buffer ) + memset( *buffer, 255, size ); + break; + case mlt_image_rgb24a: + case mlt_image_opengl: + size *= 4; + size += *width * 4; + *buffer = mlt_pool_alloc( size ); + if ( *buffer ) + memset( *buffer, 255, size ); + break; + case mlt_image_yuv422: + size *= 2; + size += *width * 2; + *buffer = mlt_pool_alloc( size ); + p = *buffer; + q = p + size; + while ( p != NULL && p != q ) + { + *p ++ = 235; + *p ++ = 128; + } + break; + case mlt_image_yuv420p: + size = size * 3 / 2; + *buffer = mlt_pool_alloc( size ); + if ( *buffer ) + memset( *buffer, 255, size ); + break; + } + + mlt_properties_set_data( properties, "image", *buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "test_image", 1 ); + } + + mlt_properties_set_int( properties, "scaled_width", *width ); + mlt_properties_set_int( properties, "scaled_height", *height ); + + return error; +} + +uint8_t *mlt_frame_get_alpha_mask( mlt_frame this ) +{ + uint8_t *alpha = NULL; + if ( this != NULL ) + { + if ( this->get_alpha_mask != NULL ) + alpha = this->get_alpha_mask( this ); + if ( alpha == NULL ) + alpha = mlt_properties_get_data( &this->parent, "alpha", NULL ); + if ( alpha == NULL ) + { + int size = mlt_properties_get_int( &this->parent, "scaled_width" ) * mlt_properties_get_int( &this->parent, "scaled_height" ); + alpha = mlt_pool_alloc( size ); + memset( alpha, 255, size ); + mlt_properties_set_data( &this->parent, "alpha", alpha, size, mlt_pool_release, NULL ); + } + } + return alpha; +} + +int mlt_frame_get_audio( mlt_frame this, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + mlt_get_audio get_audio = mlt_frame_pop_audio( this ); + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + int hide = mlt_properties_get_int( properties, "test_audio" ); + + if ( hide == 0 && get_audio != NULL ) + { + mlt_position position = mlt_frame_get_position( this ); + get_audio( this, buffer, format, frequency, channels, samples ); + mlt_frame_set_position( this, position ); + } + else if ( mlt_properties_get_data( properties, "audio", NULL ) ) + { + *buffer = mlt_properties_get_data( properties, "audio", NULL ); + *frequency = mlt_properties_get_int( properties, "audio_frequency" ); + *channels = mlt_properties_get_int( properties, "audio_channels" ); + *samples = mlt_properties_get_int( properties, "audio_samples" ); + } + else + { + int size = 0; + *samples = *samples <= 0 ? 1920 : *samples; + *channels = *channels <= 0 ? 2 : *channels; + *frequency = *frequency <= 0 ? 48000 : *frequency; + size = *samples * *channels * sizeof( int16_t ); + *buffer = mlt_pool_alloc( size ); + if ( *buffer != NULL ) + memset( *buffer, 0, size ); + mlt_properties_set_data( properties, "audio", *buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "test_audio", 1 ); + } + + mlt_properties_set_int( properties, "audio_frequency", *frequency ); + mlt_properties_set_int( properties, "audio_channels", *channels ); + mlt_properties_set_int( properties, "audio_samples", *samples ); + + if ( mlt_properties_get( properties, "meta.volume" ) ) + { + double value = mlt_properties_get_double( properties, "meta.volume" ); + + if ( value == 0.0 ) + { + memset( *buffer, 0, *samples * *channels * 2 ); + } + else if ( value != 1.0 ) + { + int total = *samples * *channels; + int16_t *p = *buffer; + while ( total -- ) + { + *p = *p * value; + p ++; + } + } + + mlt_properties_set( properties, "meta.volume", NULL ); + } + + return 0; +} + +unsigned char *mlt_frame_get_waveform( mlt_frame this, int w, int h ) +{ + int16_t *pcm = NULL; + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + mlt_audio_format format = mlt_audio_pcm; + int frequency = 32000; // lower frequency available? + int channels = 2; + double fps = mlt_profile_fps( NULL ); + int samples = mlt_sample_calculator( fps, frequency, mlt_frame_get_position( this ) ); + + // Get the pcm data + mlt_frame_get_audio( this, &pcm, &format, &frequency, &channels, &samples ); + + // Make an 8-bit buffer large enough to hold rendering + int size = w * h; + unsigned char *bitmap = ( unsigned char* )mlt_pool_alloc( size ); + if ( bitmap != NULL ) + memset( bitmap, 0, size ); + mlt_properties_set_data( properties, "waveform", bitmap, size, ( mlt_destructor )mlt_pool_release, NULL ); + + // Render vertical lines + int16_t *ubound = pcm + samples * channels; + int skip = samples / w - 1; + int i, j, k; + + // Iterate sample stream and along x coordinate + for ( i = 0; i < w && pcm < ubound; i++ ) + { + // pcm data has channels interleaved + for ( j = 0; j < channels; j++ ) + { + // Determine sample's magnitude from 2s complement; + int pcm_magnitude = *pcm < 0 ? ~(*pcm) + 1 : *pcm; + // The height of a line is the ratio of the magnitude multiplied by + // half the vertical resolution + int height = ( int )( ( double )( pcm_magnitude ) / 32768 * h / 2 ); + // Determine the starting y coordinate - left channel above center, + // right channel below - currently assumes 2 channels + int displacement = ( h / 2 ) - ( 1 - j ) * height; + // Position buffer pointer using y coordinate, stride, and x coordinate + unsigned char *p = &bitmap[ i + displacement * w ]; + + // Draw vertical line + for ( k = 0; k < height; k++ ) + p[ w * k ] = 0xFF; + + pcm++; + } + pcm += skip * channels; + } + + return bitmap; +} + +mlt_producer mlt_frame_get_original_producer( mlt_frame this ) +{ + if ( this != NULL ) + return mlt_properties_get_data( MLT_FRAME_PROPERTIES( this ), "_producer", NULL ); + return NULL; +} + +void mlt_frame_close( mlt_frame this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_FRAME_PROPERTIES( this ) ) <= 0 ) + { + mlt_deque_close( this->stack_image ); + mlt_deque_close( this->stack_audio ); + while( mlt_deque_peek_back( this->stack_service ) ) + mlt_service_close( mlt_deque_pop_back( this->stack_service ) ); + mlt_deque_close( this->stack_service ); + mlt_properties_close( &this->parent ); + free( this ); + } +} + +/***** convenience functions *****/ + +int mlt_convert_yuv422_to_rgb24a( uint8_t *yuv, uint8_t *rgba, unsigned int total ) +{ + int ret = 0; + int yy, uu, vv; + int r,g,b; + total /= 2; + while (total--) + { + yy = yuv[0]; + uu = yuv[1]; + vv = yuv[3]; + YUV2RGB(yy, uu, vv, r, g, b); + rgba[0] = r; + rgba[1] = g; + rgba[2] = b; + rgba[3] = 255; + yy = yuv[2]; + YUV2RGB(yy, uu, vv, r, g, b); + rgba[4] = r; + rgba[5] = g; + rgba[6] = b; + rgba[7] = 255; + yuv += 4; + rgba += 8; + } + return ret; +} + +int mlt_convert_rgb24a_to_yuv422( uint8_t *rgba, int width, int height, int stride, uint8_t *yuv, uint8_t *alpha ) +{ + int ret = 0; + register int y0, y1, u0, u1, v0, v1; + register int r, g, b; + register uint8_t *d = yuv; + register int i, j; + + if ( alpha ) + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgba + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + r = *s++; + g = *s++; + b = *s++; + *alpha++ = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + r = *s++; + g = *s++; + b = *s++; + *alpha++ = *s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + r = *s++; + g = *s++; + b = *s++; + *alpha++ = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + else + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgba + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + r = *s++; + g = *s++; + b = *s++; + s++; + RGB2YUV (r, g, b, y0, u0 , v0); + r = *s++; + g = *s++; + b = *s++; + s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + r = *s++; + g = *s++; + b = *s++; + s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + + return ret; +} + +int mlt_convert_rgb24_to_yuv422( uint8_t *rgb, int width, int height, int stride, uint8_t *yuv ) +{ + int ret = 0; + register int y0, y1, u0, u1, v0, v1; + register int r, g, b; + register uint8_t *d = yuv; + register int i, j; + + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgb + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + return ret; +} + +int mlt_convert_bgr24a_to_yuv422( uint8_t *rgba, int width, int height, int stride, uint8_t *yuv, uint8_t *alpha ) +{ + int ret = 0; + register int y0, y1, u0, u1, v0, v1; + register int r, g, b; + register uint8_t *d = yuv; + register int i, j; + + if ( alpha ) + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgba + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + b = *s++; + g = *s++; + r = *s++; + *alpha++ = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + b = *s++; + g = *s++; + r = *s++; + *alpha++ = *s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + b = *s++; + g = *s++; + r = *s++; + *alpha++ = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + else + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgba + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + b = *s++; + g = *s++; + r = *s++; + s++; + RGB2YUV (r, g, b, y0, u0 , v0); + b = *s++; + g = *s++; + r = *s++; + s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + b = *s++; + g = *s++; + r = *s++; + s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + return ret; +} + +int mlt_convert_bgr24_to_yuv422( uint8_t *rgb, int width, int height, int stride, uint8_t *yuv ) +{ + int ret = 0; + register int y0, y1, u0, u1, v0, v1; + register int r, g, b; + register uint8_t *d = yuv; + register int i, j; + + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgb + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + b = *s++; + g = *s++; + r = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + b = *s++; + g = *s++; + r = *s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + b = *s++; + g = *s++; + r = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + return ret; +} + +int mlt_convert_argb_to_yuv422( uint8_t *rgba, int width, int height, int stride, uint8_t *yuv, uint8_t *alpha ) +{ + int ret = 0; + register int y0, y1, u0, u1, v0, v1; + register int r, g, b; + register uint8_t *d = yuv; + register int i, j; + + if ( alpha ) + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgba + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + *alpha++ = *s++; + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *alpha++ = *s++; + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + *alpha++ = *s++; + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + else + for ( i = 0; i < height; i++ ) + { + register uint8_t *s = rgba + ( stride * i ); + for ( j = 0; j < ( width / 2 ); j++ ) + { + s++; + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + s++; + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y1, u1 , v1); + *d++ = y0; + *d++ = (u0+u1) >> 1; + *d++ = y1; + *d++ = (v0+v1) >> 1; + } + if ( width % 2 ) + { + s++; + r = *s++; + g = *s++; + b = *s++; + RGB2YUV (r, g, b, y0, u0 , v0); + *d++ = y0; + *d++ = u0; + } + } + return ret; +} + +int mlt_convert_yuv420p_to_yuv422( uint8_t *yuv420p, int width, int height, int stride, uint8_t *yuv ) +{ + int ret = 0; + register int i, j; + + int half = width >> 1; + + uint8_t *Y = yuv420p; + uint8_t *U = Y + width * height; + uint8_t *V = U + width * height / 4; + + register uint8_t *d = yuv; + + for ( i = 0; i < height; i++ ) + { + register uint8_t *u = U + ( i / 2 ) * ( half ); + register uint8_t *v = V + ( i / 2 ) * ( half ); + + for ( j = 0; j < half; j++ ) + { + *d ++ = *Y ++; + *d ++ = *u ++; + *d ++ = *Y ++; + *d ++ = *v ++; + } + } + return ret; +} + +uint8_t *mlt_resize_alpha( uint8_t *input, int owidth, int oheight, int iwidth, int iheight, uint8_t alpha_value ) +{ + uint8_t *output = NULL; + + if ( input != NULL && ( iwidth != owidth || iheight != oheight ) && ( owidth > 6 && oheight > 6 ) ) + { + uint8_t *out_line; + int offset_x = ( owidth - iwidth ) / 2; + int offset_y = ( oheight - iheight ) / 2; + int iused = iwidth; + + output = mlt_pool_alloc( owidth * oheight ); + memset( output, alpha_value, owidth * oheight ); + + offset_x -= offset_x % 2; + + out_line = output + offset_y * owidth; + out_line += offset_x; + + // Loop for the entirety of our output height. + while ( iheight -- ) + { + // We're in the input range for this row. + memcpy( out_line, input, iused ); + + // Move to next input line + input += iwidth; + + // Move to next output line + out_line += owidth; + } + } + + return output; +} + +void mlt_resize_yuv422( uint8_t *output, int owidth, int oheight, uint8_t *input, int iwidth, int iheight ) +{ + // Calculate strides + int istride = iwidth * 2; + int ostride = owidth * 2; + int offset_x = ( owidth - iwidth ); + int offset_y = ( oheight - iheight ) / 2; + uint8_t *in_line = input; + uint8_t *out_line; + int size = owidth * oheight; + uint8_t *p = output; + + // Optimisation point + if ( output == NULL || input == NULL || ( owidth <= 6 || oheight <= 6 || iwidth <= 6 || oheight <= 6 ) ) + { + return; + } + else if ( iwidth == owidth && iheight == oheight ) + { + memcpy( output, input, iheight * istride ); + return; + } + + while( size -- ) + { + *p ++ = 16; + *p ++ = 128; + } + + offset_x -= offset_x % 4; + + out_line = output + offset_y * ostride; + out_line += offset_x; + + // Loop for the entirety of our output height. + while ( iheight -- ) + { + // We're in the input range for this row. + memcpy( out_line, in_line, iwidth * 2 ); + + // Move to next input line + in_line += istride; + + // Move to next output line + out_line += ostride; + } +} + +/** A resizing function for yuv422 frames - this does not rescale, but simply + resizes. It assumes yuv422 images available on the frame so use with care. +*/ + +uint8_t *mlt_frame_resize_yuv422( mlt_frame this, int owidth, int oheight ) +{ + // Get properties + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + // Get the input image, width and height + uint8_t *input = mlt_properties_get_data( properties, "image", NULL ); + uint8_t *alpha = mlt_frame_get_alpha_mask( this ); + + int iwidth = mlt_properties_get_int( properties, "width" ); + int iheight = mlt_properties_get_int( properties, "height" ); + + // If width and height are correct, don't do anything + if ( iwidth != owidth || iheight != oheight ) + { + uint8_t alpha_value = mlt_properties_get_int( properties, "resize_alpha" ); + + // Create the output image + uint8_t *output = mlt_pool_alloc( owidth * ( oheight + 1 ) * 2 ); + + // Call the generic resize + mlt_resize_yuv422( output, owidth, oheight, input, iwidth, iheight ); + + // Now update the frame + mlt_properties_set_data( properties, "image", output, owidth * ( oheight + 1 ) * 2, ( mlt_destructor )mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "width", owidth ); + mlt_properties_set_int( properties, "height", oheight ); + + // We should resize the alpha too + alpha = mlt_resize_alpha( alpha, owidth, oheight, iwidth, iheight, alpha_value ); + if ( alpha != NULL ) + { + mlt_properties_set_data( properties, "alpha", alpha, owidth * oheight, ( mlt_destructor )mlt_pool_release, NULL ); + this->get_alpha_mask = NULL; + } + + // Return the output + return output; + } + // No change, return input + return input; +} + +/** A rescaling function for yuv422 frames - low quality, and provided for testing + only. It assumes yuv422 images available on the frame so use with care. +*/ + +uint8_t *mlt_frame_rescale_yuv422( mlt_frame this, int owidth, int oheight ) +{ + // Get properties + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + + // Get the input image, width and height + uint8_t *input = mlt_properties_get_data( properties, "image", NULL ); + int iwidth = mlt_properties_get_int( properties, "width" ); + int iheight = mlt_properties_get_int( properties, "height" ); + + // If width and height are correct, don't do anything + if ( iwidth != owidth || iheight != oheight ) + { + // Create the output image + uint8_t *output = mlt_pool_alloc( owidth * ( oheight + 1 ) * 2 ); + + // Calculate strides + int istride = iwidth * 2; + int ostride = owidth * 2; + + iwidth = iwidth - ( iwidth % 4 ); + + // Derived coordinates + int dy, dx; + + // Calculate ranges + int out_x_range = owidth / 2; + int out_y_range = oheight / 2; + int in_x_range = iwidth / 2; + int in_y_range = iheight / 2; + + // Output pointers + register uint8_t *out_line = output; + register uint8_t *out_ptr; + + // Calculate a middle pointer + uint8_t *in_middle = input + istride * in_y_range + in_x_range * 2; + uint8_t *in_line; + + // Generate the affine transform scaling values + register int scale_width = ( iwidth << 16 ) / owidth; + register int scale_height = ( iheight << 16 ) / oheight; + register int base = 0; + + int outer = out_x_range * scale_width; + int bottom = out_y_range * scale_height; + + // Loop for the entirety of our output height. + for ( dy = - bottom; dy < bottom; dy += scale_height ) + { + // Start at the beginning of the line + out_ptr = out_line; + + // Pointer to the middle of the input line + in_line = in_middle + ( dy >> 16 ) * istride; + + // Loop for the entirety of our output row. + for ( dx = - outer; dx < outer; dx += scale_width ) + { + base = dx >> 15; + base &= 0xfffffffe; + *out_ptr ++ = *( in_line + base ); + base &= 0xfffffffc; + *out_ptr ++ = *( in_line + base + 1 ); + dx += scale_width; + base = dx >> 15; + base &= 0xfffffffe; + *out_ptr ++ = *( in_line + base ); + base &= 0xfffffffc; + *out_ptr ++ = *( in_line + base + 3 ); + } + + // Move to next output line + out_line += ostride; + } + + // Now update the frame + mlt_properties_set_data( properties, "image", output, owidth * ( oheight + 1 ) * 2, ( mlt_destructor )mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "width", owidth ); + mlt_properties_set_int( properties, "height", oheight ); + + // Return the output + return output; + } + + // No change, return input + return input; +} + +int mlt_frame_mix_audio( mlt_frame this, mlt_frame that, float weight_start, float weight_end, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + int ret = 0; + int16_t *src, *dest; + int frequency_src = *frequency, frequency_dest = *frequency; + int channels_src = *channels, channels_dest = *channels; + int samples_src = *samples, samples_dest = *samples; + int i, j; + double d = 0, s = 0; + + mlt_frame_get_audio( that, &src, format, &frequency_src, &channels_src, &samples_src ); + mlt_frame_get_audio( this, &dest, format, &frequency_dest, &channels_dest, &samples_dest ); + + int silent = mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "silent_audio" ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( this ), "silent_audio", 0 ); + if ( silent ) + memset( dest, 0, samples_dest * channels_dest * sizeof( int16_t ) ); + + silent = mlt_properties_get_int( MLT_FRAME_PROPERTIES( that ), "silent_audio" ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( that ), "silent_audio", 0 ); + if ( silent ) + memset( src, 0, samples_src * channels_src * sizeof( int16_t ) ); + + if ( channels_src > 6 ) + channels_src = 0; + if ( channels_dest > 6 ) + channels_dest = 0; + if ( samples_src > 4000 ) + samples_src = 0; + if ( samples_dest > 4000 ) + samples_dest = 0; + + // determine number of samples to process + *samples = samples_src < samples_dest ? samples_src : samples_dest; + *channels = channels_src < channels_dest ? channels_src : channels_dest; + *buffer = dest; + *frequency = frequency_dest; + + // Compute a smooth ramp over start to end + float weight = weight_start; + float weight_step = ( weight_end - weight_start ) / *samples; + + if ( src == dest ) + { + *samples = samples_src; + *channels = channels_src; + *buffer = src; + *frequency = frequency_src; + return ret; + } + + // Mixdown + for ( i = 0; i < *samples; i++ ) + { + for ( j = 0; j < *channels; j++ ) + { + if ( j < channels_dest ) + d = (double) dest[ i * channels_dest + j ]; + if ( j < channels_src ) + s = (double) src[ i * channels_src + j ]; + dest[ i * channels_dest + j ] = s * weight + d * ( 1.0 - weight ); + } + weight += weight_step; + } + + return ret; +} + +// Replacement for broken mlt_frame_audio_mix - this filter uses an inline low pass filter +// to allow mixing without volume hacking +int mlt_frame_combine_audio( mlt_frame this, mlt_frame that, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + int ret = 0; + int16_t *src, *dest; + int frequency_src = *frequency, frequency_dest = *frequency; + int channels_src = *channels, channels_dest = *channels; + int samples_src = *samples, samples_dest = *samples; + int i, j; + double vp[ 6 ]; + double b_weight = 1.0; + + if ( mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "meta.mixdown" ) ) + b_weight = 1.0 - mlt_properties_get_double( MLT_FRAME_PROPERTIES( this ), "meta.volume" ); + + mlt_frame_get_audio( that, &src, format, &frequency_src, &channels_src, &samples_src ); + mlt_frame_get_audio( this, &dest, format, &frequency_dest, &channels_dest, &samples_dest ); + + int silent = mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "silent_audio" ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( this ), "silent_audio", 0 ); + if ( silent ) + memset( dest, 0, samples_dest * channels_dest * sizeof( int16_t ) ); + + silent = mlt_properties_get_int( MLT_FRAME_PROPERTIES( that ), "silent_audio" ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( that ), "silent_audio", 0 ); + if ( silent ) + memset( src, 0, samples_src * channels_src * sizeof( int16_t ) ); + + if ( src == dest ) + { + *samples = samples_src; + *channels = channels_src; + *buffer = src; + *frequency = frequency_src; + return ret; + } + + // determine number of samples to process + *samples = samples_src < samples_dest ? samples_src : samples_dest; + *channels = channels_src < channels_dest ? channels_src : channels_dest; + *buffer = dest; + *frequency = frequency_dest; + + for ( j = 0; j < *channels; j++ ) + vp[ j ] = ( double )dest[ j ]; + + double Fc = 0.5; + double B = exp(-2.0 * M_PI * Fc); + double A = 1.0 - B; + double v; + + for ( i = 0; i < *samples; i++ ) + { + for ( j = 0; j < *channels; j++ ) + { + v = ( double )( b_weight * dest[ i * channels_dest + j ] + src[ i * channels_src + j ] ); + v = v < -32767 ? -32767 : v > 32768 ? 32768 : v; + vp[ j ] = dest[ i * channels_dest + j ] = ( int16_t )( v * A + vp[ j ] * B ); + } + } + + return ret; +} + +/* Will this break when mlt_position is converted to double? -Zach */ +int mlt_sample_calculator( float fps, int frequency, int64_t position ) +{ + int samples = 0; + + if ( ( int )( fps * 100 ) == 2997 ) + { + samples = frequency / 30; + + switch ( frequency ) + { + case 48000: + if ( position % 5 != 0 ) + samples += 2; + break; + case 44100: + if ( position % 300 == 0 ) + samples = 1471; + else if ( position % 30 == 0 ) + samples = 1470; + else if ( position % 2 == 0 ) + samples = 1472; + else + samples = 1471; + break; + case 32000: + if ( position % 30 == 0 ) + samples = 1068; + else if ( position % 29 == 0 ) + samples = 1067; + else if ( position % 4 == 2 ) + samples = 1067; + else + samples = 1068; + break; + default: + samples = 0; + } + } + else if ( fps != 0 ) + { + samples = frequency / fps; + } + + return samples; +} + +int64_t mlt_sample_calculator_to_now( float fps, int frequency, int64_t frame ) +{ + int64_t samples = 0; + + // TODO: Correct rules for NTSC and drop the * 100 hack + if ( ( int )( fps * 100 ) == 2997 ) + { + samples = ( ( double )( frame * frequency ) / 30 ); + switch( frequency ) + { + case 48000: + samples += 2 * ( frame / 5 ); + break; + case 44100: + samples += frame + ( frame / 2 ) - ( frame / 30 ) + ( frame / 300 ); + break; + case 32000: + samples += ( 2 * frame ) - ( frame / 4 ) - ( frame / 29 ); + break; + } + } + else if ( fps != 0 ) + { + samples = ( ( frame * frequency ) / ( int )fps ); + } + + return samples; +} diff --git a/src/framework/mlt_frame.h b/src/framework/mlt_frame.h new file mode 100644 index 0000000..2cb5ab7 --- /dev/null +++ b/src/framework/mlt_frame.h @@ -0,0 +1,118 @@ +/* + * mlt_frame.h -- interface for all frame classes + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_FRAME_H_ +#define _MLT_FRAME_H_ + +#include "mlt_properties.h" +#include "mlt_deque.h" + +typedef int ( *mlt_get_image )( mlt_frame self, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ); +typedef int ( *mlt_get_audio )( mlt_frame self, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ); + +struct mlt_frame_s +{ + /* We're extending properties here */ + struct mlt_properties_s parent; + + /* Virtual methods */ + uint8_t * ( *get_alpha_mask )( mlt_frame self ); + + /* Private properties */ + mlt_deque stack_image; + mlt_deque stack_audio; + mlt_deque stack_service; +}; + +#define MLT_FRAME_PROPERTIES( frame ) ( &( frame )->parent ) +#define MLT_FRAME_SERVICE_STACK( frame ) ( ( frame )->stack_service ) +#define MLT_FRAME_IMAGE_STACK( frame ) ( ( frame )->stack_image ) +#define MLT_FRAME_AUDIO_STACK( frame ) ( ( frame )->stack_audio ) + +extern mlt_frame mlt_frame_init( ); +extern mlt_properties mlt_frame_properties( mlt_frame self ); +extern int mlt_frame_is_test_card( mlt_frame self ); +extern int mlt_frame_is_test_audio( mlt_frame self ); +extern double mlt_frame_get_aspect_ratio( mlt_frame self ); +extern int mlt_frame_set_aspect_ratio( mlt_frame self, double value ); +extern mlt_position mlt_frame_get_position( mlt_frame self ); +extern int mlt_frame_set_position( mlt_frame self, mlt_position value ); +extern void mlt_frame_replace_image( mlt_frame self, uint8_t *image, mlt_image_format format, int width, int height ); +extern int mlt_frame_get_image( mlt_frame self, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ); +extern uint8_t *mlt_frame_get_alpha_mask( mlt_frame self ); +extern int mlt_frame_get_audio( mlt_frame self, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ); +extern unsigned char *mlt_frame_get_waveform( mlt_frame self, int w, int h ); +extern int mlt_frame_push_get_image( mlt_frame self, mlt_get_image get_image ); +extern mlt_get_image mlt_frame_pop_get_image( mlt_frame self ); +extern int mlt_frame_push_frame( mlt_frame self, mlt_frame that ); +extern mlt_frame mlt_frame_pop_frame( mlt_frame self ); +extern int mlt_frame_push_service( mlt_frame self, void *that ); +extern void *mlt_frame_pop_service( mlt_frame self ); +extern int mlt_frame_push_service_int( mlt_frame self, int that ); +extern int mlt_frame_pop_service_int( mlt_frame self ); +extern int mlt_frame_push_audio( mlt_frame self, void *that ); +extern void *mlt_frame_pop_audio( mlt_frame self ); +extern mlt_deque mlt_frame_service_stack( mlt_frame self ); +extern mlt_producer mlt_frame_get_original_producer( mlt_frame self ); +extern void mlt_frame_close( mlt_frame self ); + +/* convenience functions */ +extern int mlt_convert_yuv422_to_rgb24a( uint8_t *yuv, uint8_t *rgba, unsigned int total ); +extern int mlt_convert_rgb24a_to_yuv422( uint8_t *rgba, int width, int height, int stride, uint8_t *yuv, uint8_t *alpha ); +extern int mlt_convert_rgb24_to_yuv422( uint8_t *rgb, int width, int height, int stride, uint8_t *yuv ); +extern int mlt_convert_bgr24a_to_yuv422( uint8_t *rgba, int width, int height, int stride, uint8_t *yuv, uint8_t *alpha ); +extern int mlt_convert_argb_to_yuv422( uint8_t *rgba, int width, int height, int stride, uint8_t *yuv, uint8_t *alpha ); +extern int mlt_convert_bgr24_to_yuv422( uint8_t *rgb, int width, int height, int stride, uint8_t *yuv ); +extern int mlt_convert_yuv420p_to_yuv422( uint8_t *yuv420p, int width, int height, int stride, uint8_t *yuv ); +extern uint8_t *mlt_frame_resize_yuv422( mlt_frame self, int owidth, int oheight ); +extern uint8_t *mlt_frame_rescale_yuv422( mlt_frame self, int owidth, int oheight ); +extern void mlt_resize_yuv422( uint8_t *output, int owidth, int oheight, uint8_t *input, int iwidth, int iheight ); +extern int mlt_frame_mix_audio( mlt_frame self, mlt_frame that, float weight_start, float weight_end, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ); +extern int mlt_frame_combine_audio( mlt_frame self, mlt_frame that, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ); +extern int mlt_sample_calculator( float fps, int frequency, int64_t position ); +extern int64_t mlt_sample_calculator_to_now( float fps, int frequency, int64_t position ); + +/* this macro scales rgb into the yuv gamut, y is scaled by 219/255 and uv by 224/255 */ +#define RGB2YUV(r, g, b, y, u, v)\ + y = ((263*r + 516*g + 100*b) >> 10) + 16;\ + u = ((-152*r - 298*g + 450*b) >> 10) + 128;\ + v = ((450*r - 377*g - 73*b) >> 10) + 128; + +/* this macro assumes the user has already scaled their rgb down into the broadcast limits */ +#define RGB2YUV_UNSCALED(r, g, b, y, u, v)\ + y = (299*r + 587*g + 114*b) >> 10;\ + u = ((-169*r - 331*g + 500*b) >> 10) + 128;\ + v = ((500*r - 419*g - 81*b) >> 10) + 128;\ + y = y < 16 ? 16 : y;\ + u = u < 16 ? 16 : u;\ + v = v < 16 ? 16 : v;\ + y = y > 235 ? 235 : y;\ + u = u > 240 ? 240 : u;\ + v = v > 240 ? 240 : v + +#define YUV2RGB( y, u, v, r, g, b ) \ + r = ((1192 * ( y - 16 ) + 1634 * ( v - 128 ) ) >> 10 ); \ + g = ((1192 * ( y - 16 ) - 832 * ( v - 128 ) - 400 * ( u - 128 ) ) >> 10 ); \ + b = ((1192 * ( y - 16 ) + 2066 * ( u - 128 ) ) >> 10 ); \ + r = r < 0 ? 0 : r > 255 ? 255 : r; \ + g = g < 0 ? 0 : g > 255 ? 255 : g; \ + b = b < 0 ? 0 : b > 255 ? 255 : b; + +#endif diff --git a/src/framework/mlt_geometry.c b/src/framework/mlt_geometry.c new file mode 100644 index 0000000..e7819bf --- /dev/null +++ b/src/framework/mlt_geometry.c @@ -0,0 +1,700 @@ +/* + * mlt_geometry.c -- provides the geometry API + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "mlt_geometry.h" +#include "mlt_tokeniser.h" +#include "mlt_factory.h" +#include "mlt_profile.h" + +#include +#include +#include +#include + +typedef struct geometry_item_s +{ + struct mlt_geometry_item_s data; + struct geometry_item_s *next, *prev; +} +*geometry_item; + +typedef struct +{ + char *data; + int length; + int nw; + int nh; + geometry_item item; +} +geometry_s, *geometry; + +// Create a new geometry structure +mlt_geometry mlt_geometry_init( ) +{ + mlt_geometry this = calloc( 1, sizeof( struct mlt_geometry_s ) ); + if ( this != NULL ) + { + this->local = calloc( 1, sizeof( geometry_s ) ); + if ( this->local != NULL ) + { + geometry self = this->local; + self->nw = mlt_profile_get()->width; + self->nh = mlt_profile_get()->height; + } + else + { + free( this ); + this = NULL; + } + } + return this; +} + +/** A linear step +*/ + +static inline double linearstep( double start, double end, double position, int length ) +{ + double o = ( end - start ) / length; + return start + position * o; +} + +static void mlt_geometry_virtual_refresh( mlt_geometry this ) +{ + geometry self = this->local; + + // Parse of all items to ensure unspecified keys are calculated correctly + if ( self->item != NULL ) + { + int i = 0; + for ( i = 0; i < 5; i ++ ) + { + geometry_item current = self->item; + while( current != NULL ) + { + int fixed = current->data.f[ i ]; + if ( !fixed ) + { + geometry_item prev = current->prev; + geometry_item next = current->next; + + double prev_value = 0; + double next_value = 0; + double value = 0; + + while( prev != NULL && !prev->data.f[ i ] ) prev = prev->prev; + while( next != NULL && !next->data.f[ i ] ) next = next->next; + + switch( i ) + { + case 0: + if ( prev ) prev_value = prev->data.x; + if ( next ) next_value = next->data.x; + break; + case 1: + if ( prev ) prev_value = prev->data.y; + if ( next ) next_value = next->data.y; + break; + case 2: + if ( prev ) prev_value = prev->data.w; + if ( next ) next_value = next->data.w; + break; + case 3: + if ( prev ) prev_value = prev->data.h; + if ( next ) next_value = next->data.h; + break; + case 4: + if ( prev ) prev_value = prev->data.mix; + if ( next ) next_value = next->data.mix; + break; + } + + // This should never happen + if ( prev == NULL ) + current->data.f[ i ] = 1; + else if ( next == NULL ) + value = prev_value; + else + value = linearstep( prev_value, next_value, current->data.frame - prev->data.frame, next->data.frame - prev->data.frame ); + + switch( i ) + { + case 0: current->data.x = value; break; + case 1: current->data.y = value; break; + case 2: current->data.w = value; break; + case 3: current->data.h = value; break; + case 4: current->data.mix = value; break; + } + } + + // Move to the next item + current = current->next; + } + } + } +} + +static int mlt_geometry_drop( mlt_geometry this, geometry_item item ) +{ + geometry self = this->local; + + if ( item == self->item ) + { + self->item = item->next; + if ( self->item != NULL ) + self->item->prev = NULL; + // To ensure correct seeding, ensure all values are fixed + if ( self->item != NULL ) + { + self->item->data.f[0] = 1; + self->item->data.f[1] = 1; + self->item->data.f[2] = 1; + self->item->data.f[3] = 1; + self->item->data.f[4] = 1; + } + } + else if ( item->next != NULL && item->prev != NULL ) + { + item->prev->next = item->next; + item->next->prev = item->prev; + } + else if ( item->next != NULL ) + { + item->next->prev = item->prev; + } + else if ( item->prev != NULL ) + { + item->prev->next = item->next; + } + + free( item ); + + return 0; +} + +static void mlt_geometry_clean( mlt_geometry this ) +{ + geometry self = this->local; + free( self->data ); + self->data = NULL; + while( self->item ) + mlt_geometry_drop( this, self->item ); +} + +// Parse the geometry specification for a given length and normalised width/height (-1 for default) +// data is constructed as: [frame=]X,Y:WxH[:mix][;[frame=]X,Y:WxH[:mix]]* +// and X, Y, W and H can have trailing % chars to indicate percentage of normalised size +int mlt_geometry_parse( mlt_geometry this, char *data, int length, int nw, int nh ) +{ + int i = 0; + + // Create a tokeniser + mlt_tokeniser tokens = mlt_tokeniser_init( ); + + // Get the local/private structure + geometry self = this->local; + + // Clean the existing geometry + mlt_geometry_clean( this ); + + // Update the info on the data + if ( length != -1 ) + self->length = length; + if ( nw != -1 ) + self->nw = nw; + if ( nh != -1 ) + self->nh = nh; + if ( data != NULL ) + self->data = strdup( data ); + + // Tokenise + if ( data != NULL ) + mlt_tokeniser_parse_new( tokens, data, ";" ); + + // Iterate through each token + for ( i = 0; i < mlt_tokeniser_count( tokens ); i ++ ) + { + struct mlt_geometry_item_s item; + char *value = mlt_tokeniser_get_string( tokens, i ); + + // Set item to 0 + memset( &item, 0, sizeof( struct mlt_geometry_item_s ) ); + + // Now parse the item + mlt_geometry_parse_item( this, &item, value ); + + // Now insert into place + mlt_geometry_insert( this, &item ); + } + + // Remove the tokeniser + mlt_tokeniser_close( tokens ); + + // ??? + return 0; +} + +// Conditionally refresh in case of a change +int mlt_geometry_refresh( mlt_geometry this, char *data, int length, int nw, int nh ) +{ + geometry self = this->local; + int changed = ( length != -1 && length != self->length ); + changed = changed || ( nw != -1 && nw != self->nw ); + changed = changed || ( nh != -1 && nh != self->nh ); + changed = changed || ( data != NULL && ( self->data == NULL || strcmp( data, self->data ) ) ); + if ( changed ) + return mlt_geometry_parse( this, data, length, nw, nh ); + return -1; +} + +int mlt_geometry_get_length( mlt_geometry this ) +{ + // Get the local/private structure + geometry self = this->local; + + // return the length + return self->length; +} + +void mlt_geometry_set_length( mlt_geometry this, int length ) +{ + // Get the local/private structure + geometry self = this->local; + + // set the length + self->length = length; +} + +int mlt_geometry_parse_item( mlt_geometry this, mlt_geometry_item item, char *value ) +{ + int ret = 0; + + // Get the local/private structure + geometry self = this->local; + + if ( value != NULL && strcmp( value, "" ) ) + { + char *p = strchr( value, '=' ); + int count = 0; + double temp; + + // Determine if a position has been specified + if ( p != NULL ) + { + temp = atof( value ); + if ( temp > -1 && temp < 1 ) + item->frame = temp * self->length; + else + item->frame = temp; + value = p + 1; + } + + // Special case - frame < 0 + if ( item->frame < 0 ) + item->frame += self->length; + + // Obtain the current value at this position - this allows new + // frames to be created which don't specify all values + mlt_geometry_fetch( this, item, item->frame ); + + // Special case - when an empty string is specified, all values are fixed + // TODO: Check if this is logical - it's convenient, but it's also odd... + if ( !*value ) + { + item->f[0] = 1; + item->f[1] = 1; + item->f[2] = 1; + item->f[3] = 1; + item->f[4] = 1; + } + + // Iterate through the remainder of value + while( *value ) + { + // Get the value + temp = strtod( value, &p ); + + // Check if a value was specified + if ( p != value ) + { + // Handle the % case + if ( *p == '%' ) + { + if ( count == 0 || count == 2 ) + temp *= self->nw / 100.0; + else if ( count == 1 || count == 3 ) + temp *= self->nh / 100.0; + p ++; + } + + // Special case - distort token + if ( *p == '!' || *p == '*' ) + { + p ++; + item->distort = 1; + } + + // Actually, we don't care about the delimiter at all.. + if ( *p ) p ++; + + // Assign to the item + switch( count ) + { + case 0: item->x = temp; item->f[0] = 1; break; + case 1: item->y = temp; item->f[1] = 1; break; + case 2: item->w = temp; item->f[2] = 1; break; + case 3: item->h = temp; item->f[3] = 1; break; + case 4: item->mix = temp; item->f[4] = 1; break; + } + } + else + { + p ++; + } + + // Update the value pointer + value = p; + count ++; + } + } + else + { + ret = 1; + } + + return ret; +} + +// Fetch a geometry item for an absolute position +int mlt_geometry_fetch( mlt_geometry this, mlt_geometry_item item, float position ) +{ + // Get the local geometry + geometry self = this->local; + + // Need to find the nearest key to the position specifed + geometry_item key = self->item; + + // Iterate through the keys until we reach last or have + while( key != NULL && key->next != NULL && position >= key->next->data.frame ) + key = key->next; + + if ( key != NULL ) + { + // Position is situated before the first key - all zeroes + if ( position < key->data.frame ) + { + memset( item, 0, sizeof( struct mlt_geometry_item_s ) ); + item->mix = 100; + } + // Position is a key itself - no iterpolation need + else if ( position == key->data.frame ) + { + memcpy( item, &key->data, sizeof( struct mlt_geometry_item_s ) ); + } + // Position is after the last key - no interpolation, but not a key frame + else if ( key->next == NULL ) + { + memcpy( item, &key->data, sizeof( struct mlt_geometry_item_s ) ); + item->key = 0; + item->f[ 0 ] = 0; + item->f[ 1 ] = 0; + item->f[ 2 ] = 0; + item->f[ 3 ] = 0; + item->f[ 4 ] = 0; + } + // Interpolation is needed - position > key and there is a following key + else + { + item->key = 0; + item->frame = position; + position -= key->data.frame; + item->x = linearstep( key->data.x, key->next->data.x, position, key->next->data.frame - key->data.frame ); + item->y = linearstep( key->data.y, key->next->data.y, position, key->next->data.frame - key->data.frame ); + item->w = linearstep( key->data.w, key->next->data.w, position, key->next->data.frame - key->data.frame ); + item->h = linearstep( key->data.h, key->next->data.h, position, key->next->data.frame - key->data.frame ); + item->mix = linearstep( key->data.mix, key->next->data.mix, position, key->next->data.frame - key->data.frame ); + item->distort = key->data.distort; + position += key->data.frame; + } + + item->frame = position; + } + else + { + memset( item, 0, sizeof( struct mlt_geometry_item_s ) ); + item->frame = position; + item->mix = 100; + } + + return key == NULL; +} + +// Specify a geometry item at an absolute position +int mlt_geometry_insert( mlt_geometry this, mlt_geometry_item item ) +{ + // Get the local/private geometry structure + geometry self = this->local; + + // Create a new local item (this may be removed if a key already exists at this position) + geometry_item new = calloc( 1, sizeof( struct geometry_item_s ) ); + memcpy( &new->data, item, sizeof( struct mlt_geometry_item_s ) ); + new->data.key = 1; + + // Determine if we need to insert or append to the list, or if it's a new list + if ( self->item != NULL ) + { + // Get the first item + geometry_item place = self->item; + + // Locate an existing nearby item + while ( place->next != NULL && item->frame > place->data.frame ) + place = place->next; + + if ( item->frame < place->data.frame ) + { + if ( place == self->item ) + self->item = new; + if ( place->prev ) + place->prev->next = new; + new->next = place; + new->prev = place->prev; + place->prev = new; + } + else if ( item->frame > place->data.frame ) + { + if ( place->next ) + place->next->prev = new; + new->next = place->next; + new->prev = place; + place->next = new; + } + else + { + memcpy( &place->data, &new->data, sizeof( struct mlt_geometry_item_s ) ); + free( new ); + } + } + else + { + // Set the first item + self->item = new; + + // To ensure correct seeding, ensure all values are fixed + self->item->data.f[0] = 1; + self->item->data.f[1] = 1; + self->item->data.f[2] = 1; + self->item->data.f[3] = 1; + self->item->data.f[4] = 1; + } + + // Refresh all geometries + mlt_geometry_virtual_refresh( this ); + + // TODO: Error checking + return 0; +} + +// Remove the key at the specified position +int mlt_geometry_remove( mlt_geometry this, int position ) +{ + int ret = 1; + + // Get the local/private geometry structure + geometry self = this->local; + + // Get the first item + geometry_item place = self->item; + + while( place != NULL && position != place->data.frame ) + place = place->next; + + if ( place != NULL && position == place->data.frame ) + ret = mlt_geometry_drop( this, place ); + + // Refresh all geometries + mlt_geometry_virtual_refresh( this ); + + return ret; +} + +// Get the key at the position or the next following +int mlt_geometry_next_key( mlt_geometry this, mlt_geometry_item item, int position ) +{ + // Get the local/private geometry structure + geometry self = this->local; + + // Get the first item + geometry_item place = self->item; + + while( place != NULL && position > place->data.frame ) + place = place->next; + + if ( place != NULL ) + memcpy( item, &place->data, sizeof( struct mlt_geometry_item_s ) ); + + return place == NULL; +} + +// Get the key at the position or the previous key +int mlt_geometry_prev_key( mlt_geometry this, mlt_geometry_item item, int position ) +{ + // Get the local/private geometry structure + geometry self = this->local; + + // Get the first item + geometry_item place = self->item; + + while( place != NULL && place->next != NULL && position >= place->next->data.frame ) + place = place->next; + + if ( place != NULL ) + memcpy( item, &place->data, sizeof( struct mlt_geometry_item_s ) ); + + return place == NULL; +} + +char *mlt_geometry_serialise_cut( mlt_geometry this, int in, int out ) +{ + geometry self = this->local; + struct mlt_geometry_item_s item; + char *ret = malloc( 1000 ); + int used = 0; + int size = 1000; + + if ( in == -1 ) + in = 0; + if ( out == -1 ) + out = mlt_geometry_get_length( this ); + + if ( ret != NULL ) + { + char temp[ 100 ]; + + strcpy( ret, "" ); + + item.frame = in; + + while( 1 ) + { + strcpy( temp, "" ); + + // If it's the first frame, then it's not necessarily a key + if ( item.frame == in ) + { + if ( mlt_geometry_fetch( this, &item, item.frame ) ) + break; + + // If the first key is larger than the current position + // then do nothing here + if ( self->item->data.frame > item.frame ) + { + item.frame ++; + continue; + } + + // To ensure correct seeding, ensure all values are fixed + item.f[0] = 1; + item.f[1] = 1; + item.f[2] = 1; + item.f[3] = 1; + item.f[4] = 1; + } + // Typically, we move from key to key + else if ( item.frame < out ) + { + if ( mlt_geometry_next_key( this, &item, item.frame ) ) + break; + + // Special case - crop at the out point + if ( item.frame > out ) + mlt_geometry_fetch( this, &item, out ); + } + // We've handled the last key + else + { + break; + } + + if ( item.frame - in != 0 ) + sprintf( temp, "%d=", item.frame - in ); + + if ( item.f[0] ) + sprintf( temp + strlen( temp ), "%.0f", item.x ); + strcat( temp, "," ); + if ( item.f[1] ) + sprintf( temp + strlen( temp ), "%.0f", item.y ); + strcat( temp, ":" ); + if ( item.f[2] ) + sprintf( temp + strlen( temp ), "%.0f", item.w ); + strcat( temp, "x" ); + if ( item.f[3] ) + sprintf( temp + strlen( temp ), "%.0f", item.h ); + if ( item.f[4] ) + sprintf( temp + strlen( temp ), ":%.0f", item.mix ); + + if ( used + strlen( temp ) > size ) + { + size += 1000; + ret = realloc( ret, size ); + } + + if ( ret != NULL && used != 0 ) + { + used ++; + strcat( ret, ";" ); + } + if ( ret != NULL ) + { + used += strlen( temp ); + strcat( ret, temp ); + } + + item.frame ++; + } + } + + return ret; +} + +// Serialise the current geometry +char *mlt_geometry_serialise( mlt_geometry this ) +{ + geometry self = this->local; + char *ret = mlt_geometry_serialise_cut( this, 0, self->length ); + if ( ret ) + { + free( self->data ); + self->data = ret; + } + return ret; +} + +// Close the geometry +void mlt_geometry_close( mlt_geometry this ) +{ + if ( this != NULL ) + { + mlt_geometry_clean( this ); + free( this->local ); + free( this ); + } +} + + diff --git a/src/framework/mlt_geometry.h b/src/framework/mlt_geometry.h new file mode 100644 index 0000000..797f6a0 --- /dev/null +++ b/src/framework/mlt_geometry.h @@ -0,0 +1,73 @@ +/* + * mlt_geometry.h -- provides the geometry API + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_GEOMETRY_H +#define _MLT_GEOMETRY_H + +#include "mlt_types.h" + +struct mlt_geometry_item_s +{ + /* Will be 1 when this is a key frame */ + int key; + /* The actual frame this corresponds to */ + int frame; + /* Distort */ + int distort; + /* x,y are upper left */ + float x, y, w, h, mix; + /* Indicates which values are fixed */ + int f[ 5 ]; +}; + +struct mlt_geometry_s +{ + void *local; +}; + +/* Create a new geometry structure */ +extern mlt_geometry mlt_geometry_init( ); +/* Parse the geometry specification for a given length and normalised width/height (-1 for default) */ +extern int mlt_geometry_parse( mlt_geometry self, char *data, int length, int nw, int nh ); +/* Conditionally refresh the geometry if it's modified */ +extern int mlt_geometry_refresh( mlt_geometry self, char *data, int length, int nw, int nh ); +/* Get and set the length */ +extern int mlt_geometry_get_length( mlt_geometry self ); +extern void mlt_geometry_set_length( mlt_geometry self, int length ); +/* Parse an item - doesn't affect the geometry itself but uses current information for evaluation */ +/* (item->frame should be specified if not included in the data itself) */ +extern int mlt_geometry_parse_item( mlt_geometry self, mlt_geometry_item item, char *data ); +/* Fetch a geometry item for an absolute position */ +extern int mlt_geometry_fetch( mlt_geometry self, mlt_geometry_item item, float position ); +/* Specify a geometry item at an absolute position */ +extern int mlt_geometry_insert( mlt_geometry self, mlt_geometry_item item ); +/* Remove the key at the specified position */ +extern int mlt_geometry_remove( mlt_geometry self, int position ); +/* Get the key at the position or the next following */ +extern int mlt_geometry_next_key( mlt_geometry self, mlt_geometry_item item, int position ); +extern int mlt_geometry_prev_key( mlt_geometry self, mlt_geometry_item item, int position ); +/* Serialise the current geometry */ +extern char *mlt_geometry_serialise_cut( mlt_geometry self, int in, int out ); +extern char *mlt_geometry_serialise( mlt_geometry self ); +/* Close the geometry */ +extern void mlt_geometry_close( mlt_geometry self ); + +#endif + diff --git a/src/framework/mlt_multitrack.c b/src/framework/mlt_multitrack.c new file mode 100644 index 0000000..1a7bcd2 --- /dev/null +++ b/src/framework/mlt_multitrack.c @@ -0,0 +1,436 @@ +/* + * mlt_multitrack.c -- multitrack service class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "mlt_multitrack.h" +#include "mlt_playlist.h" +#include "mlt_frame.h" + +#include +#include + +/** Forward reference. +*/ + +static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int index ); + +/** Constructor. +*/ + +mlt_multitrack mlt_multitrack_init( ) +{ + // Allocate the multitrack object + mlt_multitrack this = calloc( sizeof( struct mlt_multitrack_s ), 1 ); + + if ( this != NULL ) + { + mlt_producer producer = &this->parent; + if ( mlt_producer_init( producer, this ) == 0 ) + { + mlt_properties properties = MLT_MULTITRACK_PROPERTIES( this ); + producer->get_frame = producer_get_frame; + mlt_properties_set_data( properties, "multitrack", this, 0, NULL, NULL ); + mlt_properties_set( properties, "log_id", "multitrack" ); + mlt_properties_set( properties, "resource", "" ); + mlt_properties_set_int( properties, "in", 0 ); + mlt_properties_set_int( properties, "out", -1 ); + mlt_properties_set_int( properties, "length", 0 ); + producer->close = ( mlt_destructor )mlt_multitrack_close; + } + else + { + free( this ); + this = NULL; + } + } + + return this; +} + +/** Get the producer associated to this multitrack. +*/ + +mlt_producer mlt_multitrack_producer( mlt_multitrack this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Get the service associated this multitrack. +*/ + +mlt_service mlt_multitrack_service( mlt_multitrack this ) +{ + return MLT_MULTITRACK_SERVICE( this ); +} + +/** Get the properties associated this multitrack. +*/ + +mlt_properties mlt_multitrack_properties( mlt_multitrack this ) +{ + return MLT_MULTITRACK_PROPERTIES( this ); +} + +/** Initialise position related information. +*/ + +void mlt_multitrack_refresh( mlt_multitrack this ) +{ + int i = 0; + + // Obtain the properties of this multitrack + mlt_properties properties = MLT_MULTITRACK_PROPERTIES( this ); + + // We need to ensure that the multitrack reports the longest track as its length + mlt_position length = 0; + + // Obtain stats on all connected services + for ( i = 0; i < this->count; i ++ ) + { + // Get the producer from this index + mlt_track track = this->list[ i ]; + mlt_producer producer = track->producer; + + // If it's allocated then, update our stats + if ( producer != NULL ) + { + // If we have more than 1 track, we must be in continue mode + if ( this->count > 1 ) + mlt_properties_set( MLT_PRODUCER_PROPERTIES( producer ), "eof", "continue" ); + + // Determine the longest length + //if ( !mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( producer ), "hide" ) ) + length = mlt_producer_get_playtime( producer ) > length ? mlt_producer_get_playtime( producer ) : length; + } + } + + // Update multitrack properties now - we'll not destroy the in point here + mlt_events_block( properties, properties ); + mlt_properties_set_position( properties, "length", length ); + mlt_events_unblock( properties, properties ); + mlt_properties_set_position( properties, "out", length - 1 ); +} + +/** Listener for producers on the playlist. +*/ + +static void mlt_multitrack_listener( mlt_producer producer, mlt_multitrack this ) +{ + mlt_multitrack_refresh( this ); +} + +/** Connect a producer to a given track. + + Note that any producer can be connected here, but see special case treatment + of playlist in clip point determination below. +*/ + +int mlt_multitrack_connect( mlt_multitrack this, mlt_producer producer, int track ) +{ + // Connect to the producer to ourselves at the specified track + int result = mlt_service_connect_producer( MLT_MULTITRACK_SERVICE( this ), MLT_PRODUCER_SERVICE( producer ), track ); + + if ( result == 0 ) + { + // Resize the producer list if need be + if ( track >= this->size ) + { + int i; + this->list = realloc( this->list, ( track + 10 ) * sizeof( mlt_track ) ); + for ( i = this->size; i < track + 10; i ++ ) + this->list[ i ] = NULL; + this->size = track + 10; + } + + if ( this->list[ track ] != NULL ) + { + mlt_event_close( this->list[ track ]->event ); + mlt_producer_close( this->list[ track ]->producer ); + } + else + { + this->list[ track ] = malloc( sizeof( struct mlt_track_s ) ); + } + + // Assign the track in our list here + this->list[ track ]->producer = producer; + this->list[ track ]->event = mlt_events_listen( MLT_PRODUCER_PROPERTIES( producer ), this, + "producer-changed", ( mlt_listener )mlt_multitrack_listener ); + mlt_properties_inc_ref( MLT_PRODUCER_PROPERTIES( producer ) ); + mlt_event_inc_ref( this->list[ track ]->event ); + + // Increment the track count if need be + if ( track >= this->count ) + this->count = track + 1; + + // Refresh our stats + mlt_multitrack_refresh( this ); + } + + return result; +} + +/** Get the number of tracks. +*/ + +int mlt_multitrack_count( mlt_multitrack this ) +{ + return this->count; +} + +/** Get an individual track as a producer. +*/ + +mlt_producer mlt_multitrack_track( mlt_multitrack this, int track ) +{ + mlt_producer producer = NULL; + + if ( this->list != NULL && track < this->count ) + producer = this->list[ track ]->producer; + + return producer; +} + +static int position_compare( const void *p1, const void *p2 ) +{ + return *( mlt_position * )p1 - *( mlt_position * )p2; +} + +static int add_unique( mlt_position *array, int size, mlt_position position ) +{ + int i = 0; + for ( i = 0; i < size; i ++ ) + if ( array[ i ] == position ) + break; + if ( i == size ) + array[ size ++ ] = position; + return size; +} + +/** Determine the clip point. + + Special case here: a 'producer' has no concept of multiple clips - only the + playlist and multitrack producers have clip functionality. Further to that a + multitrack determines clip information from any connected tracks that happen + to be playlists. + + Additionally, it must locate clips in the correct order, for example, consider + the following track arrangement: + + playlist1 |0.0 |b0.0 |0.1 |0.1 |0.2 | + playlist2 |b1.0 |1.0 |b1.1 |1.1 | + + Note - b clips represent blanks. They are also reported as clip positions. + + When extracting clip positions from these playlists, we should get a sequence of: + + 0.0, 1.0, b0.0, 0.1, b1.1, 1.1, 0.1, 0.2, [out of playlist2], [out of playlist1] +*/ + +mlt_position mlt_multitrack_clip( mlt_multitrack this, mlt_whence whence, int index ) +{ + mlt_position position = 0; + int i = 0; + int j = 0; + mlt_position *map = malloc( 1000 * sizeof( mlt_position ) ); + int count = 0; + + for ( i = 0; i < this->count; i ++ ) + { + // Get the producer for this track + mlt_producer producer = this->list[ i ]->producer; + + // If it's assigned and not a hidden track + if ( producer != NULL ) + { + // Get the properties of this producer + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + + // Determine if it's a playlist + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + + // Special case consideration of playlists + if ( playlist != NULL ) + { + for ( j = 0; j < mlt_playlist_count( playlist ); j ++ ) + count = add_unique( map, count, mlt_playlist_clip( playlist, mlt_whence_relative_start, j ) ); + count = add_unique( map, count, mlt_producer_get_out( producer ) + 1 ); + } + else + { + count = add_unique( map, count, 0 ); + count = add_unique( map, count, mlt_producer_get_out( producer ) + 1 ); + } + } + } + + // Now sort the map + qsort( map, count, sizeof( mlt_position ), position_compare ); + + // Now locate the requested index + switch( whence ) + { + case mlt_whence_relative_start: + if ( index < count ) + position = map[ index ]; + else + position = map[ count - 1 ]; + break; + + case mlt_whence_relative_current: + position = mlt_producer_position( MLT_MULTITRACK_PRODUCER( this ) ); + for ( i = 0; i < count - 2; i ++ ) + if ( position >= map[ i ] && position < map[ i + 1 ] ) + break; + index += i; + if ( index >= 0 && index < count ) + position = map[ index ]; + else if ( index < 0 ) + position = map[ 0 ]; + else + position = map[ count - 1 ]; + break; + + case mlt_whence_relative_end: + if ( index < count ) + position = map[ count - index - 1 ]; + else + position = map[ 0 ]; + break; + } + + // Free the map + free( map ); + + return position; +} + +/** Get frame method. + + Special case here: The multitrack must be used in a conjunction with a downstream + tractor-type service, ie: + + Producer1 \ + Producer2 - multitrack - { filters/transitions } - tractor - consumer + Producer3 / + + The get_frame of a tractor pulls frames from it's connected service on all tracks and + will terminate as soon as it receives a test card with a last_track property. The + important case here is that the mulitrack does not move to the next frame until all + tracks have been pulled. + + Reasoning: In order to seek on a network such as above, the multitrack needs to ensure + that all producers are positioned on the same frame. It uses the 'last track' logic + to determine when to move to the next frame. + + Flaw: if a transition is configured to read from a b-track which happens to trigger + the last frame logic (ie: it's configured incorrectly), then things are going to go + out of sync. + + See playlist logic too. +*/ + +static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int index ) +{ + // Get the mutiltrack object + mlt_multitrack this = parent->child; + + // Check if we have a track for this index + if ( index < this->count && this->list[ index ] != NULL ) + { + // Get the producer for this track + mlt_producer producer = this->list[ index ]->producer; + + // Get the track hide property + int hide = mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( mlt_producer_cut_parent( producer ) ), "hide" ); + + // Obtain the current position + mlt_position position = mlt_producer_frame( parent ); + + // Get the parent properties + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( parent ); + + // Get the speed + double speed = mlt_properties_get_double( producer_properties, "_speed" ); + + // Make sure we're at the same point + mlt_producer_seek( producer, position ); + + // Get the frame from the producer + mlt_service_get_frame( MLT_PRODUCER_SERVICE( producer ), frame, 0 ); + + // Indicate speed of this producer + mlt_properties properties = MLT_FRAME_PROPERTIES( *frame ); + mlt_properties_set_double( properties, "_speed", speed ); + mlt_properties_set_position( properties, "_position", position ); + mlt_properties_set_int( properties, "hide", hide ); + } + else + { + // Generate a test frame + *frame = mlt_frame_init( ); + + // Update position on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_position( parent ) ); + + // Move on to the next frame + if ( index >= this->count ) + { + // Let tractor know if we've reached the end + mlt_properties_set_int( MLT_FRAME_PROPERTIES( *frame ), "last_track", 1 ); + + // Move to the next frame + mlt_producer_prepare_next( parent ); + } + } + + return 0; +} + +/** Close this instance. +*/ + +void mlt_multitrack_close( mlt_multitrack this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_MULTITRACK_PROPERTIES( this ) ) <= 0 ) + { + int i = 0; + for ( i = 0; i < this->count; i ++ ) + { + if ( this->list[ i ] != NULL ) + { + mlt_event_close( this->list[ i ]->event ); + mlt_producer_close( this->list[ i ]->producer ); + free( this->list[ i ] ); + } + } + + // Close the producer + this->parent.close = NULL; + mlt_producer_close( &this->parent ); + + // Free the list + free( this->list ); + + // Free the object + free( this ); + } +} diff --git a/src/framework/mlt_multitrack.h b/src/framework/mlt_multitrack.h new file mode 100644 index 0000000..95a5e04 --- /dev/null +++ b/src/framework/mlt_multitrack.h @@ -0,0 +1,65 @@ +/* + * mlt_multitrack.h -- multitrack service class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_MULITRACK_H_ +#define _MLT_MULITRACK_H_ + +#include "mlt_producer.h" + +/** Private definition. +*/ + +struct mlt_track_s +{ + mlt_producer producer; + mlt_event event; +}; + +typedef struct mlt_track_s *mlt_track; + +struct mlt_multitrack_s +{ + /* We're extending producer here */ + struct mlt_producer_s parent; + mlt_track *list; + int size; + int count; +}; + +/** Public final methods +*/ + +#define MLT_MULTITRACK_PRODUCER( multitrack ) ( &( multitrack )->parent ) +#define MLT_MULTITRACK_SERVICE( multitrack ) MLT_PRODUCER_SERVICE( MLT_MULTITRACK_PRODUCER( multitrack ) ) +#define MLT_MULTITRACK_PROPERTIES( multitrack ) MLT_SERVICE_PROPERTIES( MLT_MULTITRACK_SERVICE( multitrack ) ) + +extern mlt_multitrack mlt_multitrack_init( ); +extern mlt_producer mlt_multitrack_producer( mlt_multitrack self ); +extern mlt_service mlt_multitrack_service( mlt_multitrack self ); +extern mlt_properties mlt_multitrack_properties( mlt_multitrack self ); +extern int mlt_multitrack_connect( mlt_multitrack self, mlt_producer producer, int track ); +extern mlt_position mlt_multitrack_clip( mlt_multitrack self, mlt_whence whence, int index ); +extern void mlt_multitrack_close( mlt_multitrack self ); +extern int mlt_multitrack_count( mlt_multitrack self ); +extern void mlt_multitrack_refresh( mlt_multitrack self ); +extern mlt_producer mlt_multitrack_track( mlt_multitrack self, int track ); + +#endif + diff --git a/src/framework/mlt_parser.c b/src/framework/mlt_parser.c new file mode 100644 index 0000000..d269f0a --- /dev/null +++ b/src/framework/mlt_parser.c @@ -0,0 +1,243 @@ +/* + * mlt_parser.c -- service parsing functionality + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "mlt.h" +#include + +static int on_invalid( mlt_parser this, mlt_service object ) +{ + return 0; +} + +static int on_unknown( mlt_parser this, mlt_service object ) +{ + return 0; +} + +static int on_start_producer( mlt_parser this, mlt_producer object ) +{ + return 0; +} + +static int on_end_producer( mlt_parser this, mlt_producer object ) +{ + return 0; +} + +static int on_start_playlist( mlt_parser this, mlt_playlist object ) +{ + return 0; +} + +static int on_end_playlist( mlt_parser this, mlt_playlist object ) +{ + return 0; +} + +static int on_start_tractor( mlt_parser this, mlt_tractor object ) +{ + return 0; +} + +static int on_end_tractor( mlt_parser this, mlt_tractor object ) +{ + return 0; +} + +static int on_start_multitrack( mlt_parser this, mlt_multitrack object ) +{ + return 0; +} + +static int on_end_multitrack( mlt_parser this, mlt_multitrack object ) +{ + return 0; +} + +static int on_start_track( mlt_parser this ) +{ + return 0; +} + +static int on_end_track( mlt_parser this ) +{ + return 0; +} + +static int on_start_filter( mlt_parser this, mlt_filter object ) +{ + return 0; +} + +static int on_end_filter( mlt_parser this, mlt_filter object ) +{ + return 0; +} + +static int on_start_transition( mlt_parser this, mlt_transition object ) +{ + return 0; +} + +static int on_end_transition( mlt_parser this, mlt_transition object ) +{ + return 0; +} + +mlt_parser mlt_parser_new( ) +{ + mlt_parser this = calloc( 1, sizeof( struct mlt_parser_s ) ); + if ( this != NULL && mlt_properties_init( &this->parent, this ) == 0 ) + { + this->on_invalid = on_invalid; + this->on_unknown = on_unknown; + this->on_start_producer = on_start_producer; + this->on_end_producer = on_end_producer; + this->on_start_playlist = on_start_playlist; + this->on_end_playlist = on_end_playlist; + this->on_start_tractor = on_start_tractor; + this->on_end_tractor = on_end_tractor; + this->on_start_multitrack = on_start_multitrack; + this->on_end_multitrack = on_end_multitrack; + this->on_start_track = on_start_track; + this->on_end_track = on_end_track; + this->on_start_filter = on_start_filter; + this->on_end_filter = on_end_filter; + this->on_start_transition = on_start_transition; + this->on_end_transition = on_end_transition; + } + return this; +} + +mlt_properties mlt_parser_properties( mlt_parser this ) +{ + return &this->parent; +} + +int mlt_parser_start( mlt_parser this, mlt_service object ) +{ + int error = 0; + mlt_service_type type = mlt_service_identify( object ); + switch( type ) + { + case invalid_type: + error = this->on_invalid( this, object ); + break; + case unknown_type: + error = this->on_unknown( this, object ); + break; + case producer_type: + if ( mlt_producer_is_cut( ( mlt_producer )object ) ) + error = mlt_parser_start( this, ( mlt_service )mlt_producer_cut_parent( ( mlt_producer )object ) ); + error = this->on_start_producer( this, ( mlt_producer )object ); + if ( error == 0 ) + { + int i = 0; + while ( error == 0 && mlt_producer_filter( ( mlt_producer )object, i ) != NULL ) + error = mlt_parser_start( this, ( mlt_service )mlt_producer_filter( ( mlt_producer )object, i ++ ) ); + } + error = this->on_end_producer( this, ( mlt_producer )object ); + break; + case playlist_type: + error = this->on_start_playlist( this, ( mlt_playlist )object ); + if ( error == 0 ) + { + int i = 0; + while ( error == 0 && i < mlt_playlist_count( ( mlt_playlist )object ) ) + mlt_parser_start( this, ( mlt_service )mlt_playlist_get_clip( ( mlt_playlist )object, i ++ ) ); + i = 0; + while ( error == 0 && mlt_producer_filter( ( mlt_producer )object, i ) != NULL ) + error = mlt_parser_start( this, ( mlt_service )mlt_producer_filter( ( mlt_producer )object, i ++ ) ); + } + error = this->on_end_playlist( this, ( mlt_playlist )object ); + break; + case tractor_type: + error = this->on_start_tractor( this, ( mlt_tractor )object ); + if ( error == 0 ) + { + int i = 0; + mlt_service next = mlt_service_producer( object ); + mlt_parser_start( this, ( mlt_service )mlt_tractor_multitrack( ( mlt_tractor )object ) ); + while ( next != ( mlt_service )mlt_tractor_multitrack( ( mlt_tractor )object ) ) + { + mlt_parser_start( this, next ); + next = mlt_service_producer( next ); + } + while ( error == 0 && mlt_producer_filter( ( mlt_producer )object, i ) != NULL ) + error = mlt_parser_start( this, ( mlt_service )mlt_producer_filter( ( mlt_producer )object, i ++ ) ); + } + error = this->on_end_tractor( this, ( mlt_tractor )object ); + break; + case multitrack_type: + error = this->on_start_multitrack( this, ( mlt_multitrack )object ); + if ( error == 0 ) + { + int i = 0; + while ( i < mlt_multitrack_count( ( mlt_multitrack )object ) ) + { + this->on_start_track( this ); + mlt_parser_start( this, ( mlt_service )mlt_multitrack_track( ( mlt_multitrack )object , i ++ ) ); + this->on_end_track( this ); + } + i = 0; + while ( error == 0 && mlt_producer_filter( ( mlt_producer )object, i ) != NULL ) + error = mlt_parser_start( this, ( mlt_service )mlt_producer_filter( ( mlt_producer )object, i ++ ) ); + } + error = this->on_end_multitrack( this, ( mlt_multitrack )object ); + break; + case filter_type: + error = this->on_start_filter( this, ( mlt_filter )object ); + if ( error == 0 ) + { + int i = 0; + while ( error == 0 && mlt_producer_filter( ( mlt_producer )object, i ) != NULL ) + error = mlt_parser_start( this, ( mlt_service )mlt_producer_filter( ( mlt_producer )object, i ++ ) ); + } + error = this->on_end_filter( this, ( mlt_filter )object ); + break; + case transition_type: + error = this->on_start_transition( this, ( mlt_transition )object ); + if ( error == 0 ) + { + int i = 0; + while ( error == 0 && mlt_producer_filter( ( mlt_producer )object, i ) != NULL ) + error = mlt_parser_start( this, ( mlt_service )mlt_producer_filter( ( mlt_producer )object, i ++ ) ); + } + error = this->on_end_transition( this, ( mlt_transition )object ); + break; + case field_type: + break; + case consumer_type: + break; + } + return error; +} + +void mlt_parser_close( mlt_parser this ) +{ + if ( this != NULL ) + { + mlt_properties_close( &this->parent ); + free( this ); + } +} + + diff --git a/src/framework/mlt_parser.h b/src/framework/mlt_parser.h new file mode 100644 index 0000000..bdedda5 --- /dev/null +++ b/src/framework/mlt_parser.h @@ -0,0 +1,52 @@ +/* + * mlt_parser.h -- service parsing functionality + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_PARSER_H_ +#define _MLT_PARSER_H_ + +#include "mlt_types.h" + +struct mlt_parser_s +{ + struct mlt_properties_s parent; + int ( *on_invalid )( mlt_parser self, mlt_service object ); + int ( *on_unknown )( mlt_parser self, mlt_service object ); + int ( *on_start_producer )( mlt_parser self, mlt_producer object ); + int ( *on_end_producer )( mlt_parser self, mlt_producer object ); + int ( *on_start_playlist )( mlt_parser self, mlt_playlist object ); + int ( *on_end_playlist )( mlt_parser self, mlt_playlist object ); + int ( *on_start_tractor )( mlt_parser self, mlt_tractor object ); + int ( *on_end_tractor )( mlt_parser self, mlt_tractor object ); + int ( *on_start_multitrack )( mlt_parser self, mlt_multitrack object ); + int ( *on_end_multitrack )( mlt_parser self, mlt_multitrack object ); + int ( *on_start_track )( mlt_parser self ); + int ( *on_end_track )( mlt_parser self ); + int ( *on_start_filter )( mlt_parser self, mlt_filter object ); + int ( *on_end_filter )( mlt_parser self, mlt_filter object ); + int ( *on_start_transition )( mlt_parser self, mlt_transition object ); + int ( *on_end_transition )( mlt_parser self, mlt_transition object ); +}; + +extern mlt_parser mlt_parser_new( ); +extern mlt_properties mlt_parser_properties( mlt_parser self ); +extern int mlt_parser_start( mlt_parser self, mlt_service object ); +extern void mlt_parser_close( mlt_parser self ); + +#endif diff --git a/src/framework/mlt_playlist.c b/src/framework/mlt_playlist.c new file mode 100644 index 0000000..20aee7a --- /dev/null +++ b/src/framework/mlt_playlist.c @@ -0,0 +1,1500 @@ +/* + * mlt_playlist.c -- playlist service class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "mlt_playlist.h" +#include "mlt_tractor.h" +#include "mlt_multitrack.h" +#include "mlt_field.h" +#include "mlt_frame.h" +#include "mlt_transition.h" + +#include +#include +#include + +/** Virtual playlist entry. +*/ + +struct playlist_entry_s +{ + mlt_producer producer; + mlt_position frame_in; + mlt_position frame_out; + mlt_position frame_count; + int repeat; + mlt_position producer_length; + mlt_event event; + int preservation_hack; +}; + +/** Forward declarations +*/ + +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ); +static int mlt_playlist_unmix( mlt_playlist this, int clip ); +static int mlt_playlist_resize_mix( mlt_playlist this, int clip, int in, int out ); + +/** Constructor. +*/ + +mlt_playlist mlt_playlist_init( ) +{ + mlt_playlist this = calloc( sizeof( struct mlt_playlist_s ), 1 ); + if ( this != NULL ) + { + mlt_producer producer = &this->parent; + + // Construct the producer + mlt_producer_init( producer, this ); + + // Override the producer get_frame + producer->get_frame = producer_get_frame; + + // Define the destructor + producer->close = ( mlt_destructor )mlt_playlist_close; + producer->close_object = this; + + // Initialise blank + mlt_producer_init( &this->blank, NULL ); + mlt_properties_set( MLT_PRODUCER_PROPERTIES( &this->blank ), "mlt_service", "blank" ); + mlt_properties_set( MLT_PRODUCER_PROPERTIES( &this->blank ), "resource", "blank" ); + + // Indicate that this producer is a playlist + mlt_properties_set_data( MLT_PLAYLIST_PROPERTIES( this ), "playlist", this, 0, NULL, NULL ); + + // Specify the eof condition + mlt_properties_set( MLT_PLAYLIST_PROPERTIES( this ), "eof", "pause" ); + mlt_properties_set( MLT_PLAYLIST_PROPERTIES( this ), "resource", "" ); + mlt_properties_set( MLT_PLAYLIST_PROPERTIES( this ), "mlt_type", "mlt_producer" ); + mlt_properties_set_position( MLT_PLAYLIST_PROPERTIES( this ), "in", 0 ); + mlt_properties_set_position( MLT_PLAYLIST_PROPERTIES( this ), "out", -1 ); + mlt_properties_set_position( MLT_PLAYLIST_PROPERTIES( this ), "length", 0 ); + + this->size = 10; + this->list = malloc( this->size * sizeof( playlist_entry * ) ); + } + + return this; +} + +/** Get the producer associated to this playlist. +*/ + +mlt_producer mlt_playlist_producer( mlt_playlist this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Get the service associated to this playlist. +*/ + +mlt_service mlt_playlist_service( mlt_playlist this ) +{ + return MLT_PRODUCER_SERVICE( &this->parent ); +} + +/** Get the propertues associated to this playlist. +*/ + +mlt_properties mlt_playlist_properties( mlt_playlist this ) +{ + return MLT_PRODUCER_PROPERTIES( &this->parent ); +} + +/** Refresh the playlist after a clip has been changed. +*/ + +static int mlt_playlist_virtual_refresh( mlt_playlist this ) +{ + // Obtain the properties + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + int i = 0; + mlt_position frame_count = 0; + + for ( i = 0; i < this->count; i ++ ) + { + // Get the producer + mlt_producer producer = this->list[ i ]->producer; + int current_length = mlt_producer_get_out( producer ) - mlt_producer_get_in( producer ) + 1; + + // Check if the length of the producer has changed + if ( this->list[ i ]->frame_in != mlt_producer_get_in( producer ) || + this->list[ i ]->frame_out != mlt_producer_get_out( producer ) ) + { + // This clip should be removed... + if ( current_length < 1 ) + { + this->list[ i ]->frame_in = 0; + this->list[ i ]->frame_out = -1; + this->list[ i ]->frame_count = 0; + } + else + { + this->list[ i ]->frame_in = mlt_producer_get_in( producer ); + this->list[ i ]->frame_out = mlt_producer_get_out( producer ); + this->list[ i ]->frame_count = current_length; + } + + // Update the producer_length + this->list[ i ]->producer_length = current_length; + } + + // Calculate the frame_count + this->list[ i ]->frame_count = ( this->list[ i ]->frame_out - this->list[ i ]->frame_in + 1 ) * this->list[ i ]->repeat; + + // Update the frame_count for this clip + frame_count += this->list[ i ]->frame_count; + } + + // Refresh all properties + mlt_events_block( properties, properties ); + mlt_properties_set_position( properties, "length", frame_count ); + mlt_events_unblock( properties, properties ); + mlt_properties_set_position( properties, "out", frame_count - 1 ); + + return 0; +} + +/** Listener for producers on the playlist. +*/ + +static void mlt_playlist_listener( mlt_producer producer, mlt_playlist this ) +{ + mlt_playlist_virtual_refresh( this ); +} + +/** Append to the virtual playlist. +*/ + +static int mlt_playlist_virtual_append( mlt_playlist this, mlt_producer source, mlt_position in, mlt_position out ) +{ + mlt_producer producer = NULL; + mlt_properties properties = NULL; + mlt_properties parent = NULL; + + // If we have a cut, then use the in/out points from the cut + if ( mlt_producer_is_blank( source ) ) + { + // Make sure the blank is long enough to accomodate the length specified + if ( out - in + 1 > mlt_producer_get_length( &this->blank ) ) + { + mlt_properties blank_props = MLT_PRODUCER_PROPERTIES( &this->blank ); + mlt_events_block( blank_props, blank_props ); + mlt_producer_set_in_and_out( &this->blank, in, out ); + mlt_events_unblock( blank_props, blank_props ); + } + + // Now make sure the cut comes from this this->blank + if ( source == NULL ) + { + producer = mlt_producer_cut( &this->blank, in, out ); + } + else if ( !mlt_producer_is_cut( source ) || mlt_producer_cut_parent( source ) != &this->blank ) + { + producer = mlt_producer_cut( &this->blank, in, out ); + } + else + { + producer = source; + mlt_properties_inc_ref( MLT_PRODUCER_PROPERTIES( producer ) ); + } + + properties = MLT_PRODUCER_PROPERTIES( producer ); + } + else if ( mlt_producer_is_cut( source ) ) + { + producer = source; + if ( in == -1 ) + in = mlt_producer_get_in( producer ); + if ( out == -1 || out > mlt_producer_get_out( producer ) ) + out = mlt_producer_get_out( producer ); + properties = MLT_PRODUCER_PROPERTIES( producer ); + mlt_properties_inc_ref( properties ); + } + else + { + producer = mlt_producer_cut( source, in, out ); + if ( in == -1 || in < mlt_producer_get_in( producer ) ) + in = mlt_producer_get_in( producer ); + if ( out == -1 || out > mlt_producer_get_out( producer ) ) + out = mlt_producer_get_out( producer ); + properties = MLT_PRODUCER_PROPERTIES( producer ); + } + + // Fetch the cuts parent properties + parent = MLT_PRODUCER_PROPERTIES( mlt_producer_cut_parent( producer ) ); + + // Remove fezzik normalisers for fx cuts + if ( mlt_properties_get_int( parent, "meta.fx_cut" ) ) + { + mlt_service service = MLT_PRODUCER_SERVICE( mlt_producer_cut_parent( producer ) ); + mlt_filter filter = mlt_service_filter( service, 0 ); + while ( filter != NULL && mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "_fezzik" ) ) + { + mlt_service_detach( service, filter ); + filter = mlt_service_filter( service, 0 ); + } + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( producer ), "meta.fx_cut", 1 ); + } + + // Check that we have room + if ( this->count >= this->size ) + { + int i; + this->list = realloc( this->list, ( this->size + 10 ) * sizeof( playlist_entry * ) ); + for ( i = this->size; i < this->size + 10; i ++ ) this->list[ i ] = NULL; + this->size += 10; + } + + // Create the entry + this->list[ this->count ] = calloc( sizeof( playlist_entry ), 1 ); + if ( this->list[ this->count ] != NULL ) + { + this->list[ this->count ]->producer = producer; + this->list[ this->count ]->frame_in = in; + this->list[ this->count ]->frame_out = out; + this->list[ this->count ]->frame_count = out - in + 1; + this->list[ this->count ]->repeat = 1; + this->list[ this->count ]->producer_length = mlt_producer_get_out( producer ) - mlt_producer_get_in( producer ) + 1; + this->list[ this->count ]->event = mlt_events_listen( parent, this, "producer-changed", ( mlt_listener )mlt_playlist_listener ); + mlt_event_inc_ref( this->list[ this->count ]->event ); + mlt_properties_set( properties, "eof", "pause" ); + mlt_producer_set_speed( producer, 0 ); + this->count ++; + } + + return mlt_playlist_virtual_refresh( this ); +} + +static mlt_producer mlt_playlist_locate( mlt_playlist this, mlt_position *position, int *clip, int *total ) +{ + // Default producer to NULL + mlt_producer producer = NULL; + + // Loop for each producer until found + for ( *clip = 0; *clip < this->count; *clip += 1 ) + { + // Increment the total + *total += this->list[ *clip ]->frame_count; + + // Check if the position indicates that we have found the clip + // Note that 0 length clips get skipped automatically + if ( *position < this->list[ *clip ]->frame_count ) + { + // Found it, now break + producer = this->list[ *clip ]->producer; + break; + } + else + { + // Decrement position by length of this entry + *position -= this->list[ *clip ]->frame_count; + } + } + + return producer; +} + +/** Seek in the virtual playlist. +*/ + +static mlt_service mlt_playlist_virtual_seek( mlt_playlist this, int *progressive ) +{ + // Map playlist position to real producer in virtual playlist + mlt_position position = mlt_producer_frame( &this->parent ); + + // Keep the original position since we change it while iterating through the list + mlt_position original = position; + + // Clip index and total + int i = 0; + int total = 0; + + // Locate the producer for the position + mlt_producer producer = mlt_playlist_locate( this, &position, &i, &total ); + + // Get the properties + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + + // Get the eof handling + char *eof = mlt_properties_get( properties, "eof" ); + + // Seek in real producer to relative position + if ( producer != NULL ) + { + int count = this->list[ i ]->frame_count / this->list[ i ]->repeat; + *progressive = count == 1; + mlt_producer_seek( producer, (int)position % count ); + } + else if ( !strcmp( eof, "pause" ) && total > 0 ) + { + playlist_entry *entry = this->list[ this->count - 1 ]; + int count = entry->frame_count / entry->repeat; + mlt_producer this_producer = MLT_PLAYLIST_PRODUCER( this ); + mlt_producer_seek( this_producer, original - 1 ); + producer = entry->producer; + mlt_producer_seek( producer, (int)entry->frame_out % count ); + mlt_producer_set_speed( this_producer, 0 ); + mlt_producer_set_speed( producer, 0 ); + *progressive = count == 1; + } + else if ( !strcmp( eof, "loop" ) && total > 0 ) + { + playlist_entry *entry = this->list[ 0 ]; + mlt_producer this_producer = MLT_PLAYLIST_PRODUCER( this ); + mlt_producer_seek( this_producer, 0 ); + producer = entry->producer; + mlt_producer_seek( producer, 0 ); + } + else + { + producer = &this->blank; + } + + return MLT_PRODUCER_SERVICE( producer ); +} + +/** Invoked when a producer indicates that it has prematurely reached its end. +*/ + +static mlt_producer mlt_playlist_virtual_set_out( mlt_playlist this ) +{ + // Default producer to blank + mlt_producer producer = &this->blank; + + // Map playlist position to real producer in virtual playlist + mlt_position position = mlt_producer_frame( &this->parent ); + + // Loop through the virtual playlist + int i = 0; + + for ( i = 0; i < this->count; i ++ ) + { + if ( position < this->list[ i ]->frame_count ) + { + // Found it, now break + producer = this->list[ i ]->producer; + break; + } + else + { + // Decrement position by length of this entry + position -= this->list[ i ]->frame_count; + } + } + + // Seek in real producer to relative position + if ( i < this->count && this->list[ i ]->frame_out != position ) + { + // Update the frame_count for the changed clip (hmmm) + this->list[ i ]->frame_out = position; + this->list[ i ]->frame_count = this->list[ i ]->frame_out - this->list[ i ]->frame_in + 1; + + // Refresh the playlist + mlt_playlist_virtual_refresh( this ); + } + + return producer; +} + +/** Obtain the current clips index. +*/ + +int mlt_playlist_current_clip( mlt_playlist this ) +{ + // Map playlist position to real producer in virtual playlist + mlt_position position = mlt_producer_frame( &this->parent ); + + // Loop through the virtual playlist + int i = 0; + + for ( i = 0; i < this->count; i ++ ) + { + if ( position < this->list[ i ]->frame_count ) + { + // Found it, now break + break; + } + else + { + // Decrement position by length of this entry + position -= this->list[ i ]->frame_count; + } + } + + return i; +} + +/** Obtain the current clips producer. +*/ + +mlt_producer mlt_playlist_current( mlt_playlist this ) +{ + int i = mlt_playlist_current_clip( this ); + if ( i < this->count ) + return this->list[ i ]->producer; + else + return &this->blank; +} + +/** Get the position which corresponds to the start of the next clip. +*/ + +mlt_position mlt_playlist_clip( mlt_playlist this, mlt_whence whence, int index ) +{ + mlt_position position = 0; + int absolute_clip = index; + int i = 0; + + // Determine the absolute clip + switch ( whence ) + { + case mlt_whence_relative_start: + absolute_clip = index; + break; + + case mlt_whence_relative_current: + absolute_clip = mlt_playlist_current_clip( this ) + index; + break; + + case mlt_whence_relative_end: + absolute_clip = this->count - index; + break; + } + + // Check that we're in a valid range + if ( absolute_clip < 0 ) + absolute_clip = 0; + else if ( absolute_clip > this->count ) + absolute_clip = this->count; + + // Now determine the position + for ( i = 0; i < absolute_clip; i ++ ) + position += this->list[ i ]->frame_count; + + return position; +} + +/** Get all the info about the clip specified. +*/ + +int mlt_playlist_get_clip_info( mlt_playlist this, mlt_playlist_clip_info *info, int index ) +{ + int error = index < 0 || index >= this->count; + memset( info, 0, sizeof( mlt_playlist_clip_info ) ); + if ( !error ) + { + mlt_producer producer = mlt_producer_cut_parent( this->list[ index ]->producer ); + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + info->clip = index; + info->producer = producer; + info->cut = this->list[ index ]->producer; + info->start = mlt_playlist_clip( this, mlt_whence_relative_start, index ); + info->resource = mlt_properties_get( properties, "resource" ); + info->frame_in = this->list[ index ]->frame_in; + info->frame_out = this->list[ index ]->frame_out; + info->frame_count = this->list[ index ]->frame_count; + info->repeat = this->list[ index ]->repeat; + info->length = mlt_producer_get_length( producer ); + info->fps = mlt_producer_get_fps( producer ); + } + + return error; +} + +/** Get number of clips in the playlist. +*/ + +int mlt_playlist_count( mlt_playlist this ) +{ + return this->count; +} + +/** Clear the playlist. +*/ + +int mlt_playlist_clear( mlt_playlist this ) +{ + int i; + for ( i = 0; i < this->count; i ++ ) + { + mlt_event_close( this->list[ i ]->event ); + mlt_producer_close( this->list[ i ]->producer ); + } + this->count = 0; + return mlt_playlist_virtual_refresh( this ); +} + +/** Append a producer to the playlist. +*/ + +int mlt_playlist_append( mlt_playlist this, mlt_producer producer ) +{ + // Append to virtual list + return mlt_playlist_virtual_append( this, producer, 0, mlt_producer_get_playtime( producer ) - 1 ); +} + +/** Append a producer to the playlist with in/out points. +*/ + +int mlt_playlist_append_io( mlt_playlist this, mlt_producer producer, mlt_position in, mlt_position out ) +{ + // Append to virtual list + if ( in != -1 && out != -1 ) + return mlt_playlist_virtual_append( this, producer, in, out ); + else + return mlt_playlist_append( this, producer ); +} + +/** Append a blank to the playlist of a given length. +*/ + +int mlt_playlist_blank( mlt_playlist this, mlt_position length ) +{ + // Append to the virtual list + return mlt_playlist_virtual_append( this, &this->blank, 0, length ); +} + +/** Insert a producer into the playlist. +*/ + +int mlt_playlist_insert( mlt_playlist this, mlt_producer producer, int where, mlt_position in, mlt_position out ) +{ + // Append to end + mlt_events_block( MLT_PLAYLIST_PROPERTIES( this ), this ); + mlt_playlist_append_io( this, producer, in, out ); + + // Move to the position specified + mlt_playlist_move( this, this->count - 1, where ); + mlt_events_unblock( MLT_PLAYLIST_PROPERTIES( this ), this ); + + return mlt_playlist_virtual_refresh( this ); +} + +/** Remove an entry in the playlist. +*/ + +int mlt_playlist_remove( mlt_playlist this, int where ) +{ + int error = where < 0 || where >= this->count; + if ( error == 0 && mlt_playlist_unmix( this, where ) != 0 ) + { + // We need to know the current clip and the position within the playlist + int current = mlt_playlist_current_clip( this ); + mlt_position position = mlt_producer_position( MLT_PLAYLIST_PRODUCER( this ) ); + + // We need all the details about the clip we're removing + mlt_playlist_clip_info where_info; + playlist_entry *entry = this->list[ where ]; + mlt_properties properties = MLT_PRODUCER_PROPERTIES( entry->producer ); + + // Loop variable + int i = 0; + + // Get the clip info + mlt_playlist_get_clip_info( this, &where_info, where ); + + // Make sure the clip to be removed is valid and correct if necessary + if ( where < 0 ) + where = 0; + if ( where >= this->count ) + where = this->count - 1; + + // Reorganise the list + for ( i = where + 1; i < this->count; i ++ ) + this->list[ i - 1 ] = this->list[ i ]; + this->count --; + + if ( entry->preservation_hack == 0 ) + { + // Decouple from mix_in/out if necessary + if ( mlt_properties_get_data( properties, "mix_in", NULL ) != NULL ) + { + mlt_properties mix = mlt_properties_get_data( properties, "mix_in", NULL ); + mlt_properties_set_data( mix, "mix_out", NULL, 0, NULL, NULL ); + } + if ( mlt_properties_get_data( properties, "mix_out", NULL ) != NULL ) + { + mlt_properties mix = mlt_properties_get_data( properties, "mix_out", NULL ); + mlt_properties_set_data( mix, "mix_in", NULL, 0, NULL, NULL ); + } + + if ( mlt_properties_ref_count( MLT_PRODUCER_PROPERTIES( entry->producer ) ) == 1 ) + mlt_producer_clear( entry->producer ); + } + + // Close the producer associated to the clip info + mlt_event_close( entry->event ); + mlt_producer_close( entry->producer ); + + // Correct position + if ( where == current ) + mlt_producer_seek( MLT_PLAYLIST_PRODUCER( this ), where_info.start ); + else if ( where < current && this->count > 0 ) + mlt_producer_seek( MLT_PLAYLIST_PRODUCER( this ), position - where_info.frame_count ); + else if ( this->count == 0 ) + mlt_producer_seek( MLT_PLAYLIST_PRODUCER( this ), 0 ); + + // Free the entry + free( entry ); + + // Refresh the playlist + mlt_playlist_virtual_refresh( this ); + } + + return error; +} + +/** Move an entry in the playlist. +*/ + +int mlt_playlist_move( mlt_playlist this, int src, int dest ) +{ + int i; + + /* We need to ensure that the requested indexes are valid and correct it as necessary */ + if ( src < 0 ) + src = 0; + if ( src >= this->count ) + src = this->count - 1; + + if ( dest < 0 ) + dest = 0; + if ( dest >= this->count ) + dest = this->count - 1; + + if ( src != dest && this->count > 1 ) + { + int current = mlt_playlist_current_clip( this ); + mlt_position position = mlt_producer_position( MLT_PLAYLIST_PRODUCER( this ) ); + playlist_entry *src_entry = NULL; + + // We need all the details about the current clip + mlt_playlist_clip_info current_info; + + mlt_playlist_get_clip_info( this, ¤t_info, current ); + position -= current_info.start; + + if ( current == src ) + current = dest; + else if ( current > src && current < dest ) + current ++; + else if ( current == dest ) + current = src; + + src_entry = this->list[ src ]; + if ( src > dest ) + { + for ( i = src; i > dest; i -- ) + this->list[ i ] = this->list[ i - 1 ]; + } + else + { + for ( i = src; i < dest; i ++ ) + this->list[ i ] = this->list[ i + 1 ]; + } + this->list[ dest ] = src_entry; + + mlt_playlist_get_clip_info( this, ¤t_info, current ); + mlt_producer_seek( MLT_PLAYLIST_PRODUCER( this ), current_info.start + position ); + mlt_playlist_virtual_refresh( this ); + } + + return 0; +} + +/** Repeat the specified clip n times. +*/ + +int mlt_playlist_repeat_clip( mlt_playlist this, int clip, int repeat ) +{ + int error = repeat < 1 || clip < 0 || clip >= this->count; + if ( error == 0 ) + { + playlist_entry *entry = this->list[ clip ]; + entry->repeat = repeat; + mlt_playlist_virtual_refresh( this ); + } + return error; +} + +/** Resize the specified clip. +*/ + +int mlt_playlist_resize_clip( mlt_playlist this, int clip, mlt_position in, mlt_position out ) +{ + int error = clip < 0 || clip >= this->count; + if ( error == 0 && mlt_playlist_resize_mix( this, clip, in, out ) != 0 ) + { + playlist_entry *entry = this->list[ clip ]; + mlt_producer producer = entry->producer; + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + + mlt_events_block( properties, properties ); + + if ( mlt_producer_is_blank( producer ) ) + { + // Make sure the blank is long enough to accomodate the length specified + if ( out - in + 1 > mlt_producer_get_length( &this->blank ) ) + { + mlt_properties blank_props = MLT_PRODUCER_PROPERTIES( &this->blank ); + mlt_properties_set_int( blank_props, "length", out - in + 1 ); + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( producer ), "length", out - in + 1 ); + mlt_producer_set_in_and_out( &this->blank, 0, out - in ); + } + } + + if ( in <= -1 ) + in = 0; + if ( out <= -1 || out >= mlt_producer_get_length( producer ) ) + out = mlt_producer_get_length( producer ) - 1; + + if ( out < in ) + { + mlt_position t = in; + in = out; + out = t; + } + + mlt_producer_set_in_and_out( producer, in, out ); + mlt_events_unblock( properties, properties ); + mlt_playlist_virtual_refresh( this ); + } + return error; +} + +/** Split a clip on the playlist at the given position. +*/ + +int mlt_playlist_split( mlt_playlist this, int clip, mlt_position position ) +{ + int error = clip < 0 || clip >= this->count; + if ( error == 0 ) + { + playlist_entry *entry = this->list[ clip ]; + position = position < 0 ? entry->frame_count + position - 1 : position; + if ( position >= 0 && position < entry->frame_count - 1 ) + { + int in = entry->frame_in; + int out = entry->frame_out; + mlt_events_block( MLT_PLAYLIST_PROPERTIES( this ), this ); + mlt_playlist_resize_clip( this, clip, in, in + position ); + if ( !mlt_producer_is_blank( entry->producer ) ) + { + int i = 0; + mlt_properties entry_properties = MLT_PRODUCER_PROPERTIES( entry->producer ); + mlt_producer split = mlt_producer_cut( entry->producer, in + position + 1, out ); + mlt_properties split_properties = MLT_PRODUCER_PROPERTIES( split ); + mlt_playlist_insert( this, split, clip + 1, 0, -1 ); + for ( i = 0; i < mlt_properties_count( entry_properties ); i ++ ) + { + char *name = mlt_properties_get_name( entry_properties, i ); + if ( name != NULL && !strncmp( name, "meta.", 5 ) ) + mlt_properties_set( split_properties, name, mlt_properties_get_value( entry_properties, i ) ); + } + mlt_producer_close( split ); + } + else + { + mlt_playlist_insert( this, &this->blank, clip + 1, 0, out - position - 1 ); + } + mlt_events_unblock( MLT_PLAYLIST_PROPERTIES( this ), this ); + mlt_playlist_virtual_refresh( this ); + } + else + { + error = 1; + } + } + return error; +} + +/** Split the playlist at the absolute position. +*/ + +int mlt_playlist_split_at( mlt_playlist this, mlt_position position, int left ) +{ + int result = this == NULL ? -1 : 0; + if ( !result ) + { + if ( position >= 0 && position < mlt_producer_get_playtime( MLT_PLAYLIST_PRODUCER( this ) ) ) + { + int clip = mlt_playlist_get_clip_index_at( this, position ); + mlt_playlist_clip_info info; + mlt_playlist_get_clip_info( this, &info, clip ); + if ( left && position != info.start ) + mlt_playlist_split( this, clip, position - info.start - 1 ); + else if ( !left ) + mlt_playlist_split( this, clip, position - info.start ); + result = position; + } + else if ( position <= 0 ) + { + result = 0; + } + else + { + result = mlt_producer_get_playtime( MLT_PLAYLIST_PRODUCER( this ) ); + } + } + return result; +} + +/** Join 1 or more consecutive clips. +*/ + +int mlt_playlist_join( mlt_playlist this, int clip, int count, int merge ) +{ + int error = clip < 0 || clip >= this->count; + if ( error == 0 ) + { + int i = clip; + mlt_playlist new_clip = mlt_playlist_init( ); + mlt_events_block( MLT_PLAYLIST_PROPERTIES( this ), this ); + if ( clip + count >= this->count ) + count = this->count - clip - 1; + for ( i = 0; i <= count; i ++ ) + { + playlist_entry *entry = this->list[ clip ]; + mlt_playlist_append( new_clip, entry->producer ); + mlt_playlist_repeat_clip( new_clip, i, entry->repeat ); + entry->preservation_hack = 1; + mlt_playlist_remove( this, clip ); + } + mlt_events_unblock( MLT_PLAYLIST_PROPERTIES( this ), this ); + mlt_playlist_insert( this, MLT_PLAYLIST_PRODUCER( new_clip ), clip, 0, -1 ); + mlt_playlist_close( new_clip ); + } + return error; +} + +/** Mix consecutive clips for a specified length and apply transition if specified. +*/ + +int mlt_playlist_mix( mlt_playlist this, int clip, int length, mlt_transition transition ) +{ + int error = ( clip < 0 || clip + 1 >= this->count ); + if ( error == 0 ) + { + playlist_entry *clip_a = this->list[ clip ]; + playlist_entry *clip_b = this->list[ clip + 1 ]; + mlt_producer track_a = NULL; + mlt_producer track_b = NULL; + mlt_tractor tractor = mlt_tractor_new( ); + mlt_events_block( MLT_PLAYLIST_PROPERTIES( this ), this ); + + // Check length is valid for both clips and resize if necessary. + int max_size = clip_a->frame_count > clip_b->frame_count ? clip_a->frame_count : clip_b->frame_count; + length = length > max_size ? max_size : length; + + // Create the a and b tracks/cuts if necessary - note that no cuts are required if the length matches + if ( length != clip_a->frame_count ) + track_a = mlt_producer_cut( clip_a->producer, clip_a->frame_out - length + 1, clip_a->frame_out ); + else + track_a = clip_a->producer; + + if ( length != clip_b->frame_count ) + track_b = mlt_producer_cut( clip_b->producer, clip_b->frame_in, clip_b->frame_in + length - 1 ); + else + track_b = clip_b->producer; + + // Set the tracks on the tractor + mlt_tractor_set_track( tractor, track_a, 0 ); + mlt_tractor_set_track( tractor, track_b, 1 ); + + // Insert the mix object into the playlist + mlt_playlist_insert( this, MLT_TRACTOR_PRODUCER( tractor ), clip + 1, -1, -1 ); + mlt_properties_set_data( MLT_TRACTOR_PROPERTIES( tractor ), "mlt_mix", tractor, 0, NULL, NULL ); + + // Attach the transition + if ( transition != NULL ) + { + mlt_field field = mlt_tractor_field( tractor ); + mlt_field_plant_transition( field, transition, 0, 1 ); + mlt_transition_set_in_and_out( transition, 0, length - 1 ); + } + + // Close our references to the tracks if we created new cuts above (the tracks can still be used here) + if ( track_a != clip_a->producer ) + mlt_producer_close( track_a ); + if ( track_b != clip_b->producer ) + mlt_producer_close( track_b ); + + // Check if we have anything left on the right hand clip + if ( track_b == clip_b->producer ) + { + clip_b->preservation_hack = 1; + mlt_playlist_remove( this, clip + 2 ); + } + else if ( clip_b->frame_out - clip_b->frame_in > length ) + { + mlt_playlist_resize_clip( this, clip + 2, clip_b->frame_in + length, clip_b->frame_out ); + mlt_properties_set_data( MLT_PRODUCER_PROPERTIES( clip_b->producer ), "mix_in", tractor, 0, NULL, NULL ); + mlt_properties_set_data( MLT_TRACTOR_PROPERTIES( tractor ), "mix_out", clip_b->producer, 0, NULL, NULL ); + } + else + { + mlt_producer_clear( clip_b->producer ); + mlt_playlist_remove( this, clip + 2 ); + } + + // Check if we have anything left on the left hand clip + if ( track_a == clip_a->producer ) + { + clip_a->preservation_hack = 1; + mlt_playlist_remove( this, clip ); + } + else if ( clip_a->frame_out - clip_a->frame_in > length ) + { + mlt_playlist_resize_clip( this, clip, clip_a->frame_in, clip_a->frame_out - length ); + mlt_properties_set_data( MLT_PRODUCER_PROPERTIES( clip_a->producer ), "mix_out", tractor, 0, NULL, NULL ); + mlt_properties_set_data( MLT_TRACTOR_PROPERTIES( tractor ), "mix_in", clip_a->producer, 0, NULL, NULL ); + } + else + { + mlt_producer_clear( clip_a->producer ); + mlt_playlist_remove( this, clip ); + } + + // Unblock and force a fire off of change events to listeners + mlt_events_unblock( MLT_PLAYLIST_PROPERTIES( this ), this ); + mlt_playlist_virtual_refresh( this ); + mlt_tractor_close( tractor ); + } + return error; +} + +/** Add a transition to an existing mix. +*/ + +int mlt_playlist_mix_add( mlt_playlist this, int clip, mlt_transition transition ) +{ + mlt_producer producer = mlt_producer_cut_parent( mlt_playlist_get_clip( this, clip ) ); + mlt_properties properties = producer != NULL ? MLT_PRODUCER_PROPERTIES( producer ) : NULL; + mlt_tractor tractor = properties != NULL ? mlt_properties_get_data( properties, "mlt_mix", NULL ) : NULL; + int error = transition == NULL || tractor == NULL; + if ( error == 0 ) + { + mlt_field field = mlt_tractor_field( tractor ); + mlt_field_plant_transition( field, transition, 0, 1 ); + mlt_transition_set_in_and_out( transition, 0, this->list[ clip ]->frame_count - 1 ); + } + return error; +} + +/** Return the clip at the clip index. +*/ + +mlt_producer mlt_playlist_get_clip( mlt_playlist this, int clip ) +{ + if ( clip >= 0 && clip < this->count ) + return this->list[ clip ]->producer; + return NULL; +} + +/** Return the clip at the specified position. +*/ + +mlt_producer mlt_playlist_get_clip_at( mlt_playlist this, mlt_position position ) +{ + int index = 0, total = 0; + return mlt_playlist_locate( this, &position, &index, &total ); +} + +/** Return the clip index of the specified position. +*/ + +int mlt_playlist_get_clip_index_at( mlt_playlist this, mlt_position position ) +{ + int index = 0, total = 0; + mlt_playlist_locate( this, &position, &index, &total ); + return index; +} + +/** Determine if the clip is a mix. +*/ + +int mlt_playlist_clip_is_mix( mlt_playlist this, int clip ) +{ + mlt_producer producer = mlt_producer_cut_parent( mlt_playlist_get_clip( this, clip ) ); + mlt_properties properties = producer != NULL ? MLT_PRODUCER_PROPERTIES( producer ) : NULL; + mlt_tractor tractor = properties != NULL ? mlt_properties_get_data( properties, "mlt_mix", NULL ) : NULL; + return tractor != NULL; +} + +/** Remove a mixed clip - ensure that the cuts included in the mix find their way + back correctly on to the playlist. +*/ + +static int mlt_playlist_unmix( mlt_playlist this, int clip ) +{ + int error = ( clip < 0 || clip >= this->count ); + + // Ensure that the clip request is actually a mix + if ( error == 0 ) + { + mlt_producer producer = mlt_producer_cut_parent( this->list[ clip ]->producer ); + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + error = mlt_properties_get_data( properties, "mlt_mix", NULL ) == NULL || + this->list[ clip ]->preservation_hack; + } + + if ( error == 0 ) + { + playlist_entry *mix = this->list[ clip ]; + mlt_tractor tractor = ( mlt_tractor )mlt_producer_cut_parent( mix->producer ); + mlt_properties properties = MLT_TRACTOR_PROPERTIES( tractor ); + mlt_producer clip_a = mlt_properties_get_data( properties, "mix_in", NULL ); + mlt_producer clip_b = mlt_properties_get_data( properties, "mix_out", NULL ); + int length = mlt_producer_get_playtime( MLT_TRACTOR_PRODUCER( tractor ) ); + mlt_events_block( MLT_PLAYLIST_PROPERTIES( this ), this ); + + if ( clip_a != NULL ) + { + mlt_producer_set_in_and_out( clip_a, mlt_producer_get_in( clip_a ), mlt_producer_get_out( clip_a ) + length ); + } + else + { + mlt_producer cut = mlt_tractor_get_track( tractor, 0 ); + mlt_playlist_insert( this, cut, clip, -1, -1 ); + clip ++; + } + + if ( clip_b != NULL ) + { + mlt_producer_set_in_and_out( clip_b, mlt_producer_get_in( clip_b ) - length, mlt_producer_get_out( clip_b ) ); + } + else + { + mlt_producer cut = mlt_tractor_get_track( tractor, 1 ); + mlt_playlist_insert( this, cut, clip + 1, -1, -1 ); + } + + mlt_properties_set_data( properties, "mlt_mix", NULL, 0, NULL, NULL ); + mlt_playlist_remove( this, clip ); + mlt_events_unblock( MLT_PLAYLIST_PROPERTIES( this ), this ); + mlt_playlist_virtual_refresh( this ); + } + return error; +} + +static int mlt_playlist_resize_mix( mlt_playlist this, int clip, int in, int out ) +{ + int error = ( clip < 0 || clip >= this->count ); + + // Ensure that the clip request is actually a mix + if ( error == 0 ) + { + mlt_producer producer = mlt_producer_cut_parent( this->list[ clip ]->producer ); + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + error = mlt_properties_get_data( properties, "mlt_mix", NULL ) == NULL; + } + + if ( error == 0 ) + { + playlist_entry *mix = this->list[ clip ]; + mlt_tractor tractor = ( mlt_tractor )mlt_producer_cut_parent( mix->producer ); + mlt_properties properties = MLT_TRACTOR_PROPERTIES( tractor ); + mlt_producer clip_a = mlt_properties_get_data( properties, "mix_in", NULL ); + mlt_producer clip_b = mlt_properties_get_data( properties, "mix_out", NULL ); + mlt_producer track_a = mlt_tractor_get_track( tractor, 0 ); + mlt_producer track_b = mlt_tractor_get_track( tractor, 1 ); + int length = out - in + 1; + int length_diff = length - mlt_producer_get_playtime( MLT_TRACTOR_PRODUCER( tractor ) ); + mlt_events_block( MLT_PLAYLIST_PROPERTIES( this ), this ); + + if ( clip_a != NULL ) + mlt_producer_set_in_and_out( clip_a, mlt_producer_get_in( clip_a ), mlt_producer_get_out( clip_a ) - length_diff ); + + if ( clip_b != NULL ) + mlt_producer_set_in_and_out( clip_b, mlt_producer_get_in( clip_b ) + length_diff, mlt_producer_get_out( clip_b ) ); + + mlt_producer_set_in_and_out( track_a, mlt_producer_get_in( track_a ) - length_diff, mlt_producer_get_out( track_a ) ); + mlt_producer_set_in_and_out( track_b, mlt_producer_get_in( track_b ), mlt_producer_get_out( track_b ) + length_diff ); + mlt_producer_set_in_and_out( MLT_MULTITRACK_PRODUCER( mlt_tractor_multitrack( tractor ) ), in, out ); + mlt_producer_set_in_and_out( MLT_TRACTOR_PRODUCER( tractor ), in, out ); + mlt_properties_set_position( MLT_PRODUCER_PROPERTIES( mix->producer ), "length", out - in + 1 ); + mlt_producer_set_in_and_out( mix->producer, in, out ); + + mlt_events_unblock( MLT_PLAYLIST_PROPERTIES( this ), this ); + mlt_playlist_virtual_refresh( this ); + } + return error; +} + +/** Consolodate adjacent blank producers. +*/ + +void mlt_playlist_consolidate_blanks( mlt_playlist this, int keep_length ) +{ + if ( this != NULL ) + { + int i = 0; + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + + mlt_events_block( properties, properties ); + for ( i = 1; i < this->count; i ++ ) + { + playlist_entry *left = this->list[ i - 1 ]; + playlist_entry *right = this->list[ i ]; + + if ( mlt_producer_is_blank( left->producer ) && mlt_producer_is_blank( right->producer ) ) + { + mlt_playlist_resize_clip( this, i - 1, 0, left->frame_count + right->frame_count - 1 ); + mlt_playlist_remove( this, i -- ); + } + } + + if ( !keep_length && this->count > 0 ) + { + playlist_entry *last = this->list[ this->count - 1 ]; + if ( mlt_producer_is_blank( last->producer ) ) + mlt_playlist_remove( this, this->count - 1 ); + } + + mlt_events_unblock( properties, properties ); + mlt_playlist_virtual_refresh( this ); + } +} + +/** Determine if the specified clip index is a blank. +*/ + +int mlt_playlist_is_blank( mlt_playlist this, int clip ) +{ + return this == NULL || mlt_producer_is_blank( mlt_playlist_get_clip( this, clip ) ); +} + +/** Determine if the specified position is a blank. +*/ + +int mlt_playlist_is_blank_at( mlt_playlist this, mlt_position position ) +{ + return this == NULL || mlt_producer_is_blank( mlt_playlist_get_clip_at( this, position ) ); +} + +/** Replace the specified clip with a blank and return the clip. +*/ + +mlt_producer mlt_playlist_replace_with_blank( mlt_playlist this, int clip ) +{ + mlt_producer producer = NULL; + if ( !mlt_playlist_is_blank( this, clip ) ) + { + playlist_entry *entry = this->list[ clip ]; + int in = entry->frame_in; + int out = entry->frame_out; + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + producer = entry->producer; + mlt_properties_inc_ref( MLT_PRODUCER_PROPERTIES( producer ) ); + mlt_events_block( properties, properties ); + mlt_playlist_remove( this, clip ); + mlt_playlist_blank( this, out - in ); + mlt_playlist_move( this, this->count - 1, clip ); + mlt_events_unblock( properties, properties ); + mlt_playlist_virtual_refresh( this ); + mlt_producer_set_in_and_out( producer, in, out ); + } + return producer; +} + +void mlt_playlist_insert_blank( mlt_playlist this, int clip, int length ) +{ + if ( this != NULL && length >= 0 ) + { + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + mlt_events_block( properties, properties ); + mlt_playlist_blank( this, length ); + mlt_playlist_move( this, this->count - 1, clip ); + mlt_events_unblock( properties, properties ); + mlt_playlist_virtual_refresh( this ); + } +} + +void mlt_playlist_pad_blanks( mlt_playlist this, mlt_position position, int length, int find ) +{ + if ( this != NULL && length != 0 ) + { + int clip = mlt_playlist_get_clip_index_at( this, position ); + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + mlt_events_block( properties, properties ); + if ( find && clip < this->count && !mlt_playlist_is_blank( this, clip ) ) + clip ++; + if ( clip < this->count && mlt_playlist_is_blank( this, clip ) ) + { + mlt_playlist_clip_info info; + mlt_playlist_get_clip_info( this, &info, clip ); + if ( info.frame_out + length > info.frame_in ) + mlt_playlist_resize_clip( this, clip, info.frame_in, info.frame_out + length ); + else + mlt_playlist_remove( this, clip ); + } + else if ( find && clip < this->count && length > 0 ) + { + mlt_playlist_insert_blank( this, clip, length ); + } + mlt_events_unblock( properties, properties ); + mlt_playlist_virtual_refresh( this ); + } +} + +int mlt_playlist_insert_at( mlt_playlist this, mlt_position position, mlt_producer producer, int mode ) +{ + int ret = this == NULL || position < 0 || producer == NULL; + if ( ret == 0 ) + { + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + int length = mlt_producer_get_playtime( producer ); + int clip = mlt_playlist_get_clip_index_at( this, position ); + mlt_playlist_clip_info info; + mlt_playlist_get_clip_info( this, &info, clip ); + mlt_events_block( properties, this ); + if ( clip < this->count && mlt_playlist_is_blank( this, clip ) ) + { + // Split and move to new clip if need be + if ( position != info.start && mlt_playlist_split( this, clip, position - info.start ) == 0 ) + mlt_playlist_get_clip_info( this, &info, ++ clip ); + + // Split again if need be + if ( length < info.frame_count ) + mlt_playlist_split( this, clip, length - 1 ); + + // Remove + mlt_playlist_remove( this, clip ); + + // Insert + mlt_playlist_insert( this, producer, clip, -1, -1 ); + ret = clip; + } + else if ( clip < this->count ) + { + if ( position > info.start + info.frame_count / 2 ) + clip ++; + if ( mode == 1 && clip < this->count && mlt_playlist_is_blank( this, clip ) ) + { + mlt_playlist_get_clip_info( this, &info, clip ); + if ( length < info.frame_count ) + mlt_playlist_split( this, clip, length ); + mlt_playlist_remove( this, clip ); + } + mlt_playlist_insert( this, producer, clip, -1, -1 ); + ret = clip; + } + else + { + if ( mode == 1 ) + mlt_playlist_blank( this, position - mlt_properties_get_int( properties, "length" ) ); + mlt_playlist_append( this, producer ); + ret = this->count - 1; + } + mlt_events_unblock( properties, this ); + mlt_playlist_virtual_refresh( this ); + } + else + { + ret = -1; + } + return ret; +} + +int mlt_playlist_clip_start( mlt_playlist this, int clip ) +{ + mlt_playlist_clip_info info; + if ( mlt_playlist_get_clip_info( this, &info, clip ) == 0 ) + return info.start; + return clip < 0 ? 0 : mlt_producer_get_playtime( MLT_PLAYLIST_PRODUCER( this ) ); +} + +int mlt_playlist_clip_length( mlt_playlist this, int clip ) +{ + mlt_playlist_clip_info info; + if ( mlt_playlist_get_clip_info( this, &info, clip ) == 0 ) + return info.frame_count; + return 0; +} + +int mlt_playlist_blanks_from( mlt_playlist this, int clip, int bounded ) +{ + int count = 0; + mlt_playlist_clip_info info; + if ( this != NULL && clip < this->count ) + { + mlt_playlist_get_clip_info( this, &info, clip ); + if ( mlt_playlist_is_blank( this, clip ) ) + count += info.frame_count; + if ( bounded == 0 ) + bounded = this->count; + for ( clip ++; clip < this->count && bounded >= 0; clip ++ ) + { + mlt_playlist_get_clip_info( this, &info, clip ); + if ( mlt_playlist_is_blank( this, clip ) ) + count += info.frame_count; + else + bounded --; + } + } + return count; +} + +int mlt_playlist_remove_region( mlt_playlist this, mlt_position position, int length ) +{ + int index = mlt_playlist_get_clip_index_at( this, position ); + if ( index >= 0 && index < this->count ) + { + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( this ); + int clip_start = mlt_playlist_clip_start( this, index ); + int clip_length = mlt_playlist_clip_length( this, index ); + int list_length = mlt_producer_get_playtime( MLT_PLAYLIST_PRODUCER( this ) ); + mlt_events_block( properties, this ); + + if ( position + length > list_length ) + length -= ( position + length - list_length ); + + if ( clip_start < position ) + { + mlt_playlist_split( this, index ++, position - clip_start ); + clip_length -= position - clip_start; + } + + while( length > 0 ) + { + if ( mlt_playlist_clip_length( this, index ) > length ) + mlt_playlist_split( this, index, length ); + length -= mlt_playlist_clip_length( this, index ); + mlt_playlist_remove( this, index ); + } + + mlt_playlist_consolidate_blanks( this, 0 ); + mlt_events_unblock( properties, this ); + mlt_playlist_virtual_refresh( this ); + + // Just to be sure, we'll get the clip index again... + index = mlt_playlist_get_clip_index_at( this, position ); + } + return index; +} + +int mlt_playlist_move_region( mlt_playlist this, mlt_position position, int length, int new_position ) +{ + if ( this != NULL ) + { + } + return 0; +} + +/** Get the current frame. +*/ + +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ) +{ + // Check that we have a producer + if ( producer == NULL ) + { + *frame = mlt_frame_init( ); + return 0; + } + + // Get this mlt_playlist + mlt_playlist this = producer->child; + + // Need to ensure the frame is deinterlaced when repeating 1 frame + int progressive = 0; + + // Get the real producer + mlt_service real = mlt_playlist_virtual_seek( this, &progressive ); + + // Check that we have a producer + if ( real == NULL ) + { + *frame = mlt_frame_init( ); + return 0; + } + + // Get the frame + if ( !mlt_properties_get_int( MLT_SERVICE_PROPERTIES( real ), "meta.fx_cut" ) ) + { + mlt_service_get_frame( real, frame, index ); + } + else + { + mlt_producer parent = mlt_producer_cut_parent( ( mlt_producer )real ); + *frame = mlt_frame_init( ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( *frame ), "fx_cut", 1 ); + mlt_frame_push_service( *frame, NULL ); + mlt_frame_push_audio( *frame, NULL ); + mlt_service_apply_filters( MLT_PRODUCER_SERVICE( parent ), *frame, 0 ); + mlt_service_apply_filters( real, *frame, 0 ); + mlt_deque_pop_front( MLT_FRAME_IMAGE_STACK( *frame ) ); + mlt_deque_pop_front( MLT_FRAME_AUDIO_STACK( *frame ) ); + } + + // Check if we're at the end of the clip + mlt_properties properties = MLT_FRAME_PROPERTIES( *frame ); + if ( mlt_properties_get_int( properties, "end_of_clip" ) ) + mlt_playlist_virtual_set_out( this ); + + // Set the consumer progressive property + if ( progressive ) + { + mlt_properties_set_int( properties, "consumer_deinterlace", progressive ); + mlt_properties_set_int( properties, "test_audio", 1 ); + } + + // Check for notifier and call with appropriate argument + mlt_properties playlist_properties = MLT_PRODUCER_PROPERTIES( producer ); + void ( *notifier )( void * ) = mlt_properties_get_data( playlist_properties, "notifier", NULL ); + if ( notifier != NULL ) + { + void *argument = mlt_properties_get_data( playlist_properties, "notifier_arg", NULL ); + notifier( argument ); + } + + // Update position on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_frame( producer ) ); + + // Position ourselves on the next frame + mlt_producer_prepare_next( producer ); + + return 0; +} + +/** Close the playlist. +*/ + +void mlt_playlist_close( mlt_playlist this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_PLAYLIST_PROPERTIES( this ) ) <= 0 ) + { + int i = 0; + this->parent.close = NULL; + for ( i = 0; i < this->count; i ++ ) + { + mlt_event_close( this->list[ i ]->event ); + mlt_producer_close( this->list[ i ]->producer ); + free( this->list[ i ] ); + } + mlt_producer_close( &this->blank ); + mlt_producer_close( &this->parent ); + free( this->list ); + free( this ); + } +} diff --git a/src/framework/mlt_playlist.h b/src/framework/mlt_playlist.h new file mode 100644 index 0000000..a19f45e --- /dev/null +++ b/src/framework/mlt_playlist.h @@ -0,0 +1,109 @@ +/* + * mlt_playlist.h -- playlist service class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_PLAYLIST_H_ +#define _MLT_PLAYLIST_H_ + +#include "mlt_producer.h" + +/** Structure for returning clip information. +*/ + +typedef struct +{ + int clip; + mlt_producer producer; + mlt_producer cut; + mlt_position start; + char *resource; + mlt_position frame_in; + mlt_position frame_out; + mlt_position frame_count; + mlt_position length; + float fps; + int repeat; +} +mlt_playlist_clip_info; + +/** Private definition. +*/ + +typedef struct playlist_entry_s playlist_entry; + +struct mlt_playlist_s +{ + struct mlt_producer_s parent; + struct mlt_producer_s blank; + + int size; + int count; + playlist_entry **list; +}; + +/** Public final methods +*/ + +#define MLT_PLAYLIST_PRODUCER( playlist ) ( &( playlist )->parent ) +#define MLT_PLAYLIST_SERVICE( playlist ) MLT_PRODUCER_SERVICE( MLT_PLAYLIST_PRODUCER( playlist ) ) +#define MLT_PLAYLIST_PROPERTIES( playlist ) MLT_SERVICE_PROPERTIES( MLT_PLAYLIST_SERVICE( playlist ) ) + +extern mlt_playlist mlt_playlist_init( ); +extern mlt_producer mlt_playlist_producer( mlt_playlist self ); +extern mlt_service mlt_playlist_service( mlt_playlist self ); +extern mlt_properties mlt_playlist_properties( mlt_playlist self ); +extern int mlt_playlist_count( mlt_playlist self ); +extern int mlt_playlist_clear( mlt_playlist self ); +extern int mlt_playlist_append( mlt_playlist self, mlt_producer producer ); +extern int mlt_playlist_append_io( mlt_playlist self, mlt_producer producer, mlt_position in, mlt_position out ); +extern int mlt_playlist_blank( mlt_playlist self, mlt_position length ); +extern mlt_position mlt_playlist_clip( mlt_playlist self, mlt_whence whence, int index ); +extern int mlt_playlist_current_clip( mlt_playlist self ); +extern mlt_producer mlt_playlist_current( mlt_playlist self ); +extern int mlt_playlist_get_clip_info( mlt_playlist self, mlt_playlist_clip_info *info, int index ); +extern int mlt_playlist_insert( mlt_playlist self, mlt_producer producer, int where, mlt_position in, mlt_position out ); +extern int mlt_playlist_remove( mlt_playlist self, int where ); +extern int mlt_playlist_move( mlt_playlist self, int from, int to ); +extern int mlt_playlist_resize_clip( mlt_playlist self, int clip, mlt_position in, mlt_position out ); +extern int mlt_playlist_repeat_clip( mlt_playlist self, int clip, int repeat ); +extern int mlt_playlist_split( mlt_playlist self, int clip, mlt_position position ); +extern int mlt_playlist_split_at( mlt_playlist self, mlt_position position, int left ); +extern int mlt_playlist_join( mlt_playlist self, int clip, int count, int merge ); +extern int mlt_playlist_mix( mlt_playlist self, int clip, int length, mlt_transition transition ); +extern int mlt_playlist_mix_add( mlt_playlist self, int clip, mlt_transition transition ); +extern mlt_producer mlt_playlist_get_clip( mlt_playlist self, int clip ); +extern mlt_producer mlt_playlist_get_clip_at( mlt_playlist self, mlt_position position ); +extern int mlt_playlist_get_clip_index_at( mlt_playlist self, mlt_position position ); +extern int mlt_playlist_clip_is_mix( mlt_playlist self, int clip ); +extern void mlt_playlist_consolidate_blanks( mlt_playlist self, int keep_length ); +extern int mlt_playlist_is_blank( mlt_playlist self, int clip ); +extern int mlt_playlist_is_blank_at( mlt_playlist self, mlt_position position ); +extern void mlt_playlist_insert_blank( mlt_playlist self, int clip, int length ); +extern void mlt_playlist_pad_blanks( mlt_playlist self, mlt_position position, int length, int find ); +extern mlt_producer mlt_playlist_replace_with_blank( mlt_playlist self, int clip ); +extern int mlt_playlist_insert_at( mlt_playlist self, mlt_position position, mlt_producer producer, int mode ); +extern int mlt_playlist_clip_start( mlt_playlist self, int clip ); +extern int mlt_playlist_clip_length( mlt_playlist self, int clip ); +extern int mlt_playlist_blanks_from( mlt_playlist self, int clip, int bounded ); +extern int mlt_playlist_remove_region( mlt_playlist self, mlt_position position, int length ); +extern int mlt_playlist_move_region( mlt_playlist self, mlt_position position, int length, int new_position ); +extern void mlt_playlist_close( mlt_playlist self ); + +#endif + diff --git a/src/framework/mlt_pool.c b/src/framework/mlt_pool.c new file mode 100644 index 0000000..099bbd0 --- /dev/null +++ b/src/framework/mlt_pool.c @@ -0,0 +1,355 @@ +/* + * mlt_pool.c -- memory pooling functionality + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "mlt_properties.h" +#include "mlt_deque.h" + +#include +#include +#include + +// Not nice - memalign is defined here apparently? +#ifdef linux +#include +#endif + +/** Singleton repositories +*/ + +static mlt_properties pools = NULL; + +/** Private pooling structure. +*/ + +typedef struct mlt_pool_s +{ + pthread_mutex_t lock; + mlt_deque stack; + int size; + int count; +} +*mlt_pool; + +typedef struct mlt_release_s +{ + mlt_pool pool; + int references; +} +*mlt_release; + +/** Create a pool. +*/ + +static mlt_pool pool_init( int size ) +{ + // Create the pool + mlt_pool this = calloc( 1, sizeof( struct mlt_pool_s ) ); + + // Initialise it + if ( this != NULL ) + { + // Initialise the mutex + pthread_mutex_init( &this->lock, NULL ); + + // Create the stack + this->stack = mlt_deque_init( ); + + // Assign the size + this->size = size; + } + + // Return it + return this; +} + +/** Get an item from the pool. +*/ + +static void *pool_fetch( mlt_pool this ) +{ + // We will generate a release object + void *ptr = NULL; + + // Sanity check + if ( this != NULL ) + { + // Lock the pool + pthread_mutex_lock( &this->lock ); + + // Check if the stack is empty + if ( mlt_deque_count( this->stack ) != 0 ) + { + // Pop the top of the stack + ptr = mlt_deque_pop_back( this->stack ); + + // Assign the reference + ( ( mlt_release )ptr )->references = 1; + } + else + { + // We need to generate a release item +#ifdef linux + mlt_release release = memalign( 16, this->size ); +#else + mlt_release release = malloc( this->size ); +#endif + + // Initialise it + if ( release != NULL ) + { + // Increment the number of items allocated to this pool + this->count ++; + + // Assign the pool + release->pool = this; + + // Assign the reference + release->references = 1; + + // Determine the ptr + ptr = ( void * )release + sizeof( struct mlt_release_s ); + } + } + + // Unlock the pool + pthread_mutex_unlock( &this->lock ); + } + + // Return the generated release object + return ptr; +} + +/** Return an item to the pool. +*/ + +static void pool_return( void *ptr ) +{ + // Sanity checks + if ( ptr != NULL ) + { + // Get the release pointer + mlt_release that = ptr - sizeof( struct mlt_release_s ); + + // Get the pool + mlt_pool this = that->pool; + + if ( this != NULL ) + { + // Lock the pool + pthread_mutex_lock( &this->lock ); + + // Push the that back back on to the stack + mlt_deque_push_back( this->stack, ptr ); + + // Unlock the pool + pthread_mutex_unlock( &this->lock ); + + // Ensure that we don't clean up + ptr = NULL; + } + } + + // Tidy up - this will only occur if the returned item is incorrect + if ( ptr != NULL ) + { + // Free the release itself + free( ptr - sizeof( struct mlt_release_s ) ); + } +} + +/** Destroy a pool. +*/ + +static void pool_close( mlt_pool this ) +{ + if ( this != NULL ) + { + // We need to free up all items in the pool + void *release = NULL; + + // Iterate through the stack until depleted + while ( ( release = mlt_deque_pop_back( this->stack ) ) != NULL ) + { + // We'll free this item now + free( release - sizeof( struct mlt_release_s ) ); + } + + // We can now close the stack + mlt_deque_close( this->stack ); + + // Destroy the mutex + pthread_mutex_destroy( &this->lock ); + + // Close the pool + free( this ); + } +} + +/** Initialise the pool. +*/ + +void mlt_pool_init( ) +{ + // Loop variable used to create the pools + int i = 0; + + // Create the pools + pools = mlt_properties_new( ); + + // Create the pools + for ( i = 8; i < 31; i ++ ) + { + // Each properties item needs a name + char name[ 32 ]; + + // Construct a pool + mlt_pool pool = pool_init( 1 << i ); + + // Generate a name + sprintf( name, "%d", i ); + + // Register with properties + mlt_properties_set_data( pools, name, pool, 0, ( mlt_destructor )pool_close, NULL ); + } +} + +/** Allocate size bytes from the pool. +*/ + +void *mlt_pool_alloc( int size ) +{ + // This will be used to obtain the pool to use + mlt_pool pool = NULL; + + // Determines the index of the pool to use + int index = 8; + + // Minimum size pooled is 256 bytes + size = size + sizeof( mlt_release ); + while ( ( 1 << index ) < size ) + index ++; + + // Now get the pool at the index + pool = mlt_properties_get_data_at( pools, index - 8, NULL ); + + // Now get the real item + return pool_fetch( pool ); +} + +/** Allocate size bytes from the pool. +*/ + +void *mlt_pool_realloc( void *ptr, int size ) +{ + // Result to return + void *result = NULL; + + // Check if we actually have an address + if ( ptr != NULL ) + { + // Get the release pointer + mlt_release that = ptr - sizeof( struct mlt_release_s ); + + // If the current pool this ptr belongs to is big enough + if ( size > that->pool->size - sizeof( struct mlt_release_s ) ) + { + // Allocate + result = mlt_pool_alloc( size ); + + // Copy + memcpy( result, ptr, that->pool->size - sizeof( struct mlt_release_s ) ); + + // Release + mlt_pool_release( ptr ); + } + else + { + // Nothing to do + result = ptr; + } + } + else + { + // Simply allocate + result = mlt_pool_alloc( size ); + } + + return result; +} + +/** Purge unused items in the pool. +*/ + +void mlt_pool_purge( ) +{ + int i = 0; + + // For each pool + for ( i = 0; i < mlt_properties_count( pools ); i ++ ) + { + // Get the pool + mlt_pool this = mlt_properties_get_data_at( pools, i, NULL ); + + // Pointer to unused memory + void *release = NULL; + + // Lock the pool + pthread_mutex_lock( &this->lock ); + + // We'll free all unused items now + while ( ( release = mlt_deque_pop_back( this->stack ) ) != NULL ) + free( release - sizeof( struct mlt_release_s ) ); + + // Unlock the pool + pthread_mutex_unlock( &this->lock ); + } +} + +/** Release the allocated memory. +*/ + +void mlt_pool_release( void *release ) +{ + // Return to the pool + pool_return( release ); +} + +/** Close the pool. +*/ + +void mlt_pool_close( ) +{ +#ifdef _MLT_POOL_CHECKS_ + // Stats dump on close + int i = 0; + fprintf( stderr, "Usage:\n\n" ); + for ( i = 0; i < mlt_properties_count( pools ); i ++ ) + { + mlt_pool pool = mlt_properties_get_data_at( pools, i, NULL ); + if ( pool->count ) + fprintf( stderr, "%d: allocated %d returned %d %c\n", pool->size, pool->count, mlt_deque_count( pool->stack ), + pool->count != mlt_deque_count( pool->stack ) ? '*' : ' ' ); + } +#endif + + // Close the properties + mlt_properties_close( pools ); +} + diff --git a/src/framework/mlt_pool.h b/src/framework/mlt_pool.h new file mode 100644 index 0000000..48bb811 --- /dev/null +++ b/src/framework/mlt_pool.h @@ -0,0 +1,31 @@ +/* + * mlt_pool.h -- memory pooling functionality + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_POOL_H +#define _MLT_POOL_H + +extern void mlt_pool_init( ); +extern void *mlt_pool_alloc( int size ); +extern void *mlt_pool_realloc( void *ptr, int size ); +extern void mlt_pool_release( void *release ); +extern void mlt_pool_purge( ); +extern void mlt_pool_close( ); + +#endif diff --git a/src/framework/mlt_producer.c b/src/framework/mlt_producer.c new file mode 100644 index 0000000..0628166 --- /dev/null +++ b/src/framework/mlt_producer.c @@ -0,0 +1,846 @@ +/* + * mlt_producer.c -- abstraction for all producer services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "mlt_producer.h" +#include "mlt_factory.h" +#include "mlt_frame.h" +#include "mlt_parser.h" +#include "mlt_profile.h" + +#include +#include +#include +#include + +/** Forward references. +*/ + +static int producer_get_frame( mlt_service this, mlt_frame_ptr frame, int index ); +static void mlt_producer_property_changed( mlt_service owner, mlt_producer this, char *name ); +static void mlt_producer_service_changed( mlt_service owner, mlt_producer this ); + +//#define _MLT_PRODUCER_CHECKS_ 1 + +#ifdef _MLT_PRODUCER_CHECKS_ +static int producers_created = 0; +static int producers_destroyed = 0; +#endif + +/** Constructor +*/ + +int mlt_producer_init( mlt_producer this, void *child ) +{ + // Check that we haven't received NULL + int error = this == NULL; + + // Continue if no error + if ( error == 0 ) + { +#ifdef _MLT_PRODUCER_CHECKS_ + producers_created ++; +#endif + + // Initialise the producer + memset( this, 0, sizeof( struct mlt_producer_s ) ); + + // Associate with the child + this->child = child; + + // Initialise the service + if ( mlt_service_init( &this->parent, this ) == 0 ) + { + // The parent is the service + mlt_service parent = &this->parent; + + // Define the parent close + parent->close = ( mlt_destructor )mlt_producer_close; + parent->close_object = this; + + // For convenience, we'll assume the close_object is this + this->close_object = this; + + // Get the properties of the parent + mlt_properties properties = MLT_SERVICE_PROPERTIES( parent ); + + // Set the default properties + mlt_properties_set( properties, "mlt_type", "mlt_producer" ); + mlt_properties_set_position( properties, "_position", 0.0 ); + mlt_properties_set_double( properties, "_frame", 0 ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_profile_sar( NULL ) ); + mlt_properties_set_double( properties, "_speed", 1.0 ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", 14999 ); + mlt_properties_set_position( properties, "length", 15000 ); + mlt_properties_set( properties, "eof", "pause" ); + mlt_properties_set( properties, "resource", "" ); + + // Override service get_frame + parent->get_frame = producer_get_frame; + + mlt_events_listen( properties, this, "service-changed", ( mlt_listener )mlt_producer_service_changed ); + mlt_events_listen( properties, this, "property-changed", ( mlt_listener )mlt_producer_property_changed ); + mlt_events_register( properties, "producer-changed", NULL ); + } + } + + return error; +} + +/** Listener for property changes. +*/ + +static void mlt_producer_property_changed( mlt_service owner, mlt_producer this, char *name ) +{ + if ( !strcmp( name, "in" ) || !strcmp( name, "out" ) || !strcmp( name, "length" ) ) + mlt_events_fire( MLT_PRODUCER_PROPERTIES( mlt_producer_cut_parent( this ) ), "producer-changed", NULL ); +} + +/** Listener for service changes. +*/ + +static void mlt_producer_service_changed( mlt_service owner, mlt_producer this ) +{ + mlt_events_fire( MLT_PRODUCER_PROPERTIES( mlt_producer_cut_parent( this ) ), "producer-changed", NULL ); +} + +/** Create a new producer. +*/ + +mlt_producer mlt_producer_new( ) +{ + mlt_producer this = malloc( sizeof( struct mlt_producer_s ) ); + mlt_producer_init( this, NULL ); + return this; +} + +/** Determine if producer is a cut. +*/ + +int mlt_producer_is_cut( mlt_producer this ) +{ + return mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( this ), "_cut" ); +} + +/** Determine if producer is a mix. +*/ + +int mlt_producer_is_mix( mlt_producer this ) +{ + mlt_properties properties = this != NULL ? MLT_PRODUCER_PROPERTIES( this ) : NULL; + mlt_tractor tractor = properties != NULL ? mlt_properties_get_data( properties, "mlt_mix", NULL ) : NULL; + return tractor != NULL; +} + +/** Determine if the producer is a blank [from a playlist]. +*/ + +int mlt_producer_is_blank( mlt_producer this ) +{ + return this == NULL || !strcmp( mlt_properties_get( MLT_PRODUCER_PROPERTIES( mlt_producer_cut_parent( this ) ), "resource" ), "blank" ); +} + +/** Obtain the parent producer. +*/ + +mlt_producer mlt_producer_cut_parent( mlt_producer this ) +{ + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + if ( mlt_producer_is_cut( this ) ) + return mlt_properties_get_data( properties, "_cut_parent", NULL ); + else + return this; +} + +/** Create a cut of this producer +*/ + +mlt_producer mlt_producer_cut( mlt_producer this, int in, int out ) +{ + mlt_producer result = mlt_producer_new( ); + mlt_producer parent = mlt_producer_cut_parent( this ); + mlt_properties properties = MLT_PRODUCER_PROPERTIES( result ); + mlt_properties parent_props = MLT_PRODUCER_PROPERTIES( parent ); + + mlt_events_block( MLT_PRODUCER_PROPERTIES( result ), MLT_PRODUCER_PROPERTIES( result ) ); + // Special case - allow for a cut of the entire producer (this will squeeze all other cuts to 0) + if ( in <= 0 ) + in = 0; + if ( ( out < 0 || out >= mlt_producer_get_length( parent ) ) && !mlt_producer_is_blank( this ) ) + out = mlt_producer_get_length( parent ) - 1; + + mlt_properties_inc_ref( parent_props ); + mlt_properties_set_int( properties, "_cut", 1 ); + mlt_properties_set_data( properties, "_cut_parent", parent, 0, ( mlt_destructor )mlt_producer_close, NULL ); + mlt_properties_set_position( properties, "length", mlt_properties_get_position( parent_props, "length" ) ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_properties_get_double( parent_props, "aspect_ratio" ) ); + mlt_producer_set_in_and_out( result, in, out ); + + return result; +} + +/** Get the parent service object. +*/ + +mlt_service mlt_producer_service( mlt_producer this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Get the producer properties. +*/ + +mlt_properties mlt_producer_properties( mlt_producer this ) +{ + return MLT_SERVICE_PROPERTIES( &this->parent ); +} + +/** Seek to a specified position. +*/ + +int mlt_producer_seek( mlt_producer this, mlt_position position ) +{ + // Determine eof handling + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + char *eof = mlt_properties_get( properties, "eof" ); + int use_points = 1 - mlt_properties_get_int( properties, "ignore_points" ); + + // Recursive behaviour for cuts - repositions parent and then repositions cut + // hence no return on this condition + if ( mlt_producer_is_cut( this ) ) + mlt_producer_seek( mlt_producer_cut_parent( this ), position + mlt_producer_get_in( this ) ); + + // Check bounds + if ( position < 0 || mlt_producer_get_playtime( this ) == 0 ) + { + position = 0; + } + else if ( use_points && ( eof == NULL || !strcmp( eof, "pause" ) ) && position >= mlt_producer_get_playtime( this ) ) + { + mlt_producer_set_speed( this, 0 ); + position = mlt_producer_get_playtime( this ) - 1; + } + else if ( use_points && !strcmp( eof, "loop" ) && position >= mlt_producer_get_playtime( this ) ) + { + position = (int)position % (int)mlt_producer_get_playtime( this ); + } + + // Set the position + mlt_properties_set_position( MLT_PRODUCER_PROPERTIES( this ), "_position", position ); + + // Calculate the absolute frame + mlt_properties_set_position( MLT_PRODUCER_PROPERTIES( this ), "_frame", use_points * mlt_producer_get_in( this ) + position ); + + return 0; +} + +/** Get the current position (relative to in point). +*/ + +mlt_position mlt_producer_position( mlt_producer this ) +{ + return mlt_properties_get_position( MLT_PRODUCER_PROPERTIES( this ), "_position" ); +} + +/** Get the current position (relative to start of producer). +*/ + +mlt_position mlt_producer_frame( mlt_producer this ) +{ + return mlt_properties_get_position( MLT_PRODUCER_PROPERTIES( this ), "_frame" ); +} + +/** Set the playing speed. +*/ + +int mlt_producer_set_speed( mlt_producer this, double speed ) +{ + return mlt_properties_set_double( MLT_PRODUCER_PROPERTIES( this ), "_speed", speed ); +} + +/** Get the playing speed. +*/ + +double mlt_producer_get_speed( mlt_producer this ) +{ + return mlt_properties_get_double( MLT_PRODUCER_PROPERTIES( this ), "_speed" ); +} + +/** Get the frames per second. +*/ + +double mlt_producer_get_fps( mlt_producer this ) +{ + return mlt_profile_fps( NULL ); +} + +/** Set the in and out points. +*/ + +int mlt_producer_set_in_and_out( mlt_producer this, mlt_position in, mlt_position out ) +{ + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Correct ins and outs if necessary + if ( in < 0 ) + in = 0; + else if ( in >= mlt_producer_get_length( this ) ) + in = mlt_producer_get_length( this ) - 1; + + if ( out < 0 ) + out = 0; + else if ( out >= mlt_producer_get_length( this ) && !mlt_producer_is_blank( this ) ) + out = mlt_producer_get_length( this ) - 1; + else if ( out >= mlt_producer_get_length( this ) && mlt_producer_is_blank( this ) ) + mlt_properties_set_position( MLT_PRODUCER_PROPERTIES( this ), "length", out + 1 ); + + // Swap ins and outs if wrong + if ( out < in ) + { + mlt_position t = in; + in = out; + out = t; + } + + // Set the values + mlt_events_block( properties, properties ); + mlt_properties_set_position( properties, "in", in ); + mlt_events_unblock( properties, properties ); + mlt_properties_set_position( properties, "out", out ); + + return 0; +} + +/** Physically reduce the producer (typically a cut) to a 0 length. + Essentially, all 0 length cuts should be immediately removed by containers. +*/ + +int mlt_producer_clear( mlt_producer this ) +{ + if ( this != NULL ) + { + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + mlt_events_block( properties, properties ); + mlt_properties_set_position( properties, "in", 0 ); + mlt_events_unblock( properties, properties ); + mlt_properties_set_position( properties, "out", -1 ); + } + return 0; +} + +/** Get the in point. +*/ + +mlt_position mlt_producer_get_in( mlt_producer this ) +{ + return mlt_properties_get_position( MLT_PRODUCER_PROPERTIES( this ), "in" ); +} + +/** Get the out point. +*/ + +mlt_position mlt_producer_get_out( mlt_producer this ) +{ + return mlt_properties_get_position( MLT_PRODUCER_PROPERTIES( this ), "out" ); +} + +/** Get the total play time. +*/ + +mlt_position mlt_producer_get_playtime( mlt_producer this ) +{ + return mlt_producer_get_out( this ) - mlt_producer_get_in( this ) + 1; +} + +/** Get the total length of the producer. +*/ + +mlt_position mlt_producer_get_length( mlt_producer this ) +{ + return mlt_properties_get_position( MLT_PRODUCER_PROPERTIES( this ), "length" ); +} + +/** Prepare for next frame. +*/ + +void mlt_producer_prepare_next( mlt_producer this ) +{ + if ( mlt_producer_get_speed( this ) != 0 ) + mlt_producer_seek( this, mlt_producer_position( this ) + mlt_producer_get_speed( this ) ); +} + +/** Get a frame. +*/ + +static int producer_get_frame( mlt_service service, mlt_frame_ptr frame, int index ) +{ + int result = 1; + mlt_producer this = service != NULL ? service->child : NULL; + + if ( this != NULL && !mlt_producer_is_cut( this ) ) + { + // Get the properties of this producer + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Determine eof handling + char *eof = mlt_properties_get( MLT_PRODUCER_PROPERTIES( this ), "eof" ); + + // Get the speed of the producer + double speed = mlt_producer_get_speed( this ); + + // We need to use the clone if it's specified + mlt_producer clone = mlt_properties_get_data( properties, "use_clone", NULL ); + + // If no clone is specified, use this + clone = clone == NULL ? this : clone; + + // A properly instatiated producer will have a get_frame method... + if ( this->get_frame == NULL || ( !strcmp( eof, "continue" ) && mlt_producer_position( this ) > mlt_producer_get_out( this ) ) ) + { + // Generate a test frame + *frame = mlt_frame_init( ); + + // Set the position + result = mlt_frame_set_position( *frame, mlt_producer_position( this ) ); + + // Mark as a test card + mlt_properties_set_int( MLT_FRAME_PROPERTIES( *frame ), "test_image", 1 ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( *frame ), "test_audio", 1 ); + + // Calculate the next position + mlt_producer_prepare_next( this ); + } + else + { + // Get the frame from the implementation + result = this->get_frame( clone, frame, index ); + } + + // Copy the fps and speed of the producer onto the frame + properties = MLT_FRAME_PROPERTIES( *frame ); + mlt_properties_set_double( properties, "_speed", speed ); + mlt_properties_set_int( properties, "test_audio", mlt_frame_is_test_audio( *frame ) ); + mlt_properties_set_int( properties, "test_image", mlt_frame_is_test_card( *frame ) ); + if ( mlt_properties_get_data( properties, "_producer", NULL ) == NULL ) + mlt_properties_set_data( properties, "_producer", service, 0, NULL, NULL ); + } + else if ( this != NULL ) + { + // Get the speed of the cut + double speed = mlt_producer_get_speed( this ); + + // Get the parent of this cut + mlt_producer parent = mlt_producer_cut_parent( this ); + + // Get the properties of the parent + mlt_properties parent_properties = MLT_PRODUCER_PROPERTIES( parent ); + + // Get the properties of the cut + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Determine the clone index + int clone_index = mlt_properties_get_int( properties, "_clone" ); + + // Determine the clone to use + mlt_producer clone = this; + + if ( clone_index > 0 ) + { + char key[ 25 ]; + sprintf( key, "_clone.%d", clone_index - 1 ); + clone = mlt_properties_get_data( MLT_PRODUCER_PROPERTIES( mlt_producer_cut_parent( this ) ), key, NULL ); + if ( clone == NULL ) fprintf( stderr, "requested clone doesn't exist %d\n", clone_index ); + clone = clone == NULL ? this : clone; + } + else + { + clone = parent; + } + + // We need to seek to the correct position in the clone + mlt_producer_seek( clone, mlt_producer_get_in( this ) + mlt_properties_get_int( properties, "_position" ) ); + + // Assign the clone property to the parent + mlt_properties_set_data( parent_properties, "use_clone", clone, 0, NULL, NULL ); + + // Now get the frame from the parents service + result = mlt_service_get_frame( MLT_PRODUCER_SERVICE( parent ), frame, index ); + + // We're done with the clone now + mlt_properties_set_data( parent_properties, "use_clone", NULL, 0, NULL, NULL ); + + // This is useful and required by always_active transitions to determine in/out points of the cut + if ( mlt_properties_get_data( MLT_FRAME_PROPERTIES( *frame ), "_producer", NULL ) == MLT_PRODUCER_SERVICE( parent ) ) + mlt_properties_set_data( MLT_FRAME_PROPERTIES( *frame ), "_producer", this, 0, NULL, NULL ); + + mlt_properties_set_double( MLT_FRAME_PROPERTIES( *frame ), "_speed", speed ); + mlt_producer_prepare_next( this ); + } + else + { + *frame = mlt_frame_init( ); + result = 0; + } + + // Pass on all meta properties from the producer/cut on to the frame + if ( *frame != NULL && this != NULL ) + { + int i = 0; + mlt_properties p_props = MLT_PRODUCER_PROPERTIES( this ); + mlt_properties f_props = MLT_FRAME_PROPERTIES( *frame ); + int count = mlt_properties_count( p_props ); + for ( i = 0; i < count; i ++ ) + { + char *name = mlt_properties_get_name( p_props, i ); + if ( !strncmp( name, "meta.", 5 ) ) + mlt_properties_set( f_props, name, mlt_properties_get( p_props, name ) ); + else if ( !strncmp( name, "set.", 4 ) ) + mlt_properties_set( f_props, name + 4, mlt_properties_get( p_props, name ) ); + } + } + + return result; +} + +/** Attach a filter. +*/ + +int mlt_producer_attach( mlt_producer this, mlt_filter filter ) +{ + return mlt_service_attach( MLT_PRODUCER_SERVICE( this ), filter ); +} + +/** Detach a filter. +*/ + +int mlt_producer_detach( mlt_producer this, mlt_filter filter ) +{ + return mlt_service_detach( MLT_PRODUCER_SERVICE( this ), filter ); +} + +/** Retrieve a filter. +*/ + +mlt_filter mlt_producer_filter( mlt_producer this, int index ) +{ + return mlt_service_filter( MLT_PRODUCER_SERVICE( this ), index ); +} + +/** Clone this producer. +*/ + +static mlt_producer mlt_producer_clone( mlt_producer this ) +{ + mlt_producer clone = NULL; + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + char *resource = mlt_properties_get( properties, "resource" ); + char *service = mlt_properties_get( properties, "mlt_service" ); + + mlt_events_block( mlt_factory_event_object( ), mlt_factory_event_object( ) ); + + if ( service != NULL ) + clone = mlt_factory_producer( service, resource ); + + if ( clone == NULL && resource != NULL ) + clone = mlt_factory_producer( "fezzik", resource ); + + if ( clone != NULL ) + mlt_properties_inherit( MLT_PRODUCER_PROPERTIES( clone ), properties ); + + mlt_events_unblock( mlt_factory_event_object( ), mlt_factory_event_object( ) ); + + return clone; +} + +/** Create clones. +*/ + +static void mlt_producer_set_clones( mlt_producer this, int clones ) +{ + mlt_producer parent = mlt_producer_cut_parent( this ); + mlt_properties properties = MLT_PRODUCER_PROPERTIES( parent ); + int existing = mlt_properties_get_int( properties, "_clones" ); + int i = 0; + char key[ 25 ]; + + // If the number of existing clones is different, then create/remove as necessary + if ( existing != clones ) + { + if ( existing < clones ) + { + for ( i = existing; i < clones; i ++ ) + { + mlt_producer clone = mlt_producer_clone( parent ); + sprintf( key, "_clone.%d", i ); + mlt_properties_set_data( properties, key, clone, 0, ( mlt_destructor )mlt_producer_close, NULL ); + } + } + else + { + for ( i = clones; i < existing; i ++ ) + { + sprintf( key, "_clone.%d", i ); + mlt_properties_set_data( properties, key, NULL, 0, NULL, NULL ); + } + } + } + + // Ensure all properties on the parent are passed to the clones + for ( i = 0; i < clones; i ++ ) + { + mlt_producer clone = NULL; + sprintf( key, "_clone.%d", i ); + clone = mlt_properties_get_data( properties, key, NULL ); + if ( clone != NULL ) + mlt_properties_pass( MLT_PRODUCER_PROPERTIES( clone ), properties, "" ); + } + + // Update the number of clones on the properties + mlt_properties_set_int( properties, "_clones", clones ); +} + +/** Optimise for overlapping cuts from the same clip. +*/ + +typedef struct +{ + int multitrack; + int track; + int position; + int length; + int offset; +} +track_info; + +typedef struct +{ + mlt_producer cut; + int start; + int end; +} +clip_references; + +static int intersect( clip_references *a, clip_references *b ) +{ + int diff = ( a->start - b->start ) + ( a->end - b->end ); + return diff >= 0 && diff < ( a->end - a->start + 1 ); +} + +static int push( mlt_parser this, int multitrack, int track, int position ) +{ + mlt_properties properties = mlt_parser_properties( this ); + mlt_deque stack = mlt_properties_get_data( properties, "stack", NULL ); + track_info *info = malloc( sizeof( track_info ) ); + info->multitrack = multitrack; + info->track = track; + info->position = position; + info->length = 0; + info->offset = 0; + return mlt_deque_push_back( stack, info ); +} + +static track_info *pop( mlt_parser this ) +{ + mlt_properties properties = mlt_parser_properties( this ); + mlt_deque stack = mlt_properties_get_data( properties, "stack", NULL ); + return mlt_deque_pop_back( stack ); +} + +static track_info *peek( mlt_parser this ) +{ + mlt_properties properties = mlt_parser_properties( this ); + mlt_deque stack = mlt_properties_get_data( properties, "stack", NULL ); + return mlt_deque_peek_back( stack ); +} + +static int on_start_multitrack( mlt_parser this, mlt_multitrack object ) +{ + track_info *info = peek( this ); + return push( this, info->multitrack ++, info->track, info->position ); +} + +static int on_start_track( mlt_parser this ) +{ + track_info *info = peek( this ); + info->position -= info->offset; + info->length -= info->offset; + return push( this, info->multitrack, info->track ++, info->position ); +} + +static int on_start_producer( mlt_parser this, mlt_producer object ) +{ + mlt_properties properties = mlt_parser_properties( this ); + mlt_properties producers = mlt_properties_get_data( properties, "producers", NULL ); + mlt_producer parent = mlt_producer_cut_parent( object ); + if ( mlt_service_identify( ( mlt_service )mlt_producer_cut_parent( object ) ) == producer_type && mlt_producer_is_cut( object ) ) + { + int ref_count = 0; + clip_references *old_refs = NULL; + clip_references *refs = NULL; + char key[ 50 ]; + int count = 0; + track_info *info = peek( this ); + sprintf( key, "%p", parent ); + mlt_properties_get_data( producers, key, &count ); + mlt_properties_set_data( producers, key, parent, ++ count, NULL, NULL ); + old_refs = mlt_properties_get_data( properties, key, &ref_count ); + refs = malloc( ( ref_count + 1 ) * sizeof( clip_references ) ); + if ( old_refs != NULL ) + memcpy( refs, old_refs, ref_count * sizeof( clip_references ) ); + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( object ), "_clone", -1 ); + refs[ ref_count ].cut = object; + refs[ ref_count ].start = info->position; + refs[ ref_count ].end = info->position + mlt_producer_get_playtime( object ) - 1; + mlt_properties_set_data( properties, key, refs, ++ ref_count, free, NULL ); + info->position += mlt_producer_get_playtime( object ); + info->length += mlt_producer_get_playtime( object ); + } + return 0; +} + +static int on_end_track( mlt_parser this ) +{ + track_info *track = pop( this ); + track_info *multi = peek( this ); + multi->length += track->length; + multi->position += track->length; + multi->offset = track->length; + free( track ); + return 0; +} + +static int on_end_multitrack( mlt_parser this, mlt_multitrack object ) +{ + track_info *multi = pop( this ); + track_info *track = peek( this ); + track->position += multi->length; + track->length += multi->length; + free( multi ); + return 0; +} + +int mlt_producer_optimise( mlt_producer this ) +{ + int error = 1; + mlt_parser parser = mlt_parser_new( ); + if ( parser != NULL ) + { + int i = 0, j = 0, k = 0; + mlt_properties properties = mlt_parser_properties( parser ); + mlt_properties producers = mlt_properties_new( ); + mlt_deque stack = mlt_deque_init( ); + mlt_properties_set_data( properties, "producers", producers, 0, ( mlt_destructor )mlt_properties_close, NULL ); + mlt_properties_set_data( properties, "stack", stack, 0, ( mlt_destructor )mlt_deque_close, NULL ); + parser->on_start_producer = on_start_producer; + parser->on_start_track = on_start_track; + parser->on_end_track = on_end_track; + parser->on_start_multitrack = on_start_multitrack; + parser->on_end_multitrack = on_end_multitrack; + push( parser, 0, 0, 0 ); + mlt_parser_start( parser, MLT_PRODUCER_SERVICE( this ) ); + free( pop( parser ) ); + for ( k = 0; k < mlt_properties_count( producers ); k ++ ) + { + char *name = mlt_properties_get_name( producers, k ); + int count = 0; + int clones = 0; + int max_clones = 0; + mlt_producer producer = mlt_properties_get_data( producers, name, &count ); + if ( producer != NULL && count > 1 ) + { + clip_references *refs = mlt_properties_get_data( properties, name, &count ); + for ( i = 0; i < count; i ++ ) + { + clones = 0; + for ( j = i + 1; j < count; j ++ ) + { + if ( intersect( &refs[ i ], &refs[ j ] ) ) + { + clones ++; + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( refs[ j ].cut ), "_clone", clones ); + } + } + if ( clones > max_clones ) + max_clones = clones; + } + + for ( i = 0; i < count; i ++ ) + { + mlt_producer cut = refs[ i ].cut; + if ( mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( cut ), "_clone" ) == -1 ) + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( cut ), "_clone", 0 ); + } + + mlt_producer_set_clones( producer, max_clones ); + } + else if ( producer != NULL ) + { + clip_references *refs = mlt_properties_get_data( properties, name, &count ); + for ( i = 0; i < count; i ++ ) + { + mlt_producer cut = refs[ i ].cut; + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( cut ), "_clone", 0 ); + } + mlt_producer_set_clones( producer, 0 ); + } + } + mlt_parser_close( parser ); + } + return error; +} + +/** Close the producer. +*/ + +void mlt_producer_close( mlt_producer this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_PRODUCER_PROPERTIES( this ) ) <= 0 ) + { + this->parent.close = NULL; + + if ( this->close != NULL ) + { + this->close( this->close_object ); + } + else + { + int destroy = mlt_producer_is_cut( this ); + +#if _MLT_PRODUCER_CHECKS_ == 1 + // Show debug info + mlt_properties_debug( MLT_PRODUCER_PROPERTIES( this ), "Producer closing", stderr ); +#endif + +#ifdef _MLT_PRODUCER_CHECKS_ + // Increment destroyed count + producers_destroyed ++; + + // Show current stats - these should match when the app is closed + fprintf( stderr, "Producers created %d, destroyed %d\n", producers_created, producers_destroyed ); +#endif + + mlt_service_close( &this->parent ); + + if ( destroy ) + free( this ); + } + } +} diff --git a/src/framework/mlt_producer.h b/src/framework/mlt_producer.h new file mode 100644 index 0000000..c1a0531 --- /dev/null +++ b/src/framework/mlt_producer.h @@ -0,0 +1,79 @@ +/* + * mlt_producer.h -- abstraction for all producer services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_PRODUCER_H_ +#define _MLT_PRODUCER_H_ + +#include "mlt_service.h" +#include "mlt_filter.h" + +/** The interface definition for all producers. +*/ + +struct mlt_producer_s +{ + /* We're implementing service here */ + struct mlt_service_s parent; + + /* Public virtual methods */ + int ( *get_frame )( mlt_producer, mlt_frame_ptr, int ); + mlt_destructor close; + void *close_object; + + /* Private data */ + void *local; + void *child; +}; + +/** Public final methods +*/ + +#define MLT_PRODUCER_SERVICE( producer ) ( &( producer )->parent ) +#define MLT_PRODUCER_PROPERTIES( producer ) MLT_SERVICE_PROPERTIES( MLT_PRODUCER_SERVICE( producer ) ) + +extern int mlt_producer_init( mlt_producer self, void *child ); +extern mlt_producer mlt_producer_new( ); +extern mlt_service mlt_producer_service( mlt_producer self ); +extern mlt_properties mlt_producer_properties( mlt_producer self ); +extern int mlt_producer_seek( mlt_producer self, mlt_position position ); +extern mlt_position mlt_producer_position( mlt_producer self ); +extern mlt_position mlt_producer_frame( mlt_producer self ); +extern int mlt_producer_set_speed( mlt_producer self, double speed ); +extern double mlt_producer_get_speed( mlt_producer self ); +extern double mlt_producer_get_fps( mlt_producer self ); +extern int mlt_producer_set_in_and_out( mlt_producer self, mlt_position in, mlt_position out ); +extern int mlt_producer_clear( mlt_producer self ); +extern mlt_position mlt_producer_get_in( mlt_producer self ); +extern mlt_position mlt_producer_get_out( mlt_producer self ); +extern mlt_position mlt_producer_get_playtime( mlt_producer self ); +extern mlt_position mlt_producer_get_length( mlt_producer self ); +extern void mlt_producer_prepare_next( mlt_producer self ); +extern int mlt_producer_attach( mlt_producer self, mlt_filter filter ); +extern int mlt_producer_detach( mlt_producer self, mlt_filter filter ); +extern mlt_filter mlt_producer_filter( mlt_producer self, int index ); +extern mlt_producer mlt_producer_cut( mlt_producer self, int in, int out ); +extern int mlt_producer_is_cut( mlt_producer self ); +extern int mlt_producer_is_mix( mlt_producer self ); +extern int mlt_producer_is_blank( mlt_producer self ); +extern mlt_producer mlt_producer_cut_parent( mlt_producer self ); +extern int mlt_producer_optimise( mlt_producer self ); +extern void mlt_producer_close( mlt_producer self ); + +#endif diff --git a/src/framework/mlt_profile.c b/src/framework/mlt_profile.c new file mode 100644 index 0000000..5a627b5 --- /dev/null +++ b/src/framework/mlt_profile.c @@ -0,0 +1,241 @@ +/* + * mlt_profile.c -- video output definition + * Copyright (C) 2007 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "mlt_profile.h" +#include "mlt_factory.h" +#include "mlt_properties.h" + +#include +#include +#include + +#define PROFILES_DIR "/share/mlt/profiles/" + +static mlt_profile profile = NULL; + +/** Get the current profile +* Builds one for PAL DV if non-existing +*/ + +mlt_profile mlt_profile_get( ) +{ + if ( !profile ) + { + profile = calloc( 1, sizeof( struct mlt_profile_s ) ); + if ( profile ) + { + mlt_environment_set( "MLT_PROFILE", "dv_pal" ); + profile->description = strdup( "PAL 4:3 DV or DVD" ); + profile->frame_rate_num = 25; + profile->frame_rate_den = 1; + profile->width = 720; + profile->height = 576; + profile->progressive = 0; + profile->sample_aspect_num = 59; + profile->sample_aspect_den = 54; + profile->display_aspect_num = 4; + profile->display_aspect_den = 3; + } + } + return profile; +} + + +/** Load a profile from the system folder +*/ + +mlt_profile mlt_profile_select( const char *name ) +{ + char *filename = NULL; + const char *prefix = getenv( "MLT_PROFILES_PATH" ); + mlt_properties properties = mlt_properties_load( name ); + + // Try to load from file specification + if ( properties && mlt_properties_get_int( properties, "width" ) ) + { + filename = calloc( 1, strlen( name ) + 1 ); + } + // Load from $prefix/share/mlt/profiles + else if ( prefix == NULL ) + { + prefix = PREFIX; + filename = calloc( 1, strlen( prefix ) + strlen( PROFILES_DIR ) + strlen( name ) + 2 ); + strcpy( filename, prefix ); + if ( filename[ strlen( filename ) - 1 ] != '/' ) + filename[ strlen( filename ) ] = '/'; + strcat( filename, PROFILES_DIR ); + } + // Use environment variable instead + else + { + filename = calloc( 1, strlen( prefix ) + strlen( name ) + 2 ); + strcpy( filename, prefix ); + if ( filename[ strlen( filename ) - 1 ] != '/' ) + filename[ strlen( filename ) ] = '/'; + } + + // Finish loading + strcat( filename, name ); + mlt_profile_load_file( filename ); + + // Cleanup + mlt_properties_close( properties ); + free( filename ); + + return profile; +} + +/** Load a profile from specific file +*/ + +mlt_profile mlt_profile_load_file( const char *file ) +{ + // Load the profile as properties + mlt_properties properties = mlt_properties_load( file ); + if ( properties && mlt_properties_get_int( properties, "width" ) ) + { + mlt_profile_load_properties( properties ); + mlt_properties_close( properties ); + + // Set MLT_PROFILE to basename + char *filename = strdup( file ); + mlt_environment_set( "MLT_PROFILE", basename( filename ) ); + free( filename ); + } + else + { + // Cleanup + mlt_properties_close( properties ); + mlt_profile_close(); + // Failover + mlt_profile_get(); + } + + // Set MLT_NORMALISATION to appease legacy modules + char *profile_name = mlt_environment( "MLT_PROFILE" ); + if ( strstr( profile_name, "_ntsc" ) || + strstr( profile_name, "_60" ) || + strstr( profile_name, "_30" ) ) + { + mlt_environment_set( "MLT_NORMALISATION", "NTSC" ); + } + else if ( strstr( profile_name, "_pal" ) || + strstr( profile_name, "_50" ) || + strstr( profile_name, "_25" ) ) + { + mlt_environment_set( "MLT_NORMALISATION", "PAL" ); + } + + return profile; +} + +/** Load a profile from a properties object +*/ + +mlt_profile mlt_profile_load_properties( mlt_properties properties ) +{ + mlt_profile_close(); + profile = calloc( 1, sizeof( struct mlt_profile_s ) ); + if ( profile ) + { + if ( mlt_properties_get( properties, "name" ) ) + mlt_environment_set( "MLT_PROFILE", mlt_properties_get( properties, "name" ) ); + if ( mlt_properties_get( properties, "description" ) ) + profile->description = strdup( mlt_properties_get( properties, "description" ) ); + profile->frame_rate_num = mlt_properties_get_int( properties, "frame_rate_num" ); + profile->frame_rate_den = mlt_properties_get_int( properties, "frame_rate_den" ); + profile->width = mlt_properties_get_int( properties, "width" ); + profile->height = mlt_properties_get_int( properties, "height" ); + profile->progressive = mlt_properties_get_int( properties, "progressive" ); + profile->sample_aspect_num = mlt_properties_get_int( properties, "sample_aspect_num" ); + profile->sample_aspect_den = mlt_properties_get_int( properties, "sample_aspect_den" ); + profile->display_aspect_num = mlt_properties_get_int( properties, "display_aspect_num" ); + profile->display_aspect_den = mlt_properties_get_int( properties, "display_aspect_den" ); + } + return profile; +} + +/** Load an anonymous profile from string +*/ + +mlt_profile mlt_profile_load_string( const char *string ) +{ + mlt_properties properties = mlt_properties_new(); + if ( properties ) + { + const char *p = string; + while ( p ) + { + if ( strcmp( p, "" ) && p[ 0 ] != '#' ) + mlt_properties_parse( properties, p ); + p = strchr( p, '\n' ); + if ( p ) p++; + } + } + return mlt_profile_load_properties( properties ); +} + +/** Get the framerate as float +*/ + +double mlt_profile_fps( mlt_profile aprofile ) +{ + if ( aprofile ) + return ( double ) aprofile->frame_rate_num / aprofile->frame_rate_den; + else + return ( double ) mlt_profile_get()->frame_rate_num / mlt_profile_get()->frame_rate_den; +} + +/** Get the sample aspect ratio as float +*/ + +double mlt_profile_sar( mlt_profile aprofile ) +{ + if ( aprofile ) + return ( double ) aprofile->sample_aspect_num / aprofile->sample_aspect_den; + else + return ( double ) mlt_profile_get()->sample_aspect_num / mlt_profile_get()->sample_aspect_den; +} + +/** Get the display aspect ratio as float +*/ + +double mlt_profile_dar( mlt_profile aprofile ) +{ + if ( aprofile ) + return ( double ) aprofile->display_aspect_num / aprofile->display_aspect_den; + else + return ( double ) mlt_profile_get()->display_aspect_num / mlt_profile_get()->display_aspect_den; +} + +/** Free up the global profile resources +*/ + +void mlt_profile_close( ) +{ + if ( profile ) + { + if ( profile->description ) + free( profile->description ); + profile->description = NULL; + free( profile ); + profile = NULL; + } +} diff --git a/src/framework/mlt_profile.h b/src/framework/mlt_profile.h new file mode 100644 index 0000000..29f97ae --- /dev/null +++ b/src/framework/mlt_profile.h @@ -0,0 +1,49 @@ +/* + * mlt_profile.h -- video output definition + * Copyright (C) 2007 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_PROFILE_H +#define _MLT_PROFILE_H + +#include "mlt_types.h" + +struct mlt_profile_s +{ + char* description; + int frame_rate_num; + int frame_rate_den; + int width; + int height; + int progressive; + int sample_aspect_num; + int sample_aspect_den; + int display_aspect_num; + int display_aspect_den; +}; + +extern mlt_profile mlt_profile_get( ); +extern mlt_profile mlt_profile_select( const char *name ); +extern mlt_profile mlt_profile_load_file( const char *file ); +extern mlt_profile mlt_profile_load_properties( mlt_properties properties ); +extern mlt_profile mlt_profile_load_string( const char *string ); +extern double mlt_profile_fps( mlt_profile profile ); +extern double mlt_profile_sar( mlt_profile profile ); +extern double mlt_profile_dar( mlt_profile profile ); +extern void mlt_profile_close( ); +#endif diff --git a/src/framework/mlt_properties.c b/src/framework/mlt_properties.c new file mode 100644 index 0000000..d2b5a8e --- /dev/null +++ b/src/framework/mlt_properties.c @@ -0,0 +1,912 @@ +/* + * mlt_properties.c -- base properties class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "mlt_properties.h" +#include "mlt_property.h" + +#include +#include +#include +#include + +#include +#include + +/* ---------------- // Private Implementation // ---------------- */ + +/** Private implementation of the property list. +*/ + +typedef struct +{ + int hash[ 199 ]; + char **name; + mlt_property *value; + int count; + int size; + mlt_properties mirror; + int ref_count; +} +property_list; + +/** Memory leak checks. +*/ + +//#define _MLT_PROPERTY_CHECKS_ 2 + +#ifdef _MLT_PROPERTY_CHECKS_ +static int properties_created = 0; +static int properties_destroyed = 0; +#endif + +/** Basic implementation. +*/ + +int mlt_properties_init( mlt_properties this, void *child ) +{ + if ( this != NULL ) + { +#ifdef _MLT_PROPERTY_CHECKS_ + // Increment number of properties created + properties_created ++; +#endif + + // NULL all methods + memset( this, 0, sizeof( struct mlt_properties_s ) ); + + // Assign the child of the object + this->child = child; + + // Allocate the local structure + this->local = calloc( sizeof( property_list ), 1 ); + + // Increment the ref count + ( ( property_list * )this->local )->ref_count = 1; + } + + // Check that initialisation was successful + return this != NULL && this->local == NULL; +} + +/** Constructor for stand alone object. +*/ + +mlt_properties mlt_properties_new( ) +{ + // Construct a standalone properties object + mlt_properties this = calloc( sizeof( struct mlt_properties_s ), 1 ); + + // Initialise this + mlt_properties_init( this, NULL ); + + // Return the pointer + return this; +} + +/** Load properties from a file. +*/ + +mlt_properties mlt_properties_load( const char *filename ) +{ + // Construct a standalone properties object + mlt_properties this = mlt_properties_new( ); + + if ( this != NULL ) + { + // Open the file + FILE *file = fopen( filename, "r" ); + + // Load contents of file + if ( file != NULL ) + { + // Temp string + char temp[ 1024 ]; + char last[ 1024 ] = ""; + + // Read each string from the file + while( fgets( temp, 1024, file ) ) + { + // Chomp the string + temp[ strlen( temp ) - 1 ] = '\0'; + + // Check if the line starts with a . + if ( temp[ 0 ] == '.' ) + { + char temp2[ 1024 ]; + sprintf( temp2, "%s%s", last, temp ); + strcpy( temp, temp2 ); + } + else if ( strchr( temp, '=' ) ) + { + strcpy( last, temp ); + *( strchr( last, '=' ) ) = '\0'; + } + + // Parse and set the property + if ( strcmp( temp, "" ) && temp[ 0 ] != '#' ) + mlt_properties_parse( this, temp ); + } + + // Close the file + fclose( file ); + } + } + + // Return the pointer + return this; +} + +static inline int generate_hash( const char *name ) +{ + int hash = 0; + int i = 1; + while ( *name ) + hash = ( hash + ( i ++ * ( *name ++ & 31 ) ) ) % 199; + return hash; +} + +/** Special case - when a container (such as fezzik) is protecting another + producer, we need to ensure that properties are passed through to the + real producer. +*/ + +static inline void mlt_properties_do_mirror( mlt_properties this, const char *name ) +{ + property_list *list = this->local; + if ( list->mirror != NULL ) + { + char *value = mlt_properties_get( this, name ); + if ( value != NULL ) + mlt_properties_set( list->mirror, name, value ); + } +} + +/** Maintain ref count to allow multiple uses of an mlt object. +*/ + +int mlt_properties_inc_ref( mlt_properties this ) +{ + if ( this != NULL ) + { + property_list *list = this->local; + return ++ list->ref_count; + } + return 0; +} + +/** Maintain ref count to allow multiple uses of an mlt object. +*/ + +int mlt_properties_dec_ref( mlt_properties this ) +{ + if ( this != NULL ) + { + property_list *list = this->local; + return -- list->ref_count; + } + return 0; +} + +/** Return the ref count of this object. +*/ + +int mlt_properties_ref_count( mlt_properties this ) +{ + if ( this != NULL ) + { + property_list *list = this->local; + return list->ref_count; + } + return 0; +} + +/** Mirror properties set on 'this' to 'that'. +*/ + +void mlt_properties_mirror( mlt_properties this, mlt_properties that ) +{ + property_list *list = this->local; + list->mirror = that; +} + +/** Inherit all serialisable properties from that into this. +*/ + +int mlt_properties_inherit( mlt_properties this, mlt_properties that ) +{ + int count = mlt_properties_count( that ); + int i = 0; + for ( i = 0; i < count; i ++ ) + { + char *value = mlt_properties_get_value( that, i ); + if ( value != NULL ) + { + char *name = mlt_properties_get_name( that, i ); + mlt_properties_set( this, name, value ); + } + } + return 0; +} + +/** Pass all properties from 'that' that match the prefix to 'this' (excluding the prefix). +*/ + +int mlt_properties_pass( mlt_properties this, mlt_properties that, const char *prefix ) +{ + int count = mlt_properties_count( that ); + int length = strlen( prefix ); + int i = 0; + for ( i = 0; i < count; i ++ ) + { + char *name = mlt_properties_get_name( that, i ); + if ( !strncmp( name, prefix, length ) ) + { + char *value = mlt_properties_get_value( that, i ); + if ( value != NULL ) + mlt_properties_set( this, name + length, value ); + } + } + return 0; +} + +/** Locate a property by name +*/ + +static inline mlt_property mlt_properties_find( mlt_properties this, const char *name ) +{ + property_list *list = this->local; + mlt_property value = NULL; + int key = generate_hash( name ); + int i = list->hash[ key ] - 1; + + if ( i >= 0 ) + { + // Check if we're hashed + if ( list->count > 0 && + name[ 0 ] == list->name[ i ][ 0 ] && + !strcmp( list->name[ i ], name ) ) + value = list->value[ i ]; + + // Locate the item + for ( i = list->count - 1; value == NULL && i >= 0; i -- ) + if ( name[ 0 ] == list->name[ i ][ 0 ] && !strcmp( list->name[ i ], name ) ) + value = list->value[ i ]; + } + + return value; +} + +/** Add a new property. +*/ + +static mlt_property mlt_properties_add( mlt_properties this, const char *name ) +{ + property_list *list = this->local; + int key = generate_hash( name ); + + // Check that we have space and resize if necessary + if ( list->count == list->size ) + { + list->size += 50; + list->name = realloc( list->name, list->size * sizeof( const char * ) ); + list->value = realloc( list->value, list->size * sizeof( mlt_property ) ); + } + + // Assign name/value pair + list->name[ list->count ] = strdup( name ); + list->value[ list->count ] = mlt_property_init( ); + + // Assign to hash table + if ( list->hash[ key ] == 0 ) + list->hash[ key ] = list->count + 1; + + // Return and increment count accordingly + return list->value[ list->count ++ ]; +} + +/** Fetch a property by name - this includes add if not found. +*/ + +static mlt_property mlt_properties_fetch( mlt_properties this, const char *name ) +{ + // Try to find an existing property first + mlt_property property = mlt_properties_find( this, name ); + + // If it wasn't found, create one + if ( property == NULL ) + property = mlt_properties_add( this, name ); + + // Return the property + return property; +} + +/** Pass property 'name' from 'that' to 'this' +* Who to blame: Zach +*/ + +void mlt_properties_pass_property( mlt_properties this, mlt_properties that, const char *name ) +{ + // Make sure the source property isn't null. + mlt_property that_prop = mlt_properties_find( that, name ); + if( that_prop == NULL ) + return; + + mlt_property_pass( mlt_properties_fetch( this, name ), that_prop ); +} + +/** Pass all properties from 'that' to 'this' as found in comma seperated 'list'. +* Who to blame: Zach +*/ + +int mlt_properties_pass_list( mlt_properties this, mlt_properties that, const char *list ) +{ + char *props = strdup( list ); + char *ptr = props; + char *delim = " ,\t\n"; // Any combination of spaces, commas, tabs, and newlines + int count, done = 0; + + while( !done ) + { + count = strcspn( ptr, delim ); + + if( ptr[count] == '\0' ) + done = 1; + else + ptr[count] = '\0'; // Make it a real string + + mlt_properties_pass_property( this, that, ptr ); + + ptr += count + 1; + ptr += strspn( ptr, delim ); + } + + free( props ); + + return 0; +} + + +/** Set the property. +*/ + +int mlt_properties_set( mlt_properties this, const char *name, const char *value ) +{ + int error = 1; + + // Fetch the property to work with + mlt_property property = mlt_properties_fetch( this, name ); + + // Set it if not NULL + if ( property == NULL ) + { + fprintf( stderr, "Whoops - %s not found (should never occur)\n", name ); + } + else if ( value == NULL ) + { + error = mlt_property_set_string( property, value ); + mlt_properties_do_mirror( this, name ); + } + else if ( *value != '@' ) + { + error = mlt_property_set_string( property, value ); + mlt_properties_do_mirror( this, name ); + } + else if ( value[ 0 ] == '@' ) + { + int total = 0; + int current = 0; + char id[ 255 ]; + char op = '+'; + + value ++; + + while ( *value != '\0' ) + { + int length = strcspn( value, "+-*/" ); + + // Get the identifier + strncpy( id, value, length ); + id[ length ] = '\0'; + value += length; + + // Determine the value + if ( isdigit( id[ 0 ] ) ) + current = atof( id ); + else + current = mlt_properties_get_int( this, id ); + + // Apply the operation + switch( op ) + { + case '+': + total += current; + break; + case '-': + total -= current; + break; + case '*': + total *= current; + break; + case '/': + total /= current; + break; + } + + // Get the next op + op = *value != '\0' ? *value ++ : ' '; + } + + error = mlt_property_set_int( property, total ); + mlt_properties_do_mirror( this, name ); + } + + mlt_events_fire( this, "property-changed", name, NULL ); + + return error; +} + +/** Set or default the property. +*/ + +int mlt_properties_set_or_default( mlt_properties this, const char *name, const char *value, const char *def ) +{ + return mlt_properties_set( this, name, value == NULL ? def : value ); +} + +/** Get a string value by name. +*/ + +char *mlt_properties_get( mlt_properties this, const char *name ) +{ + mlt_property value = mlt_properties_find( this, name ); + return value == NULL ? NULL : mlt_property_get_string( value ); +} + +/** Get a name by index. +*/ + +char *mlt_properties_get_name( mlt_properties this, int index ) +{ + property_list *list = this->local; + if ( index >= 0 && index < list->count ) + return list->name[ index ]; + return NULL; +} + +/** Get a string value by index. +*/ + +char *mlt_properties_get_value( mlt_properties this, int index ) +{ + property_list *list = this->local; + if ( index >= 0 && index < list->count ) + return mlt_property_get_string( list->value[ index ] ); + return NULL; +} + +/** Get a data value by index. +*/ + +void *mlt_properties_get_data_at( mlt_properties this, int index, int *size ) +{ + property_list *list = this->local; + if ( index >= 0 && index < list->count ) + return mlt_property_get_data( list->value[ index ], size ); + return NULL; +} + +/** Return the number of items in the list. +*/ + +int mlt_properties_count( mlt_properties this ) +{ + property_list *list = this->local; + return list->count; +} + +/** Set a value by parsing a name=value string +*/ + +int mlt_properties_parse( mlt_properties this, const char *namevalue ) +{ + char *name = strdup( namevalue ); + char *value = NULL; + int error = 0; + char *ptr = strchr( name, '=' ); + + if ( ptr ) + { + *( ptr ++ ) = '\0'; + + if ( *ptr != '\"' ) + { + value = strdup( ptr ); + } + else + { + ptr ++; + value = strdup( ptr ); + if ( value != NULL && value[ strlen( value ) - 1 ] == '\"' ) + value[ strlen( value ) - 1 ] = '\0'; + } + } + else + { + value = strdup( "" ); + } + + error = mlt_properties_set( this, name, value ); + + free( name ); + free( value ); + + return error; +} + +/** Get a value associated to the name. +*/ + +int mlt_properties_get_int( mlt_properties this, const char *name ) +{ + mlt_property value = mlt_properties_find( this, name ); + return value == NULL ? 0 : mlt_property_get_int( value ); +} + +/** Set a value associated to the name. +*/ + +int mlt_properties_set_int( mlt_properties this, const char *name, int value ) +{ + int error = 1; + + // Fetch the property to work with + mlt_property property = mlt_properties_fetch( this, name ); + + // Set it if not NULL + if ( property != NULL ) + { + error = mlt_property_set_int( property, value ); + mlt_properties_do_mirror( this, name ); + } + + mlt_events_fire( this, "property-changed", name, NULL ); + + return error; +} + +/** Get a value associated to the name. +*/ + +int64_t mlt_properties_get_int64( mlt_properties this, const char *name ) +{ + mlt_property value = mlt_properties_find( this, name ); + return value == NULL ? 0 : mlt_property_get_int64( value ); +} + +/** Set a value associated to the name. +*/ + +int mlt_properties_set_int64( mlt_properties this, const char *name, int64_t value ) +{ + int error = 1; + + // Fetch the property to work with + mlt_property property = mlt_properties_fetch( this, name ); + + // Set it if not NULL + if ( property != NULL ) + { + error = mlt_property_set_int64( property, value ); + mlt_properties_do_mirror( this, name ); + } + + mlt_events_fire( this, "property-changed", name, NULL ); + + return error; +} + +/** Get a value associated to the name. +*/ + +double mlt_properties_get_double( mlt_properties this, const char *name ) +{ + mlt_property value = mlt_properties_find( this, name ); + return value == NULL ? 0 : mlt_property_get_double( value ); +} + +/** Set a value associated to the name. +*/ + +int mlt_properties_set_double( mlt_properties this, const char *name, double value ) +{ + int error = 1; + + // Fetch the property to work with + mlt_property property = mlt_properties_fetch( this, name ); + + // Set it if not NULL + if ( property != NULL ) + { + error = mlt_property_set_double( property, value ); + mlt_properties_do_mirror( this, name ); + } + + mlt_events_fire( this, "property-changed", name, NULL ); + + return error; +} + +/** Get a value associated to the name. +*/ + +mlt_position mlt_properties_get_position( mlt_properties this, const char *name ) +{ + mlt_property value = mlt_properties_find( this, name ); + return value == NULL ? 0 : mlt_property_get_position( value ); +} + +/** Set a value associated to the name. +*/ + +int mlt_properties_set_position( mlt_properties this, const char *name, mlt_position value ) +{ + int error = 1; + + // Fetch the property to work with + mlt_property property = mlt_properties_fetch( this, name ); + + // Set it if not NULL + if ( property != NULL ) + { + error = mlt_property_set_position( property, value ); + mlt_properties_do_mirror( this, name ); + } + + mlt_events_fire( this, "property-changed", name, NULL ); + + return error; +} + +/** Get a value associated to the name. +*/ + +void *mlt_properties_get_data( mlt_properties this, const char *name, int *length ) +{ + mlt_property value = mlt_properties_find( this, name ); + return value == NULL ? NULL : mlt_property_get_data( value, length ); +} + +/** Set a value associated to the name. +*/ + +int mlt_properties_set_data( mlt_properties this, const char *name, void *value, int length, mlt_destructor destroy, mlt_serialiser serialise ) +{ + int error = 1; + + // Fetch the property to work with + mlt_property property = mlt_properties_fetch( this, name ); + + // Set it if not NULL + if ( property != NULL ) + error = mlt_property_set_data( property, value, length, destroy, serialise ); + + mlt_events_fire( this, "property-changed", name, NULL ); + + return error; +} + +/** Rename a property. +*/ + +int mlt_properties_rename( mlt_properties this, const char *source, const char *dest ) +{ + mlt_property value = mlt_properties_find( this, dest ); + + if ( value == NULL ) + { + property_list *list = this->local; + int i = 0; + + // Locate the item + for ( i = 0; i < list->count; i ++ ) + { + if ( !strcmp( list->name[ i ], source ) ) + { + free( list->name[ i ] ); + list->name[ i ] = strdup( dest ); + list->hash[ generate_hash( dest ) ] = i + 1; + break; + } + } + } + + return value != NULL; +} + +/** Dump the properties. +*/ + +void mlt_properties_dump( mlt_properties this, FILE *output ) +{ + property_list *list = this->local; + int i = 0; + for ( i = 0; i < list->count; i ++ ) + if ( mlt_properties_get( this, list->name[ i ] ) != NULL ) + fprintf( output, "%s=%s\n", list->name[ i ], mlt_properties_get( this, list->name[ i ] ) ); +} + +void mlt_properties_debug( mlt_properties this, const char *title, FILE *output ) +{ + if ( output == NULL ) output = stderr; + fprintf( output, "%s: ", title ); + if ( this != NULL ) + { + property_list *list = this->local; + int i = 0; + fprintf( output, "[ ref=%d", list->ref_count ); + for ( i = 0; i < list->count; i ++ ) + if ( mlt_properties_get( this, list->name[ i ] ) != NULL ) + fprintf( output, ", %s=%s", list->name[ i ], mlt_properties_get( this, list->name[ i ] ) ); + else + fprintf( output, ", %s=%p", list->name[ i ], mlt_properties_get_data( this, list->name[ i ], NULL ) ); + fprintf( output, " ]" ); + } + fprintf( output, "\n" ); +} + +int mlt_properties_save( mlt_properties this, const char *filename ) +{ + int error = 1; + FILE *f = fopen( filename, "w" ); + if ( f != NULL ) + { + mlt_properties_dump( this, f ); + fclose( f ); + error = 0; + } + return error; +} + +/* This is a very basic cross platform fnmatch replacement - it will fail in +** many cases, but for the basic *.XXX and YYY*.XXX, it will work ok. +*/ + +static int mlt_fnmatch( const char *wild, const char *file ) +{ + int f = 0; + int w = 0; + + while( f < strlen( file ) && w < strlen( wild ) ) + { + if ( wild[ w ] == '*' ) + { + w ++; + if ( w == strlen( wild ) ) + f = strlen( file ); + while ( f != strlen( file ) && tolower( file[ f ] ) != tolower( wild[ w ] ) ) + f ++; + } + else if ( wild[ w ] == '?' || tolower( file[ f ] ) == tolower( wild[ w ] ) ) + { + f ++; + w ++; + } + else if ( wild[ 0 ] == '*' ) + { + w = 0; + } + else + { + return 0; + } + } + + return strlen( file ) == f && strlen( wild ) == w; +} + +static int mlt_compare( const void *this, const void *that ) +{ + return strcmp( mlt_property_get_string( *( mlt_property * )this ), mlt_property_get_string( *( mlt_property * )that ) ); +} + +/* Obtains an optionally sorted list of the files found in a directory with a specific wild card. + * Entries in the list have a numeric name (running from 0 to count - 1). Only values change + * position if sort is enabled. Designed to be posix compatible (linux, os/x, mingw etc). + */ + +int mlt_properties_dir_list( mlt_properties this, const char *dirname, const char *pattern, int sort ) +{ + DIR *dir = opendir( dirname ); + + if ( dir ) + { + char key[ 20 ]; + struct dirent *de = readdir( dir ); + char fullname[ 1024 ]; + while( de != NULL ) + { + sprintf( key, "%d", mlt_properties_count( this ) ); + snprintf( fullname, 1024, "%s/%s", dirname, de->d_name ); + if ( de->d_name[ 0 ] != '.' && mlt_fnmatch( pattern, de->d_name ) ) + mlt_properties_set( this, key, fullname ); + de = readdir( dir ); + } + + closedir( dir ); + } + + if ( sort && mlt_properties_count( this ) ) + { + property_list *list = this->local; + qsort( list->value, mlt_properties_count( this ), sizeof( mlt_property ), mlt_compare ); + } + + return mlt_properties_count( this ); +} + +/** Close the list. +*/ + +void mlt_properties_close( mlt_properties this ) +{ + if ( this != NULL && mlt_properties_dec_ref( this ) <= 0 ) + { + if ( this->close != NULL ) + { + this->close( this->close_object ); + } + else + { + property_list *list = this->local; + int index = 0; + +#if _MLT_PROPERTY_CHECKS_ == 1 + // Show debug info + mlt_properties_debug( this, "Closing", stderr ); +#endif + +#ifdef _MLT_PROPERTY_CHECKS_ + // Increment destroyed count + properties_destroyed ++; + + // Show current stats - these should match when the app is closed + fprintf( stderr, "Created %d, destroyed %d\n", properties_created, properties_destroyed ); +#endif + + // Clean up names and values + for ( index = list->count - 1; index >= 0; index -- ) + { + free( list->name[ index ] ); + mlt_property_close( list->value[ index ] ); + } + + // Clear up the list + free( list->name ); + free( list->value ); + free( list ); + + // Free this now if this has no child + if ( this->child == NULL ) + free( this ); + } + } +} + diff --git a/src/framework/mlt_properties.h b/src/framework/mlt_properties.h new file mode 100644 index 0000000..33826f3 --- /dev/null +++ b/src/framework/mlt_properties.h @@ -0,0 +1,79 @@ +/* + * mlt_properties.h -- base properties class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_PROPERTIES_H_ +#define _MLT_PROPERTIES_H_ + +#include "mlt_types.h" +#include "mlt_events.h" +#include + +/** The properties base class defines the basic property propagation and + handling. +*/ + +struct mlt_properties_s +{ + void *child; + void *local; + mlt_destructor close; + void *close_object; +}; + +/** Public interface. +*/ + +extern int mlt_properties_init( mlt_properties, void *child ); +extern mlt_properties mlt_properties_new( ); +extern mlt_properties mlt_properties_load( const char *file ); +extern int mlt_properties_inc_ref( mlt_properties self ); +extern int mlt_properties_dec_ref( mlt_properties self ); +extern int mlt_properties_ref_count( mlt_properties self ); +extern void mlt_properties_mirror( mlt_properties self, mlt_properties that ); +extern int mlt_properties_inherit( mlt_properties self, mlt_properties that ); +extern int mlt_properties_pass( mlt_properties self, mlt_properties that, const char *prefix ); +extern void mlt_properties_pass_property( mlt_properties self, mlt_properties that, const char *name ); +extern int mlt_properties_pass_list( mlt_properties self, mlt_properties that, const char *list ); +extern int mlt_properties_set( mlt_properties self, const char *name, const char *value ); +extern int mlt_properties_set_or_default( mlt_properties self, const char *name, const char *value, const char *def ); +extern int mlt_properties_parse( mlt_properties self, const char *namevalue ); +extern char *mlt_properties_get( mlt_properties self, const char *name ); +extern char *mlt_properties_get_name( mlt_properties self, int index ); +extern char *mlt_properties_get_value( mlt_properties self, int index ); +extern void *mlt_properties_get_data_at( mlt_properties self, int index, int *size ); +extern int mlt_properties_get_int( mlt_properties self, const char *name ); +extern int mlt_properties_set_int( mlt_properties self, const char *name, int value ); +extern int64_t mlt_properties_get_int64( mlt_properties self, const char *name ); +extern int mlt_properties_set_int64( mlt_properties self, const char *name, int64_t value ); +extern double mlt_properties_get_double( mlt_properties self, const char *name ); +extern int mlt_properties_set_double( mlt_properties self, const char *name, double value ); +extern mlt_position mlt_properties_get_position( mlt_properties self, const char *name ); +extern int mlt_properties_set_position( mlt_properties self, const char *name, mlt_position value ); +extern int mlt_properties_set_data( mlt_properties self, const char *name, void *value, int length, mlt_destructor, mlt_serialiser ); +extern void *mlt_properties_get_data( mlt_properties self, const char *name, int *length ); +extern int mlt_properties_rename( mlt_properties self, const char *source, const char *dest ); +extern int mlt_properties_count( mlt_properties self ); +extern void mlt_properties_dump( mlt_properties self, FILE *output ); +extern void mlt_properties_debug( mlt_properties self, const char *title, FILE *output ); +extern int mlt_properties_save( mlt_properties, const char * ); +extern int mlt_properties_dir_list( mlt_properties, const char *, const char *, int ); +extern void mlt_properties_close( mlt_properties self ); + +#endif diff --git a/src/framework/mlt_property.c b/src/framework/mlt_property.c new file mode 100644 index 0000000..516c4ca --- /dev/null +++ b/src/framework/mlt_property.c @@ -0,0 +1,340 @@ +/* + * mlt_property.c -- property class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "mlt_property.h" + +#include +#include +#include + +/** Construct and uninitialised property. +*/ + +mlt_property mlt_property_init( ) +{ + mlt_property this = malloc( sizeof( struct mlt_property_s ) ); + if ( this != NULL ) + { + this->types = 0; + this->prop_int = 0; + this->prop_position = 0; + this->prop_double = 0; + this->prop_int64 = 0; + this->prop_string = NULL; + this->data = NULL; + this->length = 0; + this->destructor = NULL; + this->serialiser = NULL; + } + return this; +} + +/** Clear a property. +*/ + +static inline void mlt_property_clear( mlt_property this ) +{ + // Special case data handling + if ( this->types & mlt_prop_data && this->destructor != NULL ) + this->destructor( this->data ); + + // Special case string handling + if ( this->types & mlt_prop_string ) + free( this->prop_string ); + + // Wipe stuff + this->types = 0; + this->prop_int = 0; + this->prop_position = 0; + this->prop_double = 0; + this->prop_int64 = 0; + this->prop_string = NULL; + this->data = NULL; + this->length = 0; + this->destructor = NULL; + this->serialiser = NULL; +} + +/** Set an int on this property. +*/ + +int mlt_property_set_int( mlt_property this, int value ) +{ + mlt_property_clear( this ); + this->types = mlt_prop_int; + this->prop_int = value; + return 0; +} + +/** Set a double on this property. +*/ + +int mlt_property_set_double( mlt_property this, double value ) +{ + mlt_property_clear( this ); + this->types = mlt_prop_double; + this->prop_double = value; + return 0; +} + +/** Set a position on this property. +*/ + +int mlt_property_set_position( mlt_property this, mlt_position value ) +{ + mlt_property_clear( this ); + this->types = mlt_prop_position; + this->prop_position = value; + return 0; +} + +/** Set a string on this property. +*/ + +int mlt_property_set_string( mlt_property this, const char *value ) +{ + if ( value != this->prop_string ) + { + mlt_property_clear( this ); + this->types = mlt_prop_string; + if ( value != NULL ) + this->prop_string = strdup( value ); + } + else + { + this->types = mlt_prop_string; + } + return this->prop_string == NULL; +} + +/** Set an int64 on this property. +*/ + +int mlt_property_set_int64( mlt_property this, int64_t value ) +{ + mlt_property_clear( this ); + this->types = mlt_prop_int64; + this->prop_int64 = value; + return 0; +} + +/** Set a data on this property. +*/ + +int mlt_property_set_data( mlt_property this, void *value, int length, mlt_destructor destructor, mlt_serialiser serialiser ) +{ + if ( this->data == value ) + this->destructor = NULL; + mlt_property_clear( this ); + this->types = mlt_prop_data; + this->data = value; + this->length = length; + this->destructor = destructor; + this->serialiser = serialiser; + return 0; +} + +static inline int mlt_property_atoi( const char *value ) +{ + if ( value == NULL ) + return 0; + else if ( value[0] == '0' && value[1] == 'x' ) + return strtol( value + 2, NULL, 16 ); + else + return strtol( value, NULL, 10 ); +} + +/** Get an int from this property. +*/ + +int mlt_property_get_int( mlt_property this ) +{ + if ( this->types & mlt_prop_int ) + return this->prop_int; + else if ( this->types & mlt_prop_double ) + return ( int )this->prop_double; + else if ( this->types & mlt_prop_position ) + return ( int )this->prop_position; + else if ( this->types & mlt_prop_int64 ) + return ( int )this->prop_int64; + else if ( this->types & mlt_prop_string ) + return mlt_property_atoi( this->prop_string ); + return 0; +} + +/** Get a double from this property. +*/ + +double mlt_property_get_double( mlt_property this ) +{ + if ( this->types & mlt_prop_double ) + return this->prop_double; + else if ( this->types & mlt_prop_int ) + return ( double )this->prop_int; + else if ( this->types & mlt_prop_position ) + return ( double )this->prop_position; + else if ( this->types & mlt_prop_int64 ) + return ( double )this->prop_int64; + else if ( this->types & mlt_prop_string ) + return atof( this->prop_string ); + return 0; +} + +/** Get a position from this property. +*/ + +mlt_position mlt_property_get_position( mlt_property this ) +{ + if ( this->types & mlt_prop_position ) + return this->prop_position; + else if ( this->types & mlt_prop_int ) + return ( mlt_position )this->prop_int; + else if ( this->types & mlt_prop_double ) + return ( mlt_position )this->prop_double; + else if ( this->types & mlt_prop_int64 ) + return ( mlt_position )this->prop_int64; + else if ( this->types & mlt_prop_string ) + return ( mlt_position )atol( this->prop_string ); + return 0; +} + +static inline int64_t mlt_property_atoll( const char *value ) +{ + if ( value == NULL ) + return 0; + else if ( value[0] == '0' && value[1] == 'x' ) + return strtoll( value + 2, NULL, 16 ); + else + return strtoll( value, NULL, 10 ); +} + +/** Get an int64 from this property. +*/ + +int64_t mlt_property_get_int64( mlt_property this ) +{ + if ( this->types & mlt_prop_int64 ) + return this->prop_int64; + else if ( this->types & mlt_prop_int ) + return ( int64_t )this->prop_int; + else if ( this->types & mlt_prop_double ) + return ( int64_t )this->prop_double; + else if ( this->types & mlt_prop_position ) + return ( int64_t )this->prop_position; + else if ( this->types & mlt_prop_string ) + return mlt_property_atoll( this->prop_string ); + return 0; +} + +/** Get a string from this property. +*/ + +char *mlt_property_get_string( mlt_property this ) +{ + // Construct a string if need be + if ( ! ( this->types & mlt_prop_string ) ) + { + if ( this->types & mlt_prop_int ) + { + this->types |= mlt_prop_string; + this->prop_string = malloc( 32 ); + sprintf( this->prop_string, "%d", this->prop_int ); + } + else if ( this->types & mlt_prop_double ) + { + this->types |= mlt_prop_string; + this->prop_string = malloc( 32 ); + sprintf( this->prop_string, "%f", this->prop_double ); + } + else if ( this->types & mlt_prop_position ) + { + this->types |= mlt_prop_string; + this->prop_string = malloc( 32 ); + sprintf( this->prop_string, "%d", (int)this->prop_position ); /* I don't know if this is wanted. -Zach */ + } + else if ( this->types & mlt_prop_int64 ) + { + this->types |= mlt_prop_string; + this->prop_string = malloc( 32 ); + sprintf( this->prop_string, "%lld", this->prop_int64 ); + } + else if ( this->types & mlt_prop_data && this->serialiser != NULL ) + { + this->types |= mlt_prop_string; + this->prop_string = this->serialiser( this->data, this->length ); + } + } + + // Return the string (may be NULL) + return this->prop_string; +} + +/** Get a data and associated length. +*/ + +void *mlt_property_get_data( mlt_property this, int *length ) +{ + // Assign length if not NULL + if ( length != NULL ) + *length = this->length; + + // Return the data (note: there is no conversion here) + return this->data; +} + +/** Close this property. +*/ + +void mlt_property_close( mlt_property this ) +{ + mlt_property_clear( this ); + free( this ); +} + +/** Pass the property 'that' to 'this'. +* Who to blame: Zach +*/ +void mlt_property_pass( mlt_property this, mlt_property that ) +{ + mlt_property_clear( this ); + + this->types = that->types; + + if ( this->types & mlt_prop_int64 ) + this->prop_int64 = that->prop_int64; + else if ( this->types & mlt_prop_int ) + this->prop_int = that->prop_int; + else if ( this->types & mlt_prop_double ) + this->prop_double = that->prop_double; + else if ( this->types & mlt_prop_position ) + this->prop_position = that->prop_position; + else if ( this->types & mlt_prop_string ) + { + if ( that->prop_string != NULL ) + this->prop_string = strdup( that->prop_string ); + } + else if ( this->types & mlt_prop_data && this->serialiser != NULL ) + { + this->types = mlt_prop_string; + this->prop_string = this->serialiser( this->data, this->length ); + } +} diff --git a/src/framework/mlt_property.h b/src/framework/mlt_property.h new file mode 100644 index 0000000..aefb5f6 --- /dev/null +++ b/src/framework/mlt_property.h @@ -0,0 +1,87 @@ +/* + * mlt_property.h -- property class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_PROPERTY_H_ +#define _MLT_PROPERTY_H_ + +#include "mlt_types.h" + +/** Bit pattern for properties. +*/ + +typedef enum +{ + mlt_prop_none = 0, + mlt_prop_int = 1, + mlt_prop_string = 2, + mlt_prop_position = 4, + mlt_prop_double = 8, + mlt_prop_data = 16, + mlt_prop_int64 = 32 +} +mlt_property_type; + +/** Property structure. +*/ + +typedef struct mlt_property_s +{ + // Stores a bit pattern of types available for this property + mlt_property_type types; + + // Atomic type handling + int prop_int; + mlt_position prop_position; + double prop_double; + int64_t prop_int64; + + // String handling + char *prop_string; + + // Generic type handling + void *data; + int length; + mlt_destructor destructor; + mlt_serialiser serialiser; +} +*mlt_property; + +/** API +*/ + +extern mlt_property mlt_property_init( ); +extern int mlt_property_set_int( mlt_property self, int value ); +extern int mlt_property_set_double( mlt_property self, double value ); +extern int mlt_property_set_position( mlt_property self, mlt_position value ); +extern int mlt_property_set_int64( mlt_property self, int64_t value ); +extern int mlt_property_set_string( mlt_property self, const char *value ); +extern int mlt_property_set_data( mlt_property self, void *value, int length, mlt_destructor destructor, mlt_serialiser serialiser ); +extern int mlt_property_get_int( mlt_property self ); +extern double mlt_property_get_double( mlt_property self ); +extern mlt_position mlt_property_get_position( mlt_property self ); +extern int64_t mlt_property_get_int64( mlt_property self ); +extern char *mlt_property_get_string( mlt_property self ); +extern void *mlt_property_get_data( mlt_property self, int *length ); +extern void mlt_property_close( mlt_property self ); + +extern void mlt_property_pass( mlt_property this, mlt_property that ); + +#endif + diff --git a/src/framework/mlt_repository.c b/src/framework/mlt_repository.c new file mode 100644 index 0000000..1e07e9d --- /dev/null +++ b/src/framework/mlt_repository.c @@ -0,0 +1,209 @@ +/* + * repository.c -- provides a map between service and shared objects + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "mlt_repository.h" +#include "mlt_properties.h" + +#include +#include +#include +#include + +struct mlt_repository_s +{ + struct mlt_properties_s parent; +}; + +static char *construct_full_file( char *output, const char *prefix, const char *file ) +{ + strcpy( output, prefix ); + if ( prefix[ strlen( prefix ) - 1 ] != '/' ) + strcat( output, "/" ); + strcat( output, file ); + return output; +} + +static char *chomp( char *input ) +{ + if ( input[ strlen( input ) - 1 ] == '\n' ) + input[ strlen( input ) - 1 ] = '\0'; + return input; +} + +static mlt_properties construct_object( const char *prefix, const char *id ) +{ + mlt_properties output = mlt_properties_new( ); + mlt_properties_set( output, "prefix", prefix ); + mlt_properties_set( output, "id", id ); + return output; +} + +static mlt_properties construct_service( mlt_properties object, const char *id ) +{ + mlt_properties output = mlt_properties_new( ); + mlt_properties_set_data( output, "object", object, 0, NULL, NULL ); + mlt_properties_set( output, "id", id ); + return output; +} + +static void *construct_instance( mlt_properties service_properties, const char *symbol, void *input ) +{ + // Extract the service + char *service = mlt_properties_get( service_properties, "id" ); + + // Get the object properties + void *object_properties = mlt_properties_get_data( service_properties, "object", NULL ); + + // Get the dlopen'd object + void *object = mlt_properties_get_data( object_properties, "dlopen", NULL ); + + // Get the dlsym'd symbol + void *( *symbol_ptr )( const char *, void * ) = mlt_properties_get_data( object_properties, symbol, NULL ); + + // Check that we have object and open if we don't + if ( object == NULL ) + { + char full_file[ 512 ]; + + // Get the prefix and id of the shared object + char *prefix = mlt_properties_get( object_properties, "prefix" ); + char *file = mlt_properties_get( object_properties, "id" ); + int flags = RTLD_NOW; + + // Very temporary hack to allow the quicktime plugins to work + // TODO: extend repository to allow this to be used on a case by case basis + if ( !strcmp( service, "kino" ) ) + flags |= RTLD_GLOBAL; + + // Construct the full file + construct_full_file( full_file, prefix, file ); + + // Open the shared object + object = dlopen( full_file, flags ); + if ( object != NULL ) + { + // Set it on the properties + mlt_properties_set_data( object_properties, "dlopen", object, 0, ( mlt_destructor )dlclose, NULL ); + } + else + { + fprintf( stderr, "Failed to load plugin: %s\n", dlerror() ); + } + } + + // Now check if we have this symbol pointer + if ( object != NULL && symbol_ptr == NULL ) + { + // Construct it now + symbol_ptr = dlsym( object, symbol ); + + // Set it on the properties + mlt_properties_set_data( object_properties, "dlsym", symbol_ptr, 0, NULL, NULL ); + } + + // Construct the service + return symbol_ptr != NULL ? symbol_ptr( service, input ) : NULL; +} + +mlt_repository mlt_repository_init( mlt_properties object_list, const char *prefix, const char *data, const char *symbol ) +{ + char full_file[ 512 ]; + FILE *file; + + // Construct the repository + mlt_repository this = calloc( sizeof( struct mlt_repository_s ), 1 ); + mlt_properties_init( &this->parent, this ); + + // Add the symbol to THIS repository properties. + mlt_properties_set( &this->parent, "_symbol", symbol ); + + // Construct full file + construct_full_file( full_file, prefix, data ); + strcat( full_file, ".dat" ); + + // Open the file + file = fopen( full_file, "r" ); + + // Parse the contents + if ( file != NULL ) + { + char full[ 512 ]; + char service[ 256 ]; + char object[ 256 ]; + + while( fgets( full, 512, file ) ) + { + chomp( full ); + + if ( full[ 0 ] != '#' && full[ 0 ] != '\0' && sscanf( full, "%s %s", service, object ) == 2 ) + { + // Get the object properties first + mlt_properties object_properties = mlt_properties_get_data( object_list, object, NULL ); + + // If their are no properties, create them now + if ( object_properties == NULL ) + { + // Construct the object + object_properties = construct_object( prefix, object ); + + // Add it to the object list + mlt_properties_set_data( object_list, object, object_properties, 0, ( mlt_destructor )mlt_properties_close, NULL ); + } + + // Now construct a property for the service + mlt_properties service_properties = construct_service( object_properties, service ); + + // Add it to the repository + mlt_properties_set_data( &this->parent, service, service_properties, 0, ( mlt_destructor )mlt_properties_close, NULL ); + } + } + + // Close the file + fclose( file ); + } + + return this; +} + +void *mlt_repository_fetch( mlt_repository this, const char *service, void *input ) +{ + // Get the service properties + mlt_properties service_properties = mlt_properties_get_data( &this->parent, service, NULL ); + + // If the service exists + if ( service_properties != NULL ) + { + // Get the symbol that is used to generate this service + char *symbol = mlt_properties_get( &this->parent, "_symbol" ); + + // Now get an instance of the service + return construct_instance( service_properties, symbol, input ); + } + + return NULL; +} + +void mlt_repository_close( mlt_repository this ) +{ + mlt_properties_close( &this->parent ); + free( this ); +} + + diff --git a/src/framework/mlt_repository.h b/src/framework/mlt_repository.h new file mode 100644 index 0000000..49e269a --- /dev/null +++ b/src/framework/mlt_repository.h @@ -0,0 +1,39 @@ +/* + * repository.h -- provides a map between service and shared objects + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_REPOSITORY_H_ +#define _MLT_REPOSITORY_H_ + +#include "mlt_types.h" + +/** Repository structure forward reference. +*/ + +typedef struct mlt_repository_s *mlt_repository; + +/** Public functions. +*/ + +extern mlt_repository mlt_repository_init( mlt_properties object_list, const char *prefix, const char *file, const char *symbol ); +extern void *mlt_repository_fetch( mlt_repository self, const char *service, void *input ); +extern void mlt_repository_close( mlt_repository self ); + +#endif + diff --git a/src/framework/mlt_service.c b/src/framework/mlt_service.c new file mode 100644 index 0000000..b1cae16 --- /dev/null +++ b/src/framework/mlt_service.c @@ -0,0 +1,529 @@ +/* + * mlt_service.c -- interface for all service classes + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "mlt_service.h" +#include "mlt_filter.h" +#include "mlt_frame.h" +#include +#include +#include +#include + +/** IMPORTANT NOTES + + The base service implements a null frame producing service - as such, + it is functional without extension and will produce test cards frames + and PAL sized audio frames. + + PLEASE DO NOT CHANGE THIS BEHAVIOUR!!! OVERRIDE THE METHODS THAT + CONTROL THIS IN EXTENDING CLASSES. +*/ + +/** Private service definition. +*/ + +typedef struct +{ + int size; + int count; + mlt_service *in; + mlt_service out; + int filter_count; + int filter_size; + mlt_filter *filters; + pthread_mutex_t mutex; +} +mlt_service_base; + +/** Private methods +*/ + +static void mlt_service_disconnect( mlt_service this ); +static void mlt_service_connect( mlt_service this, mlt_service that ); +static int service_get_frame( mlt_service this, mlt_frame_ptr frame, int index ); +static void mlt_service_property_changed( mlt_listener, mlt_properties owner, mlt_service this, void **args ); + +/** Constructor +*/ + +int mlt_service_init( mlt_service this, void *child ) +{ + int error = 0; + + // Initialise everything to NULL + memset( this, 0, sizeof( struct mlt_service_s ) ); + + // Assign the child + this->child = child; + + // Generate local space + this->local = calloc( sizeof( mlt_service_base ), 1 ); + + // Associate the methods + this->get_frame = service_get_frame; + + // Initialise the properties + error = mlt_properties_init( &this->parent, this ); + if ( error == 0 ) + { + this->parent.close = ( mlt_destructor )mlt_service_close; + this->parent.close_object = this; + + mlt_events_init( &this->parent ); + mlt_events_register( &this->parent, "service-changed", NULL ); + mlt_events_register( &this->parent, "property-changed", ( mlt_transmitter )mlt_service_property_changed ); + pthread_mutex_init( &( ( mlt_service_base * )this->local )->mutex, NULL ); + } + + return error; +} + +static void mlt_service_property_changed( mlt_listener listener, mlt_properties owner, mlt_service this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( char * )args[ 0 ] ); +} + +void mlt_service_lock( mlt_service this ) +{ + if ( this != NULL ) + pthread_mutex_lock( &( ( mlt_service_base * )this->local )->mutex ); +} + +void mlt_service_unlock( mlt_service this ) +{ + if ( this != NULL ) + pthread_mutex_unlock( &( ( mlt_service_base * )this->local )->mutex ); +} + +mlt_service_type mlt_service_identify( mlt_service this ) +{ + mlt_service_type type = invalid_type; + if ( this != NULL ) + { + mlt_properties properties = MLT_SERVICE_PROPERTIES( this ); + char *mlt_type = mlt_properties_get( properties, "mlt_type" ); + char *resource = mlt_properties_get( properties, "resource" ); + if ( mlt_type == NULL ) + type = unknown_type; + else if ( resource == NULL || !strcmp( resource, "" ) ) + type = producer_type; + else if ( !strcmp( resource, "" ) ) + type = playlist_type; + else if ( !strcmp( resource, "" ) ) + type = tractor_type; + else if ( !strcmp( resource, "" ) ) + type = multitrack_type; + else if ( !strcmp( mlt_type, "producer" ) ) + type = producer_type; + else if ( !strcmp( mlt_type, "filter" ) ) + type = filter_type; + else if ( !strcmp( mlt_type, "transition" ) ) + type = transition_type; + else if ( !strcmp( mlt_type, "consumer" ) ) + type = consumer_type; + else + type = unknown_type; + } + return type; +} + +/** Connect a producer service. + Returns: > 0 warning, == 0 success, < 0 serious error + 1 = this service does not accept input + 2 = the producer is invalid + 3 = the producer is already registered with this consumer +*/ + +int mlt_service_connect_producer( mlt_service this, mlt_service producer, int index ) +{ + int i = 0; + + // Get the service base + mlt_service_base *base = this->local; + + // Special case 'track' index - only works for last filter(s) in a particular chain + // but allows a filter to apply to the output frame regardless of which track it comes from + if ( index == -1 ) + index = 0; + + // Check if the producer is already registered with this service + for ( i = 0; i < base->count; i ++ ) + if ( base->in[ i ] == producer ) + return 3; + + // Allocate space + if ( index >= base->size ) + { + int new_size = base->size + index + 10; + base->in = realloc( base->in, new_size * sizeof( mlt_service ) ); + if ( base->in != NULL ) + { + for ( i = base->size; i < new_size; i ++ ) + base->in[ i ] = NULL; + base->size = new_size; + } + } + + // If we have space, assign the input + if ( base->in != NULL && index >= 0 && index < base->size ) + { + // Get the current service + mlt_service current = base->in[ index ]; + + // Increment the reference count on this producer + if ( producer != NULL ) + { + mlt_service_lock( producer ); + mlt_properties_inc_ref( MLT_SERVICE_PROPERTIES( producer ) ); + mlt_service_unlock( producer ); + } + + // Now we disconnect the producer service from its consumer + mlt_service_disconnect( producer ); + + // Add the service to index specified + base->in[ index ] = producer; + + // Determine the number of active tracks + if ( index >= base->count ) + base->count = index + 1; + + // Now we connect the producer to its connected consumer + mlt_service_connect( producer, this ); + + // Close the current service + mlt_service_close( current ); + + // Inform caller that all went well + return 0; + } + else + { + return -1; + } +} + +/** Disconnect this service from its consumer. +*/ + +static void mlt_service_disconnect( mlt_service this ) +{ + if ( this != NULL ) + { + // Get the service base + mlt_service_base *base = this->local; + + // Disconnect + base->out = NULL; + } +} + +/** Obtain the consumer this service is connected to. +*/ + +mlt_service mlt_service_consumer( mlt_service this ) +{ + // Get the service base + mlt_service_base *base = this->local; + + // Return the connected consumer + return base->out; +} + +/** Obtain the producer this service is connected to. +*/ + +mlt_service mlt_service_producer( mlt_service this ) +{ + // Get the service base + mlt_service_base *base = this->local; + + // Return the connected producer + return base->count > 0 ? base->in[ base->count - 1 ] : NULL; +} + +/** Associate this service to the consumer. +*/ + +static void mlt_service_connect( mlt_service this, mlt_service that ) +{ + if ( this != NULL ) + { + // Get the service base + mlt_service_base *base = this->local; + + // There's a bit more required here... + base->out = that; + } +} + +/** Get the first connected producer service. +*/ + +mlt_service mlt_service_get_producer( mlt_service this ) +{ + mlt_service producer = NULL; + + // Get the service base + mlt_service_base *base = this->local; + + if ( base->in != NULL ) + producer = base->in[ 0 ]; + + return producer; +} + +/** Default implementation of get_frame. +*/ + +static int service_get_frame( mlt_service this, mlt_frame_ptr frame, int index ) +{ + mlt_service_base *base = this->local; + if ( index < base->count ) + { + mlt_service producer = base->in[ index ]; + if ( producer != NULL ) + return mlt_service_get_frame( producer, frame, index ); + } + *frame = mlt_frame_init( ); + return 0; +} + +/** Return the properties object. +*/ + +mlt_properties mlt_service_properties( mlt_service self ) +{ + return self != NULL ? &self->parent : NULL; +} + +/** Recursively apply attached filters +*/ + +void mlt_service_apply_filters( mlt_service this, mlt_frame frame, int index ) +{ + int i; + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + mlt_properties service_properties = MLT_SERVICE_PROPERTIES( this ); + mlt_service_base *base = this->local; + mlt_position position = mlt_frame_get_position( frame ); + mlt_position this_in = mlt_properties_get_position( service_properties, "in" ); + mlt_position this_out = mlt_properties_get_position( service_properties, "out" ); + + if ( index == 0 || mlt_properties_get_int( service_properties, "_filter_private" ) == 0 ) + { + // Process the frame with the attached filters + for ( i = 0; i < base->filter_count; i ++ ) + { + if ( base->filters[ i ] != NULL ) + { + mlt_position in = mlt_filter_get_in( base->filters[ i ] ); + mlt_position out = mlt_filter_get_out( base->filters[ i ] ); + int disable = mlt_properties_get_int( MLT_FILTER_PROPERTIES( base->filters[ i ] ), "disable" ); + if ( !disable && ( ( in == 0 && out == 0 ) || ( position >= in && ( position <= out || out == 0 ) ) ) ) + { + mlt_properties_set_position( frame_properties, "in", in == 0 ? this_in : in ); + mlt_properties_set_position( frame_properties, "out", out == 0 ? this_out : out ); + mlt_filter_process( base->filters[ i ], frame ); + mlt_service_apply_filters( MLT_FILTER_SERVICE( base->filters[ i ] ), frame, index + 1 ); + } + } + } + } +} + +/** Obtain a frame. +*/ + +int mlt_service_get_frame( mlt_service this, mlt_frame_ptr frame, int index ) +{ + int result = 0; + + // Lock the service + mlt_service_lock( this ); + + // Ensure that the frame is NULL + *frame = NULL; + + // Only process if we have a valid service + if ( this != NULL && this->get_frame != NULL ) + { + mlt_properties properties = MLT_SERVICE_PROPERTIES( this ); + mlt_position in = mlt_properties_get_position( properties, "in" ); + mlt_position out = mlt_properties_get_position( properties, "out" ); + + result = this->get_frame( this, frame, index ); + + if ( result == 0 ) + { + mlt_properties_inc_ref( properties ); + properties = MLT_FRAME_PROPERTIES( *frame ); + if ( in >=0 && out > 0 ) + { + mlt_properties_set_position( properties, "in", in ); + mlt_properties_set_position( properties, "out", out ); + } + mlt_service_apply_filters( this, *frame, 1 ); + mlt_deque_push_back( MLT_FRAME_SERVICE_STACK( *frame ), this ); + } + } + + // Make sure we return a frame + if ( *frame == NULL ) + *frame = mlt_frame_init( ); + + // Unlock the service + mlt_service_unlock( this ); + + return result; +} + +static void mlt_service_filter_changed( mlt_service owner, mlt_service this ) +{ + mlt_events_fire( MLT_SERVICE_PROPERTIES( this ), "service-changed", NULL ); +} + +/** Attach a filter. +*/ + +int mlt_service_attach( mlt_service this, mlt_filter filter ) +{ + int error = this == NULL || filter == NULL; + if ( error == 0 ) + { + int i = 0; + mlt_properties properties = MLT_SERVICE_PROPERTIES( this ); + mlt_service_base *base = this->local; + + for ( i = 0; error == 0 && i < base->filter_count; i ++ ) + if ( base->filters[ i ] == filter ) + error = 1; + + if ( error == 0 ) + { + if ( base->filter_count == base->filter_size ) + { + base->filter_size += 10; + base->filters = realloc( base->filters, base->filter_size * sizeof( mlt_filter ) ); + } + + if ( base->filters != NULL ) + { + mlt_properties props = MLT_FILTER_PROPERTIES( filter ); + mlt_properties_inc_ref( MLT_FILTER_PROPERTIES( filter ) ); + base->filters[ base->filter_count ++ ] = filter; + mlt_events_fire( properties, "service-changed", NULL ); + mlt_events_listen( props, this, "service-changed", ( mlt_listener )mlt_service_filter_changed ); + mlt_events_listen( props, this, "property-changed", ( mlt_listener )mlt_service_filter_changed ); + } + else + { + error = 2; + } + } + } + return error; +} + +/** Detach a filter. +*/ + +int mlt_service_detach( mlt_service this, mlt_filter filter ) +{ + int error = this == NULL || filter == NULL; + if ( error == 0 ) + { + int i = 0; + mlt_service_base *base = this->local; + mlt_properties properties = MLT_SERVICE_PROPERTIES( this ); + + for ( i = 0; i < base->filter_count; i ++ ) + if ( base->filters[ i ] == filter ) + break; + + if ( i < base->filter_count ) + { + base->filters[ i ] = NULL; + for ( i ++ ; i < base->filter_count; i ++ ) + base->filters[ i - 1 ] = base->filters[ i ]; + base->filter_count --; + mlt_events_disconnect( MLT_FILTER_PROPERTIES( filter ), this ); + mlt_filter_close( filter ); + mlt_events_fire( properties, "service-changed", NULL ); + } + } + return error; +} + +/** Retrieve a filter. +*/ + +mlt_filter mlt_service_filter( mlt_service this, int index ) +{ + mlt_filter filter = NULL; + if ( this != NULL ) + { + mlt_service_base *base = this->local; + if ( index >= 0 && index < base->filter_count ) + filter = base->filters[ index ]; + } + return filter; +} + +/** Close the service. +*/ + +void mlt_service_close( mlt_service this ) +{ + mlt_service_lock( this ); + if ( this != NULL && mlt_properties_dec_ref( MLT_SERVICE_PROPERTIES( this ) ) <= 0 ) + { + mlt_service_unlock( this ); + if ( this->close != NULL ) + { + this->close( this->close_object ); + } + else + { + mlt_service_base *base = this->local; + int i = 0; + int count = base->filter_count; + mlt_events_block( MLT_SERVICE_PROPERTIES( this ), this ); + while( count -- ) + mlt_service_detach( this, base->filters[ 0 ] ); + free( base->filters ); + for ( i = 0; i < base->count; i ++ ) + if ( base->in[ i ] != NULL ) + mlt_service_close( base->in[ i ] ); + this->parent.close = NULL; + free( base->in ); + pthread_mutex_destroy( &base->mutex ); + free( base ); + mlt_properties_close( &this->parent ); + } + } + else + { + mlt_service_unlock( this ); + } +} + diff --git a/src/framework/mlt_service.h b/src/framework/mlt_service.h new file mode 100644 index 0000000..fea7497 --- /dev/null +++ b/src/framework/mlt_service.h @@ -0,0 +1,69 @@ +/* + * mlt_service.h -- interface for all service classes + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_SERVICE_H_ +#define _MLT_SERVICE_H_ + +#include "mlt_properties.h" + +/** The interface definition for all services. +*/ + +struct mlt_service_s +{ + /* We're extending properties here */ + struct mlt_properties_s parent; + + /* Protected virtual */ + int ( *get_frame )( mlt_service self, mlt_frame_ptr frame, int index ); + mlt_destructor close; + void *close_object; + + /* Private data */ + void *local; + void *child; +}; + +/** The public API. +*/ + +#define MLT_SERVICE_PROPERTIES( service ) ( &( service )->parent ) + +extern int mlt_service_init( mlt_service self, void *child ); +extern void mlt_service_lock( mlt_service self ); +extern void mlt_service_unlock( mlt_service self ); +extern mlt_service_type mlt_service_identify( mlt_service self ); +extern int mlt_service_connect_producer( mlt_service self, mlt_service producer, int index ); +extern int mlt_service_get_frame( mlt_service self, mlt_frame_ptr frame, int index ); +extern mlt_properties mlt_service_properties( mlt_service self ); +extern mlt_service mlt_service_consumer( mlt_service self ); +extern mlt_service mlt_service_producer( mlt_service self ); +extern int mlt_service_attach( mlt_service self, mlt_filter filter ); +extern int mlt_service_detach( mlt_service self, mlt_filter filter ); +extern void mlt_service_apply_filters( mlt_service self, mlt_frame frame, int index ); +extern mlt_filter mlt_service_filter( mlt_service self, int index ); + +extern void mlt_service_close( mlt_service self ); + +/* I'm not sure about self one - leaving it out of docs for now (only used in consumer_westley) */ +extern mlt_service mlt_service_get_producer( mlt_service self ); + +#endif + diff --git a/src/framework/mlt_tokeniser.c b/src/framework/mlt_tokeniser.c new file mode 100644 index 0000000..06103cd --- /dev/null +++ b/src/framework/mlt_tokeniser.c @@ -0,0 +1,169 @@ +/* + * mlt_tokeniser.c -- String tokeniser + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* System header files */ +#include +#include + +/* Application header files */ +#include "mlt_tokeniser.h" + +/** Initialise a tokeniser. +*/ + +mlt_tokeniser mlt_tokeniser_init( ) +{ + return calloc( 1, sizeof( mlt_tokeniser_t ) ); +} + +/** Clear the tokeniser. +*/ + +static void mlt_tokeniser_clear( mlt_tokeniser tokeniser ) +{ + int index = 0; + for ( index = 0; index < tokeniser->count; index ++ ) + free( tokeniser->tokens[ index ] ); + tokeniser->count = 0; + free( tokeniser->input ); + tokeniser->input = NULL; +} + +/** Append a string to the tokeniser. +*/ + +static int mlt_tokeniser_append( mlt_tokeniser tokeniser, char *token ) +{ + int error = 0; + + if ( tokeniser->count == tokeniser->size ) + { + tokeniser->size += 20; + tokeniser->tokens = realloc( tokeniser->tokens, tokeniser->size * sizeof( char * ) ); + } + + if ( tokeniser->tokens != NULL ) + { + tokeniser->tokens[ tokeniser->count ++ ] = strdup( token ); + } + else + { + tokeniser->count = 0; + error = -1; + } + return error; +} + +/** Parse a string by splitting on the delimiter provided. +*/ + +int mlt_tokeniser_parse_new( mlt_tokeniser tokeniser, char *string, char *delimiter ) +{ + int count = 0; + int length = strlen( string ); + int delimiter_size = strlen( delimiter ); + int index = 0; + char *token = strdup( string ); + + mlt_tokeniser_clear( tokeniser ); + tokeniser->input = strdup( string ); + strcpy( token, "" ); + + for ( index = 0; index < length; ) + { + char *start = string + index; + char *end = strstr( start, delimiter ); + + if ( end == NULL ) + { + strcat( token, start ); + mlt_tokeniser_append( tokeniser, token ); + index = length; + count ++; + } + else if ( start != end ) + { + strncat( token, start, end - start ); + index += end - start; + if ( strchr( token, '\"' ) == NULL || token[ strlen( token ) - 1 ] == '\"' ) + { + mlt_tokeniser_append( tokeniser, token ); + strcpy( token, "" ); + count ++; + } + else while ( strncmp( string + index, delimiter, delimiter_size ) == 0 ) + { + strncat( token, delimiter, delimiter_size ); + index += delimiter_size; + } + } + else + { + index += strlen( delimiter ); + } + } + + /* Special case - malformed string condition */ + if ( !strcmp( token, "" ) ) + { + count = 0 - ( count - 1 ); + mlt_tokeniser_append( tokeniser, token ); + } + + free( token ); + return count; +} + +/** Get the original input. +*/ + +char *mlt_tokeniser_get_input( mlt_tokeniser tokeniser ) +{ + return tokeniser->input; +} + +/** Get the number of tokens. +*/ + +int mlt_tokeniser_count( mlt_tokeniser tokeniser ) +{ + return tokeniser->count; +} + +/** Get a token as a string. +*/ + +char *mlt_tokeniser_get_string( mlt_tokeniser tokeniser, int index ) +{ + if ( index < tokeniser->count ) + return tokeniser->tokens[ index ]; + else + return NULL; +} + +/** Close the tokeniser. +*/ + +void mlt_tokeniser_close( mlt_tokeniser tokeniser ) +{ + mlt_tokeniser_clear( tokeniser ); + free( tokeniser->tokens ); + free( tokeniser ); +} diff --git a/src/framework/mlt_tokeniser.h b/src/framework/mlt_tokeniser.h new file mode 100644 index 0000000..357551d --- /dev/null +++ b/src/framework/mlt_tokeniser.h @@ -0,0 +1,46 @@ +/* + * mlt_tokeniser.h -- String tokeniser + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_TOKENISER_H_ +#define _MLT_TOKENISER_H_ + +/** Structure for tokeniser. +*/ + +typedef struct +{ + char *input; + char **tokens; + int count; + int size; +} +*mlt_tokeniser, mlt_tokeniser_t; + +/** Remote parser API. +*/ + +extern mlt_tokeniser mlt_tokeniser_init( ); +extern int mlt_tokeniser_parse_new( mlt_tokeniser self, char *text, char *delimiter ); +extern char *mlt_tokeniser_get_input( mlt_tokeniser self ); +extern int mlt_tokeniser_count( mlt_tokeniser self ); +extern char *mlt_tokeniser_get_string( mlt_tokeniser self, int index ); +extern void mlt_tokeniser_close( mlt_tokeniser self ); + +#endif diff --git a/src/framework/mlt_tractor.c b/src/framework/mlt_tractor.c new file mode 100644 index 0000000..5af2839 --- /dev/null +++ b/src/framework/mlt_tractor.c @@ -0,0 +1,474 @@ +/* + * mlt_tractor.c -- tractor service class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "mlt_tractor.h" +#include "mlt_frame.h" +#include "mlt_multitrack.h" +#include "mlt_field.h" + +#include +#include +#include +#include + +/** Forward references to static methods. +*/ + +static int producer_get_frame( mlt_producer this, mlt_frame_ptr frame, int track ); +static void mlt_tractor_listener( mlt_multitrack tracks, mlt_tractor this ); + +/** Constructor for the tractor. +*/ + +mlt_tractor mlt_tractor_init( ) +{ + mlt_tractor this = calloc( sizeof( struct mlt_tractor_s ), 1 ); + if ( this != NULL ) + { + mlt_producer producer = &this->parent; + if ( mlt_producer_init( producer, this ) == 0 ) + { + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + + mlt_properties_set( properties, "resource", "" ); + mlt_properties_set( properties, "mlt_type", "mlt_producer" ); + mlt_properties_set( properties, "mlt_service", "tractor" ); + mlt_properties_set_int( properties, "in", 0 ); + mlt_properties_set_int( properties, "out", -1 ); + mlt_properties_set_int( properties, "length", 0 ); + + producer->get_frame = producer_get_frame; + producer->close = ( mlt_destructor )mlt_tractor_close; + producer->close_object = this; + } + else + { + free( this ); + this = NULL; + } + } + return this; +} + +mlt_tractor mlt_tractor_new( ) +{ + mlt_tractor this = calloc( sizeof( struct mlt_tractor_s ), 1 ); + if ( this != NULL ) + { + mlt_producer producer = &this->parent; + if ( mlt_producer_init( producer, this ) == 0 ) + { + mlt_multitrack multitrack = mlt_multitrack_init( ); + mlt_field field = mlt_field_new( multitrack, this ); + mlt_properties props = MLT_PRODUCER_PROPERTIES( producer ); + + mlt_properties_set( props, "resource", "" ); + mlt_properties_set( props, "mlt_type", "mlt_producer" ); + mlt_properties_set( props, "mlt_service", "tractor" ); + mlt_properties_set_position( props, "in", 0 ); + mlt_properties_set_position( props, "out", 0 ); + mlt_properties_set_position( props, "length", 0 ); + mlt_properties_set_data( props, "multitrack", multitrack, 0, ( mlt_destructor )mlt_multitrack_close, NULL ); + mlt_properties_set_data( props, "field", field, 0, ( mlt_destructor )mlt_field_close, NULL ); + + mlt_events_listen( MLT_MULTITRACK_PROPERTIES( multitrack ), this, "producer-changed", ( mlt_listener )mlt_tractor_listener ); + + producer->get_frame = producer_get_frame; + producer->close = ( mlt_destructor )mlt_tractor_close; + producer->close_object = this; + } + else + { + free( this ); + this = NULL; + } + } + return this; +} + +/** Get the service object associated to the tractor. +*/ + +mlt_service mlt_tractor_service( mlt_tractor this ) +{ + return MLT_PRODUCER_SERVICE( &this->parent ); +} + +/** Get the producer object associated to the tractor. +*/ + +mlt_producer mlt_tractor_producer( mlt_tractor this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Get the properties object associated to the tractor. +*/ + +mlt_properties mlt_tractor_properties( mlt_tractor this ) +{ + return MLT_PRODUCER_PROPERTIES( &this->parent ); +} + +/** Get the field this tractor is harvesting. +*/ + +mlt_field mlt_tractor_field( mlt_tractor this ) +{ + return mlt_properties_get_data( MLT_TRACTOR_PROPERTIES( this ), "field", NULL ); +} + +/** Get the multitrack this tractor is pulling. +*/ + +mlt_multitrack mlt_tractor_multitrack( mlt_tractor this ) +{ + return mlt_properties_get_data( MLT_TRACTOR_PROPERTIES( this ), "multitrack", NULL ); +} + +/** Ensure the tractors in/out points match the multitrack. +*/ + +void mlt_tractor_refresh( mlt_tractor this ) +{ + mlt_multitrack multitrack = mlt_tractor_multitrack( this ); + mlt_properties properties = MLT_MULTITRACK_PROPERTIES( multitrack ); + mlt_properties self = MLT_TRACTOR_PROPERTIES( this ); + mlt_events_block( properties, self ); + mlt_events_block( self, self ); + mlt_multitrack_refresh( multitrack ); + mlt_properties_set_position( self, "in", 0 ); + mlt_properties_set_position( self, "out", mlt_properties_get_position( properties, "out" ) ); + mlt_events_unblock( self, self ); + mlt_events_unblock( properties, self ); + mlt_properties_set_position( self, "length", mlt_properties_get_position( properties, "length" ) ); +} + +static void mlt_tractor_listener( mlt_multitrack tracks, mlt_tractor this ) +{ + mlt_tractor_refresh( this ); +} + +/** Connect the tractor. +*/ + +int mlt_tractor_connect( mlt_tractor this, mlt_service producer ) +{ + int ret = mlt_service_connect_producer( MLT_TRACTOR_SERVICE( this ), producer, 0 ); + + // This is the producer we're going to connect to + if ( ret == 0 ) + this->producer = producer; + + return ret; +} + +/** Set the producer for a specific track. +*/ + +int mlt_tractor_set_track( mlt_tractor this, mlt_producer producer, int index ) +{ + return mlt_multitrack_connect( mlt_tractor_multitrack( this ), producer, index ); +} + +/** Get the producer for a specific track. +*/ + +mlt_producer mlt_tractor_get_track( mlt_tractor this, int index ) +{ + return mlt_multitrack_track( mlt_tractor_multitrack( this ), index ); +} + +static int producer_get_image( mlt_frame this, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + uint8_t *data = NULL; + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + mlt_frame frame = mlt_frame_pop_service( this ); + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + mlt_properties_set( frame_properties, "rescale.interp", mlt_properties_get( properties, "rescale.interp" ) ); + mlt_properties_set_int( frame_properties, "resize_alpha", mlt_properties_get_int( properties, "resize_alpha" ) ); + mlt_properties_set_int( frame_properties, "distort", mlt_properties_get_int( properties, "distort" ) ); + mlt_properties_set_double( frame_properties, "consumer_aspect_ratio", mlt_properties_get_double( properties, "consumer_aspect_ratio" ) ); + mlt_properties_set_int( frame_properties, "consumer_deinterlace", mlt_properties_get_int( properties, "consumer_deinterlace" ) ); + mlt_properties_set( frame_properties, "deinterlace_method", mlt_properties_get( properties, "deinterlace_method" ) ); + mlt_properties_set_int( frame_properties, "normalised_width", mlt_properties_get_int( properties, "normalised_width" ) ); + mlt_properties_set_int( frame_properties, "normalised_height", mlt_properties_get_int( properties, "normalised_height" ) ); + mlt_frame_get_image( frame, buffer, format, width, height, writable ); + mlt_properties_set_data( properties, "image", *buffer, *width * *height * 2, NULL, NULL ); + mlt_properties_set_int( properties, "width", *width ); + mlt_properties_set_int( properties, "height", *height ); + mlt_properties_set_int( properties, "format", *format ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_frame_get_aspect_ratio( frame ) ); + mlt_properties_set_int( properties, "progressive", mlt_properties_get_int( frame_properties, "progressive" ) ); + mlt_properties_set_int( properties, "distort", mlt_properties_get_int( frame_properties, "distort" ) ); + data = mlt_frame_get_alpha_mask( frame ); + mlt_properties_set_data( properties, "alpha", data, 0, NULL, NULL ); + return 0; +} + +static int producer_get_audio( mlt_frame this, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + mlt_frame frame = mlt_frame_pop_audio( this ); + mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples ); + mlt_properties_set_data( properties, "audio", *buffer, 0, NULL, NULL ); + mlt_properties_set_int( properties, "frequency", *frequency ); + mlt_properties_set_int( properties, "channels", *channels ); + return 0; +} + +static void destroy_data_queue( void *arg ) +{ + if ( arg != NULL ) + { + // Assign the correct type + mlt_deque queue = arg; + + // Iterate through each item and destroy them + while ( mlt_deque_peek_front( queue ) != NULL ) + mlt_properties_close( mlt_deque_pop_back( queue ) ); + + // Close the deque + mlt_deque_close( queue ); + } +} + +/** Get the next frame. + + TODO: This function needs to be redesigned... +*/ + +static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int track ) +{ + mlt_tractor this = parent->child; + + // We only respond to the first track requests + if ( track == 0 && this->producer != NULL ) + { + int i = 0; + int done = 0; + mlt_frame temp = NULL; + int count = 0; + int image_count = 0; + + // Get the properties of the parent producer + mlt_properties properties = MLT_PRODUCER_PROPERTIES( parent ); + + // Try to obtain the multitrack associated to the tractor + mlt_multitrack multitrack = mlt_properties_get_data( properties, "multitrack", NULL ); + + // Or a specific producer + mlt_producer producer = mlt_properties_get_data( properties, "producer", NULL ); + + // The output frame will hold the 'global' data feeds (ie: those which are targetted for the final frame) + mlt_deque data_queue = mlt_deque_init( ); + + // Determine whether this tractor feeds to the consumer or stops here + int global_feed = mlt_properties_get_int( properties, "global_feed" ); + + // If we don't have one, we're in trouble... + if ( multitrack != NULL ) + { + // Used to garbage collect all frames + char label[ 30 ]; + + // Get the id of the tractor + char *id = mlt_properties_get( properties, "_unique_id" ); + + // Will be used to store the frame properties object + mlt_properties frame_properties = NULL; + + // We'll store audio and video frames to use here + mlt_frame audio = NULL; + mlt_frame video = NULL; + mlt_frame first_video = NULL; + + // Temporary properties + mlt_properties temp_properties = NULL; + + // Get the multitrack's producer + mlt_producer target = MLT_MULTITRACK_PRODUCER( multitrack ); + mlt_producer_seek( target, mlt_producer_frame( parent ) ); + mlt_producer_set_speed( target, mlt_producer_get_speed( parent ) ); + + // We will create one frame and attach everything to it + *frame = mlt_frame_init( ); + + // Get the properties of the frame + frame_properties = MLT_FRAME_PROPERTIES( *frame ); + + // Loop through each of the tracks we're harvesting + for ( i = 0; !done; i ++ ) + { + // Get a frame from the producer + mlt_service_get_frame( this->producer, &temp, i ); + + // Get the temporary properties + temp_properties = MLT_FRAME_PROPERTIES( temp ); + + // Check for last track + done = mlt_properties_get_int( temp_properties, "last_track" ); + + // Handle fx only tracks + if ( mlt_properties_get_int( temp_properties, "fx_cut" ) ) + { + int hide = ( video == NULL ? 1 : 0 ) | ( audio == NULL ? 2 : 0 ); + mlt_properties_set_int( temp_properties, "hide", hide ); + } + + // We store all frames with a destructor on the output frame + sprintf( label, "_%s_%d", id, count ++ ); + mlt_properties_set_data( frame_properties, label, temp, 0, ( mlt_destructor )mlt_frame_close, NULL ); + + // We want to append all 'final' feeds to the global queue + if ( !done && mlt_properties_get_data( temp_properties, "data_queue", NULL ) != NULL ) + { + // Move the contents of this queue on to the output frames data queue + mlt_deque sub_queue = mlt_properties_get_data( MLT_FRAME_PROPERTIES( temp ), "data_queue", NULL ); + mlt_deque temp = mlt_deque_init( ); + while ( global_feed && mlt_deque_count( sub_queue ) ) + { + mlt_properties p = mlt_deque_pop_back( sub_queue ); + if ( mlt_properties_get_int( p, "final" ) ) + mlt_deque_push_back( data_queue, p ); + else + mlt_deque_push_back( temp, p ); + } + while( mlt_deque_count( temp ) ) + mlt_deque_push_front( sub_queue, mlt_deque_pop_back( temp ) ); + mlt_deque_close( temp ); + } + + // Now do the same with the global queue but without the conditional behaviour + if ( mlt_properties_get_data( temp_properties, "global_queue", NULL ) != NULL ) + { + mlt_deque sub_queue = mlt_properties_get_data( MLT_FRAME_PROPERTIES( temp ), "global_queue", NULL ); + while ( mlt_deque_count( sub_queue ) ) + { + mlt_properties p = mlt_deque_pop_back( sub_queue ); + mlt_deque_push_back( data_queue, p ); + } + } + + // Pick up first video and audio frames + if ( !done && !mlt_frame_is_test_audio( temp ) && !( mlt_properties_get_int( temp_properties, "hide" ) & 2 ) ) + { + // Order of frame creation is starting to get problematic + if ( audio != NULL ) + { + mlt_deque_push_front( MLT_FRAME_AUDIO_STACK( temp ), producer_get_audio ); + mlt_deque_push_front( MLT_FRAME_AUDIO_STACK( temp ), audio ); + } + audio = temp; + } + if ( !done && !mlt_frame_is_test_card( temp ) && !( mlt_properties_get_int( temp_properties, "hide" ) & 1 ) ) + { + if ( video != NULL ) + { + mlt_deque_push_front( MLT_FRAME_IMAGE_STACK( temp ), producer_get_image ); + mlt_deque_push_front( MLT_FRAME_IMAGE_STACK( temp ), video ); + } + video = temp; + if ( first_video == NULL ) + first_video = temp; + + // Ensure that all frames know the aspect ratio of the background + mlt_properties_set_double( temp_properties, "output_ratio", + mlt_properties_get_double( MLT_FRAME_PROPERTIES( first_video ), "aspect_ratio" ) ); + + mlt_properties_set_int( MLT_FRAME_PROPERTIES( temp ), "image_count", ++ image_count ); + image_count = 1; + } + } + + // Now stack callbacks + if ( audio != NULL ) + { + mlt_frame_push_audio( *frame, audio ); + mlt_frame_push_audio( *frame, producer_get_audio ); + } + + if ( video != NULL ) + { + mlt_properties video_properties = MLT_FRAME_PROPERTIES( first_video ); + mlt_frame_push_service( *frame, video ); + mlt_frame_push_service( *frame, producer_get_image ); + if ( global_feed ) + mlt_properties_set_data( frame_properties, "data_queue", data_queue, 0, NULL, NULL ); + mlt_properties_set_data( video_properties, "global_queue", data_queue, 0, destroy_data_queue, NULL ); + mlt_properties_set_int( frame_properties, "width", mlt_properties_get_int( video_properties, "width" ) ); + mlt_properties_set_int( frame_properties, "height", mlt_properties_get_int( video_properties, "height" ) ); + mlt_properties_set_int( frame_properties, "real_width", mlt_properties_get_int( video_properties, "real_width" ) ); + mlt_properties_set_int( frame_properties, "real_height", mlt_properties_get_int( video_properties, "real_height" ) ); + mlt_properties_set_int( frame_properties, "progressive", mlt_properties_get_int( video_properties, "progressive" ) ); + mlt_properties_set_double( frame_properties, "aspect_ratio", mlt_properties_get_double( video_properties, "aspect_ratio" ) ); + mlt_properties_set_int( frame_properties, "image_count", image_count ); + } + else + { + destroy_data_queue( data_queue ); + } + + mlt_frame_set_position( *frame, mlt_producer_frame( parent ) ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( *frame ), "test_audio", audio == NULL ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( *frame ), "test_image", video == NULL ); + mlt_properties_set_data( MLT_FRAME_PROPERTIES( *frame ), "consumer_lock_service", this, 0, NULL, NULL ); + } + else if ( producer != NULL ) + { + mlt_producer_seek( producer, mlt_producer_frame( parent ) ); + mlt_producer_set_speed( producer, mlt_producer_get_speed( parent ) ); + mlt_service_get_frame( this->producer, frame, track ); + } + else + { + fprintf( stderr, "tractor without a multitrack!!\n" ); + mlt_service_get_frame( this->producer, frame, track ); + } + + // Prepare the next frame + mlt_producer_prepare_next( parent ); + + // Indicate our found status + return 0; + } + else + { + // Generate a test card + *frame = mlt_frame_init( ); + return 0; + } +} + +/** Close the tractor. +*/ + +void mlt_tractor_close( mlt_tractor this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_TRACTOR_PROPERTIES( this ) ) <= 0 ) + { + this->parent.close = NULL; + mlt_producer_close( &this->parent ); + free( this ); + } +} + diff --git a/src/framework/mlt_tractor.h b/src/framework/mlt_tractor.h new file mode 100644 index 0000000..acad893 --- /dev/null +++ b/src/framework/mlt_tractor.h @@ -0,0 +1,52 @@ +/* + * mlt_tractor.h -- tractor service class + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_TRACTOR_H_ +#define _MLT_TRACTOR_H_ + +#include "mlt_producer.h" + +/** Private structure. +*/ + +struct mlt_tractor_s +{ + struct mlt_producer_s parent; + mlt_service producer; +}; + +#define MLT_TRACTOR_PRODUCER( tractor ) ( &( tractor )->parent ) +#define MLT_TRACTOR_SERVICE( tractor ) MLT_PRODUCER_SERVICE( MLT_TRACTOR_PRODUCER( tractor ) ) +#define MLT_TRACTOR_PROPERTIES( tractor ) MLT_SERVICE_PROPERTIES( MLT_TRACTOR_SERVICE( tractor ) ) + +extern mlt_tractor mlt_tractor_init( ); +extern mlt_tractor mlt_tractor_new( ); +extern mlt_service mlt_tractor_service( mlt_tractor self ); +extern mlt_producer mlt_tractor_producer( mlt_tractor self ); +extern mlt_properties mlt_tractor_properties( mlt_tractor self ); +extern mlt_field mlt_tractor_field( mlt_tractor self ); +extern mlt_multitrack mlt_tractor_multitrack( mlt_tractor self ); +extern int mlt_tractor_connect( mlt_tractor self, mlt_service service ); +extern void mlt_tractor_refresh( mlt_tractor self ); +extern int mlt_tractor_set_track( mlt_tractor self, mlt_producer producer, int index ); +extern mlt_producer mlt_tractor_get_track( mlt_tractor self, int index ); +extern void mlt_tractor_close( mlt_tractor self ); + +#endif diff --git a/src/framework/mlt_transition.c b/src/framework/mlt_transition.c new file mode 100644 index 0000000..5606c60 --- /dev/null +++ b/src/framework/mlt_transition.c @@ -0,0 +1,326 @@ +/* + * mlt_transition.c -- abstraction for all transition services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "mlt_transition.h" +#include "mlt_frame.h" + +#include +#include +#include + +/** Forward references. +*/ + +static int transition_get_frame( mlt_service this, mlt_frame_ptr frame, int index ); + +/** Constructor. +*/ + +int mlt_transition_init( mlt_transition this, void *child ) +{ + mlt_service service = &this->parent; + memset( this, 0, sizeof( struct mlt_transition_s ) ); + this->child = child; + if ( mlt_service_init( service, this ) == 0 ) + { + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + + service->get_frame = transition_get_frame; + service->close = ( mlt_destructor )mlt_transition_close; + service->close_object = this; + + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", 0 ); + mlt_properties_set_int( properties, "a_track", 0 ); + mlt_properties_set_int( properties, "b_track", 1 ); + + return 0; + } + return 1; +} + +/** Create a new transition. +*/ + +mlt_transition mlt_transition_new( ) +{ + mlt_transition this = calloc( 1, sizeof( struct mlt_transition_s ) ); + if ( this != NULL ) + mlt_transition_init( this, NULL ); + return this; +} + +/** Get the service associated to the transition. +*/ + +mlt_service mlt_transition_service( mlt_transition this ) +{ + return this != NULL ? &this->parent : NULL; +} + +/** Get the properties interface. +*/ + +mlt_properties mlt_transition_properties( mlt_transition this ) +{ + return MLT_TRANSITION_PROPERTIES( this ); +} + +/** Connect this transition with a producers a and b tracks. +*/ + +int mlt_transition_connect( mlt_transition this, mlt_service producer, int a_track, int b_track ) +{ + int ret = mlt_service_connect_producer( &this->parent, producer, a_track ); + if ( ret == 0 ) + { + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + this->producer = producer; + mlt_properties_set_int( properties, "a_track", a_track ); + mlt_properties_set_int( properties, "b_track", b_track ); + } + return ret; +} + +/** Set the in and out points. +*/ + +void mlt_transition_set_in_and_out( mlt_transition this, mlt_position in, mlt_position out ) +{ + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + mlt_properties_set_position( properties, "in", in ); + mlt_properties_set_position( properties, "out", out ); +} + +/** Get the index of the a track. +*/ + +int mlt_transition_get_a_track( mlt_transition this ) +{ + return mlt_properties_get_int( MLT_TRANSITION_PROPERTIES( this ), "a_track" ); +} + +/** Get the index of the b track. +*/ + +int mlt_transition_get_b_track( mlt_transition this ) +{ + return mlt_properties_get_int( MLT_TRANSITION_PROPERTIES( this ), "b_track" ); +} + +/** Get the in point. +*/ + +mlt_position mlt_transition_get_in( mlt_transition this ) +{ + return mlt_properties_get_position( MLT_TRANSITION_PROPERTIES( this ), "in" ); +} + +/** Get the out point. +*/ + +mlt_position mlt_transition_get_out( mlt_transition this ) +{ + return mlt_properties_get_position( MLT_TRANSITION_PROPERTIES( this ), "out" ); +} + +/** Process the frame. + + If we have no process method (unlikely), we simply return the a_frame unmolested. +*/ + +mlt_frame mlt_transition_process( mlt_transition this, mlt_frame a_frame, mlt_frame b_frame ) +{ + if ( this->process == NULL ) + return a_frame; + else + return this->process( this, a_frame, b_frame ); +} + +/** Get a frame from this transition. + + The logic is complex here. A transition is typically applied to frames on the a and + b tracks specified in the connect method above and only if both contain valid info + for the transition type (this is either audio or image). + + However, the fixed a_track may not always contain data of the correct type, eg: + + +---------+ +-------+ + |c1 | |c5 | <-- A(0,1) <-- B(0,2) <-- get frame + +---------+ +---------+-+-----+ | | + |c4 | <------+ | + +----------+-----------+-+---------+ | + |c2 |c3 | <-----------------+ + +----------+-------------+ + + During the overlap of c1 and c2, there is nothing for the A transition to do, so this + results in a no operation, but B is triggered. During the overlap of c2 and c3, again, + the A transition is inactive and because the B transition is pointing at track 0, + it too would be inactive. This isn't an ideal situation - it's better if the B + transition simply treats the frames from c3 as though they're the a track. + + For this to work, we cache all frames coming from all tracks between the a and b + tracks. Before we process, we determine that the b frame contains someting of the + right type and then we determine which frame to use as the a frame (selecting a + matching frame from a_track to b_track - 1). If both frames contain data of the + correct type, we process the transition. + + This method is invoked for each track and we return the cached frames as needed. + We clear the cache only when the requested frame is flagged as a 'last_track' frame. +*/ + +static int transition_get_frame( mlt_service service, mlt_frame_ptr frame, int index ) +{ + int error = 0; + mlt_transition this = service->child; + + mlt_properties properties = MLT_TRANSITION_PROPERTIES( this ); + + int accepts_blanks = mlt_properties_get_int( properties, "accepts_blanks" ); + int a_track = mlt_properties_get_int( properties, "a_track" ); + int b_track = mlt_properties_get_int( properties, "b_track" ); + mlt_position in = mlt_properties_get_position( properties, "in" ); + mlt_position out = mlt_properties_get_position( properties, "out" ); + int always_active = mlt_properties_get_int( properties, "always_active" ); + int type = mlt_properties_get_int( properties, "_transition_type" ); + int reverse_order = 0; + + // Ensure that we have the correct order + if ( a_track > b_track ) + { + reverse_order = 1; + a_track = b_track; + b_track = mlt_properties_get_int( properties, "a_track" ); + } + + // Only act on this operation once per multitrack iteration from the tractor + if ( !this->held ) + { + int active = 0; + int i = 0; + int a_frame = a_track; + int b_frame = b_track; + mlt_position position; + int ( *invalid )( mlt_frame ) = type == 1 ? mlt_frame_is_test_card : mlt_frame_is_test_audio; + + // Initialise temporary store + if ( this->frames == NULL ) + this->frames = calloc( sizeof( mlt_frame ), b_track + 1 ); + + // Get all frames between a and b + for( i = a_track; i <= b_track; i ++ ) + mlt_service_get_frame( this->producer, &this->frames[ i ], i ); + + // We're holding these frames until the last_track frame property is received + this->held = 1; + + // When we need to locate the a_frame + switch( type ) + { + case 1: + case 2: + // Some transitions (esp. audio) may accept blank frames + active = accepts_blanks; + + // If we're not active then... + if ( !active ) + { + // Hunt for the a_frame + while( a_frame <= b_frame && invalid( this->frames[ a_frame ] ) ) + a_frame ++; + + // Determine if we're active now + active = a_frame != b_frame && !invalid( this->frames[ b_frame ] ); + } + break; + + default: + fprintf( stderr, "invalid transition type\n" ); + break; + } + + // Now handle the non-always active case + if ( active && !always_active ) + { + // For non-always-active transitions, we need the current position of the a frame + position = mlt_frame_get_position( this->frames[ a_frame ] ); + + // If a is in range, we're active + active = position >= in && position <= out; + } + + // Finally, process the a and b frames + if ( active ) + { + mlt_frame a_frame_ptr = this->frames[ !reverse_order ? a_frame : b_frame ]; + mlt_frame b_frame_ptr = this->frames[ !reverse_order ? b_frame : a_frame ]; + int a_hide = mlt_properties_get_int( MLT_FRAME_PROPERTIES( a_frame_ptr ), "hide" ); + int b_hide = mlt_properties_get_int( MLT_FRAME_PROPERTIES( b_frame_ptr ), "hide" ); + if ( !( a_hide & type ) && !( b_hide & type ) ) + { + // Process the transition + *frame = mlt_transition_process( this, a_frame_ptr, b_frame_ptr ); + + // We need to ensure that the tractor doesn't consider this frame for output + if ( *frame == a_frame_ptr ) + b_hide |= type; + else + a_hide |= type; + + mlt_properties_set_int( MLT_FRAME_PROPERTIES( a_frame_ptr ), "hide", a_hide ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( b_frame_ptr ), "hide", b_hide ); + } + } + } + + // Obtain the frame from the cache or the producer we're attached to + if ( index >= a_track && index <= b_track ) + *frame = this->frames[ index ]; + else + error = mlt_service_get_frame( this->producer, frame, index ); + + // Determine if that was the last track + this->held = !mlt_properties_get_int( MLT_FRAME_PROPERTIES( *frame ), "last_track" ); + + return error; +} + +/** Close the transition. +*/ + +void mlt_transition_close( mlt_transition this ) +{ + if ( this != NULL && mlt_properties_dec_ref( MLT_TRANSITION_PROPERTIES( this ) ) <= 0 ) + { + this->parent.close = NULL; + if ( this->close != NULL ) + { + this->close( this ); + } + else + { + mlt_service_close( &this->parent ); + free( this->frames ); + free( this ); + } + } +} diff --git a/src/framework/mlt_transition.h b/src/framework/mlt_transition.h new file mode 100644 index 0000000..d1e32de --- /dev/null +++ b/src/framework/mlt_transition.h @@ -0,0 +1,70 @@ +/* + * mlt_transition.h -- abstraction for all transition services + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_TRANSITION_H_ +#define _MLT_TRANSITION_H_ + +#include "mlt_service.h" + +/** The interface definition for all transitions. +*/ + +struct mlt_transition_s +{ + /* We're implementing service here */ + struct mlt_service_s parent; + + /* public virtual */ + void ( *close )( mlt_transition ); + + /* protected transition method */ + mlt_frame ( *process )( mlt_transition, mlt_frame, mlt_frame ); + + /* Protected */ + void *child; + + /* track and in/out points */ + mlt_service producer; + + /* Private */ + mlt_frame *frames; + int held; +}; + +/** Public final methods +*/ + +#define MLT_TRANSITION_SERVICE( transition ) ( &( transition )->parent ) +#define MLT_TRANSITION_PROPERTIES( transition ) MLT_SERVICE_PROPERTIES( MLT_TRANSITION_SERVICE( transition ) ) + +extern int mlt_transition_init( mlt_transition self, void *child ); +extern mlt_transition mlt_transition_new( ); +extern mlt_service mlt_transition_service( mlt_transition self ); +extern mlt_properties mlt_transition_properties( mlt_transition self ); +extern int mlt_transition_connect( mlt_transition self, mlt_service producer, int a_track, int b_track ); +extern void mlt_transition_set_in_and_out( mlt_transition self, mlt_position in, mlt_position out ); +extern int mlt_transition_get_a_track( mlt_transition self ); +extern int mlt_transition_get_b_track( mlt_transition self ); +extern mlt_position mlt_transition_get_in( mlt_transition self ); +extern mlt_position mlt_transition_get_out( mlt_transition self ); +extern mlt_frame mlt_transition_process( mlt_transition self, mlt_frame a_frame, mlt_frame b_frame ); +extern void mlt_transition_close( mlt_transition self ); + +#endif diff --git a/src/framework/mlt_types.h b/src/framework/mlt_types.h new file mode 100644 index 0000000..dde83e0 --- /dev/null +++ b/src/framework/mlt_types.h @@ -0,0 +1,110 @@ +/* + * mlt_types.h -- provides forward definitions of all public types + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_TYPES_H_ +#define _MLT_TYPES_H_ + +#ifndef GCC_VERSION +#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#endif + +#include + +#include "mlt_pool.h" + +typedef enum +{ + mlt_image_none = 0, + mlt_image_rgb24, + mlt_image_rgb24a, + mlt_image_yuv422, + mlt_image_yuv420p, + mlt_image_opengl +} +mlt_image_format; + +typedef enum +{ + mlt_audio_none = 0, + mlt_audio_pcm +} +mlt_audio_format; + +typedef enum +{ + mlt_whence_relative_start, + mlt_whence_relative_current, + mlt_whence_relative_end +} +mlt_whence; + +typedef enum +{ + invalid_type, + unknown_type, + producer_type, + playlist_type, + tractor_type, + multitrack_type, + filter_type, + transition_type, + consumer_type, + field_type +} +mlt_service_type; + +/* I don't want to break anyone's applications without warning. -Zach */ +#undef DOUBLE_MLT_POSITION +#ifdef DOUBLE_MLT_POSITION +typedef double mlt_position; +#else +typedef int32_t mlt_position; +#endif + +typedef struct mlt_frame_s *mlt_frame, **mlt_frame_ptr; +typedef struct mlt_properties_s *mlt_properties; +typedef struct mlt_event_struct *mlt_event; +typedef struct mlt_service_s *mlt_service; +typedef struct mlt_producer_s *mlt_producer; +typedef struct mlt_playlist_s *mlt_playlist; +typedef struct mlt_multitrack_s *mlt_multitrack; +typedef struct mlt_filter_s *mlt_filter; +typedef struct mlt_transition_s *mlt_transition; +typedef struct mlt_tractor_s *mlt_tractor; +typedef struct mlt_field_s *mlt_field; +typedef struct mlt_consumer_s *mlt_consumer; +typedef struct mlt_parser_s *mlt_parser; +typedef struct mlt_deque_s *mlt_deque; +typedef struct mlt_geometry_s *mlt_geometry; +typedef struct mlt_geometry_item_s *mlt_geometry_item; +typedef struct mlt_profile_s *mlt_profile; + +typedef void ( *mlt_destructor )( void * ); +typedef char *( *mlt_serialiser )( void *, int length ); + +#define MLT_SERVICE(x) ( ( mlt_service )( x ) ) +#define MLT_PRODUCER(x) ( ( mlt_producer )( x ) ) +#define MLT_MULTITRACK(x) ( ( mlt_multitrack )( x ) ) +#define MLT_PLAYLIST(x) ( ( mlt_playlist )( x ) ) +#define MLT_TRACTOR(x) ( ( mlt_tractor )( x ) ) +#define MLT_FILTER(x) ( ( mlt_filter )( x ) ) +#define MLT_TRANSITION(x) ( ( mlt_transition )( x ) ) + +#endif diff --git a/src/humperdink/Makefile b/src/humperdink/Makefile new file mode 100644 index 0000000..47ba3ed --- /dev/null +++ b/src/humperdink/Makefile @@ -0,0 +1,38 @@ +include ../../config.mak + +TARGET = humperdink + +OBJS = client.o \ + io.o \ + remote.o + +CFLAGS += -I.. $(RDYNAMIC) + +LDFLAGS += -L../valerie -L../framework -lvalerie -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -d "$(DESTDIR)$(bindir)" + install -c -s -m 755 $(TARGET) "$(DESTDIR)$(bindir)" + +uninstall: + rm -f "$(DESTDIR)$(bindir)/$(TARGET)" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/humperdink/client.c b/src/humperdink/client.c new file mode 100644 index 0000000..084899a --- /dev/null +++ b/src/humperdink/client.c @@ -0,0 +1,1025 @@ +/* + * client.c -- Valerie client demo + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* System header files */ +#include +#include +#include + +/* Application header files */ +#include "client.h" +#include "io.h" + +/** Clip navigation enumeration. +*/ + +typedef enum +{ + absolute, + relative +} +dv_demo_whence; + +/** Function prototype for menu handling. +*/ + +typedef valerie_error_code (*demo_function)( dv_demo ); + +/** The menu structure. +*/ + +typedef struct +{ + char *description; + struct menu_item + { + char *option; + demo_function function; + } + array[ 50 ]; +} +*dv_demo_menu, dv_demo_menu_t; + +/** Forward reference to menu runner. +*/ + +extern valerie_error_code dv_demo_run_menu( dv_demo, dv_demo_menu ); + +/** Foward references. +*/ + +extern valerie_error_code dv_demo_list_nodes( dv_demo ); +extern valerie_error_code dv_demo_add_unit( dv_demo ); +extern valerie_error_code dv_demo_select_unit( dv_demo ); +extern valerie_error_code dv_demo_execute( dv_demo ); +extern valerie_error_code dv_demo_load( dv_demo ); +extern valerie_error_code dv_demo_transport( dv_demo ); +static void *dv_demo_status_thread( void * ); + +/** Connected menu definition. +*/ + +dv_demo_menu_t connected_menu = +{ + "Connected Menu", + { + { "Add Unit", dv_demo_add_unit }, + { "Select Unit", dv_demo_select_unit }, + { "Command Shell", dv_demo_execute }, + { NULL, NULL } + } +}; + +/** Initialise the demo structure. +*/ + +dv_demo dv_demo_init( valerie_parser parser ) +{ + dv_demo this = malloc( sizeof( dv_demo_t ) ); + if ( this != NULL ) + { + int index = 0; + memset( this, 0, sizeof( dv_demo_t ) ); + strcpy( this->last_directory, "/" ); + for ( index = 0; index < 4; index ++ ) + { + this->queues[ index ].unit = index; + this->queues[ index ].position = -1; + } + this->parser = parser; + } + return this; +} + +/** Display a status record. +*/ + +void dv_demo_show_status( dv_demo demo, valerie_status status ) +{ + if ( status->unit == demo->selected_unit && demo->showing ) + { + char temp[ 1024 ] = ""; + + sprintf( temp, "U%d ", demo->selected_unit ); + + switch( status->status ) + { + case unit_offline: + strcat( temp, "offline " ); + break; + case unit_undefined: + strcat( temp, "undefined " ); + break; + case unit_not_loaded: + strcat( temp, "unloaded " ); + break; + case unit_stopped: + strcat( temp, "stopped " ); + break; + case unit_playing: + strcat( temp, "playing " ); + break; + case unit_paused: + strcat( temp, "paused " ); + break; + case unit_disconnected: + strcat( temp, "disconnect" ); + break; + default: + strcat( temp, "unknown " ); + break; + } + + sprintf( temp + strlen( temp ), " %9d %9d %9d ", status->in, status->position, status->out ); + strcat( temp, status->clip ); + + printf( "%-80.80s\r", temp ); + fflush( stdout ); + } +} + +/** Determine action to carry out as dictated by the client unit queue. +*/ + +void dv_demo_queue_action( dv_demo demo, valerie_status status ) +{ + dv_demo_queue queue = &demo->queues[ status->unit ]; + + /* SPECIAL CASE STATUS NOTIFICATIONS TO IGNORE */ + + /* When we've issued a LOAD on the previous notification, then ignore this one. */ + if ( queue->ignore ) + { + queue->ignore --; + return; + } + + if ( queue->mode && status->status != unit_offline && queue->head != queue->tail ) + { + if ( ( status->position >= status->out && status->speed > 0 ) || status->status == unit_not_loaded ) + { + queue->position = ( queue->position + 1 ) % 50; + if ( queue->position == queue->tail ) + queue->position = queue->head; + valerie_unit_load( demo->dv_status, status->unit, queue->list[ queue->position ] ); + if ( status->status == unit_not_loaded ) + valerie_unit_play( demo->dv, queue->unit ); + queue->ignore = 1; + } + else if ( ( status->position <= status->in && status->speed < 0 ) || status->status == unit_not_loaded ) + { + if ( queue->position == -1 ) + queue->position = queue->head; + valerie_unit_load( demo->dv_status, status->unit, queue->list[ queue->position ] ); + if ( status->status == unit_not_loaded ) + valerie_unit_play( demo->dv, queue->unit ); + queue->position = ( queue->position - 1 ) % 50; + queue->ignore = 1; + } + } +} + +/** Status thread. +*/ + +static void *dv_demo_status_thread( void *arg ) +{ + dv_demo demo = arg; + valerie_status_t status; + valerie_notifier notifier = valerie_get_notifier( demo->dv_status ); + + while ( !demo->terminated ) + { + if ( valerie_notifier_wait( notifier, &status ) != -1 ) + { + dv_demo_queue_action( demo, &status ); + dv_demo_show_status( demo, &status ); + if ( status.status == unit_disconnected ) + demo->disconnected = 1; + } + } + + return NULL; +} + +/** Turn on/off status display. +*/ + +void dv_demo_change_status( dv_demo demo, int flag ) +{ + if ( demo->disconnected && flag ) + { + valerie_error_code error = valerie_connect( demo->dv ); + if ( error == valerie_ok ) + demo->disconnected = 0; + else + beep(); + } + + if ( flag ) + { + valerie_status_t status; + valerie_notifier notifier = valerie_get_notifier( demo->dv ); + valerie_notifier_get( notifier, &status, demo->selected_unit ); + demo->showing = 1; + dv_demo_show_status( demo, &status ); + } + else + { + demo->showing = 0; + printf( "%-80.80s\r", " " ); + fflush( stdout ); + } +} + +/** Add a unit. +*/ + +valerie_error_code dv_demo_add_unit( dv_demo demo ) +{ + valerie_error_code error = valerie_ok; + valerie_nodes nodes = valerie_nodes_init( demo->dv ); + valerie_units units = valerie_units_init( demo->dv ); + + if ( valerie_nodes_count( nodes ) != -1 && valerie_units_count( units ) != -1 ) + { + char pressed; + valerie_node_entry_t node; + valerie_unit_entry_t unit; + int node_index = 0; + int unit_index = 0; + + printf( "Select a Node\n\n" ); + + for ( node_index = 0; node_index < valerie_nodes_count( nodes ); node_index ++ ) + { + valerie_nodes_get( nodes, node_index, &node ); + printf( "%d: %s - %s ", node_index + 1, node.guid, node.name ); + for ( unit_index = 0; unit_index < valerie_units_count( units ); unit_index ++ ) + { + valerie_units_get( units, unit_index, &unit ); + if ( !strcmp( unit.guid, node.guid ) ) + printf( "[U%d] ", unit.unit ); + } + printf( "\n" ); + } + + printf( "0. Exit\n\n" ); + + printf( "Node: " ); + + while ( ( pressed = get_keypress( ) ) != '0' ) + { + node_index = pressed - '1'; + if ( node_index >= 0 && node_index < valerie_nodes_count( nodes ) ) + { + int unit; + printf( "%c\n\n", pressed ); + valerie_nodes_get( nodes, node_index, &node ); + if ( valerie_unit_add( demo->dv, node.guid, &unit ) == valerie_ok ) + { + printf( "Unit added as U%d\n", unit ); + demo->selected_unit = unit; + } + else + { + int index = 0; + valerie_response response = valerie_get_last_response( demo->dv ); + printf( "Failed to add unit:\n\n" ); + for( index = 1; index < valerie_response_count( response ) - 1; index ++ ) + printf( "%s\n", valerie_response_get_line( response, index ) ); + } + printf( "\n" ); + wait_for_any_key( NULL ); + break; + } + else + { + beep( ); + } + } + } + else + { + printf( "Invalid response from the server.\n\n" ); + wait_for_any_key( NULL ); + } + + valerie_nodes_close( nodes ); + valerie_units_close( units ); + + return error; +} + +/** Select a unit. +*/ + +valerie_error_code dv_demo_select_unit( dv_demo demo ) +{ + int terminated = 0; + int refresh = 1; + + while ( !terminated ) + { + valerie_units units = valerie_units_init( demo->dv ); + + if ( valerie_units_count( units ) > 0 ) + { + valerie_unit_entry_t unit; + int index = 0; + char key = '\0'; + + if ( refresh ) + { + printf( "Select a Unit\n\n" ); + + for ( index = 0; index < valerie_units_count( units ); index ++ ) + { + valerie_units_get( units, index, &unit ); + printf( "%d: U%d - %s [%s]\n", index + 1, + unit.unit, + unit.guid, + unit.online ? "online" : "offline" ); + } + printf( "0: Exit\n\n" ); + + printf( "Unit [%d]: ", demo->selected_unit + 1 ); + refresh = 0; + } + + key = get_keypress( ); + + if ( key == '\r' ) + key = demo->selected_unit + '1'; + + if ( key != '0' ) + { + if ( key >= '1' && key < '1' + valerie_units_count( units ) ) + { + demo->selected_unit = key - '1'; + printf( "%c\n\n", key ); + dv_demo_load( demo ); + refresh = 1; + } + else + { + beep( ); + } + } + else + { + printf( "0\n\n" ); + terminated = 1; + } + } + else if ( valerie_units_count( units ) == 0 ) + { + printf( "No units added - add a unit first\n\n" ); + dv_demo_add_unit( demo ); + } + else + { + printf( "Unable to obtain Unit List.\n" ); + terminated = 1; + } + + valerie_units_close( units ); + } + + return valerie_ok; +} + +/** Execute an arbitrary command. +*/ + +valerie_error_code dv_demo_execute( dv_demo demo ) +{ + valerie_error_code error = valerie_ok; + char command[ 10240 ]; + int terminated = 0; + + printf( "Miracle Shell\n" ); + printf( "Enter an empty command to exit.\n\n" ); + + while ( !terminated ) + { + terminated = 1; + printf( "Command> " ); + + if ( chomp( io_get_string( command, 10240, "" ) ) != NULL ) + { + if ( strcmp( command, "" ) ) + { + int index = 0; + valerie_response response = NULL; + error = valerie_execute( demo->dv, 10240, command ); + printf( "\n" ); + response = valerie_get_last_response( demo->dv ); + for ( index = 0; index < valerie_response_count( response ); index ++ ) + { + char *line = valerie_response_get_line( response, index ); + printf( "%4d: %s\n", index, line ); + } + printf( "\n" ); + terminated = 0; + } + } + } + + printf( "\n" ); + + return error; +} + +/** Add a file to the queue. +*/ + +valerie_error_code dv_demo_queue_add( dv_demo demo, dv_demo_queue queue, char *file ) +{ + valerie_status_t status; + valerie_notifier notifier = valerie_get_notifier( demo->dv ); + + if ( ( queue->tail + 1 ) % 50 == queue->head ) + queue->head = ( queue->head + 1 ) % 50; + strcpy( queue->list[ queue->tail ], file ); + queue->tail = ( queue->tail + 1 ) % 50; + + valerie_notifier_get( notifier, &status, queue->unit ); + valerie_notifier_put( notifier, &status ); + + return valerie_ok; +} + +/** Basic queue maintenance and status reports. +*/ + +valerie_error_code dv_demo_queue_maintenance( dv_demo demo, dv_demo_queue queue ) +{ + printf( "Queue Maintenance for Unit %d\n\n", queue->unit ); + + if ( !queue->mode ) + { + char ch; + printf( "Activate queueing? [Y] " ); + ch = get_keypress( ); + if ( ch == 'y' || ch == 'Y' || ch == '\r' ) + queue->mode = 1; + printf( "\n\n" ); + } + + if ( queue->mode ) + { + int terminated = 0; + int last_position = -2; + + term_init( ); + + while ( !terminated ) + { + int first = ( queue->position + 1 ) % 50; + int index = first; + + if ( first == queue->tail ) + index = first = queue->head; + + if ( queue->head == queue->tail ) + { + if ( last_position == -2 ) + { + printf( "Queue is empty\n" ); + printf( "\n" ); + printf( "0 = exit, t = turn off queueing\n\n" ); + last_position = -1; + } + } + else if ( last_position != queue->position ) + { + printf( "Order of play\n\n" ); + + do + { + printf( "%c%02d: %s\n", index == first ? '*' : ' ', index, queue->list[ index ] + 1 ); + index = ( index + 1 ) % 50; + if ( index == queue->tail ) + index = queue->head; + } + while( index != first ); + + printf( "\n" ); + printf( "0 = exit, t = turn off queueing, c = clear queue\n\n" ); + last_position = queue->position; + } + + dv_demo_change_status( demo, 1 ); + + switch( term_read( ) ) + { + case -1: + break; + case '0': + terminated = 1; + break; + case 't': + terminated = 1; + queue->mode = 0; + break; + case 'c': + queue->head = queue->tail = 0; + queue->position = -1; + last_position = -2; + break; + } + + dv_demo_change_status( demo, 0 ); + } + + term_exit( ); + } + + return valerie_ok; +} + +/** Load a file to the selected unit. Horrible function - sorry :-/. Not a good + demo.... +*/ + +valerie_error_code dv_demo_load( dv_demo demo ) +{ + valerie_error_code error = valerie_ok; + int terminated = 0; + int refresh = 1; + int start = 0; + + strcpy( demo->current_directory, demo->last_directory ); + + term_init( ); + + while ( !terminated ) + { + valerie_dir dir = valerie_dir_init( demo->dv, demo->current_directory ); + + if ( valerie_dir_count( dir ) == -1 ) + { + printf( "Invalid directory - retrying %s\n", demo->last_directory ); + valerie_dir_close( dir ); + dir = valerie_dir_init( demo->dv, demo->last_directory ); + if ( valerie_dir_count( dir ) == -1 ) + { + printf( "Invalid directory - going back to /\n" ); + valerie_dir_close( dir ); + dir = valerie_dir_init( demo->dv, "/" ); + strcpy( demo->current_directory, "/" ); + } + else + { + strcpy( demo->current_directory, demo->last_directory ); + } + } + + terminated = valerie_dir_count( dir ) == -1; + + if ( !terminated ) + { + int index = 0; + int selected = 0; + int max = 9; + int end = 0; + + end = valerie_dir_count( dir ); + + strcpy( demo->last_directory, demo->current_directory ); + + while ( !selected && !terminated ) + { + valerie_dir_entry_t entry; + int pressed; + + if ( refresh ) + { + char *action = "Load & Play"; + if ( demo->queues[ demo->selected_unit ].mode ) + action = "Queue"; + printf( "%s from %s\n\n", action, demo->current_directory ); + if ( strcmp( demo->current_directory, "/" ) ) + printf( "-: Parent directory\n" ); + for ( index = start; index < end && ( index - start ) < max; index ++ ) + { + valerie_dir_get( dir, index, &entry ); + printf( "%d: %s\n", index - start + 1, entry.name ); + } + while ( ( index ++ % 9 ) != 0 ) + printf( "\n" ); + printf( "\n" ); + if ( start + max < end ) + printf( "space = more files" ); + else if ( end > max ) + printf( "space = return to start of list" ); + if ( start > 0 ) + printf( ", b = previous files" ); + printf( "\n" ); + printf( "0 = abort, t = transport, x = execute command, q = queue maintenance\n\n" ); + refresh = 0; + } + + dv_demo_change_status( demo, 1 ); + + pressed = term_read( ); + switch( pressed ) + { + case -1: + break; + case '0': + terminated = 1; + break; + case 'b': + refresh = start - max >= 0; + if ( refresh ) + start = start - max; + break; + case ' ': + refresh = start + max < end; + if ( refresh ) + { + start = start + max; + } + else if ( end > max ) + { + start = 0; + refresh = 1; + } + break; + case '-': + if ( strcmp( demo->current_directory, "/" ) ) + { + selected = 1; + ( *strrchr( demo->current_directory, '/' ) ) = '\0'; + ( *( strrchr( demo->current_directory, '/' ) + 1 ) ) = '\0'; + } + break; + case 't': + dv_demo_change_status( demo, 0 ); + term_exit( ); + dv_demo_transport( demo ); + term_init( ); + selected = 1; + break; + case 'x': + dv_demo_change_status( demo, 0 ); + term_exit( ); + dv_demo_execute( demo ); + term_init( ); + selected = 1; + break; + case 'q': + dv_demo_change_status( demo, 0 ); + term_exit( ); + dv_demo_queue_maintenance( demo, &demo->queues[ demo->selected_unit ] ); + term_init( ); + selected = 1; + break; + default: + if ( pressed >= '1' && pressed <= '9' ) + { + if ( ( start + pressed - '1' ) < end ) + { + valerie_dir_get( dir, start + pressed - '1', &entry ); + selected = 1; + strcat( demo->current_directory, entry.name ); + } + } + break; + } + + dv_demo_change_status( demo, 0 ); + } + + valerie_dir_close( dir ); + } + + if ( !terminated && demo->current_directory[ strlen( demo->current_directory ) - 1 ] != '/' ) + { + if ( demo->queues[ demo->selected_unit ].mode == 0 ) + { + error = valerie_unit_load( demo->dv, demo->selected_unit, demo->current_directory ); + valerie_unit_play( demo->dv, demo->selected_unit ); + } + else + { + dv_demo_queue_add( demo, &demo->queues[ demo->selected_unit ], demo->current_directory ); + printf( "File %s added to queue.\n", demo->current_directory ); + } + strcpy( demo->current_directory, demo->last_directory ); + refresh = 0; + } + else + { + refresh = 1; + start = 0; + } + } + + term_exit( ); + + return error; +} + +/** Set the in point of the clip on the select unit. +*/ + +valerie_error_code dv_demo_set_in( dv_demo demo ) +{ + int position = 0; + valerie_status_t status; + valerie_notifier notifier = valerie_parser_get_notifier( demo->parser ); + valerie_notifier_get( notifier, &status, demo->selected_unit ); + position = status.position; + return valerie_unit_set_in( demo->dv, demo->selected_unit, position ); +} + +/** Set the out point of the clip on the selected unit. +*/ + +valerie_error_code dv_demo_set_out( dv_demo demo ) +{ + int position = 0; + valerie_status_t status; + valerie_notifier notifier = valerie_parser_get_notifier( demo->parser ); + valerie_notifier_get( notifier, &status, demo->selected_unit ); + position = status.position; + return valerie_unit_set_out( demo->dv, demo->selected_unit, position ); +} + +/** Clear the in and out points on the selected unit. +*/ + +valerie_error_code dv_demo_clear_in_out( dv_demo demo ) +{ + return valerie_unit_clear_in_out( demo->dv, demo->selected_unit ); +} + +/** Goto a user specified frame on the selected unit. +*/ + +valerie_error_code dv_demo_goto( dv_demo demo ) +{ + int frame = 0; + printf( "Frame: " ); + if ( get_int( &frame, 0 ) ) + return valerie_unit_goto( demo->dv, demo->selected_unit, frame ); + return valerie_ok; +} + +/** Manipulate playback on the selected unit. +*/ + +valerie_error_code dv_demo_transport( dv_demo demo ) +{ + valerie_error_code error = valerie_ok; + int refresh = 1; + int terminated = 0; + valerie_status_t status; + valerie_notifier notifier = valerie_get_notifier( demo->dv ); + + while ( !terminated ) + { + if ( refresh ) + { + printf( " +----+ +------+ +----+ +------+ +---+ +-----+ +------+ +-----+ +---+ \n" ); + printf( " |1=-5| |2=-2.5| |3=-1| |4=-0.5| |5=1| |6=0.5| |7=1.25| |8=2.5| |9=5| \n" ); + printf( " +----+ +------+ +----+ +------+ +---+ +-----+ +------+ +-----+ +---+ \n" ); + printf( "\n" ); + printf( "+----------------------------------------------------------------------+\n" ); + printf( "| 0 = quit, x = eXecute, 'space' = pause |\n" ); + printf( "| g = goto a frame, q = queue maintenance |\n" ); + printf( "| h = step -1, j = end of clip, k = start of clip, l = step 1 |\n" ); + printf( "| eof handling: p = pause, r = repeat, t = terminate |\n" ); + printf( "| i = set in point, o = set out point, c = clear in/out |\n" ); + printf( "| u = use point settings, d = don't use point settings |\n" ); + printf( "+----------------------------------------------------------------------+\n" ); + printf( "\n" ); + term_init( ); + refresh = 0; + } + + dv_demo_change_status( demo, 1 ); + + switch( term_read( ) ) + { + case '0': + terminated = 1; + break; + case -1: + break; + case ' ': + error = valerie_unit_pause( demo->dv, demo->selected_unit ); + break; + case '1': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, -5000 ); + break; + case '2': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, -2500 ); + break; + case '3': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, -1000 ); + break; + case '4': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, -500 ); + break; + case '5': + error = valerie_unit_play( demo->dv, demo->selected_unit ); + break; + case '6': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, 500 ); + break; + case '7': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, 1250 ); + break; + case '8': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, 2500 ); + break; + case '9': + error = valerie_unit_play_at_speed( demo->dv, demo->selected_unit, 5000 ); + break; + case 's': + error = valerie_unit_goto( demo->dv, demo->selected_unit, 0 ); + break; + case 'h': + error = valerie_unit_step( demo->dv, demo->selected_unit, -1 ); + break; + case 'j': + valerie_notifier_get( notifier, &status, demo->selected_unit ); + error = valerie_unit_goto( demo->dv, demo->selected_unit, status.tail_out ); + break; + case 'k': + valerie_notifier_get( notifier, &status, demo->selected_unit ); + error = valerie_unit_goto( demo->dv, demo->selected_unit, status.in ); + break; + case 'l': + error = valerie_unit_step( demo->dv, demo->selected_unit, 1 ); + break; + case 'p': + error = valerie_unit_set( demo->dv, demo->selected_unit, "eof", "pause" ); + break; + case 'r': + error = valerie_unit_set( demo->dv, demo->selected_unit, "eof", "loop" ); + break; + case 't': + error = valerie_unit_set( demo->dv, demo->selected_unit, "eof", "stop" ); + break; + case 'i': + error = dv_demo_set_in( demo ); + break; + case 'o': + error = dv_demo_set_out( demo ); + break; + case 'g': + dv_demo_change_status( demo, 0 ); + term_exit( ); + error = dv_demo_goto( demo ); + refresh = 1; + break; + case 'c': + error = dv_demo_clear_in_out( demo ); + break; + case 'u': + error = valerie_unit_set( demo->dv, demo->selected_unit, "points", "use" ); + break; + case 'd': + error = valerie_unit_set( demo->dv, demo->selected_unit, "points", "ignore" ); + break; + case 'x': + dv_demo_change_status( demo, 0 ); + term_exit( ); + dv_demo_execute( demo ); + refresh = 1; + break; + case 'q': + dv_demo_change_status( demo, 0 ); + term_exit( ); + dv_demo_queue_maintenance( demo, &demo->queues[ demo->selected_unit ] ); + refresh = 1; + break; + } + + dv_demo_change_status( demo, 0 ); + } + + term_exit( ); + + return error; +} + +/** Recursive menu execution. +*/ + +valerie_error_code dv_demo_run_menu( dv_demo demo, dv_demo_menu menu ) +{ + char *items = "123456789abcdefghijklmnopqrstuvwxyz"; + int refresh_menu = 1; + int terminated = 0; + int item_count = 0; + int item_selected = 0; + int index = 0; + char key; + + while( !terminated ) + { + + if ( refresh_menu ) + { + printf( "%s\n\n", menu->description ); + for ( index = 0; menu->array[ index ].option != NULL; index ++ ) + printf( "%c: %s\n", items[ index ], menu->array[ index ].option ); + printf( "0: Exit\n\n" ); + printf( "Select Option: " ); + refresh_menu = 0; + item_count = index; + } + + key = get_keypress( ); + + if ( demo->disconnected && key != '0' ) + { + valerie_error_code error = valerie_connect( demo->dv ); + if ( error == valerie_ok ) + demo->disconnected = 0; + else + beep(); + } + + if ( !demo->disconnected || key == '0' ) + { + item_selected = strchr( items, key ) - items; + + if ( key == '0' ) + { + printf( "%c\n\n", key ); + terminated = 1; + } + else if ( item_selected >= 0 && item_selected < item_count ) + { + printf( "%c\n\n", key ); + menu->array[ item_selected ].function( demo ); + refresh_menu = 1; + } + else + { + beep( ); + } + } + } + + return valerie_ok; +} + +/** Entry point for main menu. +*/ + +void dv_demo_run( dv_demo this ) +{ + this->dv = valerie_init( this->parser ); + this->dv_status = valerie_init( this->parser ); + if ( valerie_connect( this->dv ) == valerie_ok ) + { + pthread_create( &this->thread, NULL, dv_demo_status_thread, this ); + dv_demo_run_menu( this, &connected_menu ); + this->terminated = 1; + pthread_join( this->thread, NULL ); + this->terminated = 0; + } + else + { + printf( "Unable to connect." ); + wait_for_any_key( "" ); + } + + valerie_close( this->dv_status ); + valerie_close( this->dv ); + + printf( "Demo Exit.\n" ); +} + +/** Close the demo structure. +*/ + +void dv_demo_close( dv_demo demo ) +{ + free( demo ); +} diff --git a/src/humperdink/client.h b/src/humperdink/client.h new file mode 100644 index 0000000..ac29481 --- /dev/null +++ b/src/humperdink/client.h @@ -0,0 +1,66 @@ +/* + * client.h -- Valerie client demo + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _DEMO_CLIENT_H_ +#define _DEMO_CLIENT_H_ + +#include +#include +#include + +/** Queue for unit playback +*/ + +typedef struct +{ + int mode; + int unit; + int position; + int head; + int tail; + char list[ 50 ][ PATH_MAX + NAME_MAX ]; + int ignore; +} +*dv_demo_queue, dv_demo_queue_t; + +/** Structure for storing app state. +*/ + +typedef struct +{ + int disconnected; + valerie_parser parser; + valerie dv; + valerie dv_status; + int selected_unit; + char current_directory[ 512 ]; + char last_directory[ 512 ]; + int showing; + int terminated; + pthread_t thread; + dv_demo_queue_t queues[ MAX_UNITS ]; +} +*dv_demo, dv_demo_t; + +extern dv_demo dv_demo_init( valerie_parser ); +extern void dv_demo_run( dv_demo ); +extern void dv_demo_close( dv_demo ); + +#endif diff --git a/src/humperdink/io.c b/src/humperdink/io.c new file mode 100644 index 0000000..d5c4c81 --- /dev/null +++ b/src/humperdink/io.c @@ -0,0 +1,205 @@ +/* + * io.c -- Valerie client demo input/output + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* System header files */ +#include +#include +#include +#include +#include +#include +#include + +/* Application header files */ +#include "io.h" + +char *chomp( char *input ) +{ + if ( input != NULL ) + { + int length = strlen( input ); + if ( length && input[ length - 1 ] == '\n' ) + input[ length - 1 ] = '\0'; + if ( length > 1 && input[ length - 2 ] == '\r' ) + input[ length - 2 ] = '\0'; + } + return input; +} + +char *trim( char *input ) +{ + if ( input != NULL ) + { + int length = strlen( input ); + int first = 0; + while( first < length && isspace( input[ first ] ) ) + first ++; + memmove( input, input + first, length - first + 1 ); + length = length - first; + while ( length > 0 && isspace( input[ length - 1 ] ) ) + input[ -- length ] = '\0'; + } + return input; +} + +char *strip_quotes( char *input ) +{ + if ( input != NULL ) + { + char *ptr = strrchr( input, '\"' ); + if ( ptr != NULL ) + *ptr = '\0'; + if ( input[ 0 ] == '\"' ) + strcpy( input, input + 1 ); + } + return input; +} + +char *io_get_string( char *output, int maxlength, char *use ) +{ + char *value = NULL; + strcpy( output, use ); + if ( trim( chomp( fgets( output, maxlength, stdin ) ) ) != NULL ) + { + if ( !strcmp( output, "" ) ) + strcpy( output, use ); + value = output; + } + return value; +} + +int *get_int( int *output, int use ) +{ + int *value = NULL; + char temp[ 132 ]; + *output = use; + if ( trim( chomp( fgets( temp, 132, stdin ) ) ) != NULL ) + { + if ( strcmp( temp, "" ) ) + *output = atoi( temp ); + value = output; + } + return value; +} + +/** This stores the previous settings +*/ + +static struct termios oldtty; +static int mode = 0; + +/** This is called automatically on application exit to restore the + previous tty settings. +*/ + +void term_exit(void) +{ + if ( mode == 1 ) + { + tcsetattr( 0, TCSANOW, &oldtty ); + mode = 0; + } +} + +/** Init terminal so that we can grab keys without blocking. +*/ + +void term_init( ) +{ + struct termios tty; + + tcgetattr( 0, &tty ); + oldtty = tty; + + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); + tty.c_oflag |= OPOST; + tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + tty.c_cflag &= ~(CSIZE|PARENB); + tty.c_cflag |= CS8; + tty.c_cc[ VMIN ] = 1; + tty.c_cc[ VTIME ] = 0; + + tcsetattr( 0, TCSANOW, &tty ); + + mode = 1; + + atexit( term_exit ); +} + +/** Check for a keypress without blocking infinitely. + Returns: ASCII value of keypress or -1 if no keypress detected. +*/ + +int term_read( ) +{ + int n = 1; + unsigned char ch; + struct timeval tv; + fd_set rfds; + + FD_ZERO( &rfds ); + FD_SET( 0, &rfds ); + tv.tv_sec = 1; + tv.tv_usec = 0; + n = select( 1, &rfds, NULL, NULL, &tv ); + if (n > 0) + { + n = read( 0, &ch, 1 ); + tcflush( 0, TCIFLUSH ); + if (n == 1) + return ch; + return n; + } + return -1; +} + +char get_keypress( ) +{ + char value = '\0'; + int pressed = 0; + + fflush( stdout ); + + term_init( ); + while ( ( pressed = term_read( ) ) == -1 ) ; + term_exit( ); + + value = (char)pressed; + + return value; +} + +void wait_for_any_key( char *message ) +{ + if ( message == NULL ) + printf( "Press any key to continue: " ); + else + printf( "%s", message ); + + get_keypress( ); + + printf( "\n\n" ); +} + +void beep( ) +{ + printf( "%c", 7 ); + fflush( stdout ); +} diff --git a/src/humperdink/io.h b/src/humperdink/io.h new file mode 100644 index 0000000..33d449f --- /dev/null +++ b/src/humperdink/io.h @@ -0,0 +1,36 @@ +/* + * io.h -- Valerie client demo input/output + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _DEMO_IO_H_ +#define _DEMO_IO_H_ + +extern char *chomp( char * ); +extern char *trim( char * ); +extern char *strip_quotes( char * ); +extern char *io_get_string( char *, int, char * ); +extern int *get_int( int *, int ); +extern void term_init( ); +extern int term_read( ); +extern void term_exit( ); +extern char get_keypress( ); +extern void wait_for_any_key( char * ); +extern void beep( ); + +#endif diff --git a/src/humperdink/remote.c b/src/humperdink/remote.c new file mode 100644 index 0000000..6c267a4 --- /dev/null +++ b/src/humperdink/remote.c @@ -0,0 +1,73 @@ +/* + * remote.c -- Remote Valerie client demo + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* System header files */ +#include +#include + +#include + +/* Application header files */ +#include "client.h" +#include "io.h" + +/** Connect to a remote server. +*/ + +static valerie_parser create_parser( ) +{ + char server[ 132 ]; + int port; + valerie_parser parser = NULL; + + printf( "Connecting to a Server\n\n" ); + + printf( "Server [localhost]: " ); + + if ( io_get_string( server, sizeof( server ), "localhost" ) != NULL ) + { + printf( "Port [5250]: " ); + + if ( get_int( &port, 5250 ) != NULL ) + parser = valerie_parser_init_remote( server, port ); + } + + printf( "\n" ); + + return parser; +} + +/** Main function. +*/ + +int main( int argc, char **argv ) +{ + valerie_parser parser = create_parser( ); + + if ( parser != NULL ) + { + dv_demo demo = dv_demo_init( parser ); + dv_demo_run( demo ); + dv_demo_close( demo ); + valerie_parser_close( parser ); + } + + return 0; +} diff --git a/src/inigo/Makefile b/src/inigo/Makefile new file mode 100644 index 0000000..0e80dbf --- /dev/null +++ b/src/inigo/Makefile @@ -0,0 +1,37 @@ +include ../../config.mak + +TARGET = inigo + +OBJS = inigo.o \ + io.o + +CFLAGS += -I.. $(RDYNAMIC) + +LDFLAGS += -L../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) + +install: all + install -d "$(DESTDIR)$(bindir)" + install -c -s -m 755 $(TARGET) "$(DESTDIR)$(bindir)" + +uninstall: + rm -f "$(DESTDIR)$(bindir)/$(TARGET)" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/inigo/configure b/src/inigo/configure new file mode 100755 index 0000000..e69de29 diff --git a/src/inigo/inigo.c b/src/inigo/inigo.c new file mode 100644 index 0000000..78b668f --- /dev/null +++ b/src/inigo/inigo.c @@ -0,0 +1,381 @@ +/* + * inigo.c -- MLT command line utility + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +#include + +#ifdef __DARWIN__ +#include +#endif + +#include "io.h" + +static void transport_action( mlt_producer producer, char *value ) +{ + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + mlt_multitrack multitrack = mlt_properties_get_data( properties, "multitrack", NULL ); + mlt_consumer consumer = mlt_properties_get_data( properties, "transport_consumer", NULL ); + + mlt_properties_set_int( properties, "stats_off", 0 ); + + if ( strlen( value ) == 1 ) + { + switch( value[ 0 ] ) + { + case 'q': + mlt_properties_set_int( properties, "done", 1 ); + break; + case '0': + mlt_producer_set_speed( producer, 1 ); + mlt_producer_seek( producer, 0 ); + break; + case '1': + mlt_producer_set_speed( producer, -10 ); + break; + case '2': + mlt_producer_set_speed( producer, -5 ); + break; + case '3': + mlt_producer_set_speed( producer, -2 ); + break; + case '4': + mlt_producer_set_speed( producer, -1 ); + break; + case '5': + mlt_producer_set_speed( producer, 0 ); + break; + case '6': + case ' ': + mlt_producer_set_speed( producer, 1 ); + break; + case '7': + mlt_producer_set_speed( producer, 2 ); + break; + case '8': + mlt_producer_set_speed( producer, 5 ); + break; + case '9': + mlt_producer_set_speed( producer, 10 ); + break; + case 'd': + if ( multitrack != NULL ) + { + int i = 0; + mlt_position last = -1; + fprintf( stderr, "\n" ); + for ( i = 0; 1; i ++ ) + { + mlt_position time = mlt_multitrack_clip( multitrack, mlt_whence_relative_start, i ); + if ( time == last ) + break; + last = time; + fprintf( stderr, "%d: %d\n", i, (int)time ); + } + } + break; + + case 'g': + if ( multitrack != NULL ) + { + mlt_position time = mlt_multitrack_clip( multitrack, mlt_whence_relative_current, 0 ); + mlt_producer_seek( producer, time ); + } + break; + case 'H': + if ( producer != NULL ) + { + mlt_position position = mlt_producer_position( producer ); + mlt_producer_seek( producer, position - ( mlt_producer_get_fps( producer ) * 60 ) ); + } + break; + case 'h': + if ( producer != NULL ) + { + mlt_position position = mlt_producer_position( producer ); + mlt_producer_set_speed( producer, 0 ); + mlt_producer_seek( producer, position - 1 ); + } + break; + case 'j': + if ( multitrack != NULL ) + { + mlt_position time = mlt_multitrack_clip( multitrack, mlt_whence_relative_current, 1 ); + mlt_producer_seek( producer, time ); + } + break; + case 'k': + if ( multitrack != NULL ) + { + mlt_position time = mlt_multitrack_clip( multitrack, mlt_whence_relative_current, -1 ); + mlt_producer_seek( producer, time ); + } + break; + case 'l': + if ( producer != NULL ) + { + mlt_position position = mlt_producer_position( producer ); + if ( mlt_producer_get_speed( producer ) != 0 ) + mlt_producer_set_speed( producer, 0 ); + else + mlt_producer_seek( producer, position + 1 ); + } + break; + case 'L': + if ( producer != NULL ) + { + mlt_position position = mlt_producer_position( producer ); + mlt_producer_seek( producer, position + ( mlt_producer_get_fps( producer ) * 60 ) ); + } + break; + } + + mlt_properties_set_int( MLT_CONSUMER_PROPERTIES( consumer ), "refresh", 1 ); + } + + mlt_properties_set_int( properties, "stats_off", 0 ); +} + +static mlt_consumer create_consumer( char *id, mlt_producer producer ) +{ + char *arg = id != NULL ? strchr( id, ':' ) : NULL; + if ( arg != NULL ) + *arg ++ = '\0'; + mlt_consumer consumer = mlt_factory_consumer( id, arg ); + if ( consumer != NULL ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); + mlt_properties_set_data( properties, "transport_callback", transport_action, 0, NULL, NULL ); + mlt_properties_set_data( properties, "transport_producer", producer, 0, NULL, NULL ); + mlt_properties_set_data( MLT_PRODUCER_PROPERTIES( producer ), "transport_consumer", consumer, 0, NULL, NULL ); + } + return consumer; +} + +#ifdef __DARWIN__ + +static void event_handling( mlt_producer producer, mlt_consumer consumer ) +{ + SDL_Event event; + + while ( SDL_PollEvent( &event ) ) + { + switch( event.type ) + { + case SDL_QUIT: + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( consumer ), "done", 1 ); + break; + + case SDL_KEYDOWN: + if ( event.key.keysym.unicode < 0x80 && event.key.keysym.unicode > 0 ) + { + char keyboard[ 2 ] = { event.key.keysym.unicode, 0 }; + transport_action( producer, keyboard ); + } + break; + } + } +} + +#endif + +static void transport( mlt_producer producer, mlt_consumer consumer ) +{ + mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + int silent = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( consumer ), "silent" ); + struct timespec tm = { 0, 40000 }; + + if ( mlt_properties_get_int( properties, "done" ) == 0 && !mlt_consumer_is_stopped( consumer ) ) + { + if ( !silent ) + { + term_init( ); + + fprintf( stderr, "+-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+\n" ); + fprintf( stderr, "|1=-10| |2= -5| |3= -2| |4= -1| |5= 0| |6= 1| |7= 2| |8= 5| |9= 10|\n" ); + fprintf( stderr, "+-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+\n" ); + + fprintf( stderr, "+---------------------------------------------------------------------+\n" ); + fprintf( stderr, "| H = back 1 minute, L = forward 1 minute |\n" ); + fprintf( stderr, "| h = previous frame, l = next frame |\n" ); + fprintf( stderr, "| g = start of clip, j = next clip, k = previous clip |\n" ); + fprintf( stderr, "| 0 = restart, q = quit, space = play |\n" ); + fprintf( stderr, "+---------------------------------------------------------------------+\n" ); + } + + while( mlt_properties_get_int( properties, "done" ) == 0 && !mlt_consumer_is_stopped( consumer ) ) + { + int value = silent ? -1 : term_read( ); + + if ( value != -1 ) + { + char string[ 2 ] = { value, 0 }; + transport_action( producer, string ); + } + +#ifdef __DARWIN__ + event_handling( producer, consumer ); +#endif + + if ( !silent && mlt_properties_get_int( properties, "stats_off" ) == 0 ) + fprintf( stderr, "Current Position: %10d\r", (int)mlt_producer_position( producer ) ); + + if ( silent ) + nanosleep( &tm, NULL ); + } + + if ( !silent ) + fprintf( stderr, "\n" ); + } +} + +int main( int argc, char **argv ) +{ + int i; + mlt_consumer consumer = NULL; + mlt_producer inigo = NULL; + FILE *store = NULL; + char *name = NULL; + struct sched_param scp; + + // Use realtime scheduling if possible + memset( &scp, '\0', sizeof( scp ) ); + scp.sched_priority = sched_get_priority_max( SCHED_FIFO ) - 1; +#ifndef __DARWIN__ + sched_setscheduler( 0, SCHED_FIFO, &scp ); +#endif + + // Construct the factory + mlt_factory_init( NULL ); + + // Check for serialisation switch first + for ( i = 1; i < argc; i ++ ) + { + if ( !strcmp( argv[ i ], "-serialise" ) ) + { + name = argv[ ++ i ]; + if ( strstr( name, ".inigo" ) ) + store = fopen( name, "w" ); + } + } + + // Get inigo producer + if ( argc > 1 ) + inigo = mlt_factory_producer( "inigo", &argv[ 1 ] ); + + if ( argc > 1 && inigo != NULL && mlt_producer_get_length( inigo ) > 0 ) + { + // Get inigo's properties + mlt_properties inigo_props = MLT_PRODUCER_PROPERTIES( inigo ); + + // Get the last group + mlt_properties group = mlt_properties_get_data( inigo_props, "group", 0 ); + + // Parse the arguments + for ( i = 1; i < argc; i ++ ) + { + if ( !strcmp( argv[ i ], "-consumer" ) ) + { + consumer = create_consumer( argv[ ++ i ], inigo ); + while ( argv[ i + 1 ] != NULL && strstr( argv[ i + 1 ], "=" ) ) + mlt_properties_parse( group, argv[ ++ i ] ); + } + else if ( !strcmp( argv[ i ], "-serialise" ) ) + { + i ++; + } + else + { + if ( store != NULL ) + fprintf( store, "%s\n", argv[ i ] ); + + i ++; + + while ( argv[ i ] != NULL && argv[ i ][ 0 ] != '-' ) + { + if ( store != NULL ) + fprintf( store, "%s\n", argv[ i ] ); + i += 1; + } + + i --; + } + } + + // If we have no consumer, default to sdl + if ( store == NULL && consumer == NULL ) + consumer = create_consumer( NULL, inigo ); + + if ( consumer != NULL && store == NULL ) + { + // Apply group settings + mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); + mlt_properties_inherit( properties, group ); + + // Connect consumer to inigo + mlt_consumer_connect( consumer, MLT_PRODUCER_SERVICE( inigo ) ); + + // Start the consumer + mlt_consumer_start( consumer ); + + // Transport functionality + transport( inigo, consumer ); + + // Stop the consumer + mlt_consumer_stop( consumer ); + } + else if ( store != NULL ) + { + fprintf( stderr, "Project saved as %s.\n", name ); + fclose( store ); + } + } + else + { + fprintf( stderr, "Usage: inigo [ -group [ name=value ]* ]\n" + " [ -consumer id[:arg] [ name=value ]* ]\n" + " [ -filter filter[:arg] [ name=value ] * ]\n" + " [ -attach filter[:arg] [ name=value ] * ]\n" + " [ -mix length [ -mixer transition ]* ]\n" + " [ -transition id[:arg] [ name=value ] * ]\n" + " [ -blank frames ]\n" + " [ -track ]\n" + " [ -split relative-frame ]\n" + " [ -join clips ]\n" + " [ -repeat times ]\n" + " [ producer [ name=value ] * ]+\n" ); + } + + // Close the consumer + if ( consumer != NULL ) + mlt_consumer_close( consumer ); + + // Close the producer + if ( inigo != NULL ) + mlt_producer_close( inigo ); + + // Close the factory + mlt_factory_close( ); + + return 0; +} diff --git a/src/inigo/io.c b/src/inigo/io.c new file mode 100644 index 0000000..91a586f --- /dev/null +++ b/src/inigo/io.c @@ -0,0 +1,196 @@ +/* + * io.c -- inigo input/output + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +/* System header files */ +#include +#include +#include +#include +#include +#include +#include + +/* Application header files */ +#include "io.h" + +char *chomp( char *input ) +{ + if ( input != NULL ) + { + int length = strlen( input ); + if ( length && input[ length - 1 ] == '\n' ) + input[ length - 1 ] = '\0'; + if ( length > 1 && input[ length - 2 ] == '\r' ) + input[ length - 2 ] = '\0'; + } + return input; +} + +char *trim( char *input ) +{ + if ( input != NULL ) + { + int length = strlen( input ); + int first = 0; + while( first < length && isspace( input[ first ] ) ) + first ++; + memmove( input, input + first, length - first + 1 ); + length = length - first; + while ( length > 0 && isspace( input[ length - 1 ] ) ) + input[ -- length ] = '\0'; + } + return input; +} + +char *strip_quotes( char *input ) +{ + if ( input != NULL ) + { + char *ptr = strrchr( input, '\"' ); + if ( ptr != NULL ) + *ptr = '\0'; + if ( input[ 0 ] == '\"' ) + strcpy( input, input + 1 ); + } + return input; +} + +int *get_int( int *output, int use ) +{ + int *value = NULL; + char temp[ 132 ]; + *output = use; + if ( trim( chomp( fgets( temp, 132, stdin ) ) ) != NULL ) + { + if ( strcmp( temp, "" ) ) + *output = atoi( temp ); + value = output; + } + return value; +} + +/** This stores the previous settings +*/ + +static struct termios oldtty; +static int mode = 0; + +/** This is called automatically on application exit to restore the + previous tty settings. +*/ + +void term_exit(void) +{ + if ( mode == 1 ) + { + tcsetattr( 0, TCSANOW, &oldtty ); + mode = 0; + } +} + +/** Init terminal so that we can grab keys without blocking. +*/ + +void term_init( ) +{ + struct termios tty; + + tcgetattr( 0, &tty ); + oldtty = tty; + + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); + tty.c_oflag |= OPOST; + tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + tty.c_cflag &= ~(CSIZE|PARENB); + tty.c_cflag |= CS8; + tty.c_cc[ VMIN ] = 1; + tty.c_cc[ VTIME ] = 0; + + tcsetattr( 0, TCSANOW, &tty ); + + mode = 1; + + atexit( term_exit ); +} + +/** Check for a keypress without blocking infinitely. + Returns: ASCII value of keypress or -1 if no keypress detected. +*/ + +int term_read( ) +{ + int n = 1; + unsigned char ch; + struct timeval tv; + fd_set rfds; + + FD_ZERO( &rfds ); + FD_SET( 0, &rfds ); + tv.tv_sec = 0; + tv.tv_usec = 40000; + n = select( 1, &rfds, NULL, NULL, &tv ); + if (n > 0) + { + n = read( 0, &ch, 1 ); + tcflush( 0, TCIFLUSH ); + if (n == 1) + return ch; + return n; + } + return -1; +} + +char get_keypress( ) +{ + char value = '\0'; + int pressed = 0; + + fflush( stdout ); + + term_init( ); + while ( ( pressed = term_read( ) ) == -1 ) ; + term_exit( ); + + value = (char)pressed; + + return value; +} + +void wait_for_any_key( char *message ) +{ + if ( message == NULL ) + printf( "Press any key to continue: " ); + else + printf( "%s", message ); + + get_keypress( ); + + printf( "\n\n" ); +} + +void beep( ) +{ + printf( "%c", 7 ); + fflush( stdout ); +} diff --git a/src/inigo/io.h b/src/inigo/io.h new file mode 100644 index 0000000..1a35944 --- /dev/null +++ b/src/inigo/io.h @@ -0,0 +1,45 @@ +/* + * io.h -- inigo input/output + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _DEMO_IO_H_ +#define _DEMO_IO_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern char *chomp( char * ); +extern char *trim( char * ); +extern char *strip_quotes( char * ); +extern char *get_string( char *, int, char * ); +extern int *get_int( int *, int ); +extern void term_init( ); +extern int term_read( ); +extern void term_exit( ); +extern char get_keypress( ); +extern void wait_for_any_key( char * ); +extern void beep( ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/miracle/Makefile b/src/miracle/Makefile new file mode 100644 index 0000000..7fa8a29 --- /dev/null +++ b/src/miracle/Makefile @@ -0,0 +1,71 @@ +include ../../config.mak + +TARGET = miracle + +ifneq ($(targetos), Darwin) +LIBNAME = libmiracle$(LIBSUF) +LIBTARGET = $(LIBNAME).$(version) +SHFLAGS += -Wl,-soname,$(LIBTARGET) +else +LIBNAME = libmiracle$(LIBSUF) +LIBTARGET = libmiracle.$(version)$(LIBSUF) +SHFLAGS += -install_name $(libdir)/$(LIBTARGET) +endif + +APP_OBJS = miracle.o + +LIB_OBJS = miracle_log.o \ + miracle_server.o \ + miracle_connection.o \ + miracle_local.o \ + miracle_unit.o \ + miracle_commands.o \ + miracle_unit_commands.o + +INCS = miracle_server.h \ + miracle_local.h \ + miracle_log.h + +OBJS = $(APP_OBJS) $(LIB_OBJS) + +CFLAGS += -I.. $(RDYNAMIC) + +LDFLAGS += -L../valerie -lvalerie -L../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(APP_OBJS) $(LIBTARGET) + $(CC) -o $@ $(APP_OBJS) -L. -lmiracle $(LDFLAGS) + +$(LIBTARGET): $(LIB_OBJS) + $(CC) $(SHFLAGS) -o $@ $(LIB_OBJS) $(LDFLAGS) + ln -sf $(LIBTARGET) $(LIBNAME) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(TARGET) $(LIBNAME) $(LIBTARGET) + +install: all + install -d "$(DESTDIR)$(bindir)" + install -c -s -m 755 $(TARGET) "$(DESTDIR)$(bindir)" + install -m 755 $(LIBTARGET) $(DESTDIR)$(libdir) + ln -sf $(LIBTARGET) $(DESTDIR)$(libdir)/$(LIBNAME) + mkdir -p "$(DESTDIR)$(prefix)/include/mlt/miracle" + install -m 644 $(INCS) "$(DESTDIR)$(prefix)/include/mlt/miracle" + +uninstall: + rm -f "$(DESTDIR)$(bindir)/$(TARGET)" + rm -f "$(DESTDIR)$(libdir)/$(LIBTARGET)" + rm -f "$(DESTDIR)$(libdir)/$(LIBNAME)" + rm -rf "$(DESTDIR)$(prefix)/include/mlt/miracle" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/miracle/configure b/src/miracle/configure new file mode 100755 index 0000000..85d890d --- /dev/null +++ b/src/miracle/configure @@ -0,0 +1,2 @@ +#!/bin/sh +echo "miracle -I$prefix/include/mlt -D_REENTRANT -L$libdir -lmiracle" >> ../../packages.dat diff --git a/src/miracle/miracle.c b/src/miracle/miracle.c new file mode 100644 index 0000000..04b04be --- /dev/null +++ b/src/miracle/miracle.c @@ -0,0 +1,122 @@ +/* + * miracle.c -- MLT Video TCP Server + * + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Authors: + * Dan Dennedy + * Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* System header files */ +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Application header files */ +#include "miracle_server.h" +#include "miracle_log.h" + +/** Our dv server. +*/ + +static miracle_server server = NULL; + +/** atexit shutdown handler for the server. +*/ + +static void main_cleanup( ) +{ + miracle_server_close( server ); +} + +/** Report usage and exit. +*/ + +void usage( char *app ) +{ + fprintf( stderr, "Usage: %s [-test] [-port NNNN]\n", app ); + exit( 0 ); +} + +/** The main function. +*/ + +int main( int argc, char **argv ) +{ + int error = 0; + int index = 0; + int background = 1; + struct timespec tm = { 5, 0 }; + struct sched_param scp; + + // Use realtime scheduling if possible + memset( &scp, '\0', sizeof( scp ) ); + scp.sched_priority = sched_get_priority_max( SCHED_FIFO ) - 1; +#ifndef __DARWIN__ + sched_setscheduler( 0, SCHED_FIFO, &scp ); +#endif + + mlt_factory_init( NULL ); + + server = miracle_server_init( argv[ 0 ] ); + + for ( index = 1; index < argc; index ++ ) + { + if ( !strcmp( argv[ index ], "-port" ) ) + miracle_server_set_port( server, atoi( argv[ ++ index ] ) ); + else if ( !strcmp( argv[ index ], "-proxy" ) ) + miracle_server_set_proxy( server, argv[ ++ index ] ); + else if ( !strcmp( argv[ index ], "-test" ) ) + background = 0; + else + usage( argv[ 0 ] ); + } + + /* Optionally detatch ourselves from the controlling tty */ + + if ( background ) + { + if ( fork() ) + return 0; + setsid(); + miracle_log_init( log_syslog, LOG_INFO ); + } + else + { + miracle_log_init( log_stderr, LOG_DEBUG ); + } + + atexit( main_cleanup ); + + /* Set the config script */ + miracle_server_set_config( server, "/etc/miracle.conf" ); + + /* Execute the server */ + error = miracle_server_execute( server ); + + /* We need to wait until we're exited.. */ + while ( !server->shutdown ) + nanosleep( &tm, NULL ); + + return error; +} diff --git a/src/miracle/miracle_commands.c b/src/miracle/miracle_commands.c new file mode 100644 index 0000000..c00e9f6 --- /dev/null +++ b/src/miracle/miracle_commands.c @@ -0,0 +1,248 @@ +/* + * global_commands.c + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "miracle_unit.h" +#include "miracle_commands.h" +#include "miracle_log.h" + +static miracle_unit g_units[MAX_UNITS]; + + +/** Return the miracle_unit given a numeric index. +*/ + +miracle_unit miracle_get_unit( int n ) +{ + if (n < MAX_UNITS) + return g_units[n]; + else + return NULL; +} + +/** Destroy the miracle_unit given its numeric index. +*/ + +void miracle_delete_unit( int n ) +{ + if (n < MAX_UNITS) + { + miracle_unit unit = miracle_get_unit(n); + if (unit != NULL) + { + miracle_unit_close( unit ); + g_units[ n ] = NULL; + miracle_log( LOG_NOTICE, "Deleted unit U%d.", n ); + } + } +} + +/** Destroy all allocated units on the server. +*/ + +void miracle_delete_all_units( void ) +{ + int i; + for (i = 0; i < MAX_UNITS; i++) + { + if ( miracle_get_unit(i) != NULL ) + { + miracle_unit_close( miracle_get_unit(i) ); + miracle_log( LOG_NOTICE, "Deleted unit U%d.", i ); + } + } +} + +/** Add a DV virtual vtr to the server. +*/ +response_codes miracle_add_unit( command_argument cmd_arg ) +{ + int i = 0; + for ( i = 0; i < MAX_UNITS; i ++ ) + if ( g_units[ i ] == NULL ) + break; + + if ( i < MAX_UNITS ) + { + char *arg = cmd_arg->argument; + g_units[ i ] = miracle_unit_init( i, arg ); + if ( g_units[ i ] != NULL ) + { + miracle_unit_set_notifier( g_units[ i ], valerie_parser_get_notifier( cmd_arg->parser ), cmd_arg->root_dir ); + valerie_response_printf( cmd_arg->response, 10, "U%1d\n\n", i ); + } + return g_units[ i ] != NULL ? RESPONSE_SUCCESS_N : RESPONSE_ERROR; + } + valerie_response_printf( cmd_arg->response, 1024, "no more units can be created\n\n" ); + + return RESPONSE_ERROR; +} + + +/** List all AV/C nodes on the bus. +*/ +response_codes miracle_list_nodes( command_argument cmd_arg ) +{ + response_codes error = RESPONSE_SUCCESS_N; + return error; +} + + +/** List units already added to server. +*/ +response_codes miracle_list_units( command_argument cmd_arg ) +{ + response_codes error = RESPONSE_SUCCESS_N; + int i = 0; + + for ( i = 0; i < MAX_UNITS; i ++ ) + { + miracle_unit unit = miracle_get_unit( i ); + if ( unit != NULL ) + { + mlt_properties properties = unit->properties; + char *constructor = mlt_properties_get( properties, "constructor" ); + int node = mlt_properties_get_int( properties, "node" ); + int online = !mlt_properties_get_int( properties, "offline" ); + valerie_response_printf( cmd_arg->response, 1024, "U%d %02d %s %d\n", i, node, constructor, online ); + } + } + valerie_response_printf( cmd_arg->response, 1024, "\n" ); + + return error; +} + +static int filter_files( const struct dirent *de ) +{ + return de->d_name[ 0 ] != '.'; +} + +/** List clips in a directory. +*/ +response_codes miracle_list_clips( command_argument cmd_arg ) +{ + response_codes error = RESPONSE_BAD_FILE; + const char *dir_name = (const char*) cmd_arg->argument; + DIR *dir; + char fullname[1024]; + struct dirent **de = NULL; + int i, n; + + snprintf( fullname, 1023, "%s%s", cmd_arg->root_dir, dir_name ); + dir = opendir( fullname ); + if (dir != NULL) + { + struct stat info; + error = RESPONSE_SUCCESS_N; + n = scandir( fullname, &de, filter_files, alphasort ); + for (i = 0; i < n; i++ ) + { + snprintf( fullname, 1023, "%s%s/%s", cmd_arg->root_dir, dir_name, de[i]->d_name ); + if ( stat( fullname, &info ) == 0 && S_ISDIR( info.st_mode ) ) + valerie_response_printf( cmd_arg->response, 1024, "\"%s/\"\n", de[i]->d_name ); + } + for (i = 0; i < n; i++ ) + { + snprintf( fullname, 1023, "%s%s/%s", cmd_arg->root_dir, dir_name, de[i]->d_name ); + if ( lstat( fullname, &info ) == 0 && + ( S_ISREG( info.st_mode ) || S_ISLNK( info.st_mode ) || ( strstr( fullname, ".clip" ) && info.st_mode | S_IXUSR ) ) ) + valerie_response_printf( cmd_arg->response, 1024, "\"%s\" %llu\n", de[i]->d_name, (unsigned long long) info.st_size ); + free( de[ i ] ); + } + free( de ); + closedir( dir ); + valerie_response_write( cmd_arg->response, "\n", 1 ); + } + + return error; +} + +/** Set a server configuration property. +*/ + +response_codes miracle_set_global_property( command_argument cmd_arg ) +{ + char *key = (char*) cmd_arg->argument; + char *value = NULL; + + value = strchr( key, '=' ); + if (value == NULL) + return RESPONSE_OUT_OF_RANGE; + *value = 0; + value++; + miracle_log( LOG_DEBUG, "SET %s = %s", key, value ); + + if ( strncasecmp( key, "root", 1024) == 0 ) + { + int len = strlen(value); + int i; + + /* stop all units and unload clips */ + for (i = 0; i < MAX_UNITS; i++) + { + if (g_units[i] != NULL) + miracle_unit_terminate( g_units[i] ); + } + + /* set the property */ + strncpy( cmd_arg->root_dir, value, 1023 ); + + /* add a trailing slash if needed */ + if ( len && cmd_arg->root_dir[ len - 1 ] != '/') + { + cmd_arg->root_dir[ len ] = '/'; + cmd_arg->root_dir[ len + 1 ] = '\0'; + } + } + else + return RESPONSE_OUT_OF_RANGE; + + return RESPONSE_SUCCESS; +} + +/** Get a server configuration property. +*/ + +response_codes miracle_get_global_property( command_argument cmd_arg ) +{ + char *key = (char*) cmd_arg->argument; + + if ( strncasecmp( key, "root", 1024) == 0 ) + { + valerie_response_write( cmd_arg->response, cmd_arg->root_dir, strlen(cmd_arg->root_dir) ); + return RESPONSE_SUCCESS_1; + } + else + return RESPONSE_OUT_OF_RANGE; + + return RESPONSE_SUCCESS; +} + + diff --git a/src/miracle/miracle_commands.h b/src/miracle/miracle_commands.h new file mode 100644 index 0000000..947554d --- /dev/null +++ b/src/miracle/miracle_commands.h @@ -0,0 +1,52 @@ +/* + * global_commands.h + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef _GLOBAL_COMMANDS_H_ +#define _GLOBAL_COMMANDS_H_ + +#include +#include "miracle_unit.h" +#include "miracle_connection.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern miracle_unit miracle_get_unit( int ); +extern void miracle_delete_unit( int ); +extern void miracle_delete_all_units( void ); +extern int miracle_unit_status( int n, valerie_status status, int root_offset ); +//extern void raw1394_start_service_threads( void ); +//extern void raw1394_stop_service_threads( void ); + +extern response_codes miracle_add_unit( command_argument ); +extern response_codes miracle_list_nodes( command_argument ); +extern response_codes miracle_list_units( command_argument ); +extern response_codes miracle_list_clips( command_argument ); +extern response_codes miracle_set_global_property( command_argument ); +extern response_codes miracle_get_global_property( command_argument ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/miracle/miracle_connection.c b/src/miracle/miracle_connection.c new file mode 100644 index 0000000..b7f30fd --- /dev/null +++ b/src/miracle/miracle_connection.c @@ -0,0 +1,292 @@ +/* + * miracle_connection.c -- DV Connection Handler + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +/* System header files */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Application header files */ +#include "miracle_commands.h" +#include "miracle_connection.h" +#include "miracle_server.h" +#include "miracle_log.h" + +/** This is a generic replacement for fgets which operates on a file + descriptor. Unlike fgets, we can also specify a line terminator. Maximum + of (max - 1) chars can be read into buf from fd. If we reach the + end-of-file, *eof_chk is set to 1. +*/ + +int fdgetline( int fd, char *buf, int max, char line_terminator, int *eof_chk ) +{ + int count = 0; + char tmp [1]; + *eof_chk = 0; + + if (fd) + while (count < max - 1) { + if (read (fd, tmp, 1) > 0) { + if (tmp [0] != line_terminator) + buf [count++] = tmp [0]; + else + break; + +/* Is it an EOF character (ctrl-D, i.e. ascii 4)? If so we definitely want + to break. */ + + if (tmp [0] == 4) { + *eof_chk = 1; + break; + } + } else { + *eof_chk = 1; + break; + } + } + + buf [count] = '\0'; + + return count; +} + +static int connection_initiate( int ); +static int connection_send( int, valerie_response ); +static int connection_read( int, char *, int ); +static void connection_close( int ); + +static int connection_initiate( int fd ) +{ + int error = 0; + valerie_response response = valerie_response_init( ); + valerie_response_set_error( response, 100, "VTR Ready" ); + error = connection_send( fd, response ); + valerie_response_close( response ); + return error; +} + +static int connection_send( int fd, valerie_response response ) +{ + int error = 0; + int index = 0; + int code = valerie_response_get_error_code( response ); + + if ( code != -1 ) + { + int items = valerie_response_count( response ); + + if ( items == 0 ) + valerie_response_set_error( response, 500, "Unknown error" ); + + if ( code == 200 && items > 2 ) + valerie_response_set_error( response, 201, "OK" ); + else if ( code == 200 && items > 1 ) + valerie_response_set_error( response, 202, "OK" ); + + code = valerie_response_get_error_code( response ); + items = valerie_response_count( response ); + + for ( index = 0; !error && index < items; index ++ ) + { + char *line = valerie_response_get_line( response, index ); + int length = strlen( line ); + if ( length == 0 && index != valerie_response_count( response ) - 1 && write( fd, " ", 1 ) != 1 ) + error = -1; + else if ( length > 0 && write( fd, line, length ) != length ) + error = -1; + if ( write( fd, "\r\n", 2 ) != 2 ) + error = -1; + } + + if ( ( code == 201 || code == 500 ) && strcmp( valerie_response_get_line( response, items - 1 ), "" ) ) + write( fd, "\r\n", 2 ); + } + else + { + char *message = "500 Empty Response\r\n\r\n"; + write( fd, message, strlen( message ) ); + } + + return error; +} + +static int connection_read( int fd, char *command, int length ) +{ + int eof_chk; + int nchars = fdgetline( fd, command, length, '\n', &eof_chk ); + char *cr = strchr( command, '\r'); + if ( cr != NULL ) + cr[0] = '\0'; + if ( eof_chk || strncasecmp( command, "BYE", 3 ) == 0 ) + nchars = 0; + return nchars; +} + +int connection_status( int fd, valerie_notifier notifier ) +{ + int error = 0; + int index = 0; + valerie_status_t status; + char text[ 10240 ]; + valerie_socket socket = valerie_socket_init_fd( fd ); + + for ( index = 0; !error && index < MAX_UNITS; index ++ ) + { + valerie_notifier_get( notifier, &status, index ); + valerie_status_serialise( &status, text, sizeof( text ) ); + error = valerie_socket_write_data( socket, text, strlen( text ) ) != strlen( text ); + } + + while ( !error ) + { + if ( valerie_notifier_wait( notifier, &status ) == 0 ) + { + valerie_status_serialise( &status, text, sizeof( text ) ); + error = valerie_socket_write_data( socket, text, strlen( text ) ) != strlen( text ); + } + else + { + struct timeval tv = { 0, 0 }; + fd_set rfds; + + FD_ZERO( &rfds ); + FD_SET( fd, &rfds ); + + if ( select( socket->fd + 1, &rfds, NULL, NULL, &tv ) ) + error = 1; + } + } + + valerie_socket_close( socket ); + + return error; +} + +static void connection_close( int fd ) +{ + close( fd ); +} + +void *parser_thread( void *arg ) +{ + struct hostent *he; + connection_t *connection = arg; + mlt_properties owner = connection->owner; + char address[ 512 ]; + char command[ 1024 ]; + int fd = connection->fd; + valerie_parser parser = connection->parser; + valerie_response response = NULL; + + /* Get the connecting clients ip information */ + he = gethostbyaddr( (char *) &( connection->sin.sin_addr.s_addr ), sizeof(u_int32_t), AF_INET); + if ( he != NULL ) + strcpy( address, he->h_name ); + else + inet_ntop( AF_INET, &( connection->sin.sin_addr.s_addr), address, 32 ); + + miracle_log( LOG_NOTICE, "Connection established with %s (%d)", address, fd ); + + /* Execute the commands received. */ + if ( connection_initiate( fd ) == 0 ) + { + int error = 0; + + while( !error && connection_read( fd, command, 1024 ) ) + { + response = NULL; + + if ( !strncmp( command, "PUSH ", 5 ) ) + { + char temp[ 20 ]; + int bytes; + char *buffer = NULL; + int total = 0; + mlt_service service = NULL; + + connection_read( fd, temp, 20 ); + bytes = atoi( temp ); + buffer = malloc( bytes + 1 ); + while ( total < bytes ) + { + int count = read( fd, buffer + total, bytes - total ); + if ( count >= 0 ) + total += count; + else + break; + } + buffer[ bytes ] = '\0'; + if ( bytes > 0 && total == bytes ) + { + if ( mlt_properties_get( owner, "push-parser-off" ) == 0 ) + { + service = ( mlt_service )mlt_factory_producer( "westley-xml", buffer ); + mlt_events_fire( owner, "push-received", &response, command, service, NULL ); + if ( response == NULL ) + response = valerie_parser_push( parser, command, service ); + } + else + { + response = valerie_parser_received( parser, command, buffer ); + } + } + error = connection_send( fd, response ); + valerie_response_close( response ); + mlt_service_close( service ); + free( buffer ); + } + else if ( strncmp( command, "STATUS", 6 ) ) + { + mlt_events_fire( owner, "command-received", &response, command, NULL ); + if ( response == NULL ) + response = valerie_parser_execute( parser, command ); + miracle_log( LOG_INFO, "%s \"%s\" %d", address, command, valerie_response_get_error_code( response ) ); + error = connection_send( fd, response ); + valerie_response_close( response ); + } + else + { + error = connection_status( fd, valerie_parser_get_notifier( parser ) ); + } + } + } + + /* Free the resources associated with this connection. */ + connection_close( fd ); + + miracle_log( LOG_NOTICE, "Connection with %s (%d) closed", address, fd ); + + free( connection ); + + return NULL; +} diff --git a/src/miracle/miracle_connection.h b/src/miracle/miracle_connection.h new file mode 100644 index 0000000..ea6d1e8 --- /dev/null +++ b/src/miracle/miracle_connection.h @@ -0,0 +1,92 @@ +/* + * miracle_connection.h -- DV Connection Handler + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _DV_CONNECTION_H_ +#define _DV_CONNECTION_H_ + +#include +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Connection structure +*/ + +typedef struct +{ + mlt_properties owner; + int fd; + struct sockaddr_in sin; + valerie_parser parser; +} +connection_t; + +/** Enumeration for responses. +*/ + +typedef enum +{ + RESPONSE_SUCCESS = 200, + RESPONSE_SUCCESS_N = 201, + RESPONSE_SUCCESS_1 = 202, + RESPONSE_UNKNOWN_COMMAND = 400, + RESPONSE_TIMEOUT = 401, + RESPONSE_MISSING_ARG = 402, + RESPONSE_INVALID_UNIT = 403, + RESPONSE_BAD_FILE = 404, + RESPONSE_OUT_OF_RANGE = 405, + RESPONSE_TOO_MANY_FILES = 406, + RESPONSE_ERROR = 500 +} +response_codes; + +/* the following struct is passed as the single argument + to all command callback functions */ + +typedef struct +{ + valerie_parser parser; + valerie_response response; + valerie_tokeniser tokeniser; + char *command; + int unit; + void *argument; + char *root_dir; +} +command_argument_t, *command_argument; + +/* A handler is defined as follows. */ +typedef int (*command_handler_t) ( command_argument ); + + +extern void *parser_thread( void *arg ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/miracle/miracle_local.c b/src/miracle/miracle_local.c new file mode 100644 index 0000000..62daa84 --- /dev/null +++ b/src/miracle/miracle_local.c @@ -0,0 +1,597 @@ +/* + * miracle_local.c -- Local Miracle Parser + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +/* System header files */ +#include +#include +#include + +/* Needed for backtrace on linux */ +#ifdef linux +#include +#endif + +/* Valerie header files */ +#include + +/* MLT header files. */ +#include + +/* Application header files */ +#include "miracle_local.h" +#include "miracle_connection.h" +#include "miracle_commands.h" +#include "miracle_unit_commands.h" +#include "miracle_log.h" + +/** Private miracle_local structure. +*/ + +typedef struct +{ + valerie_parser parser; + char root_dir[1024]; +} +*miracle_local, miracle_local_t; + +/** Forward declarations. +*/ + +static valerie_response miracle_local_connect( miracle_local ); +static valerie_response miracle_local_execute( miracle_local, char * ); +static valerie_response miracle_local_push( miracle_local, char *, mlt_service ); +static valerie_response miracle_local_receive( miracle_local, char *, char * ); +static void miracle_local_close( miracle_local ); +response_codes miracle_help( command_argument arg ); +response_codes miracle_run( command_argument arg ); +response_codes miracle_shutdown( command_argument arg ); + +/** DV Parser constructor. +*/ + +valerie_parser miracle_parser_init_local( ) +{ + valerie_parser parser = malloc( sizeof( valerie_parser_t ) ); + miracle_local local = malloc( sizeof( miracle_local_t ) ); + + if ( parser != NULL ) + { + memset( parser, 0, sizeof( valerie_parser_t ) ); + + parser->connect = (parser_connect)miracle_local_connect; + parser->execute = (parser_execute)miracle_local_execute; + parser->push = (parser_push)miracle_local_push; + parser->received = (parser_received)miracle_local_receive; + parser->close = (parser_close)miracle_local_close; + parser->real = local; + + if ( local != NULL ) + { + memset( local, 0, sizeof( miracle_local_t ) ); + local->parser = parser; + local->root_dir[0] = '/'; + } + + // Construct the factory + mlt_factory_init( getenv( "MLT_REPOSITORY" ) ); + } + return parser; +} + +/** response status code/message pair +*/ + +typedef struct +{ + int code; + char *message; +} +responses_t; + +/** response messages +*/ + +static responses_t responses [] = +{ + {RESPONSE_SUCCESS, "OK"}, + {RESPONSE_SUCCESS_N, "OK"}, + {RESPONSE_SUCCESS_1, "OK"}, + {RESPONSE_UNKNOWN_COMMAND, "Unknown command"}, + {RESPONSE_TIMEOUT, "Operation timed out"}, + {RESPONSE_MISSING_ARG, "Argument missing"}, + {RESPONSE_INVALID_UNIT, "Unit not found"}, + {RESPONSE_BAD_FILE, "Failed to locate or open clip"}, + {RESPONSE_OUT_OF_RANGE, "Argument value out of range"}, + {RESPONSE_TOO_MANY_FILES, "Too many files open"}, + {RESPONSE_ERROR, "Server Error"} +}; + +/** Argument types. +*/ + +typedef enum +{ + ATYPE_NONE, + ATYPE_FLOAT, + ATYPE_STRING, + ATYPE_INT, + ATYPE_PAIR +} +arguments_types; + +/** A command definition. +*/ + +typedef struct +{ +/* The command string corresponding to this operation (e.g. "play") */ + char *command; +/* The function associated with it */ + response_codes (*operation) ( command_argument ); +/* a boolean to indicate if this is a unit or global command + unit commands require a unit identifier as first argument */ + int is_unit; +/* What type is the argument (RTTI :-) ATYPE_whatever */ + int type; +/* online help information */ + char *help; +} +command_t; + +/* The following define the queue of commands available to the user. The + first entry is the name of the command (the string which must be typed), + the second command is the function associated with it, the third argument + is for the type of the argument, and the last argument specifies whether + this is something which should be handled immediately or whether it + should be queued (only robot motion commands need to be queued). */ + +static command_t vocabulary[] = +{ + {"BYE", NULL, 0, ATYPE_NONE, "Terminates the session. Units are not removed and task queue is not flushed."}, + {"HELP", miracle_help, 0, ATYPE_NONE, "Display this information!"}, + {"NLS", miracle_list_nodes, 0, ATYPE_NONE, "List the AV/C nodes on the 1394 bus."}, + {"UADD", miracle_add_unit, 0, ATYPE_STRING, "Create a new DV unit (virtual VTR) to transmit to receiver specified in GUID argument."}, + {"ULS", miracle_list_units, 0, ATYPE_NONE, "Lists the units that have already been added to the server."}, + {"CLS", miracle_list_clips, 0, ATYPE_STRING, "Lists the clips at directory name argument."}, + {"SET", miracle_set_global_property, 0, ATYPE_PAIR, "Set a server configuration property."}, + {"GET", miracle_get_global_property, 0, ATYPE_STRING, "Get a server configuration property."}, + {"RUN", miracle_run, 0, ATYPE_STRING, "Run a batch file." }, + {"LIST", miracle_list, 1, ATYPE_NONE, "List the playlist associated to a unit."}, + {"LOAD", miracle_load, 1, ATYPE_STRING, "Load clip specified in absolute filename argument."}, + {"INSERT", miracle_insert, 1, ATYPE_STRING, "Insert a clip at the given clip index."}, + {"REMOVE", miracle_remove, 1, ATYPE_NONE, "Remove a clip at the given clip index."}, + {"CLEAN", miracle_clean, 1, ATYPE_NONE, "Clean a unit by removing all but the currently playing clip."}, + {"WIPE", miracle_wipe, 1, ATYPE_NONE, "Clean a unit by removing everything before the currently playing clip."}, + {"CLEAR", miracle_clear, 1, ATYPE_NONE, "Clear a unit by removing all clips."}, + {"MOVE", miracle_move, 1, ATYPE_INT, "Move a clip to another clip index."}, + {"APND", miracle_append, 1, ATYPE_STRING, "Append a clip specified in absolute filename argument."}, + {"PLAY", miracle_play, 1, ATYPE_NONE, "Play a loaded clip at speed -2000 to 2000 where 1000 = normal forward speed."}, + {"STOP", miracle_stop, 1, ATYPE_NONE, "Stop a loaded and playing clip."}, + {"PAUSE", miracle_pause, 1, ATYPE_NONE, "Pause a playing clip."}, + {"REW", miracle_rewind, 1, ATYPE_NONE, "Rewind a unit. If stopped, seek to beginning of clip. If playing, play fast backwards."}, + {"FF", miracle_ff, 1, ATYPE_NONE, "Fast forward a unit. If stopped, seek to beginning of clip. If playing, play fast forwards."}, + {"STEP", miracle_step, 1, ATYPE_INT, "Step argument number of frames forward or backward."}, + {"GOTO", miracle_goto, 1, ATYPE_INT, "Jump to frame number supplied as argument."}, + {"SIN", miracle_set_in_point, 1, ATYPE_INT, "Set the IN point of the loaded clip to frame number argument. -1 = reset in point to 0"}, + {"SOUT", miracle_set_out_point, 1, ATYPE_INT, "Set the OUT point of the loaded clip to frame number argument. -1 = reset out point to maximum."}, + {"USTA", miracle_get_unit_status, 1, ATYPE_NONE, "Report information about the unit."}, + {"USET", miracle_set_unit_property, 1, ATYPE_PAIR, "Set a unit configuration property."}, + {"UGET", miracle_get_unit_property, 1, ATYPE_STRING, "Get a unit configuration property."}, + {"XFER", miracle_transfer, 1, ATYPE_STRING, "Transfer the unit's clip to another unit specified as argument."}, + {"SHUTDOWN", miracle_shutdown, 0, ATYPE_NONE, "Shutdown the server."}, + {NULL, NULL, 0, ATYPE_NONE, NULL} +}; + +/** Usage message +*/ + +static char helpstr [] = + "Miracle -- A Multimedia Playout Server\n" + " Copyright (C) 2002-2003 Ushodaya Enterprises Limited\n" + " Authors:\n" + " Dan Dennedy \n" + " Charles Yates \n" + "Available commands:\n"; + +/** Lookup the response message for a status code. +*/ + +inline char *get_response_msg( int code ) +{ + int i = 0; + for ( i = 0; responses[ i ].message != NULL && code != responses[ i ].code; i ++ ) ; + return responses[ i ].message; +} + +/** Tell the user the miracle command set +*/ + +response_codes miracle_help( command_argument cmd_arg ) +{ + int i = 0; + + valerie_response_printf( cmd_arg->response, 10240, "%s", helpstr ); + + for ( i = 0; vocabulary[ i ].command != NULL; i ++ ) + valerie_response_printf( cmd_arg->response, 1024, + "%-10.10s%s\n", + vocabulary[ i ].command, + vocabulary[ i ].help ); + + valerie_response_printf( cmd_arg->response, 2, "\n" ); + + return RESPONSE_SUCCESS_N; +} + +/** Execute a batch file. +*/ + +response_codes miracle_run( command_argument cmd_arg ) +{ + valerie_response temp = valerie_parser_run( cmd_arg->parser, (char *)cmd_arg->argument ); + + if ( temp != NULL ) + { + int index = 0; + + valerie_response_set_error( cmd_arg->response, + valerie_response_get_error_code( temp ), + valerie_response_get_error_string( temp ) ); + + for ( index = 1; index < valerie_response_count( temp ); index ++ ) + valerie_response_printf( cmd_arg->response, 10240, "%s\n", valerie_response_get_line( temp, index ) ); + + valerie_response_close( temp ); + } + + return valerie_response_get_error_code( cmd_arg->response ); +} + +response_codes miracle_shutdown( command_argument cmd_arg ) +{ + exit( 0 ); + return RESPONSE_SUCCESS; +} + +/** Processes 'thread' id +*/ + +static pthread_t self; + +/* Signal handler to deal with various shutdown signals. Basically this + should clean up and power down the motor. Note that the death of any + child thread will kill all thrads. */ + +void signal_handler( int sig ) +{ + if ( pthread_equal( self, pthread_self( ) ) ) + { + +#ifdef _GNU_SOURCE + miracle_log( LOG_DEBUG, "Received %s - shutting down.", strsignal(sig) ); +#else + miracle_log( LOG_DEBUG, "Received signal %i - shutting down.", sig ); +#endif + + exit(EXIT_SUCCESS); + } +} + +static void sigsegv_handler() +{ +#ifdef linux + void *array[ 10 ]; + size_t size; + char **strings; + size_t i; + + miracle_log( LOG_CRIT, "\a\nMiracle experienced a segmentation fault.\n" + "Dumping stack from the offending thread\n\n" ); + size = backtrace( array, 10 ); + strings = backtrace_symbols( array, size ); + + miracle_log( LOG_CRIT, "Obtained %zd stack frames.\n", size ); + + for ( i = 0; i < size; i++ ) + miracle_log( LOG_CRIT, "%s", strings[ i ] ); + + free( strings ); + + miracle_log( LOG_CRIT, "\nDone dumping - exiting.\n" ); +#else + miracle_log( LOG_CRIT, "\a\nMiracle experienced a segmentation fault.\n" ); +#endif + exit( EXIT_FAILURE ); +} + + + +/** Local 'connect' function. +*/ + +static valerie_response miracle_local_connect( miracle_local local ) +{ + valerie_response response = valerie_response_init( ); + + self = pthread_self( ); + + valerie_response_set_error( response, 100, "VTR Ready" ); + + signal( SIGHUP, signal_handler ); + signal( SIGINT, signal_handler ); + signal( SIGTERM, SIG_DFL ); + signal( SIGSTOP, signal_handler ); + signal( SIGPIPE, signal_handler ); + signal( SIGALRM, signal_handler ); + signal( SIGCHLD, SIG_IGN ); + if ( getenv( "MLT_SIGSEGV" ) ) + signal( SIGSEGV, sigsegv_handler ); + + return response; +} + +/** Set the error and determine the message associated to this command. +*/ + +void miracle_command_set_error( command_argument cmd, response_codes code ) +{ + valerie_response_set_error( cmd->response, code, get_response_msg( code ) ); +} + +/** Parse the unit argument. +*/ + +int miracle_command_parse_unit( command_argument cmd, int argument ) +{ + int unit = -1; + char *string = valerie_tokeniser_get_string( cmd->tokeniser, argument ); + if ( string != NULL && ( string[ 0 ] == 'U' || string[ 0 ] == 'u' ) && strlen( string ) > 1 ) + unit = atoi( string + 1 ); + return unit; +} + +/** Parse a normal argument. +*/ + +void *miracle_command_parse_argument( command_argument cmd, int argument, arguments_types type, char *command ) +{ + void *ret = NULL; + char *value = valerie_tokeniser_get_string( cmd->tokeniser, argument ); + + if ( value != NULL ) + { + switch( type ) + { + case ATYPE_NONE: + break; + + case ATYPE_FLOAT: + ret = malloc( sizeof( float ) ); + if ( ret != NULL ) + *( float * )ret = atof( value ); + break; + + case ATYPE_STRING: + ret = strdup( value ); + break; + + case ATYPE_PAIR: + if ( strchr( command, '=' ) ) + { + char *ptr = strchr( command, '=' ); + while ( *( ptr - 1 ) != ' ' ) + ptr --; + ret = strdup( ptr ); + ptr = ret; + while( ptr[ strlen( ptr ) - 1 ] == ' ' ) + ptr[ strlen( ptr ) - 1 ] = '\0'; + } + break; + + case ATYPE_INT: + ret = malloc( sizeof( int ) ); + if ( ret != NULL ) + *( int * )ret = atoi( value ); + break; + } + } + + return ret; +} + +/** Get the error code - note that we simply the success return. +*/ + +response_codes miracle_command_get_error( command_argument cmd ) +{ + response_codes ret = valerie_response_get_error_code( cmd->response ); + if ( ret == RESPONSE_SUCCESS_N || ret == RESPONSE_SUCCESS_1 ) + ret = RESPONSE_SUCCESS; + return ret; +} + +/** Execute the command. +*/ + +static valerie_response miracle_local_execute( miracle_local local, char *command ) +{ + command_argument_t cmd; + cmd.parser = local->parser; + cmd.response = valerie_response_init( ); + cmd.tokeniser = valerie_tokeniser_init( ); + cmd.command = command; + cmd.unit = -1; + cmd.argument = NULL; + cmd.root_dir = local->root_dir; + + /* Set the default error */ + miracle_command_set_error( &cmd, RESPONSE_UNKNOWN_COMMAND ); + + /* Parse the command */ + if ( valerie_tokeniser_parse_new( cmd.tokeniser, command, " " ) > 0 ) + { + int index = 0; + char *value = valerie_tokeniser_get_string( cmd.tokeniser, 0 ); + int found = 0; + + /* Strip quotes from all tokens */ + for ( index = 0; index < valerie_tokeniser_count( cmd.tokeniser ); index ++ ) + valerie_util_strip( valerie_tokeniser_get_string( cmd.tokeniser, index ), '\"' ); + + /* Search the vocabulary array for value */ + for ( index = 1; !found && vocabulary[ index ].command != NULL; index ++ ) + if ( ( found = !strcasecmp( vocabulary[ index ].command, value ) ) ) + break; + + /* If we found something, the handle the args and call the handler. */ + if ( found ) + { + int position = 1; + + miracle_command_set_error( &cmd, RESPONSE_SUCCESS ); + + if ( vocabulary[ index ].is_unit ) + { + cmd.unit = miracle_command_parse_unit( &cmd, position ); + if ( cmd.unit == -1 ) + miracle_command_set_error( &cmd, RESPONSE_MISSING_ARG ); + position ++; + } + + if ( miracle_command_get_error( &cmd ) == RESPONSE_SUCCESS ) + { + cmd.argument = miracle_command_parse_argument( &cmd, position, vocabulary[ index ].type, command ); + if ( cmd.argument == NULL && vocabulary[ index ].type != ATYPE_NONE ) + miracle_command_set_error( &cmd, RESPONSE_MISSING_ARG ); + position ++; + } + + if ( miracle_command_get_error( &cmd ) == RESPONSE_SUCCESS ) + { + response_codes error = vocabulary[ index ].operation( &cmd ); + miracle_command_set_error( &cmd, error ); + } + + free( cmd.argument ); + } + } + + valerie_tokeniser_close( cmd.tokeniser ); + + return cmd.response; +} + +static valerie_response miracle_local_receive( miracle_local local, char *command, char *doc ) +{ + command_argument_t cmd; + cmd.parser = local->parser; + cmd.response = valerie_response_init( ); + cmd.tokeniser = valerie_tokeniser_init( ); + cmd.command = command; + cmd.unit = -1; + cmd.argument = NULL; + cmd.root_dir = local->root_dir; + + /* Set the default error */ + miracle_command_set_error( &cmd, RESPONSE_SUCCESS ); + + /* Parse the command */ + if ( valerie_tokeniser_parse_new( cmd.tokeniser, command, " " ) > 0 ) + { + int index = 0; + int position = 1; + + /* Strip quotes from all tokens */ + for ( index = 0; index < valerie_tokeniser_count( cmd.tokeniser ); index ++ ) + valerie_util_strip( valerie_tokeniser_get_string( cmd.tokeniser, index ), '\"' ); + + cmd.unit = miracle_command_parse_unit( &cmd, position ); + if ( cmd.unit == -1 ) + miracle_command_set_error( &cmd, RESPONSE_MISSING_ARG ); + position ++; + + miracle_receive( &cmd, doc ); + miracle_command_set_error( &cmd, RESPONSE_SUCCESS ); + + free( cmd.argument ); + } + + valerie_tokeniser_close( cmd.tokeniser ); + + return cmd.response; +} + +static valerie_response miracle_local_push( miracle_local local, char *command, mlt_service service ) +{ + command_argument_t cmd; + cmd.parser = local->parser; + cmd.response = valerie_response_init( ); + cmd.tokeniser = valerie_tokeniser_init( ); + cmd.command = command; + cmd.unit = -1; + cmd.argument = NULL; + cmd.root_dir = local->root_dir; + + /* Set the default error */ + miracle_command_set_error( &cmd, RESPONSE_SUCCESS ); + + /* Parse the command */ + if ( valerie_tokeniser_parse_new( cmd.tokeniser, command, " " ) > 0 ) + { + int index = 0; + int position = 1; + + /* Strip quotes from all tokens */ + for ( index = 0; index < valerie_tokeniser_count( cmd.tokeniser ); index ++ ) + valerie_util_strip( valerie_tokeniser_get_string( cmd.tokeniser, index ), '\"' ); + + cmd.unit = miracle_command_parse_unit( &cmd, position ); + if ( cmd.unit == -1 ) + miracle_command_set_error( &cmd, RESPONSE_MISSING_ARG ); + position ++; + + miracle_push( &cmd, service ); + miracle_command_set_error( &cmd, RESPONSE_SUCCESS ); + + free( cmd.argument ); + } + + valerie_tokeniser_close( cmd.tokeniser ); + + return cmd.response; +} + +/** Close the parser. +*/ + +static void miracle_local_close( miracle_local local ) +{ + miracle_delete_all_units(); +#ifdef linux + //pthread_kill_other_threads_np(); + miracle_log( LOG_DEBUG, "Clean shutdown." ); + //free( local ); + //mlt_factory_close( ); +#endif +} diff --git a/src/miracle/miracle_local.h b/src/miracle/miracle_local.h new file mode 100644 index 0000000..06f9439 --- /dev/null +++ b/src/miracle/miracle_local.h @@ -0,0 +1,41 @@ +/* + * miracle_local.h -- Local Miracle Parser + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _MIRACLE_LOCAL_H_ +#define _MIRACLE_LOCAL_H_ + +/* Application header files */ +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Local parser API. +*/ + +extern valerie_parser miracle_parser_init_local( ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/miracle/miracle_log.c b/src/miracle/miracle_log.c new file mode 100644 index 0000000..8741843 --- /dev/null +++ b/src/miracle/miracle_log.c @@ -0,0 +1,57 @@ +/* + * miracle_log.c -- logging facility implementation + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +#include "miracle_log.h" + +static int log_output = log_stderr; +static int threshold = LOG_DEBUG; + +void miracle_log_init( enum log_output method, int new_threshold ) +{ + log_output = method; + threshold = new_threshold; + if (method == log_syslog) + openlog( "miracle", LOG_CONS, LOG_DAEMON ); + +} + +void miracle_log( int priority, char *format, ... ) +{ + va_list list; + va_start( list, format ); + if ( LOG_PRI(priority) <= threshold ) + { + if ( log_output == log_syslog ) + { + vsyslog( priority, format, list ); + } + else + { + char line[1024]; + if ( snprintf( line, 1024, "(%d) %s\n", priority, format ) != 0 ) + vfprintf( stderr, line, list ); + } + } + va_end( list ); +} diff --git a/src/miracle/miracle_log.h b/src/miracle/miracle_log.h new file mode 100644 index 0000000..044343f --- /dev/null +++ b/src/miracle/miracle_log.h @@ -0,0 +1,43 @@ +/* + * miracle_log.h -- logging facility header + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _LOG_H_ +#define _LOG_H_ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum log_output { + log_stderr, + log_syslog +}; + +void miracle_log_init( enum log_output method, int threshold ); +void miracle_log( int priority, char *format, ... ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/miracle/miracle_server.c b/src/miracle/miracle_server.c new file mode 100644 index 0000000..bde78b5 --- /dev/null +++ b/src/miracle/miracle_server.c @@ -0,0 +1,323 @@ +/* + * miracle_server.c + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* System header files */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* Application header files */ +#include "miracle_server.h" +#include "miracle_connection.h" +#include "miracle_local.h" +#include "miracle_log.h" +#include "miracle_commands.h" +#include +#include + +#define VERSION "0.0.1" + +static void miracle_command_received( mlt_listener listener, mlt_properties owner, miracle_server this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( valerie_response ** )args[ 0 ], ( char * )args[ 1 ] ); +} + +static void miracle_doc_received( mlt_listener listener, mlt_properties owner, miracle_server this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( valerie_response ** )args[ 0 ], ( char * )args[ 1 ], ( char * )args[ 2 ] ); +} + +static void miracle_push_received( mlt_listener listener, mlt_properties owner, miracle_server this, void **args ) +{ + if ( listener != NULL ) + listener( owner, this, ( valerie_response ** )args[ 0 ], ( char * )args[ 1 ], ( mlt_service )args[ 2 ] ); +} + +/** Initialise a server structure. +*/ + +miracle_server miracle_server_init( char *id ) +{ + miracle_server server = malloc( sizeof( miracle_server_t ) ); + if ( server != NULL ) + memset( server, 0, sizeof( miracle_server_t ) ); + if ( server != NULL && mlt_properties_init( &server->parent, server ) == 0 ) + { + server->id = id; + server->port = DEFAULT_TCP_PORT; + server->socket = -1; + server->shutdown = 1; + mlt_events_init( &server->parent ); + mlt_events_register( &server->parent, "command-received", ( mlt_transmitter )miracle_command_received ); + mlt_events_register( &server->parent, "doc-received", ( mlt_transmitter )miracle_doc_received ); + mlt_events_register( &server->parent, "push-received", ( mlt_transmitter )miracle_push_received ); + } + return server; +} + +const char *miracle_server_id( miracle_server server ) +{ + return server != NULL && server->id != NULL ? server->id : "miracle"; +} + +void miracle_server_set_config( miracle_server server, char *config ) +{ + if ( server != NULL ) + { + free( server->config ); + server->config = config != NULL ? strdup( config ) : NULL; + } +} + +/** Set the port of the server. +*/ + +void miracle_server_set_port( miracle_server server, int port ) +{ + server->port = port; +} + +void miracle_server_set_proxy( miracle_server server, char *proxy ) +{ + valerie_tokeniser tokeniser = valerie_tokeniser_init( ); + server->proxy = 1; + server->remote_port = DEFAULT_TCP_PORT; + valerie_tokeniser_parse_new( tokeniser, proxy, ":" ); + strcpy( server->remote_server, valerie_tokeniser_get_string( tokeniser, 0 ) ); + if ( valerie_tokeniser_count( tokeniser ) == 2 ) + server->remote_port = atoi( valerie_tokeniser_get_string( tokeniser, 1 ) ); + valerie_tokeniser_close( tokeniser ); +} + +/** Wait for a connection. +*/ + +static int miracle_server_wait_for_connect( miracle_server server ) +{ + struct timeval tv; + fd_set rfds; + + /* Wait for a 1 second. */ + tv.tv_sec = 1; + tv.tv_usec = 0; + + FD_ZERO( &rfds ); + FD_SET( server->socket, &rfds ); + + return select( server->socket + 1, &rfds, NULL, NULL, &tv); +} + +/** Run the server thread. +*/ + +static void *miracle_server_run( void *arg ) +{ + miracle_server server = arg; + pthread_t cmd_parse_info; + connection_t *tmp = NULL; + pthread_attr_t thread_attributes; + socklen_t socksize; + + socksize = sizeof( struct sockaddr ); + + miracle_log( LOG_NOTICE, "%s version %s listening on port %i", server->id, VERSION, server->port ); + + /* Create the initial thread. We want all threads to be created detached so + their resources get freed automatically. (CY: ... hmmph...) */ + pthread_attr_init( &thread_attributes ); + pthread_attr_setdetachstate( &thread_attributes, PTHREAD_CREATE_DETACHED ); + + while ( !server->shutdown ) + { + /* Wait for a new connection. */ + if ( miracle_server_wait_for_connect( server ) ) + { + /* Create a new block of data to hold a copy of the incoming connection for + our server thread. The thread should free this when it terminates. */ + + tmp = (connection_t*) malloc( sizeof(connection_t) ); + tmp->owner = &server->parent; + tmp->parser = server->parser; + tmp->fd = accept( server->socket, (struct sockaddr*) &(tmp->sin), &socksize ); + + /* Pass the connection to a parser thread :-/ */ + if ( tmp->fd != -1 ) + pthread_create( &cmd_parse_info, &thread_attributes, parser_thread, tmp ); + } + } + + miracle_log( LOG_NOTICE, "%s version %s server terminated.", server->id, VERSION ); + + return NULL; +} + +/** Execute the server thread. +*/ + +int miracle_server_execute( miracle_server server ) +{ + int error = 0; + valerie_response response = NULL; + int index = 0; + struct sockaddr_in ServerAddr; + int flag = 1; + + server->shutdown = 0; + + ServerAddr.sin_family = AF_INET; + ServerAddr.sin_port = htons( server->port ); + ServerAddr.sin_addr.s_addr = INADDR_ANY; + + /* Create socket, and bind to port. Listen there. Backlog = 5 + should be sufficient for listen (). */ + server->socket = socket( AF_INET, SOCK_STREAM, 0 ); + + if ( server->socket == -1 ) + { + server->shutdown = 1; + perror( "socket" ); + miracle_log( LOG_ERR, "%s unable to create socket.", server->id ); + return -1; + } + + setsockopt( server->socket, SOL_SOCKET, SO_REUSEADDR, (char *)&flag, sizeof( int ) ); + + if ( bind( server->socket, (struct sockaddr *) &ServerAddr, sizeof (ServerAddr) ) != 0 ) + { + server->shutdown = 1; + perror( "bind" ); + miracle_log( LOG_ERR, "%s unable to bind to port %d.", server->id, server->port ); + return -1; + } + + if ( listen( server->socket, 5 ) != 0 ) + { + server->shutdown = 1; + perror( "listen" ); + miracle_log( LOG_ERR, "%s unable to listen on port %d.", server->id, server->port ); + return -1; + } + + fcntl( server->socket, F_SETFL, O_NONBLOCK ); + + if ( !server->proxy ) + { + miracle_log( LOG_NOTICE, "Starting server on %d.", server->port ); + server->parser = miracle_parser_init_local( ); + } + else + { + miracle_log( LOG_NOTICE, "Starting proxy for %s:%d on %d.", server->remote_server, server->remote_port, server->port ); + server->parser = valerie_parser_init_remote( server->remote_server, server->remote_port ); + } + + response = valerie_parser_connect( server->parser ); + + if ( response != NULL && valerie_response_get_error_code( response ) == 100 ) + { + /* read configuration file */ + if ( response != NULL && !server->proxy && server->config != NULL ) + { + valerie_response_close( response ); + response = valerie_parser_run( server->parser, server->config ); + + if ( valerie_response_count( response ) > 1 ) + { + if ( valerie_response_get_error_code( response ) > 299 ) + miracle_log( LOG_ERR, "Error evaluating server configuration. Processing stopped." ); + for ( index = 0; index < valerie_response_count( response ); index ++ ) + miracle_log( LOG_DEBUG, "%4d: %s", index, valerie_response_get_line( response, index ) ); + } + } + + if ( response != NULL ) + { + int result; + valerie_response_close( response ); + result = pthread_create( &server->thread, NULL, miracle_server_run, server ); + if ( result ) + { + miracle_log( LOG_CRIT, "Failed to launch TCP listener thread" ); + error = -1; + } + } + } + else + { + miracle_log( LOG_ERR, "Error connecting to parser. Processing stopped." ); + server->shutdown = 1; + error = -1; + } + + return error; +} + +/** Fetch a units properties +*/ + +mlt_properties miracle_server_fetch_unit( miracle_server server, int index ) +{ + miracle_unit unit = miracle_get_unit( index ); + return unit != NULL ? unit->properties : NULL; +} + +/** Shutdown the server. +*/ + +void miracle_server_shutdown( miracle_server server ) +{ + if ( server != NULL && !server->shutdown ) + { + server->shutdown = 1; + pthread_join( server->thread, NULL ); + miracle_server_set_config( server, NULL ); + valerie_parser_close( server->parser ); + server->parser = NULL; + close( server->socket ); + } +} + +/** Close the server. +*/ + +void miracle_server_close( miracle_server server ) +{ + if ( server != NULL && mlt_properties_dec_ref( &server->parent ) <= 0 ) + { + mlt_properties_close( &server->parent ); + miracle_server_shutdown( server ); + free( server ); + } +} diff --git a/src/miracle/miracle_server.h b/src/miracle/miracle_server.h new file mode 100644 index 0000000..efc5f70 --- /dev/null +++ b/src/miracle/miracle_server.h @@ -0,0 +1,76 @@ +/* + * miracle_server.h + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _MIRACLE_SERVER_H_ +#define _MIRACLE_SERVER_H_ + +/* System header files */ +#include + +/* Application header files */ +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Servers default port +*/ + +#define DEFAULT_TCP_PORT 5250 + +/** Structure for the server +*/ + +typedef struct +{ + struct mlt_properties_s parent; + char *id; + int port; + int socket; + valerie_parser parser; + pthread_t thread; + int shutdown; + int proxy; + char remote_server[ 50 ]; + int remote_port; + char *config; +} +*miracle_server, miracle_server_t; + +/** API for the server +*/ + +extern miracle_server miracle_server_init( char * ); +extern const char *miracle_server_id( miracle_server ); +extern void miracle_server_set_config( miracle_server, char * ); +extern void miracle_server_set_port( miracle_server, int ); +extern void miracle_server_set_proxy( miracle_server, char * ); +extern int miracle_server_execute( miracle_server ); +extern mlt_properties miracle_server_fetch_unit( miracle_server, int ); +extern void miracle_server_shutdown( miracle_server ); +extern void miracle_server_close( miracle_server ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/miracle/miracle_unit.c b/src/miracle/miracle_unit.c new file mode 100644 index 0000000..611c395 --- /dev/null +++ b/src/miracle/miracle_unit.c @@ -0,0 +1,769 @@ +/* + * miracle_unit.c -- Transmission Unit Implementation + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "miracle_unit.h" +#include "miracle_log.h" +#include "miracle_local.h" + +#include + +/* Forward references */ +static void miracle_unit_status_communicate( miracle_unit ); + +/** Allocate a new DV transmission unit. + + \return A new miracle_unit handle. +*/ + +miracle_unit miracle_unit_init( int index, char *constructor ) +{ + miracle_unit this = NULL; + mlt_consumer consumer = NULL; + + char *id = strdup( constructor ); + char *arg = strchr( id, ':' ); + + if ( arg != NULL ) + *arg ++ = '\0'; + + consumer = mlt_factory_consumer( id, arg ); + + if ( consumer != NULL ) + { + mlt_playlist playlist = mlt_playlist_init( ); + this = calloc( sizeof( miracle_unit_t ), 1 ); + this->properties = mlt_properties_new( ); + mlt_properties_init( this->properties, this ); + mlt_properties_set_int( this->properties, "unit", index ); + mlt_properties_set_int( this->properties, "generation", 0 ); + mlt_properties_set( this->properties, "constructor", constructor ); + mlt_properties_set( this->properties, "id", id ); + mlt_properties_set( this->properties, "arg", arg ); + mlt_properties_set_data( this->properties, "consumer", consumer, 0, ( mlt_destructor )mlt_consumer_close, NULL ); + mlt_properties_set_data( this->properties, "playlist", playlist, 0, ( mlt_destructor )mlt_playlist_close, NULL ); + mlt_consumer_connect( consumer, MLT_PLAYLIST_SERVICE( playlist ) ); + } + + return this; +} + +static char *strip_root( miracle_unit unit, char *file ) +{ + mlt_properties properties = unit->properties; + char *root = mlt_properties_get( properties, "root" ); + if ( file != NULL && root != NULL ) + { + int length = strlen( root ); + if ( root[ length - 1 ] == '/' ) + length --; + if ( !strncmp( file, root, length ) ) + file += length; + } + return file; +} + +/** Communicate the current status to all threads waiting on the notifier. +*/ + +static void miracle_unit_status_communicate( miracle_unit unit ) +{ + if ( unit != NULL ) + { + mlt_properties properties = unit->properties; + char *root_dir = mlt_properties_get( properties, "root" ); + valerie_notifier notifier = mlt_properties_get_data( properties, "notifier", NULL ); + valerie_status_t status; + + if ( root_dir != NULL && notifier != NULL ) + { + if ( miracle_unit_get_status( unit, &status ) == 0 ) + /* if ( !( ( status.status == unit_playing || status.status == unit_paused ) && + strcmp( status.clip, "" ) && + !strcmp( status.tail_clip, "" ) && + status.position == 0 && + status.in == 0 && + status.out == 0 ) ) */ + valerie_notifier_put( notifier, &status ); + } + } +} + +/** Set the notifier info +*/ + +void miracle_unit_set_notifier( miracle_unit this, valerie_notifier notifier, char *root_dir ) +{ + mlt_properties properties = this->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_properties playlist_properties = MLT_PLAYLIST_PROPERTIES( playlist ); + + mlt_properties_set( properties, "root", root_dir ); + mlt_properties_set_data( properties, "notifier", notifier, 0, NULL, NULL ); + mlt_properties_set_data( playlist_properties, "notifier_arg", this, 0, NULL, NULL ); + mlt_properties_set_data( playlist_properties, "notifier", miracle_unit_status_communicate, 0, NULL, NULL ); + + miracle_unit_status_communicate( this ); +} + +/** Create or locate a producer for the file specified. +*/ + +static mlt_producer locate_producer( miracle_unit unit, char *file ) +{ + return mlt_factory_producer( "fezzik", file ); +} + +/** Update the generation count. +*/ + +static void update_generation( miracle_unit unit ) +{ + mlt_properties properties = unit->properties; + int generation = mlt_properties_get_int( properties, "generation" ); + mlt_properties_set_int( properties, "generation", ++ generation ); +} + +/** Wipe all clips on the playlist for this unit. +*/ + +static void clear_unit( miracle_unit unit ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_producer producer = MLT_PLAYLIST_PRODUCER( playlist ); + + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_clear( playlist ); + mlt_producer_seek( producer, 0 ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + + update_generation( unit ); +} + +/** Wipe all but the playing clip from the unit. +*/ + +static void clean_unit( miracle_unit unit ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_playlist_clip_info info; + int current = mlt_playlist_current_clip( playlist ); + mlt_producer producer = MLT_PLAYLIST_PRODUCER( playlist ); + mlt_position position = mlt_producer_frame( producer ); + double speed = mlt_producer_get_speed( producer ); + mlt_playlist_get_clip_info( playlist, &info, current ); + + if ( info.producer != NULL ) + { + mlt_properties_inc_ref( MLT_PRODUCER_PROPERTIES( info.producer ) ); + position -= info.start; + clear_unit( unit ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_append_io( playlist, info.producer, info.frame_in, info.frame_out ); + mlt_producer_seek( producer, position ); + mlt_producer_set_speed( producer, speed ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_producer_close( info.producer ); + } + + update_generation( unit ); +} + +/** Remove everything up to the current clip from the unit. +*/ + +static void wipe_unit( miracle_unit unit ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_playlist_clip_info info; + int current = mlt_playlist_current_clip( playlist ); + mlt_playlist_get_clip_info( playlist, &info, current ); + + if ( info.producer != NULL && info.start > 0 ) + { + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_remove_region( playlist, 0, info.start - 1 ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + } + + update_generation( unit ); +} + +/** Generate a report on all loaded clips. +*/ + +void miracle_unit_report_list( miracle_unit unit, valerie_response response ) +{ + int i; + mlt_properties properties = unit->properties; + int generation = mlt_properties_get_int( properties, "generation" ); + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + + valerie_response_printf( response, 1024, "%d\n", generation ); + + for ( i = 0; i < mlt_playlist_count( playlist ); i ++ ) + { + mlt_playlist_clip_info info; + char *title; + mlt_playlist_get_clip_info( playlist , &info, i ); + title = mlt_properties_get( MLT_PRODUCER_PROPERTIES( info.producer ), "title" ); + if ( title == NULL ) + title = strip_root( unit, info.resource ); + valerie_response_printf( response, 10240, "%d \"%s\" %d %d %d %d %.2f\n", + i, + title, + info.frame_in, + info.frame_out, + info.frame_count, + info.length, + info.fps ); + } + valerie_response_printf( response, 1024, "\n" ); +} + +/** Load a clip into the unit clearing existing play list. + + \todo error handling + \param unit A miracle_unit handle. + \param clip The absolute file name of the clip to load. + \param in The starting frame (-1 for 0) + \param out The ending frame (-1 for maximum) +*/ + +valerie_error_code miracle_unit_load( miracle_unit unit, char *clip, int32_t in, int32_t out, int flush ) +{ + // Now try to create a producer + mlt_producer instance = locate_producer( unit, clip ); + + if ( instance != NULL ) + { + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + int original = mlt_producer_get_playtime( MLT_PLAYLIST_PRODUCER( playlist ) ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_append_io( playlist, instance, in, out ); + mlt_playlist_remove_region( playlist, 0, original ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + miracle_log( LOG_DEBUG, "loaded clip %s", clip ); + update_generation( unit ); + miracle_unit_status_communicate( unit ); + mlt_producer_close( instance ); + return valerie_ok; + } + + return valerie_invalid_file; +} + +valerie_error_code miracle_unit_insert( miracle_unit unit, char *clip, int index, int32_t in, int32_t out ) +{ + mlt_producer instance = locate_producer( unit, clip ); + + if ( instance != NULL ) + { + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + fprintf( stderr, "inserting clip %s before %d\n", clip, index ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_insert( playlist, instance, index, in, out ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + miracle_log( LOG_DEBUG, "inserted clip %s at %d", clip, index ); + update_generation( unit ); + miracle_unit_status_communicate( unit ); + mlt_producer_close( instance ); + return valerie_ok; + } + + return valerie_invalid_file; +} + +valerie_error_code miracle_unit_remove( miracle_unit unit, int index ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_remove( playlist, index ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + miracle_log( LOG_DEBUG, "removed clip at %d", index ); + update_generation( unit ); + miracle_unit_status_communicate( unit ); + return valerie_ok; +} + +valerie_error_code miracle_unit_clean( miracle_unit unit ) +{ + clean_unit( unit ); + miracle_log( LOG_DEBUG, "Cleaned playlist" ); + miracle_unit_status_communicate( unit ); + return valerie_ok; +} + +valerie_error_code miracle_unit_wipe( miracle_unit unit ) +{ + wipe_unit( unit ); + miracle_log( LOG_DEBUG, "Wiped playlist" ); + miracle_unit_status_communicate( unit ); + return valerie_ok; +} + +valerie_error_code miracle_unit_clear( miracle_unit unit ) +{ + mlt_consumer consumer = mlt_properties_get_data( unit->properties, "consumer", NULL ); + clear_unit( unit ); + mlt_consumer_purge( consumer ); + miracle_log( LOG_DEBUG, "Cleared playlist" ); + miracle_unit_status_communicate( unit ); + return valerie_ok; +} + +valerie_error_code miracle_unit_move( miracle_unit unit, int src, int dest ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_move( playlist, src, dest ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + miracle_log( LOG_DEBUG, "moved clip %d to %d", src, dest ); + update_generation( unit ); + miracle_unit_status_communicate( unit ); + return valerie_ok; +} + +/** Add a clip to the unit play list. + + \todo error handling + \param unit A miracle_unit handle. + \param clip The absolute file name of the clip to load. + \param in The starting frame (-1 for 0) + \param out The ending frame (-1 for maximum) +*/ + +valerie_error_code miracle_unit_append( miracle_unit unit, char *clip, int32_t in, int32_t out ) +{ + mlt_producer instance = locate_producer( unit, clip ); + + if ( instance != NULL ) + { + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_append_io( playlist, instance, in, out ); + miracle_log( LOG_DEBUG, "appended clip %s", clip ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + update_generation( unit ); + miracle_unit_status_communicate( unit ); + mlt_producer_close( instance ); + return valerie_ok; + } + + return valerie_invalid_file; +} + +/** Add an mlt_service to the playlist + + \param unit A miracle_unit handle. + \param service the service to add +*/ + +valerie_error_code miracle_unit_append_service( miracle_unit unit, mlt_service service ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + mlt_playlist_append( playlist, ( mlt_producer )service ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + miracle_log( LOG_DEBUG, "appended clip" ); + update_generation( unit ); + miracle_unit_status_communicate( unit ); + return valerie_ok; +} + +/** Start playing the unit. + + \todo error handling + \param unit A miracle_unit handle. + \param speed An integer that specifies the playback rate as a + percentage multiplied by 100. +*/ + +void miracle_unit_play( miracle_unit_t *unit, int speed ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_producer producer = MLT_PLAYLIST_PRODUCER( playlist ); + mlt_consumer consumer = mlt_properties_get_data( unit->properties, "consumer", NULL ); + mlt_producer_set_speed( producer, ( double )speed / 1000 ); + mlt_consumer_start( consumer ); + miracle_unit_status_communicate( unit ); +} + +/** Stop playback. + + Terminates the dv_pump and halts dv1394 transmission. + + \param unit A miracle_unit handle. +*/ + +void miracle_unit_terminate( miracle_unit unit ) +{ + mlt_consumer consumer = mlt_properties_get_data( unit->properties, "consumer", NULL ); + mlt_playlist playlist = mlt_properties_get_data( unit->properties, "playlist", NULL ); + mlt_producer producer = MLT_PLAYLIST_PRODUCER( playlist ); + mlt_producer_set_speed( producer, 0 ); + mlt_consumer_stop( consumer ); + miracle_unit_status_communicate( unit ); +} + +/** Query the status of unit playback. + + \param unit A miracle_unit handle. + \return 1 if the unit is not playing, 0 if playing. +*/ + +int miracle_unit_has_terminated( miracle_unit unit ) +{ + mlt_consumer consumer = mlt_properties_get_data( unit->properties, "consumer", NULL ); + return mlt_consumer_is_stopped( consumer ); +} + +/** Transfer the currently loaded clip to another unit +*/ + +int miracle_unit_transfer( miracle_unit dest_unit, miracle_unit src_unit ) +{ + int i; + mlt_properties dest_properties = dest_unit->properties; + mlt_playlist dest_playlist = mlt_properties_get_data( dest_properties, "playlist", NULL ); + mlt_properties src_properties = src_unit->properties; + mlt_playlist src_playlist = mlt_properties_get_data( src_properties, "playlist", NULL ); + mlt_playlist tmp_playlist = mlt_playlist_init( ); + + for ( i = 0; i < mlt_playlist_count( src_playlist ); i ++ ) + { + mlt_playlist_clip_info info; + mlt_playlist_get_clip_info( src_playlist, &info, i ); + if ( info.producer != NULL ) + mlt_playlist_append_io( tmp_playlist, info.producer, info.frame_in, info.frame_out ); + } + + clear_unit( src_unit ); + + mlt_service_lock( MLT_PLAYLIST_SERVICE( dest_playlist ) ); + + for ( i = 0; i < mlt_playlist_count( tmp_playlist ); i ++ ) + { + mlt_playlist_clip_info info; + mlt_playlist_get_clip_info( tmp_playlist, &info, i ); + if ( info.producer != NULL ) + mlt_playlist_append_io( dest_playlist, info.producer, info.frame_in, info.frame_out ); + } + + mlt_service_unlock( MLT_PLAYLIST_SERVICE( dest_playlist ) ); + + update_generation( dest_unit ); + miracle_unit_status_communicate( dest_unit ); + + mlt_playlist_close( tmp_playlist ); + + return 0; +} + +/** Determine if unit is offline. +*/ + +int miracle_unit_is_offline( miracle_unit unit ) +{ + return 0; +} + +/** Obtain the status for a given unit +*/ + +int miracle_unit_get_status( miracle_unit unit, valerie_status status ) +{ + int error = unit == NULL; + + memset( status, 0, sizeof( valerie_status_t ) ); + + if ( !error ) + { + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_producer producer = MLT_PLAYLIST_PRODUCER( playlist ); + mlt_producer clip = mlt_playlist_current( playlist ); + + mlt_playlist_clip_info info; + int clip_index = mlt_playlist_current_clip( playlist ); + mlt_playlist_get_clip_info( playlist, &info, clip_index ); + + if ( info.resource != NULL && strcmp( info.resource, "" ) ) + { + char *title = mlt_properties_get( MLT_PRODUCER_PROPERTIES( info.producer ), "title" ); + if ( title == NULL ) + title = strip_root( unit, info.resource ); + strncpy( status->clip, title, sizeof( status->clip ) ); + status->speed = (int)( mlt_producer_get_speed( producer ) * 1000.0 ); + status->fps = mlt_producer_get_fps( producer ); + status->in = info.frame_in; + status->out = info.frame_out; + status->position = mlt_producer_frame( clip ); + status->length = mlt_producer_get_length( clip ); + strncpy( status->tail_clip, title, sizeof( status->tail_clip ) ); + status->tail_in = info.frame_in; + status->tail_out = info.frame_out; + status->tail_position = mlt_producer_frame( clip ); + status->tail_length = mlt_producer_get_length( clip ); + status->clip_index = mlt_playlist_current_clip( playlist ); + status->seek_flag = 1; + } + + status->generation = mlt_properties_get_int( properties, "generation" ); + + if ( miracle_unit_has_terminated( unit ) ) + status->status = unit_stopped; + else if ( !strcmp( status->clip, "" ) ) + status->status = unit_not_loaded; + else if ( status->speed == 0 ) + status->status = unit_paused; + else + status->status = unit_playing; + } + else + { + status->status = unit_undefined; + } + + status->unit = mlt_properties_get_int( unit->properties, "unit" ); + + return error; +} + +/** Change position in the playlist. +*/ + +void miracle_unit_change_position( miracle_unit unit, int clip, int32_t position ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_producer producer = MLT_PLAYLIST_PRODUCER( playlist ); + mlt_playlist_clip_info info; + + if ( clip < 0 ) + { + clip = 0; + position = 0; + } + else if ( clip >= mlt_playlist_count( playlist ) ) + { + clip = mlt_playlist_count( playlist ) - 1; + position = INT_MAX; + } + + if ( mlt_playlist_get_clip_info( playlist, &info, clip ) == 0 ) + { + int32_t frame_start = info.start; + int32_t frame_offset = position; + + if ( frame_offset < 0 ) + frame_offset = info.frame_out; + if ( frame_offset < info.frame_in ) + frame_offset = info.frame_in; + if ( frame_offset >= info.frame_out ) + frame_offset = info.frame_out; + + mlt_producer_seek( producer, frame_start + frame_offset - info.frame_in ); + } + + miracle_unit_status_communicate( unit ); +} + +/** Get the index of the current clip. +*/ + +int miracle_unit_get_current_clip( miracle_unit unit ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + int clip_index = mlt_playlist_current_clip( playlist ); + return clip_index; +} + +/** Set a clip's in point +*/ + +int miracle_unit_set_clip_in( miracle_unit unit, int index, int32_t position ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_playlist_clip_info info; + int error = mlt_playlist_get_clip_info( playlist, &info, index ); + + if ( error == 0 ) + { + miracle_unit_play( unit, 0 ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + error = mlt_playlist_resize_clip( playlist, index, position, info.frame_out ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + update_generation( unit ); + miracle_unit_change_position( unit, index, 0 ); + } + + return error; +} + +/** Set a clip's out point. +*/ + +int miracle_unit_set_clip_out( miracle_unit unit, int index, int32_t position ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_playlist_clip_info info; + int error = mlt_playlist_get_clip_info( playlist, &info, index ); + + if ( error == 0 ) + { + miracle_unit_play( unit, 0 ); + mlt_service_lock( MLT_PLAYLIST_SERVICE( playlist ) ); + error = mlt_playlist_resize_clip( playlist, index, info.frame_in, position ); + mlt_service_unlock( MLT_PLAYLIST_SERVICE( playlist ) ); + update_generation( unit ); + miracle_unit_status_communicate( unit ); + miracle_unit_change_position( unit, index, -1 ); + } + + return error; +} + +/** Step by specified position. +*/ + +void miracle_unit_step( miracle_unit unit, int32_t offset ) +{ + mlt_properties properties = unit->properties; + mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL ); + mlt_producer producer = MLT_PLAYLIST_PRODUCER( playlist ); + mlt_position position = mlt_producer_frame( producer ); + mlt_producer_seek( producer, position + offset ); +} + +/** Set the unit's clip mode regarding in and out points. +*/ + +//void miracle_unit_set_mode( miracle_unit unit, dv_player_clip_mode mode ) +//{ + //dv_player player = miracle_unit_get_dv_player( unit ); + //if ( player != NULL ) + //dv_player_set_clip_mode( player, mode ); + //miracle_unit_status_communicate( unit ); +//} + +/** Get the unit's clip mode regarding in and out points. +*/ + +//dv_player_clip_mode miracle_unit_get_mode( miracle_unit unit ) +//{ + //dv_player player = miracle_unit_get_dv_player( unit ); + //return dv_player_get_clip_mode( player ); +//} + +/** Set the unit's clip mode regarding eof handling. +*/ + +//void miracle_unit_set_eof_action( miracle_unit unit, dv_player_eof_action action ) +//{ + //dv_player player = miracle_unit_get_dv_player( unit ); + //dv_player_set_eof_action( player, action ); + //miracle_unit_status_communicate( unit ); +//} + +/** Get the unit's clip mode regarding eof handling. +*/ + +//dv_player_eof_action miracle_unit_get_eof_action( miracle_unit unit ) +//{ + //dv_player player = miracle_unit_get_dv_player( unit ); + //return dv_player_get_eof_action( player ); +//} + +int miracle_unit_set( miracle_unit unit, char *name_value ) +{ + mlt_properties properties = NULL; + + if ( strncmp( name_value, "consumer.", 9 ) ) + { + mlt_playlist playlist = mlt_properties_get_data( unit->properties, "playlist", NULL ); + properties = MLT_PLAYLIST_PROPERTIES( playlist ); + } + else + { + mlt_consumer consumer = mlt_properties_get_data( unit->properties, "consumer", NULL ); + properties = MLT_CONSUMER_PROPERTIES( consumer ); + name_value += 9; + } + + return mlt_properties_parse( properties, name_value ); +} + +char *miracle_unit_get( miracle_unit unit, char *name ) +{ + mlt_playlist playlist = mlt_properties_get_data( unit->properties, "playlist", NULL ); + mlt_properties properties = MLT_PLAYLIST_PROPERTIES( playlist ); + return mlt_properties_get( properties, name ); +} + +/** Release the unit + + \todo error handling + \param unit A miracle_unit handle. +*/ + +void miracle_unit_close( miracle_unit unit ) +{ + if ( unit != NULL ) + { + miracle_log( LOG_DEBUG, "closing unit..." ); + miracle_unit_terminate( unit ); + mlt_properties_close( unit->properties ); + free( unit ); + miracle_log( LOG_DEBUG, "... unit closed." ); + } +} + diff --git a/src/miracle/miracle_unit.h b/src/miracle/miracle_unit.h new file mode 100644 index 0000000..0b185d0 --- /dev/null +++ b/src/miracle/miracle_unit.h @@ -0,0 +1,82 @@ +/* + * dvunit.h -- Transmission Unit Header + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _DV_UNIT_H_ +#define _DV_UNIT_H_ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct +{ + mlt_properties properties; +} +miracle_unit_t, *miracle_unit; + +extern miracle_unit miracle_unit_init( int index, char *arg ); +extern void miracle_unit_report_list( miracle_unit unit, valerie_response response ); +extern void miracle_unit_allow_stdin( miracle_unit unit, int flag ); +extern valerie_error_code miracle_unit_load( miracle_unit unit, char *clip, int32_t in, int32_t out, int flush ); +extern valerie_error_code miracle_unit_insert( miracle_unit unit, char *clip, int index, int32_t in, int32_t out ); +extern valerie_error_code miracle_unit_append( miracle_unit unit, char *clip, int32_t in, int32_t out ); +extern valerie_error_code miracle_unit_append_service( miracle_unit unit, mlt_service service ); +extern valerie_error_code miracle_unit_remove( miracle_unit unit, int index ); +extern valerie_error_code miracle_unit_clean( miracle_unit unit ); +extern valerie_error_code miracle_unit_wipe( miracle_unit unit ); +extern valerie_error_code miracle_unit_clear( miracle_unit unit ); +extern valerie_error_code miracle_unit_move( miracle_unit unit, int src, int dest ); +extern int miracle_unit_transfer( miracle_unit dest_unit, miracle_unit src_unit ); +extern void miracle_unit_play( miracle_unit_t *unit, int speed ); +extern void miracle_unit_terminate( miracle_unit ); +extern int miracle_unit_has_terminated( miracle_unit ); +extern int miracle_unit_get_nodeid( miracle_unit unit ); +extern int miracle_unit_get_channel( miracle_unit unit ); +extern int miracle_unit_is_offline( miracle_unit unit ); +extern void miracle_unit_set_notifier( miracle_unit, valerie_notifier, char * ); +extern int miracle_unit_get_status( miracle_unit, valerie_status ); +extern void miracle_unit_change_position( miracle_unit, int, int32_t position ); +extern void miracle_unit_change_speed( miracle_unit unit, int speed ); +extern int miracle_unit_set_clip_in( miracle_unit unit, int index, int32_t position ); +extern int miracle_unit_set_clip_out( miracle_unit unit, int index, int32_t position ); +//extern void miracle_unit_set_mode( miracle_unit unit, dv_player_clip_mode mode ); +//extern dv_player_clip_mode miracle_unit_get_mode( miracle_unit unit ); +//extern void miracle_unit_set_eof_action( miracle_unit unit, dv_player_eof_action mode ); +//extern dv_player_eof_action miracle_unit_get_eof_action( miracle_unit unit ); +extern void miracle_unit_step( miracle_unit unit, int32_t offset ); +extern void miracle_unit_close( miracle_unit unit ); +extern void miracle_unit_suspend( miracle_unit ); +extern void miracle_unit_restore( miracle_unit ); +extern int miracle_unit_set( miracle_unit, char *name_value ); +extern char * miracle_unit_get( miracle_unit, char *name ); +extern int miracle_unit_get_current_clip( miracle_unit ); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/miracle/miracle_unit_commands.c b/src/miracle/miracle_unit_commands.c new file mode 100644 index 0000000..7cc3b9a --- /dev/null +++ b/src/miracle/miracle_unit_commands.c @@ -0,0 +1,485 @@ +/* + * unit_commands.c + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "miracle_unit.h" +#include "miracle_commands.h" +#include "miracle_log.h" + +int miracle_load( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + char *filename = (char*) cmd_arg->argument; + char fullname[1024]; + int flush = 1; + char *service; + + if ( filename[0] == '!' ) + { + flush = 0; + filename ++; + } + + service = strchr( filename, ':' ); + if ( service != NULL ) + { + service = filename; + filename = strchr( service, ':' ); + *filename ++ = '\0'; + + if ( strlen( cmd_arg->root_dir ) && filename[0] == '/' ) + filename++; + + snprintf( fullname, 1023, "%s:%s%s", service, cmd_arg->root_dir, filename ); + } + else + { + if ( strlen( cmd_arg->root_dir ) && filename[0] == '/' ) + filename++; + + snprintf( fullname, 1023, "%s%s", cmd_arg->root_dir, filename ); + } + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + int32_t in = -1, out = -1; + if ( valerie_tokeniser_count( cmd_arg->tokeniser ) == 5 ) + { + in = atol( valerie_tokeniser_get_string( cmd_arg->tokeniser, 3 ) ); + out = atol( valerie_tokeniser_get_string( cmd_arg->tokeniser, 4 ) ); + } + if ( miracle_unit_load( unit, fullname, in, out, flush ) != valerie_ok ) + return RESPONSE_BAD_FILE; + } + return RESPONSE_SUCCESS; +} + +int miracle_list( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit( cmd_arg->unit ); + + if ( unit != NULL ) + { + miracle_unit_report_list( unit, cmd_arg->response ); + return RESPONSE_SUCCESS; + } + + return RESPONSE_INVALID_UNIT; +} + +static int parse_clip( command_argument cmd_arg, int arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + int clip = miracle_unit_get_current_clip( unit ); + + if ( valerie_tokeniser_count( cmd_arg->tokeniser ) > arg ) + { + char *token = valerie_tokeniser_get_string( cmd_arg->tokeniser, arg ); + if ( token[ 0 ] == '+' ) + clip += atoi( token + 1 ); + else if ( token[ 0 ] == '-' ) + clip -= atoi( token + 1 ); + else + clip = atoi( token ); + } + + return clip; +} + +int miracle_insert( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + char *filename = (char*) cmd_arg->argument; + char fullname[1024]; + + if ( strlen( cmd_arg->root_dir ) && filename[0] == '/' ) + filename++; + + snprintf( fullname, 1023, "%s%s", cmd_arg->root_dir, filename ); + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + long in = -1, out = -1; + int index = parse_clip( cmd_arg, 3 ); + + if ( valerie_tokeniser_count( cmd_arg->tokeniser ) == 6 ) + { + in = atoi( valerie_tokeniser_get_string( cmd_arg->tokeniser, 4 ) ); + out = atoi( valerie_tokeniser_get_string( cmd_arg->tokeniser, 5 ) ); + } + + switch( miracle_unit_insert( unit, fullname, index, in, out ) ) + { + case valerie_ok: + return RESPONSE_SUCCESS; + default: + return RESPONSE_BAD_FILE; + } + } + return RESPONSE_SUCCESS; +} + +int miracle_remove( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + int index = parse_clip( cmd_arg, 2 ); + + if ( miracle_unit_remove( unit, index ) != valerie_ok ) + return RESPONSE_BAD_FILE; + } + return RESPONSE_SUCCESS; +} + +int miracle_clean( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + if ( miracle_unit_clean( unit ) != valerie_ok ) + return RESPONSE_BAD_FILE; + } + return RESPONSE_SUCCESS; +} + +int miracle_wipe( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + if ( miracle_unit_wipe( unit ) != valerie_ok ) + return RESPONSE_BAD_FILE; + } + return RESPONSE_SUCCESS; +} + +int miracle_clear( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + if ( miracle_unit_clear( unit ) != valerie_ok ) + return RESPONSE_BAD_FILE; + } + return RESPONSE_SUCCESS; +} + +int miracle_move( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + + if ( unit != NULL ) + { + if ( valerie_tokeniser_count( cmd_arg->tokeniser ) > 2 ) + { + int src = parse_clip( cmd_arg, 2 ); + int dest = parse_clip( cmd_arg, 3 ); + + if ( miracle_unit_move( unit, src, dest ) != valerie_ok ) + return RESPONSE_BAD_FILE; + } + else + { + return RESPONSE_MISSING_ARG; + } + } + else + { + return RESPONSE_INVALID_UNIT; + } + + return RESPONSE_SUCCESS; +} + +int miracle_append( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + char *filename = (char*) cmd_arg->argument; + char fullname[1024]; + + if ( strlen( cmd_arg->root_dir ) && filename[0] == '/' ) + filename++; + + snprintf( fullname, 1023, "%s%s", cmd_arg->root_dir, filename ); + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + int32_t in = -1, out = -1; + if ( valerie_tokeniser_count( cmd_arg->tokeniser ) == 5 ) + { + in = atol( valerie_tokeniser_get_string( cmd_arg->tokeniser, 3 ) ); + out = atol( valerie_tokeniser_get_string( cmd_arg->tokeniser, 4 ) ); + } + switch ( miracle_unit_append( unit, fullname, in, out ) ) + { + case valerie_ok: + return RESPONSE_SUCCESS; + default: + return RESPONSE_BAD_FILE; + } + } + return RESPONSE_SUCCESS; +} + +int miracle_push( command_argument cmd_arg, mlt_service service ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + if ( unit != NULL && service != NULL ) + if ( miracle_unit_append_service( unit, service ) == valerie_ok ) + return RESPONSE_SUCCESS; + return RESPONSE_BAD_FILE; +} + +int miracle_receive( command_argument cmd_arg, char *doc ) +{ + mlt_producer producer = mlt_factory_producer( "westley-xml", doc ); + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + if ( unit != NULL && producer != NULL ) + { + if ( miracle_unit_append_service( unit, MLT_PRODUCER_SERVICE( producer ) ) == valerie_ok ) + { + mlt_producer_close( producer ); + return RESPONSE_SUCCESS; + } + } + mlt_producer_close( producer ); + return RESPONSE_BAD_FILE; +} + +int miracle_play( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + + if ( unit == NULL ) + { + return RESPONSE_INVALID_UNIT; + } + else + { + int speed = 1000; + if ( valerie_tokeniser_count( cmd_arg->tokeniser ) == 3 ) + speed = atoi( valerie_tokeniser_get_string( cmd_arg->tokeniser, 2 ) ); + miracle_unit_play( unit, speed ); + } + + return RESPONSE_SUCCESS; +} + +int miracle_stop( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + if ( unit == NULL ) + return RESPONSE_INVALID_UNIT; + else + miracle_unit_terminate( unit ); + return RESPONSE_SUCCESS; +} + +int miracle_pause( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + if ( unit == NULL ) + return RESPONSE_INVALID_UNIT; + else + miracle_unit_play( unit, 0 ); + return RESPONSE_SUCCESS; +} + +int miracle_rewind( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + if ( unit == NULL ) + return RESPONSE_INVALID_UNIT; + else + miracle_unit_play( unit, -2000 ); + return RESPONSE_SUCCESS; +} + +int miracle_step( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + { + miracle_unit_play( unit, 0 ); + miracle_unit_step( unit, *(int*) cmd_arg->argument ); + } + return RESPONSE_SUCCESS; +} + +int miracle_goto( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + int clip = parse_clip( cmd_arg, 3 ); + + if (unit == NULL || miracle_unit_is_offline(unit)) + return RESPONSE_INVALID_UNIT; + else + miracle_unit_change_position( unit, clip, *(int*) cmd_arg->argument ); + return RESPONSE_SUCCESS; +} + +int miracle_ff( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + if ( unit == NULL ) + return RESPONSE_INVALID_UNIT; + else + miracle_unit_play( unit, 2000 ); + return RESPONSE_SUCCESS; +} + +int miracle_set_in_point( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + int clip = parse_clip( cmd_arg, 3 ); + + if ( unit == NULL ) + return RESPONSE_INVALID_UNIT; + else + { + int position = *(int *) cmd_arg->argument; + + switch( miracle_unit_set_clip_in( unit, clip, position ) ) + { + case -1: + return RESPONSE_BAD_FILE; + case -2: + return RESPONSE_OUT_OF_RANGE; + } + } + return RESPONSE_SUCCESS; +} + +int miracle_set_out_point( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + int clip = parse_clip( cmd_arg, 3 ); + + if ( unit == NULL ) + return RESPONSE_INVALID_UNIT; + else + { + int position = *(int *) cmd_arg->argument; + + switch( miracle_unit_set_clip_out( unit, clip, position ) ) + { + case -1: + return RESPONSE_BAD_FILE; + case -2: + return RESPONSE_OUT_OF_RANGE; + } + } + + return RESPONSE_SUCCESS; +} + +int miracle_get_unit_status( command_argument cmd_arg ) +{ + valerie_status_t status; + int error = miracle_unit_get_status( miracle_get_unit( cmd_arg->unit ), &status ); + + if ( error == -1 ) + return RESPONSE_INVALID_UNIT; + else + { + char text[ 10240 ]; + valerie_response_printf( cmd_arg->response, sizeof( text ), valerie_status_serialise( &status, text, sizeof( text ) ) ); + return RESPONSE_SUCCESS_1; + } + return 0; +} + + +int miracle_set_unit_property( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + char *name_value = (char*) cmd_arg->argument; + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else + miracle_unit_set( unit, name_value ); + return RESPONSE_SUCCESS; +} + +int miracle_get_unit_property( command_argument cmd_arg ) +{ + miracle_unit unit = miracle_get_unit(cmd_arg->unit); + char *name = (char*) cmd_arg->argument; + char *value = miracle_unit_get( unit, name ); + if (unit == NULL) + return RESPONSE_INVALID_UNIT; + else if ( value != NULL ) + valerie_response_printf( cmd_arg->response, 1024, "%s\n", value ); + return RESPONSE_SUCCESS; +} + + +int miracle_transfer( command_argument cmd_arg ) +{ + miracle_unit src_unit = miracle_get_unit(cmd_arg->unit); + int dest_unit_id = -1; + char *string = (char*) cmd_arg->argument; + if ( string != NULL && ( string[ 0 ] == 'U' || string[ 0 ] == 'u' ) && strlen( string ) > 1 ) + dest_unit_id = atoi( string + 1 ); + + if ( src_unit != NULL && dest_unit_id != -1 ) + { + miracle_unit dest_unit = miracle_get_unit( dest_unit_id ); + if ( dest_unit != NULL && !miracle_unit_is_offline(dest_unit) && dest_unit != src_unit ) + { + miracle_unit_transfer( dest_unit, src_unit ); + return RESPONSE_SUCCESS; + } + } + return RESPONSE_INVALID_UNIT; +} diff --git a/src/miracle/miracle_unit_commands.h b/src/miracle/miracle_unit_commands.h new file mode 100644 index 0000000..67fc269 --- /dev/null +++ b/src/miracle/miracle_unit_commands.h @@ -0,0 +1,61 @@ +/* + * unit_commands.h + * Copyright (C) 2002-2003 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef _UNIT_COMMANDS_H_ +#define _UNIT_COMMANDS_H_ + +#include "miracle_connection.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern response_codes miracle_list( command_argument ); +extern response_codes miracle_load( command_argument ); +extern response_codes miracle_insert( command_argument ); +extern response_codes miracle_remove( command_argument ); +extern response_codes miracle_clean( command_argument ); +extern response_codes miracle_wipe( command_argument ); +extern response_codes miracle_clear( command_argument ); +extern response_codes miracle_move( command_argument ); +extern response_codes miracle_append( command_argument ); +extern response_codes miracle_play( command_argument ); +extern response_codes miracle_stop( command_argument ); +extern response_codes miracle_pause( command_argument ); +extern response_codes miracle_rewind( command_argument ); +extern response_codes miracle_step( command_argument ); +extern response_codes miracle_goto( command_argument ); +extern response_codes miracle_ff( command_argument ); +extern response_codes miracle_set_in_point( command_argument ); +extern response_codes miracle_set_out_point( command_argument ); +extern response_codes miracle_get_unit_status( command_argument ); +extern response_codes miracle_set_unit_property( command_argument ); +extern response_codes miracle_get_unit_property( command_argument ); +extern response_codes miracle_transfer( command_argument ); +extern response_codes miracle_push( command_argument, mlt_service ); +extern response_codes miracle_receive( command_argument, char * ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/modules/Makefile b/src/modules/Makefile new file mode 100644 index 0000000..d43d65e --- /dev/null +++ b/src/modules/Makefile @@ -0,0 +1,32 @@ +include ../../config.mak +include make.inc + +all clean depend: + list='$(SUBDIRS)'; \ + for subdir in $$list; do \ + if [ -f $$subdir/Makefile -a ! -f disable-$$subdir ] ; \ + then $(MAKE) -C $$subdir $@ || exit 1; \ + fi \ + done + +distclean: + rm -f consumers.dat filters.dat producers.dat transitions.dat make.inc; \ + list='$(SUBDIRS)'; \ + for subdir in $$list; do \ + if [ -f $$subdir/Makefile -a ! -f disable-$$subdir ] ; \ + then $(MAKE) -C $$subdir $@ || exit 1; \ + fi \ + done + +install: + install -m 644 producers.dat filters.dat transitions.dat consumers.dat "$(DESTDIR)$(prefix)/lib/mlt/modules" + list='$(SUBDIRS)'; \ + for subdir in $$list; do \ + if [ -f $$subdir/Makefile -a ! -f disable-$$subdir ] ; \ + then $(MAKE) DESTDIR=$(DESTDIR) -C $$subdir $@ || exit 1; \ + fi \ + done + +uninstall: + rm -rf "$(DESTDIR)$(prefix)/lib/mlt/modules" + diff --git a/src/modules/avformat/Makefile b/src/modules/avformat/Makefile new file mode 100644 index 0000000..4e14f3c --- /dev/null +++ b/src/modules/avformat/Makefile @@ -0,0 +1,62 @@ +include ../../../config.mak +include config.mak + +TARGET = ../libmltavformat$(LIBSUF) + +OBJS = factory.o \ + producer_avformat.o \ + consumer_avformat.o \ + filter_avcolour_space.o \ + filter_avresample.o + +ifdef MMX_FLAGS + OBJS += filter_avdeinterlace.o +endif + +CFLAGS+=-I../.. + +LDFLAGS+=-L../../framework + +LDFLAGS+=-lavformat$(AVFORMAT_SUFFIX) -lavcodec$(AVFORMAT_SUFFIX) -lavutil$(AVFORMAT_SUFFIX) $(EXTRA_LIBS) -lmlt + +ifdef SWSCALE + CFLAGS+=-DSWSCALE + LDFLAGS+=-lswscale$(AVFORMAT_SUFFIX) +endif + +ifdef LOCAL_FFMPEG + LOCAL_FFMPEG_OBJS = ffmpeg/libavformat/libavformat$(AVFORMAT_SUFFIX) \ + ffmpeg/libavcodec/libavcodec$(AVFORMAT_SUFFIX) \ + ffmpeg/libavutil/libavutil$(AVFORMAT_SUFFIX) +endif + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(LOCAL_FFMPEG_OBJS): + if [ $(LOCAL_FFMPEG) ] ; then \ + $(MAKE) -C ffmpeg lib ; \ + fi + +$(TARGET): $(OBJS) $(LOCAL_FFMPEG_OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +depend: $(SRCS) + if [ $(LOCAL_FFMPEG) ] ; then $(MAKE) -C ffmpeg dep ; fi + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + if [ $(LOCAL_FFMPEG) ] ; then $(MAKE) -C ffmpeg distclean ; fi + rm -f .depend + +clean: + #if [ $(LOCAL_FFMPEG) ] ; then $(MAKE) -C ffmpeg clean ; fi + rm -f $(OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/avformat/config.mak b/src/modules/avformat/config.mak new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/modules/avformat/config.mak @@ -0,0 +1 @@ + diff --git a/src/modules/avformat/configure b/src/modules/avformat/configure new file mode 100755 index 0000000..8a72d9e --- /dev/null +++ b/src/modules/avformat/configure @@ -0,0 +1,159 @@ +#!/bin/sh + + +if [ "$help" = "1" ] +then + cat << EOF +FFMPEG/avformat options: + + --avformat-svn - Obtain ffmpeg from Subversion + --avformat-svn-extra - Add extra configure options for --avformat-svn + --avformat-shared=path - Link against a shared installation of ffmpeg (default) + --avformat-static=path - Link against a static ffmpeg dev tree + --avformat-ldextra=libs - Provide additional libs to link with + --avformat-suffix=suff - Specify a custom suffix for an ffmpeg shared build + --avformat-swscale - Use ffmpeg libswcale instead of img_convert + +EOF + +else + targetos=$(uname -s) + case $targetos in + Darwin) + export LIBSUF=.dylib + ;; + Linux) + export LIBSUF=.so + ;; + *) + ;; + esac + + bits=$(uname -m) + case $bits in + x86_64) + export LIBDIR=lib64 + ;; + *) + export LIBDIR=lib + ;; + esac + + echo > config.mak + + export static_ffmpeg= + export shared_ffmpeg=`which ffmpeg` + export extra_libs= + export svn_ffmpeg= + export svn_ffmpeg_extra= + export avformat_suffix= + export swscale= + + if [ "$shared_ffmpeg" != "" -a -f "$shared_ffmpeg" ] + then + # Chop ffmpeg + shared_ffmpeg=`dirname $shared_ffmpeg` + # Chop bin + shared_ffmpeg=`dirname $shared_ffmpeg` + fi + + for i in "$@" + do + case $i in + --avformat-static=* ) static_ffmpeg="${i#--avformat-static=}" ;; + --avformat-shared=* ) shared_ffmpeg="${i#--avformat-shared=}" ;; + --avformat-ldextra=* ) extra_libs="${i#--avformat-ldextra=}" ;; + --avformat-svn ) svn_ffmpeg=true ;; + --avformat-svn-extra=* ) svn_ffmpeg_extra="${i#--avformat-svn-extra=}" ;; + --avformat-cvs ) svn_ffmpeg=true ;; + --avformat-suffix=* ) avformat_suffix="${i#--avformat-suffix=}" ;; + --avformat-swscale ) swscale=true ;; + --avformat-swscaler ) swscale=true ;; + esac + done + + if [ "$svn_ffmpeg" != "" ] + then + if [ "$gpl" = "true" ] + then + enable_gpl="--enable-gpl" + if [ "$swscale" != "" ] + then + enable_swscale="--enable-swscaler" + echo "SWSCALE=1" >> config.mak + fi + elif [ "$swscale" != "" ] + then + echo + echo "ERROR ERROR ERROR ERROR ERROR ERROR" + echo "--enable-gpl is required to use --avformat-swscale with --avformat-svn!" + echo + exit + fi + if [ ! -d "ffmpeg" ] + then + echo + echo "Checking out ffmpeg/avformat - no password required" + echo + svn checkout svn://svn.mplayerhq.hu/ffmpeg/trunk ffmpeg + fi + [ -d "ffmpeg" ] && ( cd ffmpeg ; ./configure $enable_gpl $enable_swscale $svn_ffmpeg_extra ) + #[ ! -f "ffmpeg/ffmpeg.patch" ] && ( cd ffmpeg ; cp ../ffmpeg.patch . ; patch -p0 < ffmpeg.patch ) + echo "CFLAGS+=-I`pwd`/ffmpeg/libavformat -I`pwd`/ffmpeg/libavcodec -I`pwd`/ffmpeg/libavutil -I`pwd`/ffmpeg/libswscale" >> config.mak + echo "LDFLAGS+=-L`pwd`/ffmpeg/libavformat -L`pwd`/ffmpeg/libavcodec -L`pwd`/ffmpeg/libavutil -L`pwd`/ffmpeg/libswscale" >> config.mak + [ $targetos = "Darwin" ] && + echo "LDFLAGS+=-single_module" >> config.mak + echo "LOCAL_FFMPEG=1" >> config.mak + extra_libs="$extra_libs -lz" + elif [ "$static_ffmpeg" != "" ] + then + if [ -d "$static_ffmpeg" ] + then + echo "CFLAGS+=-I$static_ffmpeg/libavformat -I$static_ffmpeg/libavcodec -I$static_ffmpeg/libavutil" >> config.mak + echo "LDFLAGS+=-L$static_ffmpeg/libavformat -L$static_ffmpeg/libavcodec -L$static_ffmpeg/libavutil" >> config.mak + [ $targetos = "Darwin" ] && + echo "LDFLAGS+=-single_module" >> config.mak + if [ "$swscale" != "" ] + then + echo "CFLAGS+=-I$static_ffmpeg/libswscale" >> config.mak + echo "LDFLAGS+=-L$static_ffmpeg/libswscale" >> config.mak + echo "SWSCALE=1" >> config.mak + fi + else + echo "avformat: Invalid path specified: $static_ffmpeg" + touch ../disable-avformat + echo 0 + fi + else + if [ -d "$shared_ffmpeg/include/ffmpeg" -a -e "$shared_ffmpeg/$LIBDIR/libavformat$avformat_suffix$LIBSUF" ] + then + echo "CFLAGS+=-I$shared_ffmpeg/include/ffmpeg " >> config.mak + echo "LDFLAGS+=-L$shared_ffmpeg/$LIBDIR" >> config.mak + [ "$swscale" != "" ] && echo "SWSCALE=1" >> config.mak + else + echo "avformat: No build environment found. " + echo " Try configuring mlt with --avformat-svn." + touch ../disable-avformat + exit 0 + fi + fi + + echo "EXTRA_LIBS=$extra_libs" >> config.mak + echo "AVFORMAT_SUFFIX=$avformat_suffix" >> config.mak + +cat << EOF >> ../producers.dat +avformat libmltavformat$LIBSUF +EOF + +cat << EOF >> ../filters.dat +avdeinterlace libmltavformat$LIBSUF +avresample libmltavformat$LIBSUF +avcolour_space libmltavformat$LIBSUF +EOF + +cat << EOF >> ../consumers.dat +avformat libmltavformat$LIBSUF +EOF + +fi + diff --git a/src/modules/avformat/consumer_avformat.c b/src/modules/avformat/consumer_avformat.c new file mode 100644 index 0000000..d17f3f3 --- /dev/null +++ b/src/modules/avformat/consumer_avformat.c @@ -0,0 +1,1192 @@ +/* + * consumer_avformat.c -- an encoder based on avformat + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// Local header files +#include "consumer_avformat.h" + +// mlt Header files +#include + +// System header files +#include +#include +#include +#include +#include +#include +#include + +// avformat header files +#include +#ifdef SWSCALE +#include +#endif + +// +// This structure should be extended and made globally available in mlt +// + +typedef struct +{ + int16_t *buffer; + int size; + int used; + double time; + int frequency; + int channels; +} +*sample_fifo, sample_fifo_s; + +sample_fifo sample_fifo_init( int frequency, int channels ) +{ + sample_fifo this = calloc( 1, sizeof( sample_fifo_s ) ); + this->frequency = frequency; + this->channels = channels; + return this; +} + +// sample_fifo_clear and check are temporarily aborted (not working as intended) + +void sample_fifo_clear( sample_fifo this, double time ) +{ + int words = ( float )( time - this->time ) * this->frequency * this->channels; + if ( ( int )( ( float )time * 100 ) < ( int )( ( float )this->time * 100 ) && this->used > words && words > 0 ) + { + memmove( this->buffer, &this->buffer[ words ], ( this->used - words ) * sizeof( int16_t ) ); + this->used -= words; + this->time = time; + } + else if ( ( int )( ( float )time * 100 ) != ( int )( ( float )this->time * 100 ) ) + { + this->used = 0; + this->time = time; + } +} + +void sample_fifo_check( sample_fifo this, double time ) +{ + if ( this->used == 0 ) + { + if ( ( int )( ( float )time * 100 ) < ( int )( ( float )this->time * 100 ) ) + this->time = time; + } +} + +void sample_fifo_append( sample_fifo this, int16_t *samples, int count ) +{ + if ( ( this->size - this->used ) < count ) + { + this->size += count * 5; + this->buffer = realloc( this->buffer, this->size * sizeof( int16_t ) ); + } + + memcpy( &this->buffer[ this->used ], samples, count * sizeof( int16_t ) ); + this->used += count; +} + +int sample_fifo_used( sample_fifo this ) +{ + return this->used; +} + +int sample_fifo_fetch( sample_fifo this, int16_t *samples, int count ) +{ + if ( count > this->used ) + count = this->used; + + memcpy( samples, this->buffer, count * sizeof( int16_t ) ); + this->used -= count; + memmove( this->buffer, &this->buffer[ count ], this->used * sizeof( int16_t ) ); + + this->time += ( double )count / this->channels / this->frequency; + + return count; +} + +void sample_fifo_close( sample_fifo this ) +{ + free( this->buffer ); + free( this ); +} + +// Forward references. +static int consumer_start( mlt_consumer this ); +static int consumer_stop( mlt_consumer this ); +static int consumer_is_stopped( mlt_consumer this ); +static void *consumer_thread( void *arg ); +static void consumer_close( mlt_consumer this ); + +/** Initialise the dv consumer. +*/ + +mlt_consumer consumer_avformat_init( char *arg ) +{ + // Allocate the consumer + mlt_consumer this = mlt_consumer_new( ); + + // If memory allocated and initialises without error + if ( this != NULL ) + { + // Get properties from the consumer + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Assign close callback + this->close = consumer_close; + + // Interpret the argument + if ( arg != NULL ) + mlt_properties_set( properties, "target", arg ); + + // sample and frame queue + mlt_properties_set_data( properties, "frame_queue", mlt_deque_init( ), 0, ( mlt_destructor )mlt_deque_close, NULL ); + + // Set avformat defaults (all lifted from ffmpeg.c) + mlt_properties_set_int( properties, "audio_bit_rate", 128000 ); + mlt_properties_set_int( properties, "video_bit_rate", 200 * 1000 ); + mlt_properties_set_int( properties, "video_bit_rate_tolerance", 4000 * 1000 ); + mlt_properties_set_int( properties, "gop_size", 12 ); + mlt_properties_set_int( properties, "b_frames", 0 ); + mlt_properties_set_int( properties, "mb_decision", FF_MB_DECISION_SIMPLE ); + mlt_properties_set_double( properties, "qscale", 0 ); + mlt_properties_set_int( properties, "me_method", ME_EPZS ); + mlt_properties_set_int( properties, "mb_cmp", FF_CMP_SAD ); + mlt_properties_set_int( properties, "ildct_cmp", FF_CMP_VSAD ); + mlt_properties_set_int( properties, "sub_cmp", FF_CMP_SAD ); + mlt_properties_set_int( properties, "cmp", FF_CMP_SAD ); + mlt_properties_set_int( properties, "pre_cmp", FF_CMP_SAD ); + mlt_properties_set_int( properties, "pre_me", 0 ); + mlt_properties_set_double( properties, "lumi_mask", 0 ); + mlt_properties_set_double( properties, "dark_mask", 0 ); + mlt_properties_set_double( properties, "scplx_mask", 0 ); + mlt_properties_set_double( properties, "tcplx_mask", 0 ); + mlt_properties_set_double( properties, "p_mask", 0 ); + mlt_properties_set_int( properties, "qns", 0 ); + mlt_properties_set_int( properties, "video_qmin", 2 ); + mlt_properties_set_int( properties, "video_qmax", 31 ); + mlt_properties_set_int( properties, "video_lmin", 2*FF_QP2LAMBDA ); + mlt_properties_set_int( properties, "video_lmax", 31*FF_QP2LAMBDA ); + mlt_properties_set_int( properties, "video_mb_qmin", 2 ); + mlt_properties_set_int( properties, "video_mb_qmax", 31 ); + mlt_properties_set_int( properties, "video_qdiff", 3 ); + mlt_properties_set_double( properties, "video_qblur", 0.5 ); + mlt_properties_set_double( properties, "video_qcomp", 0.5 ); + mlt_properties_set_int( properties, "video_rc_max_rate", 0 ); + mlt_properties_set_int( properties, "video_rc_min_rate", 0 ); + mlt_properties_set_int( properties, "video_rc_buffer_size", 0 ); + mlt_properties_set_double( properties, "video_rc_buffer_aggressivity", 1.0 ); + mlt_properties_set_double( properties, "video_rc_initial_cplx", 0 ); + mlt_properties_set_double( properties, "video_i_qfactor", -0.8 ); + mlt_properties_set_double( properties, "video_b_qfactor", 1.25 ); + mlt_properties_set_double( properties, "video_i_qoffset", 0 ); + mlt_properties_set_double( properties, "video_b_qoffset", 1.25 ); + mlt_properties_set_int( properties, "video_intra_quant_bias", FF_DEFAULT_QUANT_BIAS ); + mlt_properties_set_int( properties, "video_inter_quant_bias", FF_DEFAULT_QUANT_BIAS ); + mlt_properties_set_int( properties, "dct_algo", 0 ); + mlt_properties_set_int( properties, "idct_algo", 0 ); + mlt_properties_set_int( properties, "me_threshold", 0 ); + mlt_properties_set_int( properties, "mb_threshold", 0 ); + mlt_properties_set_int( properties, "intra_dc_precision", 0 ); + mlt_properties_set_int( properties, "strict", 0 ); + mlt_properties_set_int( properties, "error_rate", 0 ); + mlt_properties_set_int( properties, "noise_reduction", 0 ); + mlt_properties_set_int( properties, "sc_threshold", 0 ); + mlt_properties_set_int( properties, "me_range", 0 ); + mlt_properties_set_int( properties, "coder", 0 ); + mlt_properties_set_int( properties, "context", 0 ); + mlt_properties_set_int( properties, "predictor", 0 ); + mlt_properties_set_int( properties, "ildct", 0 ); + mlt_properties_set_int( properties, "ilme", 0 ); + + // Ensure termination at end of the stream + mlt_properties_set_int( properties, "terminate_on_pause", 1 ); + + // Set up start/stop/terminated callbacks + this->start = consumer_start; + this->stop = consumer_stop; + this->is_stopped = consumer_is_stopped; + } + + // Return this + return this; +} + +/** Start the consumer. +*/ + +static int consumer_start( mlt_consumer this ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Check that we're not already running + if ( !mlt_properties_get_int( properties, "running" ) ) + { + // Allocate a thread + pthread_t *thread = calloc( 1, sizeof( pthread_t ) ); + + // Get the width and height + int width = mlt_properties_get_int( properties, "width" ); + int height = mlt_properties_get_int( properties, "height" ); + + // Obtain the size property + char *size = mlt_properties_get( properties, "size" ); + + // Interpret it + if ( size != NULL ) + { + int tw, th; + if ( sscanf( size, "%dx%d", &tw, &th ) == 2 && tw > 0 && th > 0 ) + { + width = tw; + height = th; + } + else + { + fprintf( stderr, "consumer_avformat: Invalid size property %s - ignoring.\n", size ); + } + } + + // Now ensure we honour the multiple of two requested by libavformat + mlt_properties_set_int( properties, "width", ( width / 2 ) * 2 ); + mlt_properties_set_int( properties, "height", ( height / 2 ) * 2 ); + + // Assign the thread to properties + mlt_properties_set_data( properties, "thread", thread, sizeof( pthread_t ), free, NULL ); + + // Set the running state + mlt_properties_set_int( properties, "running", 1 ); + + // Create the thread + pthread_create( thread, NULL, consumer_thread, this ); + } + return 0; +} + +/** Stop the consumer. +*/ + +static int consumer_stop( mlt_consumer this ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Check that we're running + if ( mlt_properties_get_int( properties, "running" ) ) + { + // Get the thread + pthread_t *thread = mlt_properties_get_data( properties, "thread", NULL ); + + // Stop the thread + mlt_properties_set_int( properties, "running", 0 ); + + // Wait for termination + pthread_join( *thread, NULL ); + } + + return 0; +} + +/** Determine if the consumer is stopped. +*/ + +static int consumer_is_stopped( mlt_consumer this ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + return !mlt_properties_get_int( properties, "running" ); +} + +/** Add an audio output stream +*/ + +static AVStream *add_audio_stream( mlt_consumer this, AVFormatContext *oc, int codec_id ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Create a new stream + AVStream *st = av_new_stream( oc, 1 ); + + // If created, then initialise from properties + if ( st != NULL ) + { + AVCodecContext *c = st->codec; + c->codec_id = codec_id; + c->codec_type = CODEC_TYPE_AUDIO; + + // Put sample parameters + c->bit_rate = mlt_properties_get_int( properties, "audio_bit_rate" ); + c->sample_rate = mlt_properties_get_int( properties, "frequency" ); + c->channels = mlt_properties_get_int( properties, "channels" ); + + if (oc->oformat->flags & AVFMT_GLOBALHEADER) + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + + // Allow the user to override the audio fourcc + if ( mlt_properties_get( properties, "afourcc" ) ) + { + char *tail = NULL; + char *arg = mlt_properties_get( properties, "afourcc" ); + int tag = strtol( arg, &tail, 0); + if( !tail || *tail ) + tag = arg[ 0 ] + ( arg[ 1 ] << 8 ) + ( arg[ 2 ] << 16 ) + ( arg[ 3 ] << 24 ); + c->codec_tag = tag; + } + } + else + { + fprintf( stderr, "Could not allocate a stream for audio\n" ); + } + + return st; +} + +static int open_audio( AVFormatContext *oc, AVStream *st, int audio_outbuf_size ) +{ + // We will return the audio input size from here + int audio_input_frame_size = 0; + + // Get the context + AVCodecContext *c = st->codec; + + // Find the encoder + AVCodec *codec = avcodec_find_encoder( c->codec_id ); + + // Continue if codec found and we can open it + if ( codec != NULL && avcodec_open(c, codec) >= 0 ) + { + // ugly hack for PCM codecs (will be removed ASAP with new PCM + // support to compute the input frame size in samples + if ( c->frame_size <= 1 ) + { + audio_input_frame_size = audio_outbuf_size / c->channels; + switch(st->codec->codec_id) + { + case CODEC_ID_PCM_S16LE: + case CODEC_ID_PCM_S16BE: + case CODEC_ID_PCM_U16LE: + case CODEC_ID_PCM_U16BE: + audio_input_frame_size >>= 1; + break; + default: + break; + } + } + else + { + audio_input_frame_size = c->frame_size; + } + + // Some formats want stream headers to be seperate (hmm) + if( !strcmp( oc->oformat->name, "mp4" ) || + !strcmp( oc->oformat->name, "mov" ) || + !strcmp( oc->oformat->name, "3gp" ) ) + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + } + else + { + fprintf( stderr, "Unable to encode audio - disabling audio output.\n" ); + } + + return audio_input_frame_size; +} + +static void close_audio( AVFormatContext *oc, AVStream *st ) +{ + avcodec_close( st->codec ); +} + +/** Add a video output stream +*/ + +static AVStream *add_video_stream( mlt_consumer this, AVFormatContext *oc, int codec_id ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Create a new stream + AVStream *st = av_new_stream( oc, 0 ); + + if ( st != NULL ) + { + char *pix_fmt = mlt_properties_get( properties, "pix_fmt" ); + double ar = mlt_properties_get_double( properties, "display_ratio" ); + AVCodecContext *c = st->codec; + c->codec_id = codec_id; + c->codec_type = CODEC_TYPE_VIDEO; + + // put sample parameters + c->bit_rate = mlt_properties_get_int( properties, "video_bit_rate" ); + c->bit_rate_tolerance = mlt_properties_get_int( properties, "video_bit_rate_tolerance" ); + c->width = mlt_properties_get_int( properties, "width" ); + c->height = mlt_properties_get_int( properties, "height" ); + c->time_base.num = mlt_properties_get_int( properties, "frame_rate_den" ); + c->time_base.den = mlt_properties_get_int( properties, "frame_rate_num" ); + c->gop_size = mlt_properties_get_int( properties, "gop_size" ); + c->pix_fmt = pix_fmt ? avcodec_get_pix_fmt( pix_fmt ) : PIX_FMT_YUV420P; + + if ( mlt_properties_get_int( properties, "b_frames" ) ) + { + c->max_b_frames = mlt_properties_get_int( properties, "b_frames" ); + c->b_frame_strategy = 0; + c->b_quant_factor = 2.0; + } + + c->mb_decision = mlt_properties_get_int( properties, "mb_decision" ); + c->sample_aspect_ratio = av_d2q( ar * c->height / c->width , 255); + c->mb_cmp = mlt_properties_get_int( properties, "mb_cmp" ); + c->ildct_cmp = mlt_properties_get_int( properties, "ildct_cmp" ); + c->me_sub_cmp = mlt_properties_get_int( properties, "sub_cmp" ); + c->me_cmp = mlt_properties_get_int( properties, "cmp" ); + c->me_pre_cmp = mlt_properties_get_int( properties, "pre_cmp" ); + c->pre_me = mlt_properties_get_int( properties, "pre_me" ); + c->lumi_masking = mlt_properties_get_double( properties, "lumi_mask" ); + c->dark_masking = mlt_properties_get_double( properties, "dark_mask" ); + c->spatial_cplx_masking = mlt_properties_get_double( properties, "scplx_mask" ); + c->temporal_cplx_masking = mlt_properties_get_double( properties, "tcplx_mask" ); + c->p_masking = mlt_properties_get_double( properties, "p_mask" ); + c->quantizer_noise_shaping= mlt_properties_get_int( properties, "qns" ); + c->qmin = mlt_properties_get_int( properties, "video_qmin" ); + c->qmax = mlt_properties_get_int( properties, "video_qmax" ); + c->lmin = mlt_properties_get_int( properties, "video_lmin" ); + c->lmax = mlt_properties_get_int( properties, "video_lmax" ); + c->mb_qmin = mlt_properties_get_int( properties, "video_mb_qmin" ); + c->mb_qmax = mlt_properties_get_int( properties, "video_mb_qmax" ); + c->max_qdiff = mlt_properties_get_int( properties, "video_qdiff" ); + c->qblur = mlt_properties_get_double( properties, "video_qblur" ); + c->qcompress = mlt_properties_get_double( properties, "video_qcomp" ); + + if ( mlt_properties_get_double( properties, "qscale" ) > 0 ) + { + c->flags |= CODEC_FLAG_QSCALE; + st->quality = FF_QP2LAMBDA * mlt_properties_get_double( properties, "qscale" ); + } + + // Allow the user to override the video fourcc + if ( mlt_properties_get( properties, "vfourcc" ) ) + { + char *tail = NULL; + const char *arg = mlt_properties_get( properties, "vfourcc" ); + int tag = strtol( arg, &tail, 0); + if( !tail || *tail ) + tag = arg[ 0 ] + ( arg[ 1 ] << 8 ) + ( arg[ 2 ] << 16 ) + ( arg[ 3 ] << 24 ); + c->codec_tag = tag; + } + + // Some formats want stream headers to be seperate + if ( oc->oformat->flags & AVFMT_GLOBALHEADER ) + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + + c->rc_max_rate = mlt_properties_get_int( properties, "video_rc_max_rate" ); + c->rc_min_rate = mlt_properties_get_int( properties, "video_rc_min_rate" ); + c->rc_buffer_size = mlt_properties_get_int( properties, "video_rc_buffer_size" ); + c->rc_initial_buffer_occupancy = c->rc_buffer_size*3/4; + c->rc_buffer_aggressivity= mlt_properties_get_double( properties, "video_rc_buffer_aggressivity" ); + c->rc_initial_cplx= mlt_properties_get_double( properties, "video_rc_initial_cplx" ); + c->i_quant_factor = mlt_properties_get_double( properties, "video_i_qfactor" ); + c->b_quant_factor = mlt_properties_get_double( properties, "video_b_qfactor" ); + c->i_quant_offset = mlt_properties_get_double( properties, "video_i_qoffset" ); + c->b_quant_offset = mlt_properties_get_double( properties, "video_b_qoffset" ); + c->intra_quant_bias = mlt_properties_get_int( properties, "video_intra_quant_bias" ); + c->inter_quant_bias = mlt_properties_get_int( properties, "video_inter_quant_bias" ); + c->dct_algo = mlt_properties_get_int( properties, "dct_algo" ); + c->idct_algo = mlt_properties_get_int( properties, "idct_algo" ); + c->me_threshold= mlt_properties_get_int( properties, "me_threshold" ); + c->mb_threshold= mlt_properties_get_int( properties, "mb_threshold" ); + c->intra_dc_precision= mlt_properties_get_int( properties, "intra_dc_precision" ); + c->strict_std_compliance = mlt_properties_get_int( properties, "strict" ); + c->error_rate = mlt_properties_get_int( properties, "error_rate" ); + c->noise_reduction= mlt_properties_get_int( properties, "noise_reduction" ); + c->scenechange_threshold= mlt_properties_get_int( properties, "sc_threshold" ); + c->me_range = mlt_properties_get_int( properties, "me_range" ); + c->coder_type= mlt_properties_get_int( properties, "coder" ); + c->context_model= mlt_properties_get_int( properties, "context" ); + c->prediction_method= mlt_properties_get_int( properties, "predictor" ); + c->me_method = mlt_properties_get_int( properties, "me_method" ); + if ( mlt_properties_get_int( properties, "progressive" ) == 0 && + mlt_properties_get_int( properties, "deinterlace" ) == 0 ) + { + if ( mlt_properties_get_int( properties, "ildct" ) ) + c->flags |= CODEC_FLAG_INTERLACED_DCT; + if ( mlt_properties_get_int( properties, "ilme" ) ) + c->flags |= CODEC_FLAG_INTERLACED_ME; + } + } + else + { + fprintf( stderr, "Could not allocate a stream for video\n" ); + } + + return st; +} + +static AVFrame *alloc_picture( int pix_fmt, int width, int height ) +{ + // Allocate a frame + AVFrame *picture = avcodec_alloc_frame(); + + // Determine size of the + int size = avpicture_get_size(pix_fmt, width, height); + + // Allocate the picture buf + uint8_t *picture_buf = av_malloc(size); + + // If we have both, then fill the image + if ( picture != NULL && picture_buf != NULL ) + { + // Fill the frame with the allocated buffer + avpicture_fill( (AVPicture *)picture, picture_buf, pix_fmt, width, height); + } + else + { + // Something failed - clean up what we can + av_free( picture ); + av_free( picture_buf ); + picture = NULL; + } + + return picture; +} + +static int open_video(AVFormatContext *oc, AVStream *st) +{ + // Get the codec + AVCodecContext *video_enc = st->codec; + + // find the video encoder + AVCodec *codec = avcodec_find_encoder( video_enc->codec_id ); + + if( codec && codec->pix_fmts ) + { + const enum PixelFormat *p = codec->pix_fmts; + for( ; *p!=-1; p++ ) + { + if( *p == video_enc->pix_fmt ) + break; + } + if( *p == -1 ) + video_enc->pix_fmt = codec->pix_fmts[ 0 ]; + } + + // Open the codec safely + return codec != NULL && avcodec_open( video_enc, codec ) >= 0; +} + +void close_video(AVFormatContext *oc, AVStream *st) +{ + avcodec_close(st->codec); +} + +static inline long time_difference( struct timeval *time1 ) +{ + struct timeval time2; + gettimeofday( &time2, NULL ); + return time2.tv_sec * 1000000 + time2.tv_usec - time1->tv_sec * 1000000 - time1->tv_usec; +} + +/** The main thread - the argument is simply the consumer. +*/ + +static void *consumer_thread( void *arg ) +{ + // Map the argument to the object + mlt_consumer this = arg; + + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Get the terminate on pause property + int terminate_on_pause = mlt_properties_get_int( properties, "terminate_on_pause" ); + int terminated = 0; + + // Determine if feed is slow (for realtime stuff) + int real_time_output = mlt_properties_get_int( properties, "real_time" ); + + // Time structures + struct timeval ante; + + // Get the frame rate + int fps = mlt_properties_get_double( properties, "fps" ); + + // Get width and height + int width = mlt_properties_get_int( properties, "width" ); + int height = mlt_properties_get_int( properties, "height" ); + int img_width = width; + int img_height = height; + + // Get default audio properties + mlt_audio_format aud_fmt = mlt_audio_pcm; + int channels = mlt_properties_get_int( properties, "channels" ); + int frequency = mlt_properties_get_int( properties, "frequency" ); + int16_t *pcm = NULL; + int samples = 0; + + // AVFormat audio buffer and frame size + int audio_outbuf_size = 10000; + uint8_t *audio_outbuf = av_malloc( audio_outbuf_size ); + int audio_input_frame_size = 0; + + // AVFormat video buffer and frame count + int frame_count = 0; + int video_outbuf_size = ( 1024 * 1024 ); + uint8_t *video_outbuf = av_malloc( video_outbuf_size ); + + // Used for the frame properties + mlt_frame frame = NULL; + mlt_properties frame_properties = NULL; + + // Get the queues + mlt_deque queue = mlt_properties_get_data( properties, "frame_queue", NULL ); + sample_fifo fifo = mlt_properties_get_data( properties, "sample_fifo", NULL ); + + // Need two av pictures for converting + AVFrame *output = NULL; + AVFrame *input = alloc_picture( PIX_FMT_YUV422, width, height ); + + // For receiving images from an mlt_frame + uint8_t *image; + mlt_image_format img_fmt = mlt_image_yuv422; + + // For receiving audio samples back from the fifo + int16_t *buffer = av_malloc( 48000 * 2 ); + int count = 0; + + // Allocate the context + AVFormatContext *oc = av_alloc_format_context( ); + + // Streams + AVStream *audio_st = NULL; + AVStream *video_st = NULL; + + // Time stamps + double audio_pts = 0; + double video_pts = 0; + + // Loop variable + int i; + + // Frames despatched + long int frames = 0; + long int total_time = 0; + + // Determine the format + AVOutputFormat *fmt = NULL; + char *filename = mlt_properties_get( properties, "target" ); + char *format = mlt_properties_get( properties, "format" ); + char *vcodec = mlt_properties_get( properties, "vcodec" ); + char *acodec = mlt_properties_get( properties, "acodec" ); + + // Used to store and override codec ids + int audio_codec_id; + int video_codec_id; + + // Check for user selected format first + if ( format != NULL ) + fmt = guess_format( format, NULL, NULL ); + + // Otherwise check on the filename + if ( fmt == NULL && filename != NULL ) + fmt = guess_format( NULL, filename, NULL ); + + // Otherwise default to mpeg + if ( fmt == NULL ) + fmt = guess_format( "mpeg", NULL, NULL ); + + // We need a filename - default to stdout? + if ( filename == NULL || !strcmp( filename, "" ) ) + filename = "pipe:"; + + // Get the codec ids selected + audio_codec_id = fmt->audio_codec; + video_codec_id = fmt->video_codec; + + // Check for audio codec overides + if ( acodec != NULL ) + { + AVCodec *p = first_avcodec; + while( p != NULL ) + { + if ( !strcmp( p->name, acodec ) && p->type == CODEC_TYPE_AUDIO ) + break; + p = p->next; + } + if ( p != NULL ) + audio_codec_id = p->id; + else + fprintf( stderr, "consumer_avcodec: audio codec %s unrecognised - ignoring\n", acodec ); + } + + // Check for video codec overides + if ( vcodec != NULL ) + { + AVCodec *p = first_avcodec; + while( p != NULL ) + { + if ( !strcmp( p->name, vcodec ) && p->type == CODEC_TYPE_VIDEO ) + break; + p = p->next; + } + if ( p != NULL ) + video_codec_id = p->id; + else + fprintf( stderr, "consumer_avcodec: video codec %s unrecognised - ignoring\n", vcodec ); + } + + // Update the output context + + // Write metadata + char *tmp = NULL; + int metavalue; + + tmp = mlt_properties_get( properties, "meta.attr.title.markup"); + if (tmp != NULL) snprintf( oc->title, sizeof(oc->title), "%s", tmp ); + + tmp = mlt_properties_get( properties, "meta.attr.comment.markup"); + if (tmp != NULL) snprintf( oc->comment, sizeof(oc->comment), "%s", tmp ); + + tmp = mlt_properties_get( properties, "meta.attr.author.markup"); + if (tmp != NULL) snprintf( oc->author, sizeof(oc->author), "%s", tmp ); + + tmp = mlt_properties_get( properties, "meta.attr.copyright.markup"); + if (tmp != NULL) snprintf( oc->copyright, sizeof(oc->copyright), "%s", tmp ); + + tmp = mlt_properties_get( properties, "meta.attr.album.markup"); + if (tmp != NULL) snprintf( oc->album, sizeof(oc->album), "%s", tmp ); + + metavalue = mlt_properties_get_int( properties, "meta.attr.year.markup"); + if (metavalue != 0) oc->year = metavalue; + + metavalue = mlt_properties_get_int( properties, "meta.attr.track.markup"); + if (metavalue != 0) oc->track = metavalue; + + oc->oformat = fmt; + snprintf( oc->filename, sizeof(oc->filename), "%s", filename ); + + // Add audio and video streams + if ( fmt->video_codec != CODEC_ID_NONE ) + video_st = add_video_stream( this, oc, video_codec_id ); + if ( fmt->audio_codec != CODEC_ID_NONE ) + audio_st = add_audio_stream( this, oc, audio_codec_id ); + + // Set the parameters (even though we have none...) + if ( av_set_parameters(oc, NULL) >= 0 ) + { + if ( video_st && !open_video( oc, video_st ) ) + video_st = NULL; + if ( audio_st ) + audio_input_frame_size = open_audio( oc, audio_st, audio_outbuf_size ); + + // Open the output file, if needed + if ( !( fmt->flags & AVFMT_NOFILE ) ) + { + if (url_fopen(&oc->pb, filename, URL_WRONLY) < 0) + { + fprintf(stderr, "Could not open '%s'\n", filename); + mlt_properties_set_int( properties, "running", 0 ); + } + } + + // Write the stream header, if any + if ( mlt_properties_get_int( properties, "running" ) ) + av_write_header( oc ); + } + else + { + fprintf(stderr, "Invalid output format parameters\n"); + mlt_properties_set_int( properties, "running", 0 ); + } + + // Allocate picture + if ( video_st ) + output = alloc_picture( video_st->codec->pix_fmt, width, height ); + + // Last check - need at least one stream + if ( audio_st == NULL && video_st == NULL ) + mlt_properties_set_int( properties, "running", 0 ); + + // Get the starting time (can ignore the times above) + gettimeofday( &ante, NULL ); + + // Loop while running + while( mlt_properties_get_int( properties, "running" ) && !terminated ) + { + // Get the frame + frame = mlt_consumer_rt_frame( this ); + + // Check that we have a frame to work with + if ( frame != NULL ) + { + // Increment frames despatched + frames ++; + + // Default audio args + frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Check for the terminated condition + terminated = terminate_on_pause && mlt_properties_get_double( frame_properties, "_speed" ) == 0.0; + + // Get audio and append to the fifo + if ( !terminated && audio_st ) + { + samples = mlt_sample_calculator( fps, frequency, count ++ ); + mlt_frame_get_audio( frame, &pcm, &aud_fmt, &frequency, &channels, &samples ); + + // Create the fifo if we don't have one + if ( fifo == NULL ) + { + fifo = sample_fifo_init( frequency, channels ); + mlt_properties_set_data( properties, "sample_fifo", fifo, 0, ( mlt_destructor )sample_fifo_close, NULL ); + } + + if ( mlt_properties_get_double( frame_properties, "_speed" ) != 1.0 ) + memset( pcm, 0, samples * channels * 2 ); + + // Append the samples + sample_fifo_append( fifo, pcm, samples * channels ); + total_time += ( samples * 1000000 ) / frequency; + } + + // Encode the image + if ( !terminated && video_st ) + mlt_deque_push_back( queue, frame ); + else + mlt_frame_close( frame ); + } + + // While we have stuff to process, process... + while ( 1 ) + { + if (audio_st) + audio_pts = (double)audio_st->pts.val * audio_st->time_base.num / audio_st->time_base.den; + else + audio_pts = 0.0; + + if (video_st) + video_pts = (double)video_st->pts.val * video_st->time_base.num / video_st->time_base.den; + else + video_pts = 0.0; + + // Write interleaved audio and video frames + if ( !video_st || ( video_st && audio_st && audio_pts < video_pts ) ) + { + if ( channels * audio_input_frame_size < sample_fifo_used( fifo ) ) + { + AVCodecContext *c; + AVPacket pkt; + av_init_packet( &pkt ); + + c = audio_st->codec; + + sample_fifo_fetch( fifo, buffer, channels * audio_input_frame_size ); + + pkt.size = avcodec_encode_audio( c, audio_outbuf, audio_outbuf_size, buffer ); + // Write the compressed frame in the media file + if ( c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE ) + pkt.pts = av_rescale_q( c->coded_frame->pts, c->time_base, audio_st->time_base ); + pkt.flags |= PKT_FLAG_KEY; + pkt.stream_index= audio_st->index; + pkt.data= audio_outbuf; + + if ( pkt.size ) + if ( av_interleaved_write_frame( oc, &pkt ) != 0) + fprintf(stderr, "Error while writing audio frame\n"); + + audio_pts += c->frame_size; + } + else + { + break; + } + } + else if ( video_st ) + { + if ( mlt_deque_count( queue ) ) + { + int out_size, ret; + AVCodecContext *c; + + frame = mlt_deque_pop_front( queue ); + frame_properties = MLT_FRAME_PROPERTIES( frame ); + + c = video_st->codec; + + if ( mlt_properties_get_int( frame_properties, "rendered" ) ) + { + int i = 0; + int j = 0; + uint8_t *p; + uint8_t *q; + + mlt_events_fire( properties, "consumer-frame-show", frame, NULL ); + + mlt_frame_get_image( frame, &image, &img_fmt, &img_width, &img_height, 0 ); + + q = image; + + // Convert the mlt frame to an AVPicture + for ( i = 0; i < height; i ++ ) + { + p = input->data[ 0 ] + i * input->linesize[ 0 ]; + j = width; + while( j -- ) + { + *p ++ = *q ++; + *p ++ = *q ++; + } + } + + // Do the colour space conversion +#ifdef SWSCALE + struct SwsContext *context = sws_getContext( width, height, PIX_FMT_YUV422, + width, height, video_st->codec->pix_fmt, SWS_FAST_BILINEAR, NULL, NULL, NULL); + sws_scale( context, input->data, input->linesize, 0, height, + output->data, output->linesize); + sws_freeContext( context ); +#else + img_convert( ( AVPicture * )output, video_st->codec->pix_fmt, ( AVPicture * )input, PIX_FMT_YUV422, width, height ); +#endif + + // Apply the alpha if applicable + if ( video_st->codec->pix_fmt == PIX_FMT_RGBA32 ) + { + uint8_t *alpha = mlt_frame_get_alpha_mask( frame ); + register int n; + + for ( i = 0; i < height; i ++ ) + { + n = ( width + 7 ) / 8; + p = output->data[ 0 ] + i * output->linesize[ 0 ]; + + #ifndef __DARWIN__ + p += 3; + #endif + + switch( width % 8 ) + { + case 0: do { *p = *alpha++; p += 4; + case 7: *p = *alpha++; p += 4; + case 6: *p = *alpha++; p += 4; + case 5: *p = *alpha++; p += 4; + case 4: *p = *alpha++; p += 4; + case 3: *p = *alpha++; p += 4; + case 2: *p = *alpha++; p += 4; + case 1: *p = *alpha++; p += 4; + } + while( --n ); + } + } + } + } + + if (oc->oformat->flags & AVFMT_RAWPICTURE) + { + // raw video case. The API will change slightly in the near future for that + AVPacket pkt; + av_init_packet(&pkt); + + pkt.flags |= PKT_FLAG_KEY; + pkt.stream_index= video_st->index; + pkt.data= (uint8_t *)output; + pkt.size= sizeof(AVPicture); + + ret = av_write_frame(oc, &pkt); + video_pts += c->frame_size; + } + else + { + // Set the quality + output->quality = video_st->quality; + + // Set frame interlace hints + output->interlaced_frame = !mlt_properties_get_int( frame_properties, "progressive" ); + output->top_field_first = mlt_properties_get_int( frame_properties, "top_field_first" ); + + // Encode the image + out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, output ); + + // If zero size, it means the image was buffered + if (out_size > 0) + { + AVPacket pkt; + av_init_packet( &pkt ); + + if ( c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE ) + pkt.pts= av_rescale_q( c->coded_frame->pts, c->time_base, video_st->time_base ); + if( c->coded_frame && c->coded_frame->key_frame ) + pkt.flags |= PKT_FLAG_KEY; + pkt.stream_index= video_st->index; + pkt.data= video_outbuf; + pkt.size= out_size; + + // write the compressed frame in the media file + ret = av_interleaved_write_frame(oc, &pkt); + video_pts += c->frame_size; + } + else + { + fprintf( stderr, "Error with video encode\n" ); + } + } + frame_count++; + mlt_frame_close( frame ); + } + else + { + break; + } + } + } + + if ( real_time_output && frames % 12 == 0 ) + { + long passed = time_difference( &ante ); + if ( fifo != NULL ) + { + long pending = ( ( ( long )sample_fifo_used( fifo ) * 1000 ) / frequency ) * 1000; + passed -= pending; + } + if ( passed < total_time ) + { + long total = ( total_time - passed ); + struct timespec t = { total / 1000000, ( total % 1000000 ) * 1000 }; + nanosleep( &t, NULL ); + } + } + } + +#ifdef FLUSH + if ( ! real_time_output ) + { + // Flush audio fifo + if ( audio_st && audio_st->codec->frame_size > 1 ) for (;;) + { + AVCodecContext *c = audio_st->codec; + AVPacket pkt; + av_init_packet( &pkt ); + pkt.size = 0; + + if ( /*( c->capabilities & CODEC_CAP_SMALL_LAST_FRAME ) &&*/ + ( channels * audio_input_frame_size < sample_fifo_used( fifo ) ) ) + { + sample_fifo_fetch( fifo, buffer, channels * audio_input_frame_size ); + pkt.size = avcodec_encode_audio( c, audio_outbuf, audio_outbuf_size, buffer ); + } + if ( pkt.size <= 0 ) + pkt.size = avcodec_encode_audio( c, audio_outbuf, audio_outbuf_size, NULL ); + if ( pkt.size <= 0 ) + break; + + // Write the compressed frame in the media file + if ( c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE ) + pkt.pts = av_rescale_q( c->coded_frame->pts, c->time_base, audio_st->time_base ); + pkt.flags |= PKT_FLAG_KEY; + pkt.stream_index = audio_st->index; + pkt.data = audio_outbuf; + if ( av_interleaved_write_frame( oc, &pkt ) != 0 ) + { + fprintf(stderr, "Error while writing flushed audio frame\n"); + break; + } + } + + // Flush video + if ( video_st && !( oc->oformat->flags & AVFMT_RAWPICTURE ) ) for (;;) + { + AVCodecContext *c = video_st->codec; + AVPacket pkt; + av_init_packet( &pkt ); + + // Encode the image + pkt.size = avcodec_encode_video( c, video_outbuf, video_outbuf_size, NULL ); + if ( pkt.size <= 0 ) + break; + + if ( c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE ) + pkt.pts= av_rescale_q( c->coded_frame->pts, c->time_base, video_st->time_base ); + if( c->coded_frame && c->coded_frame->key_frame ) + pkt.flags |= PKT_FLAG_KEY; + pkt.stream_index = video_st->index; + pkt.data = video_outbuf; + + // write the compressed frame in the media file + if ( av_interleaved_write_frame( oc, &pkt ) != 0 ) + { + fprintf(stderr, "Error while writing flushed video frame\n"); + break; + } + } + } +#endif + + // close each codec + if (video_st) + close_video(oc, video_st); + if (audio_st) + close_audio(oc, audio_st); + + // Write the trailer, if any + av_write_trailer(oc); + + // Free the streams + for(i = 0; i < oc->nb_streams; i++) + av_freep(&oc->streams[i]); + + // Close the output file + if (!(fmt->flags & AVFMT_NOFILE)) +#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(0<<8)+0) + url_fclose(oc->pb); +#else + url_fclose(&oc->pb); +#endif + + // Clean up input and output frames + if ( output ) + av_free( output->data[0] ); + av_free( output ); + av_free( input->data[0] ); + av_free( input ); + av_free( video_outbuf ); + av_free( buffer ); + + // Free the stream + av_free(oc); + + // Just in case we terminated on pause + mlt_properties_set_int( properties, "running", 0 ); + + mlt_consumer_stopped( this ); + + return NULL; +} + +/** Close the consumer. +*/ + +static void consumer_close( mlt_consumer this ) +{ + // Stop the consumer + mlt_consumer_stop( this ); + + // Close the parent + mlt_consumer_close( this ); + + // Free the memory + free( this ); +} diff --git a/src/modules/avformat/consumer_avformat.h b/src/modules/avformat/consumer_avformat.h new file mode 100644 index 0000000..070d5ef --- /dev/null +++ b/src/modules/avformat/consumer_avformat.h @@ -0,0 +1,28 @@ +/* + * consumer_avformat.h -- avformat consumer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CONSUMER_AVFORMAT_H_ +#define _CONSUMER_AVFORMAT_H_ + +#include + +extern mlt_consumer consumer_avformat_init( char *file ); + +#endif diff --git a/src/modules/avformat/factory.c b/src/modules/avformat/factory.c new file mode 100644 index 0000000..e97b8d2 --- /dev/null +++ b/src/modules/avformat/factory.c @@ -0,0 +1,128 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include +#include "producer_avformat.h" +#include "consumer_avformat.h" +#include "filter_avcolour_space.h" +#include "filter_avdeinterlace.h" +#include "filter_avresample.h" + +// ffmpeg Header files +#include + +// A static flag used to determine if avformat has been initialised +static int avformat_initialised = 0; + +// A locking mutex +static pthread_mutex_t avformat_mutex; + +#if 0 +// These 3 functions should override the alloc functions in libavformat +// but some formats or codecs seem to crash when used (wmv in particular) + +void *av_malloc( unsigned int size ) +{ + return mlt_pool_alloc( size ); +} + +void *av_realloc( void *ptr, unsigned int size ) +{ + return mlt_pool_realloc( ptr, size ); +} + +void av_free( void *ptr ) +{ + return mlt_pool_release( ptr ); +} +#endif + +void avformat_destroy( void *ignore ) +{ + // Clean up + // av_free_static( ); -XXX this is deprecated + + // Destroy the mutex + pthread_mutex_destroy( &avformat_mutex ); +} + +void avformat_lock( ) +{ + // Lock the mutex now + pthread_mutex_lock( &avformat_mutex ); +} + +void avformat_unlock( ) +{ + // Unlock the mutex now + pthread_mutex_unlock( &avformat_mutex ); +} + +static void avformat_init( ) +{ + // Initialise avformat if necessary + if ( avformat_initialised == 0 ) + { + avformat_initialised = 1; + pthread_mutex_init( &avformat_mutex, NULL ); + av_register_all( ); + mlt_factory_register_for_clean_up( NULL, avformat_destroy ); + av_log_set_level( -1 ); + } +} + +void *mlt_create_producer( char *id, void *arg ) +{ + avformat_init( ); + if ( !strcmp( id, "avformat" ) ) + return producer_avformat_init( arg ); + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + avformat_init( ); + if ( !strcmp( id, "avcolour_space" ) ) + return filter_avcolour_space_init( arg ); +#ifdef USE_MMX + if ( !strcmp( id, "avdeinterlace" ) ) + return filter_avdeinterlace_init( arg ); +#endif + if ( !strcmp( id, "avresample" ) ) + return filter_avresample_init( arg ); + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + avformat_init( ); + if ( !strcmp( id, "avformat" ) ) + return consumer_avformat_init( arg ); + return NULL; +} + diff --git a/src/modules/avformat/ffmpeg.patch b/src/modules/avformat/ffmpeg.patch new file mode 100644 index 0000000..88f9a0d --- /dev/null +++ b/src/modules/avformat/ffmpeg.patch @@ -0,0 +1,15 @@ +=================================================================== +RCS file: /cvsroot/ffmpeg/ffmpeg/libavcodec/ffv1.c,v +retrieving revision 1.20 +diff -u -r1.20 ffv1.c +--- libavcodec/ffv1.c 21 May 2004 14:37:16 -0000 1.20 ++++ libavcodec/ffv1.c 9 Jun 2004 15:04:31 -0000 +@@ -453,7 +453,7 @@ + + static void encode_rgb_frame(FFV1Context *s, uint32_t *src, int w, int h, int stride){ + int x, y, p, i; +- const int ring_size=2; ++ int ring_size=2; + int_fast16_t sample_buffer[3][ring_size][w+6], *sample[3][ring_size]; + s->run_index=0; + diff --git a/src/modules/avformat/filter_avcolour_space.c b/src/modules/avformat/filter_avcolour_space.c new file mode 100644 index 0000000..1ce4736 --- /dev/null +++ b/src/modules/avformat/filter_avcolour_space.c @@ -0,0 +1,243 @@ +/* + * filter_avcolour_space.c -- Colour space filter + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_avcolour_space.h" + +#include + +// ffmpeg Header files +#include +#ifdef SWSCALE +#include +#endif + +#include +#include + +static inline int is_big_endian( ) +{ + union { int i; char c[ 4 ]; } big_endian_test; + big_endian_test.i = 1; + + return big_endian_test.c[ 0 ] != 1; +} + +static inline int convert_mlt_to_av_cs( mlt_image_format format ) +{ + int value = 0; + + switch( format ) + { + case mlt_image_rgb24: + value = PIX_FMT_RGB24; + break; + case mlt_image_rgb24a: + value = PIX_FMT_RGBA32; + break; + case mlt_image_yuv422: + value = PIX_FMT_YUV422; + break; + case mlt_image_yuv420p: + value = PIX_FMT_YUV420P; + break; + case mlt_image_opengl: + case mlt_image_none: + fprintf( stderr, "Invalid format...\n" ); + break; + } + + return value; +} + +static inline void convert_image( uint8_t *out, uint8_t *in, int out_fmt, int in_fmt, int width, int height ) +{ + AVPicture input; + AVPicture output; + avpicture_fill( &input, in, in_fmt, width, height ); + avpicture_fill( &output, out, out_fmt, width, height ); +#ifdef SWSCALE + struct SwsContext *context = sws_getContext( width, height, in_fmt, + width, height, out_fmt, SWS_FAST_BILINEAR, NULL, NULL, NULL); + sws_scale( context, input.data, input.linesize, 0, height, + output.data, output.linesize); + sws_freeContext( context ); +#else + img_convert( &output, out_fmt, &input, in_fmt, width, height ); +#endif +} + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + mlt_filter filter = mlt_frame_pop_service( this ); + mlt_properties properties = MLT_FRAME_PROPERTIES( this ); + int output_format = *format; + mlt_image_format forced = mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "forced" ); + int error = 0; + + // Allow this filter to force processing in a colour space other than requested + *format = forced != 0 ? forced : *format; + + error = mlt_frame_get_image( this, image, format, width, height, 0 ); + + if ( error == 0 && *format != output_format && *image != NULL && output_format != mlt_image_opengl ) + { + int in_fmt = convert_mlt_to_av_cs( *format ); + int out_fmt = convert_mlt_to_av_cs( output_format ); + int size = avpicture_get_size( out_fmt, *width, *height ); + uint8_t *output = mlt_pool_alloc( size ); + convert_image( output, *image, out_fmt, in_fmt, *width, *height ); + + // Special case for alpha rgb input + if ( *format == mlt_image_rgb24a ) + { + register uint8_t *alpha = mlt_frame_get_alpha_mask( this ); + register int len = *width * *height; + register uint8_t *bits = *image; + register int n = ( len + 7 ) / 8; + + if( !is_big_endian( ) ) + bits += 3; + + // Extract alpha mask from the image using Duff's Device + switch( len % 8 ) + { + case 0: do { *alpha ++ = *bits; bits += 4; + case 7: *alpha ++ = *bits; bits += 4; + case 6: *alpha ++ = *bits; bits += 4; + case 5: *alpha ++ = *bits; bits += 4; + case 4: *alpha ++ = *bits; bits += 4; + case 3: *alpha ++ = *bits; bits += 4; + case 2: *alpha ++ = *bits; bits += 4; + case 1: *alpha ++ = *bits; bits += 4; + } + while( --n ); + } + } + + // Update the output + *image = output; + *format = output_format; + mlt_properties_set_data( properties, "image", output, size, mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "format", output_format ); + + // Special case for alpha rgb output + if ( *format == mlt_image_rgb24a ) + { + // Fetch the alpha + register uint8_t *alpha = mlt_frame_get_alpha_mask( this ); + + if ( alpha != NULL ) + { + register uint8_t *bits = *image; + register int len = *width * *height; + register int n = ( len + 7 ) / 8; + + if( !is_big_endian( ) ) + bits += 3; + + // Merge the alpha mask into the RGB image using Duff's Device + switch( len % 8 ) + { + case 0: do { *bits = *alpha++; bits += 4; + case 7: *bits = *alpha++; bits += 4; + case 6: *bits = *alpha++; bits += 4; + case 5: *bits = *alpha++; bits += 4; + case 4: *bits = *alpha++; bits += 4; + case 3: *bits = *alpha++; bits += 4; + case 2: *bits = *alpha++; bits += 4; + case 1: *bits = *alpha++; bits += 4; + } + while( --n ); + } + } + } + } + else if ( error == 0 && *format != output_format && *image != NULL && output_format == mlt_image_opengl ) + { + if ( *format == mlt_image_yuv422 ) + { + int size = *width * *height * 4; + uint8_t *output = mlt_pool_alloc( size ); + int h = *height; + int w = *width; + uint8_t *o = output + size; + int ostride = w * 4; + uint8_t *p = *image; + uint8_t *alpha = mlt_frame_get_alpha_mask( this ) + *width * *height; + int r, g, b; + + while( h -- ) + { + w = *width; + o -= ostride; + alpha -= *width; + while( w >= 2 ) + { + YUV2RGB( *p, *( p + 1 ), *( p + 3 ), r, g, b ); + *o ++ = r; + *o ++ = g; + *o ++ = b; + *o ++ = *alpha ++; + YUV2RGB( *( p + 2 ), *( p + 1 ), *( p + 3 ), r, g, b ); + *o ++ = r; + *o ++ = g; + *o ++ = b; + *o ++ = *alpha ++; + w -= 2; + p += 4; + } + o -= ostride; + alpha -= *width; + } + + mlt_properties_set_data( properties, "image", output, size, mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "format", output_format ); + *image = output; + *format = output_format; + } + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + mlt_frame_push_service( frame, this ); + mlt_frame_push_get_image( frame, filter_get_image ); + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_avcolour_space_init( void *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + this->process = filter_process; + return this; +} + diff --git a/src/modules/avformat/filter_avcolour_space.h b/src/modules/avformat/filter_avcolour_space.h new file mode 100644 index 0000000..10616ae --- /dev/null +++ b/src/modules/avformat/filter_avcolour_space.h @@ -0,0 +1,28 @@ +/* + * filter_avcolour_space.h -- colourspace filter + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_AVCOLOUR_SPACE_H +#define _FILTER_AVCOLOUR_SPACE_H + +#include + +extern mlt_filter filter_avcolour_space_init( void *arg ); + +#endif diff --git a/src/modules/avformat/filter_avdeinterlace.c b/src/modules/avformat/filter_avdeinterlace.c new file mode 100644 index 0000000..c6f2f8d --- /dev/null +++ b/src/modules/avformat/filter_avdeinterlace.c @@ -0,0 +1,350 @@ +/* + * filter_avdeinterlace.c -- deinterlace filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_avdeinterlace.h" + +#include + +#include +#include + +// ffmpeg Header files +#include + +#ifdef USE_MMX +#include "mmx.h" +#endif + +#ifdef USE_MMX +#define DEINT_INPLACE_LINE_LUM \ + movd_m2r(lum_m4[0],mm0);\ + movd_m2r(lum_m3[0],mm1);\ + movd_m2r(lum_m2[0],mm2);\ + movd_m2r(lum_m1[0],mm3);\ + movd_m2r(lum[0],mm4);\ + punpcklbw_r2r(mm7,mm0);\ + movd_r2m(mm2,lum_m4[0]);\ + punpcklbw_r2r(mm7,mm1);\ + punpcklbw_r2r(mm7,mm2);\ + punpcklbw_r2r(mm7,mm3);\ + punpcklbw_r2r(mm7,mm4);\ + paddw_r2r(mm3,mm1);\ + psllw_i2r(1,mm2);\ + paddw_r2r(mm4,mm0);\ + psllw_i2r(2,mm1);\ + paddw_r2r(mm6,mm2);\ + paddw_r2r(mm2,mm1);\ + psubusw_r2r(mm0,mm1);\ + psrlw_i2r(3,mm1);\ + packuswb_r2r(mm7,mm1);\ + movd_r2m(mm1,lum_m2[0]); + +#define DEINT_LINE_LUM \ + movd_m2r(lum_m4[0],mm0);\ + movd_m2r(lum_m3[0],mm1);\ + movd_m2r(lum_m2[0],mm2);\ + movd_m2r(lum_m1[0],mm3);\ + movd_m2r(lum[0],mm4);\ + punpcklbw_r2r(mm7,mm0);\ + punpcklbw_r2r(mm7,mm1);\ + punpcklbw_r2r(mm7,mm2);\ + punpcklbw_r2r(mm7,mm3);\ + punpcklbw_r2r(mm7,mm4);\ + paddw_r2r(mm3,mm1);\ + psllw_i2r(1,mm2);\ + paddw_r2r(mm4,mm0);\ + psllw_i2r(2,mm1);\ + paddw_r2r(mm6,mm2);\ + paddw_r2r(mm2,mm1);\ + psubusw_r2r(mm0,mm1);\ + psrlw_i2r(3,mm1);\ + packuswb_r2r(mm7,mm1);\ + movd_r2m(mm1,dst[0]); +#endif + +/* filter parameters: [-1 4 2 4 -1] // 8 */ +static inline void deinterlace_line(uint8_t *dst, + const uint8_t *lum_m4, const uint8_t *lum_m3, + const uint8_t *lum_m2, const uint8_t *lum_m1, + const uint8_t *lum, + int size) +{ +#ifndef USE_MMX + uint8_t *cm = cropTbl + MAX_NEG_CROP; + int sum; + + for(;size > 0;size--) { + sum = -lum_m4[0]; + sum += lum_m3[0] << 2; + sum += lum_m2[0] << 1; + sum += lum_m1[0] << 2; + sum += -lum[0]; + dst[0] = cm[(sum + 4) >> 3]; + lum_m4++; + lum_m3++; + lum_m2++; + lum_m1++; + lum++; + dst++; + } +#else + + { + mmx_t rounder; + rounder.uw[0]=4; + rounder.uw[1]=4; + rounder.uw[2]=4; + rounder.uw[3]=4; + pxor_r2r(mm7,mm7); + movq_m2r(rounder,mm6); + } + for (;size > 3; size-=4) { + DEINT_LINE_LUM + lum_m4+=4; + lum_m3+=4; + lum_m2+=4; + lum_m1+=4; + lum+=4; + dst+=4; + } +#endif +} +static inline void deinterlace_line_inplace(uint8_t *lum_m4, uint8_t *lum_m3, uint8_t *lum_m2, uint8_t *lum_m1, uint8_t *lum, + int size) +{ +#ifndef USE_MMX + uint8_t *cm = cropTbl + MAX_NEG_CROP; + int sum; + + for(;size > 0;size--) { + sum = -lum_m4[0]; + sum += lum_m3[0] << 2; + sum += lum_m2[0] << 1; + lum_m4[0]=lum_m2[0]; + sum += lum_m1[0] << 2; + sum += -lum[0]; + lum_m2[0] = cm[(sum + 4) >> 3]; + lum_m4++; + lum_m3++; + lum_m2++; + lum_m1++; + lum++; + } +#else + + { + mmx_t rounder; + rounder.uw[0]=4; + rounder.uw[1]=4; + rounder.uw[2]=4; + rounder.uw[3]=4; + pxor_r2r(mm7,mm7); + movq_m2r(rounder,mm6); + } + for (;size > 3; size-=4) { + DEINT_INPLACE_LINE_LUM + lum_m4+=4; + lum_m3+=4; + lum_m2+=4; + lum_m1+=4; + lum+=4; + } +#endif +} + +/* deinterlacing : 2 temporal taps, 3 spatial taps linear filter. The + top field is copied as is, but the bottom field is deinterlaced + against the top field. */ +static inline void deinterlace_bottom_field(uint8_t *dst, int dst_wrap, + const uint8_t *src1, int src_wrap, + int width, int height) +{ + const uint8_t *src_m2, *src_m1, *src_0, *src_p1, *src_p2; + int y; + + src_m2 = src1; + src_m1 = src1; + src_0=&src_m1[src_wrap]; + src_p1=&src_0[src_wrap]; + src_p2=&src_p1[src_wrap]; + for(y=0;y<(height-2);y+=2) { + memcpy(dst,src_m1,width); + dst += dst_wrap; + deinterlace_line(dst,src_m2,src_m1,src_0,src_p1,src_p2,width); + src_m2 = src_0; + src_m1 = src_p1; + src_0 = src_p2; + src_p1 += 2*src_wrap; + src_p2 += 2*src_wrap; + dst += dst_wrap; + } + memcpy(dst,src_m1,width); + dst += dst_wrap; + /* do last line */ + deinterlace_line(dst,src_m2,src_m1,src_0,src_0,src_0,width); +} + +static inline void deinterlace_bottom_field_inplace(uint8_t *src1, int src_wrap, + int width, int height) +{ + uint8_t *src_m1, *src_0, *src_p1, *src_p2; + int y; + uint8_t *buf; + buf = (uint8_t*)av_malloc(width); + + src_m1 = src1; + memcpy(buf,src_m1,width); + src_0=&src_m1[src_wrap]; + src_p1=&src_0[src_wrap]; + src_p2=&src_p1[src_wrap]; + for(y=0;y<(height-2);y+=2) { + deinterlace_line_inplace(buf,src_m1,src_0,src_p1,src_p2,width); + src_m1 = src_p1; + src_0 = src_p2; + src_p1 += 2*src_wrap; + src_p2 += 2*src_wrap; + } + /* do last line */ + deinterlace_line_inplace(buf,src_m1,src_0,src_0,src_0,width); + av_free(buf); +} + + +/* deinterlace - if not supported return -1 */ +static int mlt_avpicture_deinterlace(AVPicture *dst, const AVPicture *src, + int pix_fmt, int width, int height) +{ + int i; + + if (pix_fmt != PIX_FMT_YUV420P && + pix_fmt != PIX_FMT_YUV422P && + pix_fmt != PIX_FMT_YUV422 && + pix_fmt != PIX_FMT_YUV444P && + pix_fmt != PIX_FMT_YUV411P) + return -1; + if ((width & 3) != 0 || (height & 3) != 0) + return -1; + + if ( pix_fmt != PIX_FMT_YUV422 ) + { + for(i=0;i<3;i++) { + if (i == 1) { + switch(pix_fmt) { + case PIX_FMT_YUV420P: + width >>= 1; + height >>= 1; + break; + case PIX_FMT_YUV422P: + width >>= 1; + break; + case PIX_FMT_YUV411P: + width >>= 2; + break; + default: + break; + } + } + if (src == dst) { + deinterlace_bottom_field_inplace(dst->data[i], dst->linesize[i], + width, height); + } else { + deinterlace_bottom_field(dst->data[i],dst->linesize[i], + src->data[i], src->linesize[i], + width, height); + } + } + } + else { + if (src == dst) { + deinterlace_bottom_field_inplace(dst->data[0], dst->linesize[0], + width, height); + } else { + deinterlace_bottom_field(dst->data[0],dst->linesize[0], + src->data[0], src->linesize[0], + width, height); + } + } + +#ifdef USE_MMX + emms(); +#endif + return 0; +} + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + int error = 0; + int deinterlace = mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "consumer_deinterlace" ); + + // Determine if we need a writable version or not + if ( deinterlace && !writable ) + writable = !mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "progressive" ); + + // Get the input image + error = mlt_frame_get_image( this, image, format, width, height, writable ); + + // Check that we want progressive and we aren't already progressive + if ( deinterlace && *format == mlt_image_yuv422 && *image != NULL && !mlt_properties_get_int( MLT_FRAME_PROPERTIES( this ), "progressive" ) ) + { + // Create a picture + AVPicture *output = mlt_pool_alloc( sizeof( AVPicture ) ); + + // Fill the picture + if ( *format == mlt_image_yuv422 ) + { + avpicture_fill( output, *image, PIX_FMT_YUV422, *width, *height ); + mlt_avpicture_deinterlace( output, output, PIX_FMT_YUV422, *width, *height ); + } + + // Free the picture + mlt_pool_release( output ); + + // Make sure that others know the frame is deinterlaced + mlt_properties_set_int( MLT_FRAME_PROPERTIES( this ), "progressive", 1 ); + } + + return error; +} + +/** Deinterlace filter processing - this should be lazy evaluation here... +*/ + +static mlt_frame deinterlace_process( mlt_filter this, mlt_frame frame ) +{ + // Push the get_image method on to the stack + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_avdeinterlace_init( void *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + this->process = deinterlace_process; + return this; +} + diff --git a/src/modules/avformat/filter_avdeinterlace.h b/src/modules/avformat/filter_avdeinterlace.h new file mode 100644 index 0000000..068a01a --- /dev/null +++ b/src/modules/avformat/filter_avdeinterlace.h @@ -0,0 +1,28 @@ +/* + * filter_avdeinterlace.h -- deinterlace filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_AVDEINTERLACE_H_ +#define _FILTER_AVDEINTERLACE_H_ + +#include + +extern mlt_filter filter_avdeinterlace_init( void *arg ); + +#endif diff --git a/src/modules/avformat/filter_avresample.c b/src/modules/avformat/filter_avresample.c new file mode 100644 index 0000000..6fb2038 --- /dev/null +++ b/src/modules/avformat/filter_avresample.c @@ -0,0 +1,197 @@ +/* + * filter_avresample.c -- adjust audio sample frequency + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_avresample.h" + +#include + +#include +#include +#include + +// ffmpeg Header files +#include + +/** Get the audio. +*/ + +static int resample_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the properties of the frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + + // Get the filter service + mlt_filter filter = mlt_frame_pop_audio( frame ); + + // Get the filter properties + mlt_properties filter_properties = MLT_FILTER_PROPERTIES( filter ); + + // Get the resample information + int output_rate = mlt_properties_get_int( filter_properties, "frequency" ); + int16_t *sample_buffer = mlt_properties_get_data( filter_properties, "buffer", NULL ); + + // Obtain the resample context if it exists + ReSampleContext *resample = mlt_properties_get_data( filter_properties, "audio_resample", NULL ); + + // Used to return number of channels in the source + int channels_avail = *channels; + + // Loop variable + int i; + + // If no resample frequency is specified, default to requested value + if ( output_rate == 0 ) + output_rate = *frequency; + + // Get the producer's audio + mlt_frame_get_audio( frame, buffer, format, frequency, &channels_avail, samples ); + + // Duplicate channels as necessary + if ( channels_avail < *channels ) + { + int size = *channels * *samples * sizeof( int16_t ); + int16_t *new_buffer = mlt_pool_alloc( size ); + int j, k = 0; + + // Duplicate the existing channels + for ( i = 0; i < *samples; i++ ) + { + for ( j = 0; j < *channels; j++ ) + { + new_buffer[ ( i * *channels ) + j ] = (*buffer)[ ( i * channels_avail ) + k ]; + k = ( k + 1 ) % channels_avail; + } + } + + // Update the audio buffer now - destroys the old + mlt_properties_set_data( properties, "audio", new_buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + + *buffer = new_buffer; + } + else if ( channels_avail == 6 && *channels == 2 ) + { + // Nasty hack for ac3 5.1 audio - may be a cause of failure? + int size = *channels * *samples * sizeof( int16_t ); + int16_t *new_buffer = mlt_pool_alloc( size ); + + // Drop all but the first *channels + for ( i = 0; i < *samples; i++ ) + { + new_buffer[ ( i * *channels ) + 0 ] = (*buffer)[ ( i * channels_avail ) + 2 ]; + new_buffer[ ( i * *channels ) + 1 ] = (*buffer)[ ( i * channels_avail ) + 3 ]; + } + + // Update the audio buffer now - destroys the old + mlt_properties_set_data( properties, "audio", new_buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + + *buffer = new_buffer; + } + + // Return now if no work to do + if ( output_rate != *frequency ) + { + // Will store number of samples created + int used = 0; + + // Create a resampler if nececessary + if ( resample == NULL || *frequency != mlt_properties_get_int( filter_properties, "last_frequency" ) ) + { + // Create the resampler + resample = audio_resample_init( *channels, *channels, output_rate, *frequency ); + + // And store it on properties + mlt_properties_set_data( filter_properties, "audio_resample", resample, 0, ( mlt_destructor )audio_resample_close, NULL ); + + // And remember what it was created for + mlt_properties_set_int( filter_properties, "last_frequency", *frequency ); + } + + // Resample the audio + used = audio_resample( resample, sample_buffer, *buffer, *samples ); + + // Resize if necessary + if ( used > *samples ) + { + *buffer = mlt_pool_realloc( *buffer, *samples * *channels * sizeof( int16_t ) ); + mlt_properties_set_data( properties, "audio", *buffer, *channels * used * sizeof( int16_t ), mlt_pool_release, NULL ); + } + + // Copy samples + memcpy( *buffer, sample_buffer, *channels * used * sizeof( int16_t ) ); + + // Update output variables + *samples = used; + *frequency = output_rate; + } + + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Only call this if we have a means to get audio + if ( mlt_frame_is_test_audio( frame ) == 0 ) + { + // Push the filter on to the stack + mlt_frame_push_audio( frame, this ); + + // Assign our get_audio method + mlt_frame_push_audio( frame, resample_get_audio ); + } + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_avresample_init( char *arg ) +{ + // Create a filter + mlt_filter this = mlt_filter_new( ); + + // Initialise if successful + if ( this != NULL ) + { + // Calculate size of the buffer + int size = AVCODEC_MAX_AUDIO_FRAME_SIZE * sizeof( int16_t ); + + // Allocate the buffer + int16_t *buffer = mlt_pool_alloc( size ); + + // Assign the process method + this->process = filter_process; + + // Deal with argument + if ( arg != NULL ) + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "frequency", arg ); + + // Default to 2 channel output + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "channels", 2 ); + + // Store the buffer + mlt_properties_set_data( MLT_FILTER_PROPERTIES( this ), "buffer", buffer, size, mlt_pool_release, NULL ); + } + + return this; +} diff --git a/src/modules/avformat/filter_avresample.h b/src/modules/avformat/filter_avresample.h new file mode 100644 index 0000000..2f36e4e --- /dev/null +++ b/src/modules/avformat/filter_avresample.h @@ -0,0 +1,28 @@ +/* + * filter_avresample.h -- adjust audio sample frequency + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_AVRESAMPLE_H_ +#define _FILTER_AVRESAMPLE_H_ + +#include + +extern mlt_filter filter_avresample_init( char *arg ); + +#endif diff --git a/src/modules/avformat/mmx.h b/src/modules/avformat/mmx.h new file mode 100644 index 0000000..7e94cfd --- /dev/null +++ b/src/modules/avformat/mmx.h @@ -0,0 +1,243 @@ +/* + * mmx.h + * Copyright (C) 1997-2001 H. Dietz and R. Fisher + */ +#ifndef AVCODEC_I386MMX_H +#define AVCODEC_I386MMX_H + +/* + * The type of an value that fits in an MMX register (note that long + * long constant values MUST be suffixed by LL and unsigned long long + * values by ULL, lest they be truncated by the compiler) + */ + +typedef union { + long long q; /* Quadword (64-bit) value */ + unsigned long long uq; /* Unsigned Quadword */ + int d[2]; /* 2 Doubleword (32-bit) values */ + unsigned int ud[2]; /* 2 Unsigned Doubleword */ + short w[4]; /* 4 Word (16-bit) values */ + unsigned short uw[4]; /* 4 Unsigned Word */ + char b[8]; /* 8 Byte (8-bit) values */ + unsigned char ub[8]; /* 8 Unsigned Byte */ + float s[2]; /* Single-precision (32-bit) value */ +} mmx_t; /* On an 8-byte (64-bit) boundary */ + + +#define mmx_i2r(op,imm,reg) \ + __asm__ __volatile__ (#op " %0, %%" #reg \ + : /* nothing */ \ + : "i" (imm) ) + +#define mmx_m2r(op,mem,reg) \ + __asm__ __volatile__ (#op " %0, %%" #reg \ + : /* nothing */ \ + : "m" (mem)) + +#define mmx_r2m(op,reg,mem) \ + __asm__ __volatile__ (#op " %%" #reg ", %0" \ + : "=m" (mem) \ + : /* nothing */ ) + +#define mmx_r2r(op,regs,regd) \ + __asm__ __volatile__ (#op " %" #regs ", %" #regd) + + +#define emms() __asm__ __volatile__ ("emms") + +#define movd_m2r(var,reg) mmx_m2r (movd, var, reg) +#define movd_r2m(reg,var) mmx_r2m (movd, reg, var) +#define movd_r2r(regs,regd) mmx_r2r (movd, regs, regd) + +#define movq_m2r(var,reg) mmx_m2r (movq, var, reg) +#define movq_r2m(reg,var) mmx_r2m (movq, reg, var) +#define movq_r2r(regs,regd) mmx_r2r (movq, regs, regd) + +#define packssdw_m2r(var,reg) mmx_m2r (packssdw, var, reg) +#define packssdw_r2r(regs,regd) mmx_r2r (packssdw, regs, regd) +#define packsswb_m2r(var,reg) mmx_m2r (packsswb, var, reg) +#define packsswb_r2r(regs,regd) mmx_r2r (packsswb, regs, regd) + +#define packuswb_m2r(var,reg) mmx_m2r (packuswb, var, reg) +#define packuswb_r2r(regs,regd) mmx_r2r (packuswb, regs, regd) + +#define paddb_m2r(var,reg) mmx_m2r (paddb, var, reg) +#define paddb_r2r(regs,regd) mmx_r2r (paddb, regs, regd) +#define paddd_m2r(var,reg) mmx_m2r (paddd, var, reg) +#define paddd_r2r(regs,regd) mmx_r2r (paddd, regs, regd) +#define paddw_m2r(var,reg) mmx_m2r (paddw, var, reg) +#define paddw_r2r(regs,regd) mmx_r2r (paddw, regs, regd) + +#define paddsb_m2r(var,reg) mmx_m2r (paddsb, var, reg) +#define paddsb_r2r(regs,regd) mmx_r2r (paddsb, regs, regd) +#define paddsw_m2r(var,reg) mmx_m2r (paddsw, var, reg) +#define paddsw_r2r(regs,regd) mmx_r2r (paddsw, regs, regd) + +#define paddusb_m2r(var,reg) mmx_m2r (paddusb, var, reg) +#define paddusb_r2r(regs,regd) mmx_r2r (paddusb, regs, regd) +#define paddusw_m2r(var,reg) mmx_m2r (paddusw, var, reg) +#define paddusw_r2r(regs,regd) mmx_r2r (paddusw, regs, regd) + +#define pand_m2r(var,reg) mmx_m2r (pand, var, reg) +#define pand_r2r(regs,regd) mmx_r2r (pand, regs, regd) + +#define pandn_m2r(var,reg) mmx_m2r (pandn, var, reg) +#define pandn_r2r(regs,regd) mmx_r2r (pandn, regs, regd) + +#define pcmpeqb_m2r(var,reg) mmx_m2r (pcmpeqb, var, reg) +#define pcmpeqb_r2r(regs,regd) mmx_r2r (pcmpeqb, regs, regd) +#define pcmpeqd_m2r(var,reg) mmx_m2r (pcmpeqd, var, reg) +#define pcmpeqd_r2r(regs,regd) mmx_r2r (pcmpeqd, regs, regd) +#define pcmpeqw_m2r(var,reg) mmx_m2r (pcmpeqw, var, reg) +#define pcmpeqw_r2r(regs,regd) mmx_r2r (pcmpeqw, regs, regd) + +#define pcmpgtb_m2r(var,reg) mmx_m2r (pcmpgtb, var, reg) +#define pcmpgtb_r2r(regs,regd) mmx_r2r (pcmpgtb, regs, regd) +#define pcmpgtd_m2r(var,reg) mmx_m2r (pcmpgtd, var, reg) +#define pcmpgtd_r2r(regs,regd) mmx_r2r (pcmpgtd, regs, regd) +#define pcmpgtw_m2r(var,reg) mmx_m2r (pcmpgtw, var, reg) +#define pcmpgtw_r2r(regs,regd) mmx_r2r (pcmpgtw, regs, regd) + +#define pmaddwd_m2r(var,reg) mmx_m2r (pmaddwd, var, reg) +#define pmaddwd_r2r(regs,regd) mmx_r2r (pmaddwd, regs, regd) + +#define pmulhw_m2r(var,reg) mmx_m2r (pmulhw, var, reg) +#define pmulhw_r2r(regs,regd) mmx_r2r (pmulhw, regs, regd) + +#define pmullw_m2r(var,reg) mmx_m2r (pmullw, var, reg) +#define pmullw_r2r(regs,regd) mmx_r2r (pmullw, regs, regd) + +#define por_m2r(var,reg) mmx_m2r (por, var, reg) +#define por_r2r(regs,regd) mmx_r2r (por, regs, regd) + +#define pslld_i2r(imm,reg) mmx_i2r (pslld, imm, reg) +#define pslld_m2r(var,reg) mmx_m2r (pslld, var, reg) +#define pslld_r2r(regs,regd) mmx_r2r (pslld, regs, regd) +#define psllq_i2r(imm,reg) mmx_i2r (psllq, imm, reg) +#define psllq_m2r(var,reg) mmx_m2r (psllq, var, reg) +#define psllq_r2r(regs,regd) mmx_r2r (psllq, regs, regd) +#define psllw_i2r(imm,reg) mmx_i2r (psllw, imm, reg) +#define psllw_m2r(var,reg) mmx_m2r (psllw, var, reg) +#define psllw_r2r(regs,regd) mmx_r2r (psllw, regs, regd) + +#define psrad_i2r(imm,reg) mmx_i2r (psrad, imm, reg) +#define psrad_m2r(var,reg) mmx_m2r (psrad, var, reg) +#define psrad_r2r(regs,regd) mmx_r2r (psrad, regs, regd) +#define psraw_i2r(imm,reg) mmx_i2r (psraw, imm, reg) +#define psraw_m2r(var,reg) mmx_m2r (psraw, var, reg) +#define psraw_r2r(regs,regd) mmx_r2r (psraw, regs, regd) + +#define psrld_i2r(imm,reg) mmx_i2r (psrld, imm, reg) +#define psrld_m2r(var,reg) mmx_m2r (psrld, var, reg) +#define psrld_r2r(regs,regd) mmx_r2r (psrld, regs, regd) +#define psrlq_i2r(imm,reg) mmx_i2r (psrlq, imm, reg) +#define psrlq_m2r(var,reg) mmx_m2r (psrlq, var, reg) +#define psrlq_r2r(regs,regd) mmx_r2r (psrlq, regs, regd) +#define psrlw_i2r(imm,reg) mmx_i2r (psrlw, imm, reg) +#define psrlw_m2r(var,reg) mmx_m2r (psrlw, var, reg) +#define psrlw_r2r(regs,regd) mmx_r2r (psrlw, regs, regd) + +#define psubb_m2r(var,reg) mmx_m2r (psubb, var, reg) +#define psubb_r2r(regs,regd) mmx_r2r (psubb, regs, regd) +#define psubd_m2r(var,reg) mmx_m2r (psubd, var, reg) +#define psubd_r2r(regs,regd) mmx_r2r (psubd, regs, regd) +#define psubw_m2r(var,reg) mmx_m2r (psubw, var, reg) +#define psubw_r2r(regs,regd) mmx_r2r (psubw, regs, regd) + +#define psubsb_m2r(var,reg) mmx_m2r (psubsb, var, reg) +#define psubsb_r2r(regs,regd) mmx_r2r (psubsb, regs, regd) +#define psubsw_m2r(var,reg) mmx_m2r (psubsw, var, reg) +#define psubsw_r2r(regs,regd) mmx_r2r (psubsw, regs, regd) + +#define psubusb_m2r(var,reg) mmx_m2r (psubusb, var, reg) +#define psubusb_r2r(regs,regd) mmx_r2r (psubusb, regs, regd) +#define psubusw_m2r(var,reg) mmx_m2r (psubusw, var, reg) +#define psubusw_r2r(regs,regd) mmx_r2r (psubusw, regs, regd) + +#define punpckhbw_m2r(var,reg) mmx_m2r (punpckhbw, var, reg) +#define punpckhbw_r2r(regs,regd) mmx_r2r (punpckhbw, regs, regd) +#define punpckhdq_m2r(var,reg) mmx_m2r (punpckhdq, var, reg) +#define punpckhdq_r2r(regs,regd) mmx_r2r (punpckhdq, regs, regd) +#define punpckhwd_m2r(var,reg) mmx_m2r (punpckhwd, var, reg) +#define punpckhwd_r2r(regs,regd) mmx_r2r (punpckhwd, regs, regd) + +#define punpcklbw_m2r(var,reg) mmx_m2r (punpcklbw, var, reg) +#define punpcklbw_r2r(regs,regd) mmx_r2r (punpcklbw, regs, regd) +#define punpckldq_m2r(var,reg) mmx_m2r (punpckldq, var, reg) +#define punpckldq_r2r(regs,regd) mmx_r2r (punpckldq, regs, regd) +#define punpcklwd_m2r(var,reg) mmx_m2r (punpcklwd, var, reg) +#define punpcklwd_r2r(regs,regd) mmx_r2r (punpcklwd, regs, regd) + +#define pxor_m2r(var,reg) mmx_m2r (pxor, var, reg) +#define pxor_r2r(regs,regd) mmx_r2r (pxor, regs, regd) + + +/* 3DNOW extensions */ + +#define pavgusb_m2r(var,reg) mmx_m2r (pavgusb, var, reg) +#define pavgusb_r2r(regs,regd) mmx_r2r (pavgusb, regs, regd) + + +/* AMD MMX extensions - also available in intel SSE */ + + +#define mmx_m2ri(op,mem,reg,imm) \ + __asm__ __volatile__ (#op " %1, %0, %%" #reg \ + : /* nothing */ \ + : "X" (mem), "X" (imm)) +#define mmx_r2ri(op,regs,regd,imm) \ + __asm__ __volatile__ (#op " %0, %%" #regs ", %%" #regd \ + : /* nothing */ \ + : "X" (imm) ) + +#define mmx_fetch(mem,hint) \ + __asm__ __volatile__ ("prefetch" #hint " %0" \ + : /* nothing */ \ + : "X" (mem)) + + +#define maskmovq(regs,maskreg) mmx_r2ri (maskmovq, regs, maskreg) + +#define movntq_r2m(mmreg,var) mmx_r2m (movntq, mmreg, var) + +#define pavgb_m2r(var,reg) mmx_m2r (pavgb, var, reg) +#define pavgb_r2r(regs,regd) mmx_r2r (pavgb, regs, regd) +#define pavgw_m2r(var,reg) mmx_m2r (pavgw, var, reg) +#define pavgw_r2r(regs,regd) mmx_r2r (pavgw, regs, regd) + +#define pextrw_r2r(mmreg,reg,imm) mmx_r2ri (pextrw, mmreg, reg, imm) + +#define pinsrw_r2r(reg,mmreg,imm) mmx_r2ri (pinsrw, reg, mmreg, imm) + +#define pmaxsw_m2r(var,reg) mmx_m2r (pmaxsw, var, reg) +#define pmaxsw_r2r(regs,regd) mmx_r2r (pmaxsw, regs, regd) + +#define pmaxub_m2r(var,reg) mmx_m2r (pmaxub, var, reg) +#define pmaxub_r2r(regs,regd) mmx_r2r (pmaxub, regs, regd) + +#define pminsw_m2r(var,reg) mmx_m2r (pminsw, var, reg) +#define pminsw_r2r(regs,regd) mmx_r2r (pminsw, regs, regd) + +#define pminub_m2r(var,reg) mmx_m2r (pminub, var, reg) +#define pminub_r2r(regs,regd) mmx_r2r (pminub, regs, regd) + +#define pmovmskb(mmreg,reg) \ + __asm__ __volatile__ ("movmskps %" #mmreg ", %" #reg) + +#define pmulhuw_m2r(var,reg) mmx_m2r (pmulhuw, var, reg) +#define pmulhuw_r2r(regs,regd) mmx_r2r (pmulhuw, regs, regd) + +#define prefetcht0(mem) mmx_fetch (mem, t0) +#define prefetcht1(mem) mmx_fetch (mem, t1) +#define prefetcht2(mem) mmx_fetch (mem, t2) +#define prefetchnta(mem) mmx_fetch (mem, nta) + +#define psadbw_m2r(var,reg) mmx_m2r (psadbw, var, reg) +#define psadbw_r2r(regs,regd) mmx_r2r (psadbw, regs, regd) + +#define pshufw_m2r(var,reg,imm) mmx_m2ri(pshufw, var, reg, imm) +#define pshufw_r2r(regs,regd,imm) mmx_r2ri(pshufw, regs, regd, imm) + +#define sfence() __asm__ __volatile__ ("sfence\n\t") + +#endif /* AVCODEC_I386MMX_H */ diff --git a/src/modules/avformat/producer_avformat.c b/src/modules/avformat/producer_avformat.c new file mode 100644 index 0000000..c3cf306 --- /dev/null +++ b/src/modules/avformat/producer_avformat.c @@ -0,0 +1,1067 @@ +/* + * producer_avformat.c -- avformat producer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// Local header files +#include "producer_avformat.h" + +// MLT Header files +#include + +// ffmpeg Header files +#include +#ifdef SWSCALE +#include +#endif + +// System header files +#include +#include +#include +#include + +void avformat_lock( ); +void avformat_unlock( ); + +// Forward references. +static int producer_open( mlt_producer this, char *file ); +static int producer_get_frame( mlt_producer this, mlt_frame_ptr frame, int index ); + +/** Constructor for libavformat. +*/ + +mlt_producer producer_avformat_init( char *file ) +{ + mlt_producer this = NULL; + + // Check that we have a non-NULL argument + if ( file != NULL ) + { + // Construct the producer + this = calloc( 1, sizeof( struct mlt_producer_s ) ); + + // Initialise it + if ( mlt_producer_init( this, NULL ) == 0 ) + { + // Get the properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Set the resource property (required for all producers) + mlt_properties_set( properties, "resource", file ); + + // Register our get_frame implementation + this->get_frame = producer_get_frame; + + // Open the file + if ( producer_open( this, file ) != 0 ) + { + // Clean up + mlt_producer_close( this ); + this = NULL; + } + } + } + + return this; +} + +/** Find the default streams. +*/ + +static void find_default_streams( AVFormatContext *context, int *audio_index, int *video_index ) +{ + int i; + + // Allow for multiple audio and video streams in the file and select first of each (if available) + for( i = 0; i < context->nb_streams; i++ ) + { + // Get the codec context + AVCodecContext *codec_context = context->streams[ i ]->codec; + + if ( avcodec_find_decoder( codec_context->codec_id ) == NULL ) + continue; + + // Determine the type and obtain the first index of each type + switch( codec_context->codec_type ) + { + case CODEC_TYPE_VIDEO: + if ( *video_index < 0 ) + *video_index = i; + break; + case CODEC_TYPE_AUDIO: + if ( *audio_index < 0 ) + *audio_index = i; + break; + default: + break; + } + } +} + +/** Producer file destructor. +*/ + +static void producer_file_close( void *context ) +{ + if ( context != NULL ) + { + // Lock the mutex now + avformat_lock( ); + + // Close the file + av_close_input_file( context ); + + // Unlock the mutex now + avformat_unlock( ); + } +} + +/** Producer file destructor. +*/ + +static void producer_codec_close( void *codec ) +{ + if ( codec != NULL ) + { + // Lock the mutex now + avformat_lock( ); + + // Close the file + avcodec_close( codec ); + + // Unlock the mutex now + avformat_unlock( ); + } +} + +/** Open the file. +*/ + +static int producer_open( mlt_producer this, char *file ) +{ + // Return an error code (0 == no error) + int error = 0; + + // Context for avformat + AVFormatContext *context = NULL; + + // Get the properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // We will treat everything with the producer fps + double fps = mlt_producer_get_fps( this ); + + // Lock the mutex now + avformat_lock( ); + + // If "MRL", then create AVInputFormat + AVInputFormat *format = NULL; + AVFormatParameters *params = NULL; + char *standard = NULL; + char *mrl = strchr( file, ':' ); + + // AV option (0 = both, 1 = video, 2 = audio) + int av = 0; + + // Setting lowest log level + av_log_set_level( -1 ); + + // Only if there is not a protocol specification that avformat can handle + if ( mrl && !url_exist( file ) ) + { + // 'file' becomes format abbreviation + mrl[0] = 0; + + // Lookup the format + format = av_find_input_format( file ); + + // Eat the format designator + file = ++mrl; + + if ( format ) + { + // Allocate params + params = calloc( sizeof( AVFormatParameters ), 1 ); + + // These are required by video4linux (defaults) + params->width = 640; + params->height = 480; + params->time_base= (AVRational){1,25}; + // params->device = file; + params->channels = 2; + params->sample_rate = 48000; + } + + // XXX: this does not work anymore since avdevice + // TODO: make producer_avddevice? + // Parse out params + mrl = strchr( file, '?' ); + while ( mrl ) + { + mrl[0] = 0; + char *name = strdup( ++mrl ); + char *value = strchr( name, ':' ); + if ( value ) + { + value[0] = 0; + value++; + char *t = strchr( value, '&' ); + if ( t ) + t[0] = 0; + if ( !strcmp( name, "frame_rate" ) ) + params->time_base.den = atoi( value ); + else if ( !strcmp( name, "frame_rate_base" ) ) + params->time_base.num = atoi( value ); + else if ( !strcmp( name, "sample_rate" ) ) + params->sample_rate = atoi( value ); + else if ( !strcmp( name, "channels" ) ) + params->channels = atoi( value ); + else if ( !strcmp( name, "width" ) ) + params->width = atoi( value ); + else if ( !strcmp( name, "height" ) ) + params->height = atoi( value ); + else if ( !strcmp( name, "standard" ) ) + { + standard = strdup( value ); + params->standard = standard; + } + else if ( !strcmp( name, "av" ) ) + av = atoi( value ); + } + free( name ); + mrl = strchr( mrl, '&' ); + } + } + + // Now attempt to open the file + error = av_open_input_file( &context, file, format, 0, params ) < 0; + + // Cleanup AVFormatParameters + free( standard ); + free( params ); + + // If successful, then try to get additional info + if ( error == 0 ) + { + // Get the stream info + error = av_find_stream_info( context ) < 0; + + // Continue if no error + if ( error == 0 ) + { + // We will default to the first audio and video streams found + int audio_index = -1; + int video_index = -1; + int av_bypass = 0; + + // Now set properties where we can (use default unknowns if required) + if ( context->duration != AV_NOPTS_VALUE ) + { + // This isn't going to be accurate for all formats + mlt_position frames = ( mlt_position )( ( ( double )context->duration / ( double )AV_TIME_BASE ) * fps + 0.5 ); + mlt_properties_set_position( properties, "out", frames - 1 ); + mlt_properties_set_position( properties, "length", frames ); + } + + // Find default audio and video streams + find_default_streams( context, &audio_index, &video_index ); + + if ( context->start_time != AV_NOPTS_VALUE ) + mlt_properties_set_double( properties, "_start_time", context->start_time ); + + // Check if we're seekable (something funny about mpeg here :-/) + if ( strcmp( file, "pipe:" ) && strncmp( file, "http://", 6 ) ) + { + mlt_properties_set_int( properties, "seekable", av_seek_frame( context, -1, mlt_properties_get_double( properties, "_start_time" ), AVSEEK_FLAG_BACKWARD ) >= 0 ); + mlt_properties_set_data( properties, "dummy_context", context, 0, producer_file_close, NULL ); + av_open_input_file( &context, file, NULL, 0, NULL ); + av_find_stream_info( context ); + } + else + av_bypass = 1; + + // Store selected audio and video indexes on properties + mlt_properties_set_int( properties, "audio_index", audio_index ); + mlt_properties_set_int( properties, "video_index", video_index ); + mlt_properties_set_int( properties, "_last_position", -1 ); + + // Fetch the width, height and aspect ratio + if ( video_index != -1 ) + { + AVCodecContext *codec_context = context->streams[ video_index ]->codec; + mlt_properties_set_int( properties, "width", codec_context->width ); + mlt_properties_set_int( properties, "height", codec_context->height ); + mlt_properties_set_double( properties, "aspect_ratio", av_q2d( codec_context->sample_aspect_ratio ) ); + } + + // Read Metadata + if (context->title != NULL) + mlt_properties_set(properties, "meta.attr.title.markup", context->title ); + if (context->author != NULL) + mlt_properties_set(properties, "meta.attr.author.markup", context->author ); + if (context->copyright != NULL) + mlt_properties_set(properties, "meta.attr.copyright.markup", context->copyright ); + if (context->comment != NULL) + mlt_properties_set(properties, "meta.attr.comment.markup", context->comment ); + if (context->album != NULL) + mlt_properties_set(properties, "meta.attr.album.markup", context->album ); + if (context->year != 0) + mlt_properties_set_int(properties, "meta.attr.year.markup", context->year ); + if (context->track != 0) + mlt_properties_set_int(properties, "meta.attr.track.markup", context->track ); + + // We're going to cheat here - for a/v files, we will have two contexts (reasoning will be clear later) + if ( av == 0 && !av_bypass && audio_index != -1 && video_index != -1 ) + { + // We'll use the open one as our video_context + mlt_properties_set_data( properties, "video_context", context, 0, producer_file_close, NULL ); + + // And open again for our audio context + av_open_input_file( &context, file, NULL, 0, NULL ); + av_find_stream_info( context ); + + // Audio context + mlt_properties_set_data( properties, "audio_context", context, 0, producer_file_close, NULL ); + } + else if ( av != 2 && video_index != -1 ) + { + // We only have a video context + mlt_properties_set_data( properties, "video_context", context, 0, producer_file_close, NULL ); + } + else if ( audio_index != -1 ) + { + // We only have an audio context + mlt_properties_set_data( properties, "audio_context", context, 0, producer_file_close, NULL ); + } + else + { + // Something has gone wrong + error = -1; + } + + mlt_properties_set_int( properties, "av_bypass", av_bypass ); + } + } + + // Unlock the mutex now + avformat_unlock( ); + + return error; +} + +/** Convert a frame position to a time code. +*/ + +static double producer_time_of_frame( mlt_producer this, mlt_position position ) +{ + return ( double )position / mlt_producer_get_fps( this ); +} + +static inline void convert_image( AVFrame *frame, uint8_t *buffer, int pix_fmt, mlt_image_format format, int width, int height ) +{ +#ifdef SWSCALE + if ( format == mlt_image_yuv420p ) + { + struct SwsContext *context = sws_getContext( width, height, pix_fmt, + width, height, PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL); + AVPicture output; + output.data[0] = buffer; + output.data[1] = buffer + width * height; + output.data[2] = buffer + ( 3 * width * height ) / 2; + output.linesize[0] = width; + output.linesize[1] = width >> 1; + output.linesize[2] = width >> 1; + sws_scale( context, frame->data, frame->linesize, 0, height, + output.data, output.linesize); + sws_freeContext( context ); + } + else if ( format == mlt_image_rgb24 ) + { + struct SwsContext *context = sws_getContext( width, height, pix_fmt, + width, height, PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL); + AVPicture output; + avpicture_fill( &output, buffer, PIX_FMT_RGB24, width, height ); + sws_scale( context, frame->data, frame->linesize, 0, height, + output.data, output.linesize); + sws_freeContext( context ); + } + else + { + struct SwsContext *context = sws_getContext( width, height, pix_fmt, + width, height, PIX_FMT_YUYV422, SWS_FAST_BILINEAR, NULL, NULL, NULL); + AVPicture output; + avpicture_fill( &output, buffer, PIX_FMT_YUYV422, width, height ); + sws_scale( context, frame->data, frame->linesize, 0, height, + output.data, output.linesize); + sws_freeContext( context ); + } +#else + if ( format == mlt_image_yuv420p ) + { + AVPicture pict; + pict.data[0] = buffer; + pict.data[1] = buffer + width * height; + pict.data[2] = buffer + ( 3 * width * height ) / 2; + pict.linesize[0] = width; + pict.linesize[1] = width >> 1; + pict.linesize[2] = width >> 1; + img_convert( &pict, PIX_FMT_YUV420P, (AVPicture *)frame, pix_fmt, width, height ); + } + else if ( format == mlt_image_rgb24 ) + { + AVPicture output; + avpicture_fill( &output, buffer, PIX_FMT_RGB24, width, height ); + img_convert( &output, PIX_FMT_RGB24, (AVPicture *)frame, pix_fmt, width, height ); + } + else + { + AVPicture output; + avpicture_fill( &output, buffer, PIX_FMT_YUV422, width, height ); + img_convert( &output, PIX_FMT_YUV422, (AVPicture *)frame, pix_fmt, width, height ); + } +#endif +} + +/** Get an image from a frame. +*/ + +static int producer_get_image( mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the properties from the frame + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Obtain the frame number of this frame + mlt_position position = mlt_properties_get_position( frame_properties, "avformat_position" ); + + // Get the producer + mlt_producer this = mlt_properties_get_data( frame_properties, "avformat_producer", NULL ); + + // Get the producer properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Fetch the video_context + AVFormatContext *context = mlt_properties_get_data( properties, "video_context", NULL ); + + // Get the video_index + int index = mlt_properties_get_int( properties, "video_index" ); + + // Obtain the expected frame numer + mlt_position expected = mlt_properties_get_position( properties, "_video_expected" ); + + // Get the video stream + AVStream *stream = context->streams[ index ]; + + // Get codec context + AVCodecContext *codec_context = stream->codec; + + // Packet + AVPacket pkt; + + // Get the conversion frame + AVFrame *av_frame = mlt_properties_get_data( properties, "av_frame", NULL ); + + // Special case pause handling flag + int paused = 0; + + // Special case ffwd handling + int ignore = 0; + + // We may want to use the source fps if available + double source_fps = mlt_properties_get_double( properties, "source_fps" ); + double fps = mlt_producer_get_fps( this ); + + // This is the physical frame position in the source + int req_position = ( int )( position / fps * source_fps + 0.5 ); + + // Get the seekable status + int seekable = mlt_properties_get_int( properties, "seekable" ); + + // Generate the size in bytes + int size = 0; + + // Hopefully provide better support for streams... + int av_bypass = mlt_properties_get_int( properties, "av_bypass" ); + + // Determines if we have to decode all frames in a sequence + int must_decode = 1; + + // Set the result arguments that we know here (only *buffer is now required) + *width = codec_context->width; + *height = codec_context->height; + + switch ( *format ) + { + case mlt_image_yuv420p: + size = *width * 3 * ( *height + 1 ) / 2; + break; + case mlt_image_rgb24: + size = *width * ( *height + 1 ) * 3; + break; + default: + *format = mlt_image_yuv422; + size = *width * ( *height + 1 ) * 2; + break; + } + + // Set this on the frame properties + mlt_properties_set_int( frame_properties, "width", *width ); + mlt_properties_set_int( frame_properties, "height", *height ); + + // Construct the output image + *buffer = mlt_pool_alloc( size ); + + // Temporary hack to improve intra frame only + must_decode = strcmp( codec_context->codec->name, "mjpeg" ) && + strcmp( codec_context->codec->name, "rawvideo" ) && + strcmp( codec_context->codec->name, "dvvideo" ); + + // Seek if necessary + if ( position != expected ) + { + if ( av_frame != NULL && position + 1 == expected ) + { + // We're paused - use last image + paused = 1; + } + else if ( !seekable && position > expected && ( position - expected ) < 250 ) + { + // Fast forward - seeking is inefficient for small distances - just ignore following frames + ignore = ( int )( ( position - expected ) / fps * source_fps ); + } + else if ( seekable && ( position < expected || position - expected >= 12 ) ) + { + // Calculate the timestamp for the requested frame + int64_t timestamp = ( int64_t )( ( double )req_position / source_fps * AV_TIME_BASE + 0.5 ); + if ( ( uint64_t )context->start_time != AV_NOPTS_VALUE ) + timestamp += context->start_time; + if ( must_decode ) + timestamp -= AV_TIME_BASE; + if ( timestamp < 0 ) + timestamp = 0; + + // Set to the timestamp + av_seek_frame( context, -1, timestamp, AVSEEK_FLAG_BACKWARD ); + + // Remove the cached info relating to the previous position + mlt_properties_set_int( properties, "_current_position", -1 ); + mlt_properties_set_int( properties, "_last_position", -1 ); + mlt_properties_set_data( properties, "av_frame", NULL, 0, NULL, NULL ); + av_frame = NULL; + } + } + + // Duplicate the last image if necessary (see comment on rawvideo below) + int current_position = mlt_properties_get_int( properties, "_current_position" ); + int got_picture = mlt_properties_get_int( properties, "_got_picture" ); + if ( av_frame != NULL && got_picture && ( paused || current_position >= req_position ) && av_bypass == 0 ) + { + // Duplicate it + convert_image( av_frame, *buffer, codec_context->pix_fmt, *format, *width, *height ); + + // Set this on the frame properties + mlt_properties_set_data( frame_properties, "image", *buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + } + else + { + int ret = 0; + int int_position = 0; + got_picture = 0; + + av_init_packet( &pkt ); + + // Construct an AVFrame for YUV422 conversion + if ( av_frame == NULL ) + { + av_frame = avcodec_alloc_frame( ); + mlt_properties_set_data( properties, "av_frame", av_frame, 0, av_free, NULL ); + } + + while( ret >= 0 && !got_picture ) + { + // Read a packet + ret = av_read_frame( context, &pkt ); + + // We only deal with video from the selected video_index + if ( ret >= 0 && pkt.stream_index == index && pkt.size > 0 ) + { + // Determine time code of the packet + int_position = ( int )( av_q2d( stream->time_base ) * pkt.dts * source_fps + 0.5 ); + if ( context->start_time != AV_NOPTS_VALUE ) + int_position -= ( int )( context->start_time * source_fps / AV_TIME_BASE + 0.5 ); + int last_position = mlt_properties_get_int( properties, "_last_position" ); + if ( int_position == last_position ) + int_position = last_position + 1; + mlt_properties_set_int( properties, "_last_position", int_position ); + + // Decode the image + if ( must_decode || int_position >= req_position ) + ret = avcodec_decode_video( codec_context, av_frame, &got_picture, pkt.data, pkt.size ); + + if ( got_picture ) + { + // Handle ignore + if ( int_position < req_position ) + { + ignore = 0; + got_picture = 0; + } + else if ( int_position >= req_position ) + { + ignore = 0; + } + else if ( ignore -- ) + { + got_picture = 0; + } + } + } + + // Now handle the picture if we have one + if ( got_picture ) + { + mlt_properties_set_int( frame_properties, "progressive", !av_frame->interlaced_frame ); + mlt_properties_set_int( frame_properties, "top_field_first", av_frame->top_field_first ); + convert_image( av_frame, *buffer, codec_context->pix_fmt, *format, *width, *height ); + mlt_properties_set_data( frame_properties, "image", *buffer, size, (mlt_destructor)mlt_pool_release, NULL ); + mlt_properties_set_int( properties, "_current_position", int_position ); + mlt_properties_set_int( properties, "_got_picture", 1 ); + } + + // We're finished with this packet regardless + av_free_packet( &pkt ); + } + } + + // Very untidy - for rawvideo, the packet contains the frame, hence the free packet + // above will break the pause behaviour - so we wipe the frame now + if ( !strcmp( codec_context->codec->name, "rawvideo" ) ) + mlt_properties_set_data( properties, "av_frame", NULL, 0, NULL, NULL ); + + // Set the field order property for this frame + mlt_properties_set_int( frame_properties, "top_field_first", mlt_properties_get_int( properties, "top_field_first" ) ); + + // Regardless of speed, we expect to get the next frame (cos we ain't too bright) + mlt_properties_set_position( properties, "_video_expected", position + 1 ); + + return 0; +} + +/** Set up video handling. +*/ + +static void producer_set_up_video( mlt_producer this, mlt_frame frame ) +{ + // Get the properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Fetch the video_context + AVFormatContext *context = mlt_properties_get_data( properties, "video_context", NULL ); + + // Get the video_index + int index = mlt_properties_get_int( properties, "video_index" ); + + // Get the frame properties + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + if ( context != NULL && index != -1 ) + { + // Get the video stream + AVStream *stream = context->streams[ index ]; + + // Get codec context + AVCodecContext *codec_context = stream->codec; + + // Get the codec + AVCodec *codec = mlt_properties_get_data( properties, "video_codec", NULL ); + + // Initialise the codec if necessary + if ( codec == NULL ) + { + // Find the codec + codec = avcodec_find_decoder( codec_context->codec_id ); + + // If we don't have a codec and we can't initialise it, we can't do much more... + if ( codec != NULL && avcodec_open( codec_context, codec ) >= 0 ) + { + // Now store the codec with its destructor + mlt_properties_set_data( properties, "video_codec", codec_context, 0, producer_codec_close, NULL ); + } + else + { + // Remember that we can't use this later + mlt_properties_set_int( properties, "video_index", -1 ); + } + } + + // No codec, no show... + if ( codec != NULL ) + { + double source_fps = 0; + int norm_aspect_ratio = mlt_properties_get_int( properties, "norm_aspect_ratio" ); + double force_aspect_ratio = mlt_properties_get_double( properties, "force_aspect_ratio" ); + double aspect_ratio; + + // XXX: We won't know the real aspect ratio until an image is decoded + // but we do need it now (to satisfy filter_resize) - take a guess based + // on pal/ntsc + if ( force_aspect_ratio > 0.0 ) + { + aspect_ratio = force_aspect_ratio; + } + else if ( !norm_aspect_ratio && codec_context->sample_aspect_ratio.num > 0 ) + { + aspect_ratio = av_q2d( codec_context->sample_aspect_ratio ); + } + else + { + int is_pal = mlt_producer_get_fps( this ) == 25.0; + aspect_ratio = is_pal ? 59.0/54.0 : 10.0/11.0; + } + + // Determine the fps + source_fps = ( double )codec_context->time_base.den / ( codec_context->time_base.num == 0 ? 1 : codec_context->time_base.num ); + + // We'll use fps if it's available + if ( source_fps > 0 ) + mlt_properties_set_double( properties, "source_fps", source_fps ); + else + mlt_properties_set_double( properties, "source_fps", mlt_producer_get_fps( this ) ); + mlt_properties_set_double( properties, "aspect_ratio", aspect_ratio ); + + // Set the width and height + mlt_properties_set_int( frame_properties, "width", codec_context->width ); + mlt_properties_set_int( frame_properties, "height", codec_context->height ); + mlt_properties_set_double( frame_properties, "aspect_ratio", aspect_ratio ); + + mlt_frame_push_get_image( frame, producer_get_image ); + mlt_properties_set_data( frame_properties, "avformat_producer", this, 0, NULL, NULL ); + } + else + { + mlt_properties_set_int( frame_properties, "test_image", 1 ); + } + } + else + { + mlt_properties_set_int( frame_properties, "test_image", 1 ); + } +} + +/** Get the audio from a frame. +*/ + +static int producer_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the properties from the frame + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Obtain the frame number of this frame + mlt_position position = mlt_properties_get_position( frame_properties, "avformat_position" ); + + // Get the producer + mlt_producer this = mlt_properties_get_data( frame_properties, "avformat_producer", NULL ); + + // Get the producer properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Fetch the audio_context + AVFormatContext *context = mlt_properties_get_data( properties, "audio_context", NULL ); + + // Get the audio_index + int index = mlt_properties_get_int( properties, "audio_index" ); + + // Get the seekable status + int seekable = mlt_properties_get_int( properties, "seekable" ); + + // Obtain the expected frame numer + mlt_position expected = mlt_properties_get_position( properties, "_audio_expected" ); + + // Obtain the resample context if it exists (not always needed) + ReSampleContext *resample = mlt_properties_get_data( properties, "audio_resample", NULL ); + + // Obtain the audio buffer + int16_t *audio_buffer = mlt_properties_get_data( properties, "audio_buffer", NULL ); + + // Get amount of audio used + int audio_used = mlt_properties_get_int( properties, "_audio_used" ); + + // Calculate the real time code + double real_timecode = producer_time_of_frame( this, position ); + + // Get the audio stream + AVStream *stream = context->streams[ index ]; + + // Get codec context + AVCodecContext *codec_context = stream->codec; + + // Packet + AVPacket pkt; + + // Number of frames to ignore (for ffwd) + int ignore = 0; + + // Flag for paused (silence) + int paused = 0; + + // Check for resample and create if necessary + if ( resample == NULL && codec_context->channels <= 2 ) + { + // Create the resampler + resample = audio_resample_init( *channels, codec_context->channels, *frequency, codec_context->sample_rate ); + + // And store it on properties + mlt_properties_set_data( properties, "audio_resample", resample, 0, ( mlt_destructor )audio_resample_close, NULL ); + } + else if ( resample == NULL ) + { + *channels = codec_context->channels; + *frequency = codec_context->sample_rate; + } + + // Check for audio buffer and create if necessary + if ( audio_buffer == NULL ) + { + // Allocate the audio buffer + audio_buffer = mlt_pool_alloc( AVCODEC_MAX_AUDIO_FRAME_SIZE * sizeof( int16_t ) ); + + // And store it on properties for reuse + mlt_properties_set_data( properties, "audio_buffer", audio_buffer, 0, ( mlt_destructor )mlt_pool_release, NULL ); + } + + // Seek if necessary + if ( position != expected ) + { + if ( position + 1 == expected ) + { + // We're paused - silence required + paused = 1; + } + else if ( !seekable && position > expected && ( position - expected ) < 250 ) + { + // Fast forward - seeking is inefficient for small distances - just ignore following frames + ignore = position - expected; + } + else if ( position < expected || position - expected >= 12 ) + { + // Set to the real timecode + if ( av_seek_frame( context, -1, mlt_properties_get_double( properties, "_start_time" ) + real_timecode * 1000000.0, AVSEEK_FLAG_BACKWARD ) != 0 ) + paused = 1; + + // Clear the usage in the audio buffer + audio_used = 0; + } + } + + // Get the audio if required + if ( !paused ) + { + int ret = 0; + int got_audio = 0; + int16_t *temp = av_malloc( sizeof( int16_t ) * AVCODEC_MAX_AUDIO_FRAME_SIZE ); + + av_init_packet( &pkt ); + + while( ret >= 0 && !got_audio ) + { + // Check if the buffer already contains the samples required + if ( audio_used >= *samples && ignore == 0 ) + { + got_audio = 1; + break; + } + + // Read a packet + ret = av_read_frame( context, &pkt ); + + int len = pkt.size; + uint8_t *ptr = pkt.data; + + // We only deal with audio from the selected audio_index + while ( ptr != NULL && ret >= 0 && pkt.stream_index == index && len > 0 ) + { + int data_size = sizeof( int16_t ) * AVCODEC_MAX_AUDIO_FRAME_SIZE; + + // Decode the audio +#if (LIBAVCODEC_VERSION_INT >= ((51<<16)+(29<<8)+0)) + ret = avcodec_decode_audio2( codec_context, temp, &data_size, ptr, len ); +#else + ret = avcodec_decode_audio( codec_context, temp, &data_size, ptr, len ); +#endif + if ( ret < 0 ) + { + ret = 0; + break; + } + + len -= ret; + ptr += ret; + + if ( data_size > 0 ) + { + if ( resample != NULL ) + { + audio_used += audio_resample( resample, &audio_buffer[ audio_used * *channels ], temp, data_size / ( codec_context->channels * sizeof( int16_t ) ) ); + } + else + { + memcpy( &audio_buffer[ audio_used * *channels ], temp, data_size ); + audio_used += data_size / ( codec_context->channels * sizeof( int16_t ) ); + } + + // Handle ignore + while ( ignore && audio_used > *samples ) + { + ignore --; + audio_used -= *samples; + memmove( audio_buffer, &audio_buffer[ *samples * *channels ], audio_used * sizeof( int16_t ) ); + } + } + + // If we're behind, ignore this packet + float current_pts = av_q2d( stream->time_base ) * pkt.pts; + if ( seekable && ( !ignore && current_pts <= ( real_timecode - 0.02 ) ) ) + ignore = 1; + } + + // We're finished with this packet regardless + av_free_packet( &pkt ); + } + + *buffer = mlt_pool_alloc( *samples * *channels * sizeof( int16_t ) ); + mlt_properties_set_data( frame_properties, "audio", *buffer, 0, ( mlt_destructor )mlt_pool_release, NULL ); + + // Now handle the audio if we have enough + if ( audio_used >= *samples ) + { + memcpy( *buffer, audio_buffer, *samples * *channels * sizeof( int16_t ) ); + audio_used -= *samples; + memmove( audio_buffer, &audio_buffer[ *samples * *channels ], audio_used * *channels * sizeof( int16_t ) ); + } + else + { + memset( *buffer, 0, *samples * *channels * sizeof( int16_t ) ); + } + + // Store the number of audio samples still available + mlt_properties_set_int( properties, "_audio_used", audio_used ); + + // Release the temporary audio + av_free( temp ); + } + else + { + // Get silence and don't touch the context + mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples ); + } + + // Regardless of speed (other than paused), we expect to get the next frame + if ( !paused ) + mlt_properties_set_position( properties, "_audio_expected", position + 1 ); + + return 0; +} + +/** Set up audio handling. +*/ + +static void producer_set_up_audio( mlt_producer this, mlt_frame frame ) +{ + // Get the properties + mlt_properties properties = MLT_PRODUCER_PROPERTIES( this ); + + // Fetch the audio_context + AVFormatContext *context = mlt_properties_get_data( properties, "audio_context", NULL ); + + // Get the audio_index + int index = mlt_properties_get_int( properties, "audio_index" ); + + // Deal with audio context + if ( context != NULL && index != -1 ) + { + // Get the frame properties + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Get the audio stream + AVStream *stream = context->streams[ index ]; + + // Get codec context + AVCodecContext *codec_context = stream->codec; + + // Get the codec + AVCodec *codec = mlt_properties_get_data( properties, "audio_codec", NULL ); + + // Initialise the codec if necessary + if ( codec == NULL ) + { + // Find the codec + codec = avcodec_find_decoder( codec_context->codec_id ); + + // If we don't have a codec and we can't initialise it, we can't do much more... + if ( codec != NULL && avcodec_open( codec_context, codec ) >= 0 ) + { + // Now store the codec with its destructor + mlt_properties_set_data( properties, "audio_codec", codec_context, 0, producer_codec_close, NULL ); + + } + else + { + // Remember that we can't use this later + mlt_properties_set_int( properties, "audio_index", -1 ); + } + } + + // No codec, no show... + if ( codec != NULL ) + { + mlt_frame_push_audio( frame, producer_get_audio ); + mlt_properties_set_data( frame_properties, "avformat_producer", this, 0, NULL, NULL ); + mlt_properties_set_int( frame_properties, "frequency", codec_context->sample_rate ); + mlt_properties_set_int( frame_properties, "channels", codec_context->channels ); + } + } +} + +/** Our get frame implementation. +*/ + +static int producer_get_frame( mlt_producer this, mlt_frame_ptr frame, int index ) +{ + // Create an empty frame + *frame = mlt_frame_init( ); + + // Update timecode on the frame we're creating + mlt_frame_set_position( *frame, mlt_producer_position( this ) ); + + // Set the position of this producer + mlt_properties_set_position( MLT_FRAME_PROPERTIES( *frame ), "avformat_position", mlt_producer_frame( this ) ); + + // Set up the video + producer_set_up_video( this, *frame ); + + // Set up the audio + producer_set_up_audio( this, *frame ); + + // Set the aspect_ratio + mlt_properties_set_double( MLT_FRAME_PROPERTIES( *frame ), "aspect_ratio", mlt_properties_get_double( MLT_PRODUCER_PROPERTIES( this ), "aspect_ratio" ) ); + + // Calculate the next timecode + mlt_producer_prepare_next( this ); + + return 0; +} diff --git a/src/modules/avformat/producer_avformat.h b/src/modules/avformat/producer_avformat.h new file mode 100644 index 0000000..5517d4d --- /dev/null +++ b/src/modules/avformat/producer_avformat.h @@ -0,0 +1,28 @@ +/* + * producer_avformat.h -- avformat producer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PRODUCER_AVFORMAT_H_ +#define _PRODUCER_AVFORMAT_H_ + +#include + +extern mlt_producer producer_avformat_init( char *file ); + +#endif diff --git a/src/modules/configure b/src/modules/configure new file mode 100755 index 0000000..ee65413 --- /dev/null +++ b/src/modules/configure @@ -0,0 +1,36 @@ +#!/bin/sh + +# Clean up disables if not in help mode +[ "$help" != "1" ] && rm -f disable-* producers.dat filters.dat transitions.dat consumers.dat + +# Create the make.inc file +echo SUBDIRS = `find . -maxdepth 1 -type d | grep -v CVS | grep -v "^.$" | sed 's/\.\///'` > make.inc + +# Iterate through arguments +for i in "$@" +do + case $i in + --disable-* ) touch disable-${i#--disable-} ;; + esac +done + +# Iterate through each of the components +for i in * +do + if [ -x $i/configure -a \( "$help" = "1" -o ! -f disable-$i \) ] + then + if [ "$gpl" = "true" -o ! -f $i/gpl ] + then + [ "$help" = "0" ] && echo "Configuring modules/$i:" + olddir2=`pwd` + cd $i + ./configure "$@" + [ $? != 0 ] && exit 1 + cd $olddir2 + elif [ "$help" = "0" ] + then + touch disable-$i + fi + fi +done + diff --git a/src/modules/core/Makefile b/src/modules/core/Makefile new file mode 100644 index 0000000..3511589 --- /dev/null +++ b/src/modules/core/Makefile @@ -0,0 +1,61 @@ +include ../../../config.mak + +TARGET = ../libmltcore$(LIBSUF) + +OBJS = factory.o \ + producer_colour.o \ + producer_noise.o \ + producer_ppm.o \ + filter_brightness.o \ + filter_channelcopy.o \ + filter_data_feed.o \ + filter_data_show.o \ + filter_gamma.o \ + filter_greyscale.o \ + filter_luma.o \ + filter_mirror.o \ + filter_mono.o \ + filter_obscure.o \ + filter_region.o \ + filter_rescale.o \ + filter_resize.o \ + filter_transition.o \ + filter_watermark.o \ + transition_composite.o \ + transition_luma.o \ + transition_mix.o \ + transition_region.o \ + consumer_null.o + +ASM_OBJS = + +CFLAGS += -I../.. + +LDFLAGS+=-L../../framework -lmlt + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) $(ASM_OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(ASM_OBJS) $(LDFLAGS) + +composite_line_yuv_mmx.o: composite_line_yuv_mmx.S + $(CC) -o $@ -c composite_line_yuv_mmx.S + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(ASM_OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(prefix)/lib/mlt/modules" + install -m 644 ../data_fx.properties "$(DESTDIR)$(prefix)/lib/mlt/modules" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/core/composite_line_yuv_mmx.S b/src/modules/core/composite_line_yuv_mmx.S new file mode 100644 index 0000000..27af4dc --- /dev/null +++ b/src/modules/core/composite_line_yuv_mmx.S @@ -0,0 +1,211 @@ + .file "composite_line_yuv_mmx" + .version "01.01" + +gcc2_compiled.: +.data + +.text + .align 16 + +#if !defined(__MINGW32__) && !defined(__CYGWIN__) +.globl composite_line_yuv_mmx + .type composite_line_yuv_mmx,@function +composite_line_yuv_mmx: +#else +.globl _composite_line_yuv_mmx +_composite_line_yuv_mmx: +#endif + +/* + * Arguments + * + * dest: 8(%ebp) %esi + * src: 12(%ebp) + * width_src: 16(%ebp) + * alpha: 20(%ebp) + * weight: 24(%ebp) + * luma: 28(%ebp) + * softness: 32(%ebp) + */ + +/* + * Function call entry + */ + pushl %ebp + movl %esp,%ebp + subl $28,%esp + pushl %edi + pushl %esi + pushl %ebx + +/* Initialise */ + movl 8(%ebp), %esi # get dest + movl $0, %edx # j = 0 + +.loop: + + movl $0xffff, %ecx # a = 255 + cmpl $0, 20(%ebp) # if alpha == NULL + je .noalpha + movl 20(%ebp), %edi # a = alpha[ j ] + movb (%edi,%edx), %cl +.noalpha: + movl %ecx, -24(%ebp) # save ecx + + movl 24(%ebp), %eax # mix = weight + cmpl $0, 28(%ebp) # if luma == NULL + je .noluma + movl 28(%ebp), %edi # mix = ... + movl %edx, %ebx + sall $1, %ebx + movw (%edi,%ebx), %bx # luma[ j*2 ] + cmpl %ebx, %eax + jl .luma0 + movl %ebx, %ecx + addl 32(%ebp), %ecx # + softness + cmpl %ecx, %eax + jge .luma1 + /* TODO: linear interpolate between edges */ + subw %bx, %ax + sall $8, %eax + subw %bx, %cx + movl %edx, %ebx + divw %cx + movl %ebx, %edx + jmp .noluma +.luma0: + movl $0, %eax + jmp .noluma +.luma1: + movl $0xffff, %eax +.noluma: + shrl $8, %eax + + movl %edx, %ebx # edx will be destroyed by mulw + movl -24(%ebp), %ecx # restore ecx + mull %ecx # mix = mix * a... + movl %ebx, %edx # restore edx + shrl $8, %eax # >>8 + andl $0xff, %eax + +/* put alpha and (1-alpha) into mm0 */ +/* 0 aa 0 1-a 0 aa 0 1-a */ + + /* duplicate word */ + movl %eax, %ecx + shll $16, %ecx + orl %eax, %ecx + + movd %ecx, %mm1 + + /* (1 << 16) - mix */ + movl $0x000000ff, %ecx + subl %eax, %ecx + andl $0xff, %ecx + + /* duplicate word */ + movl %ecx, %eax + shll $16, %eax + orl %eax, %ecx + + movd %ecx, %mm0 + + /* unpack words into double words */ + punpcklwd %mm1, %mm0 + +/* put src yuv and dest yuv into mm1 */ +/* 0 UVs 0 UVd 0 Ys 0 Yd */ + + movl 12(%ebp), %edi # get src + movb (%edi), %cl + shll $8, %ecx + movb 1(%edi), %al + shll $24, %eax + orl %eax, %ecx + + movb (%esi), %al # get dest + orl %eax, %ecx + movb 1(%esi), %al + shll $16, %eax + orl %eax, %ecx + + movd %ecx, %mm1 + punpcklbw %mm4, %mm1 + +/* alpha composite */ + pmaddwd %mm1, %mm0 + psrld $8, %mm0 + +/* store result */ + movd %mm0, %eax + movb %al, (%esi) + pextrw $2, %mm0, %eax + movl $128, %eax + movb %al, 1(%esi) + +/* for..next */ + addl $1, %edx # j++ + cmpl 16(%ebp), %edx # if ( j == width_src ) + jge .out + + addl $2, %esi + addl $2, 12(%ebp) + + jmp .loop + +.out: + emms + leal -40(%ebp),%esp + popl %ebx + popl %esi + popl %edi + movl %ebp,%esp + popl %ebp + ret + + +/********************************************/ + +.align 8 +#if !defined(__MINGW32__) && !defined(__CYGWIN__) +.globl composite_have_mmx + .type composite_have_mmx,@function +composite_have_mmx: +#else +.globl _composite_have_mmx +_composite_have_mmx: +#endif + + push %ebx + +# Check if bit 21 in flags word is writeable + + pushfl + popl %eax + movl %eax,%ebx + xorl $0x00200000, %eax + pushl %eax + popfl + pushfl + popl %eax + + cmpl %eax, %ebx + + je .notfound + +# OK, we have CPUID + + movl $1, %eax + cpuid + + test $0x00800000, %edx + jz .notfound + + movl $1, %eax + jmp .out2 + +.notfound: + movl $0, %eax +.out2: + popl %ebx + ret diff --git a/src/modules/core/configure b/src/modules/core/configure new file mode 100755 index 0000000..160844b --- /dev/null +++ b/src/modules/core/configure @@ -0,0 +1,42 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + +cat << EOF >> ../producers.dat +color libmltcore$LIBSUF +colour libmltcore$LIBSUF +noise libmltcore$LIBSUF +ppm libmltcore$LIBSUF +EOF + +cat << EOF >> ../filters.dat +brightness libmltcore$LIBSUF +channelcopy libmltcore$LIBSUF +data_feed libmltcore$LIBSUF +data_show libmltcore$LIBSUF +gamma libmltcore$LIBSUF +greyscale libmltcore$LIBSUF +luma libmltcore$LIBSUF +mirror libmltcore$LIBSUF +mono libmltcore$LIBSUF +obscure libmltcore$LIBSUF +region libmltcore$LIBSUF +rescale libmltcore$LIBSUF +resize libmltcore$LIBSUF +transition libmltcore$LIBSUF +watermark libmltcore$LIBSUF +EOF + +cat << EOF >> ../transitions.dat +composite libmltcore$LIBSUF +luma libmltcore$LIBSUF +mix libmltcore$LIBSUF +region libmltcore$LIBSUF +EOF + +cat << EOF >> ../consumers.dat +null libmltcore$LIBSUF +EOF + +fi diff --git a/src/modules/core/consumer_null.c b/src/modules/core/consumer_null.c new file mode 100644 index 0000000..6b1fb01 --- /dev/null +++ b/src/modules/core/consumer_null.c @@ -0,0 +1,183 @@ +/* + * consumer_null.c -- a null consumer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// Local header files +#include "consumer_null.h" + +// mlt Header files +#include + +// System header files +#include +#include +#include +#include + +// Forward references. +static int consumer_start( mlt_consumer this ); +static int consumer_stop( mlt_consumer this ); +static int consumer_is_stopped( mlt_consumer this ); +static void *consumer_thread( void *arg ); +static void consumer_close( mlt_consumer this ); + +/** Initialise the dv consumer. +*/ + +mlt_consumer consumer_null_init( char *arg ) +{ + // Allocate the consumer + mlt_consumer this = mlt_consumer_new( ); + + // If memory allocated and initialises without error + if ( this != NULL ) + { + // Assign close callback + this->close = consumer_close; + + // Set up start/stop/terminated callbacks + this->start = consumer_start; + this->stop = consumer_stop; + this->is_stopped = consumer_is_stopped; + } + + // Return this + return this; +} + +/** Start the consumer. +*/ + +static int consumer_start( mlt_consumer this ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Check that we're not already running + if ( !mlt_properties_get_int( properties, "running" ) ) + { + // Allocate a thread + pthread_t *thread = calloc( 1, sizeof( pthread_t ) ); + + // Assign the thread to properties + mlt_properties_set_data( properties, "thread", thread, sizeof( pthread_t ), free, NULL ); + + // Set the running state + mlt_properties_set_int( properties, "running", 1 ); + mlt_properties_set_int( properties, "joined", 0 ); + + // Create the thread + pthread_create( thread, NULL, consumer_thread, this ); + } + return 0; +} + +/** Stop the consumer. +*/ + +static int consumer_stop( mlt_consumer this ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Check that we're running + if ( !mlt_properties_get_int( properties, "joined" ) ) + { + // Get the thread + pthread_t *thread = mlt_properties_get_data( properties, "thread", NULL ); + + // Stop the thread + mlt_properties_set_int( properties, "running", 0 ); + mlt_properties_set_int( properties, "joined", 1 ); + + // Wait for termination + pthread_join( *thread, NULL ); + } + + return 0; +} + +/** Determine if the consumer is stopped. +*/ + +static int consumer_is_stopped( mlt_consumer this ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + return !mlt_properties_get_int( properties, "running" ); +} + +/** The main thread - the argument is simply the consumer. +*/ + +static void *consumer_thread( void *arg ) +{ + // Map the argument to the object + mlt_consumer this = arg; + + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( this ); + + // Convenience functionality + int terminate_on_pause = mlt_properties_get_int( properties, "terminate_on_pause" ); + int terminated = 0; + + // Frame and size + mlt_frame frame = NULL; + + // Loop while running + while( !terminated && mlt_properties_get_int( properties, "running" ) ) + { + // Get the frame + frame = mlt_consumer_rt_frame( this ); + + // Check for termination + if ( terminate_on_pause && frame != NULL ) + terminated = mlt_properties_get_double( MLT_FRAME_PROPERTIES( frame ), "_speed" ) == 0.0; + + // Check that we have a frame to work with + if ( frame != NULL ) + { + // Close the frame + mlt_events_fire( properties, "consumer-frame-show", frame, NULL ); + mlt_frame_close( frame ); + } + } + + // Indicate that the consumer is stopped + mlt_properties_set_int( properties, "running", 0 ); + mlt_consumer_stopped( this ); + + return NULL; +} + +/** Close the consumer. +*/ + +static void consumer_close( mlt_consumer this ) +{ + // Stop the consumer + mlt_consumer_stop( this ); + + // Close the parent + mlt_consumer_close( this ); + + // Free the memory + free( this ); +} diff --git a/src/modules/core/consumer_null.h b/src/modules/core/consumer_null.h new file mode 100644 index 0000000..2b9f204 --- /dev/null +++ b/src/modules/core/consumer_null.h @@ -0,0 +1,28 @@ +/* + * consumer_null.h -- null consumer + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CONSUMER_NULL_H_ +#define _CONSUMER_NULL_H + +#include + +extern mlt_consumer consumer_null_init( char *arg ); + +#endif diff --git a/src/modules/core/factory.c b/src/modules/core/factory.c new file mode 100644 index 0000000..380a247 --- /dev/null +++ b/src/modules/core/factory.c @@ -0,0 +1,112 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "producer_colour.h" +#include "producer_noise.h" +#include "producer_ppm.h" +#include "filter_brightness.h" +#include "filter_channelcopy.h" +#include "filter_data.h" +#include "filter_gamma.h" +#include "filter_greyscale.h" +#include "filter_luma.h" +#include "filter_mirror.h" +#include "filter_mono.h" +#include "filter_obscure.h" +#include "filter_rescale.h" +#include "filter_resize.h" +#include "filter_region.h" +#include "filter_transition.h" +#include "filter_watermark.h" +#include "transition_composite.h" +#include "transition_luma.h" +#include "transition_mix.h" +#include "transition_region.h" +#include "consumer_null.h" + +void *mlt_create_producer( char *id, void *arg ) +{ + if ( !strcmp( id, "color" ) ) + return producer_colour_init( arg ); + if ( !strcmp( id, "colour" ) ) + return producer_colour_init( arg ); + if ( !strcmp( id, "noise" ) ) + return producer_noise_init( arg ); + if ( !strcmp( id, "ppm" ) ) + return producer_ppm_init( arg ); + return NULL; +} + +void *mlt_create_filter( char *id, void *arg ) +{ + if ( !strcmp( id, "brightness" ) ) + return filter_brightness_init( arg ); + if ( !strcmp( id, "channelcopy" ) ) + return filter_channelcopy_init( arg ); + if ( !strcmp( id, "data_feed" ) ) + return filter_data_feed_init( arg ); + if ( !strcmp( id, "data_show" ) ) + return filter_data_show_init( arg ); + if ( !strcmp( id, "gamma" ) ) + return filter_gamma_init( arg ); + if ( !strcmp( id, "greyscale" ) ) + return filter_greyscale_init( arg ); + if ( !strcmp( id, "luma" ) ) + return filter_luma_init( arg ); + if ( !strcmp( id, "mirror" ) ) + return filter_mirror_init( arg ); + if ( !strcmp( id, "mono" ) ) + return filter_mono_init( arg ); + if ( !strcmp( id, "obscure" ) ) + return filter_obscure_init( arg ); + if ( !strcmp( id, "region" ) ) + return filter_region_init( arg ); + if ( !strcmp( id, "rescale" ) ) + return filter_rescale_init( arg ); + if ( !strcmp( id, "resize" ) ) + return filter_resize_init( arg ); + else if ( !strcmp( id, "transition" ) ) + return filter_transition_init( arg ); + if ( !strcmp( id, "watermark" ) ) + return filter_watermark_init( arg ); + return NULL; +} + +void *mlt_create_transition( char *id, void *arg ) +{ + if ( !strcmp( id, "composite" ) ) + return transition_composite_init( arg ); + if ( !strcmp( id, "luma" ) ) + return transition_luma_init( arg ); + if ( !strcmp( id, "mix" ) ) + return transition_mix_init( arg ); + if ( !strcmp( id, "region" ) ) + return transition_region_init( arg ); + return NULL; +} + +void *mlt_create_consumer( char *id, void *arg ) +{ + if ( !strcmp( id, "null" ) ) + return consumer_null_init( arg ); + return NULL; +} diff --git a/src/modules/core/filter_brightness.c b/src/modules/core/filter_brightness.c new file mode 100644 index 0000000..6a79e9f --- /dev/null +++ b/src/modules/core/filter_brightness.c @@ -0,0 +1,103 @@ +/* + * filter_brightness.c -- gamma filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_brightness.h" + +#include + +#include +#include +#include + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Get the image + int error = mlt_frame_get_image( this, image, format, width, height, 1 ); + + // Only process if we have no error and a valid colour space + if ( error == 0 && *format == mlt_image_yuv422 ) + { + // Get the brightness level + double level = mlt_properties_get_double( MLT_FRAME_PROPERTIES( this ), "brightness" ); + + // Only process if level is something other than 1 + if ( level != 1.0 ) + { + uint8_t *p = *image; + uint8_t *q = *image + *width * *height * 2; + int32_t x = 0; + int32_t m = level * ( 1 << 16 ); + + while ( p != q ) + { + x = ( *p * m ) >> 16; + *p = x < 16 ? 16 : x > 235 ? 235 : x; + p += 2; + } + } + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Get the starting brightness level + double level = fabs( mlt_properties_get_double( MLT_FILTER_PROPERTIES( this ), "start" ) ); + + // If there is an end adjust gain to the range + if ( mlt_properties_get( MLT_FILTER_PROPERTIES( this ), "end" ) != NULL ) + { + // Determine the time position of this frame in the transition duration + mlt_position in = mlt_filter_get_in( this ); + mlt_position out = mlt_filter_get_out( this ); + mlt_position time = mlt_frame_get_position( frame ); + double position = ( double )( time - in ) / ( double )( out - in + 1 ); + double end = fabs( mlt_properties_get_double( MLT_FILTER_PROPERTIES( this ), "end" ) ); + level += ( end - level ) * position; + } + + // Push the frame filter + mlt_properties_set_double( MLT_FRAME_PROPERTIES( frame ), "brightness", level ); + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_brightness_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "start", arg == NULL ? "1" : arg ); + } + return this; +} + diff --git a/src/modules/core/filter_brightness.h b/src/modules/core/filter_brightness.h new file mode 100644 index 0000000..b76bed4 --- /dev/null +++ b/src/modules/core/filter_brightness.h @@ -0,0 +1,28 @@ +/* + * filter_brightness.h -- gamma filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_BRIGHTNESS_H_ +#define _FILTER_BRIGHTNESS_H_ + +#include + +extern mlt_filter filter_brightness_init( char *arg ); + +#endif diff --git a/src/modules/core/filter_channelcopy.c b/src/modules/core/filter_channelcopy.c new file mode 100644 index 0000000..bf7ab0b --- /dev/null +++ b/src/modules/core/filter_channelcopy.c @@ -0,0 +1,97 @@ +/* + * filter_channelcopy.c -- copy one audio channel to another + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_channelcopy.h" + +#include + +#include +#include +#define __USE_ISOC99 1 +#include + +/** Get the audio. +*/ + +static int filter_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the properties of the a frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + int i, j; + int from = mlt_properties_get_int( properties, "channelcopy.from" ); + int to = mlt_properties_get_int( properties, "channelcopy.to" ); + + // Get the producer's audio + mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples ); + + // Duplicate channels as necessary + { + int size = *channels * *samples * 2; + int16_t *new_buffer = mlt_pool_alloc( size ); + + mlt_properties_set_data( properties, "audio", new_buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + + // Duplicate the existing channels + for ( i = 0; i < *samples; i++ ) + { + for ( j = 0; j < *channels; j++ ) + { + new_buffer[ ( i * *channels ) + j ] = (*buffer)[ ( i * *channels ) + ( j == to ? from : j ) ]; + } + } + *buffer = new_buffer; + } + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + mlt_properties frame_props = MLT_FRAME_PROPERTIES( frame ); + + // Propogate the parameters + mlt_properties_set_int( frame_props, "channelcopy.to", mlt_properties_get_int( properties, "to" ) ); + mlt_properties_set_int( frame_props, "channelcopy.from", mlt_properties_get_int( properties, "from" ) ); + + // Override the get_audio method + mlt_frame_push_audio( frame, filter_get_audio ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_channelcopy_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + if ( arg != NULL ) + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "to", atoi( arg ) ); + else + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "to", 1 ); + } + return this; +} diff --git a/src/modules/core/filter_channelcopy.h b/src/modules/core/filter_channelcopy.h new file mode 100644 index 0000000..2f0441a --- /dev/null +++ b/src/modules/core/filter_channelcopy.h @@ -0,0 +1,28 @@ +/* + * filter_channelcopy.h -- copy audio channel from one to another + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_CHANNELCOPY_H_ +#define _FILTER_CHANNELCOPY_H_ + +#include + +extern mlt_filter filter_channelcopy_init( char *arg ); + +#endif diff --git a/src/modules/core/filter_data.h b/src/modules/core/filter_data.h new file mode 100644 index 0000000..aed4647 --- /dev/null +++ b/src/modules/core/filter_data.h @@ -0,0 +1,30 @@ +/* + * filter_data.h -- data feed and show filters + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _MLT_FILTER_DATA_H_ +#define _MLT_FILTER_DATA_H_ + +#include + +extern mlt_filter filter_data_feed_init( char * ); +extern mlt_filter filter_data_show_init( char * ); + +#endif + diff --git a/src/modules/core/filter_data_feed.c b/src/modules/core/filter_data_feed.c new file mode 100644 index 0000000..0c1b1bb --- /dev/null +++ b/src/modules/core/filter_data_feed.c @@ -0,0 +1,178 @@ +/* + * filter_data_feed.c -- data feed filter + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include "filter_data.h" +#include + +/** This filter should be used in conjuction with the data_show filter. + The concept of the data_feed is that it can be used to pass titles + or images to render on the frame, but doesn't actually do it + itself. data_feed imposes few rules on what's passed on and the + validity is confirmed in data_show before use. +*/ + +/** Data queue destructor. +*/ + +static void destroy_data_queue( void *arg ) +{ + if ( arg != NULL ) + { + // Assign the correct type + mlt_deque queue = arg; + + // Iterate through each item and destroy them + while ( mlt_deque_peek_front( queue ) != NULL ) + mlt_properties_close( mlt_deque_pop_back( queue ) ); + + // Close the deque + mlt_deque_close( queue ); + } +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Get the filter properties + mlt_properties filter_properties = MLT_FILTER_PROPERTIES( this ); + + // Get the frame properties + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Get the data queue + mlt_deque data_queue = mlt_properties_get_data( frame_properties, "data_queue", NULL ); + + // Get the type of the data feed + char *type = mlt_properties_get( filter_properties, "type" ); + + // Get the in and out points of this filter + int in = mlt_filter_get_in( this ); + int out = mlt_filter_get_out( this ); + + // Create the data queue if it doesn't exist + if ( data_queue == NULL ) + { + // Create the queue + data_queue = mlt_deque_init( ); + + // Assign it to the frame with the destructor + mlt_properties_set_data( frame_properties, "data_queue", data_queue, 0, destroy_data_queue, NULL ); + } + + // Now create the data feed + if ( data_queue != NULL && type != NULL && !strcmp( type, "attr_check" ) ) + { + int i = 0; + int count = mlt_properties_count( frame_properties ); + + for ( i = 0; i < count; i ++ ) + { + char *name = mlt_properties_get_name( frame_properties, i ); + + // Only deal with meta.attr.name values here - these should have a value of 1 to be considered + // Additional properties of the form are meta.attr.name.property are passed down on the feed + if ( !strncmp( name, "meta.attr.", 10 ) && strchr( name + 10, '.' ) == NULL && mlt_properties_get_int( frame_properties, name ) == 1 ) + { + // Temp var to hold name + '.' for pass method + char temp[ 132 ]; + + // Create a new data feed + mlt_properties feed = mlt_properties_new( ); + + // Assign it the base properties + mlt_properties_set( feed, "id", mlt_properties_get( filter_properties, "_unique_id" ) ); + mlt_properties_set( feed, "type", strrchr( name, '.' ) + 1 ); + mlt_properties_set_position( feed, "position", mlt_frame_get_position( frame ) ); + + // Assign in/out of service we're connected to + mlt_properties_set_position( feed, "in", mlt_properties_get_position( frame_properties, "in" ) ); + mlt_properties_set_position( feed, "out", mlt_properties_get_position( frame_properties, "out" ) ); + + // Pass all meta properties + sprintf( temp, "%s.", name ); + mlt_properties_pass( feed, frame_properties, temp ); + + // Push it on to the queue + mlt_deque_push_back( data_queue, feed ); + + // Make sure this attribute only gets processed once + mlt_properties_set_int( frame_properties, name, 0 ); + } + } + } + else if ( data_queue != NULL ) + { + // Create a new data feed + mlt_properties feed = mlt_properties_new( ); + + // Assign it the base properties + mlt_properties_set( feed, "id", mlt_properties_get( filter_properties, "_unique_id" ) ); + mlt_properties_set( feed, "type", type ); + mlt_properties_set_position( feed, "position", mlt_frame_get_position( frame ) ); + + // Assign in/out of service we're connected to + mlt_properties_set_position( feed, "in", mlt_properties_get_position( frame_properties, "in" ) ); + mlt_properties_set_position( feed, "out", mlt_properties_get_position( frame_properties, "out" ) ); + + // Correct in/out to the filter if specified + if ( in != 0 ) + mlt_properties_set_position( feed, "in", in ); + if ( out != 0 ) + mlt_properties_set_position( feed, "out", out ); + + // Pass the properties which start with a "feed." prefix + // Note that 'feed.text' in the filter properties becomes 'text' on the feed + mlt_properties_pass( feed, filter_properties, "feed." ); + + // Push it on to the queue + mlt_deque_push_back( data_queue, feed ); + } + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_data_feed_init( char *arg ) +{ + // Create the filter + mlt_filter this = mlt_filter_new( ); + + // Initialise it + if ( this != NULL ) + { + // Get the properties + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + // Assign the argument (default to titles) + mlt_properties_set( properties, "type", arg == NULL ? "titles" : arg ); + + // Specify the processing method + this->process = filter_process; + } + + return this; +} + diff --git a/src/modules/core/filter_data_show.c b/src/modules/core/filter_data_show.c new file mode 100644 index 0000000..6931f50 --- /dev/null +++ b/src/modules/core/filter_data_show.c @@ -0,0 +1,344 @@ +/* + * filter_data_show.c -- data feed filter + * Copyright (C) 2004-2005 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_data.h" +#include +#include +#include + +/** Handle the profile. +*/ + +static mlt_filter obtain_filter( mlt_filter filter, char *type ) +{ + // Result to return + mlt_filter result = NULL; + + // Miscelaneous variable + int i = 0; + int type_len = strlen( type ); + + // Get the properties of the data show filter + mlt_properties filter_properties = MLT_FILTER_PROPERTIES( filter ); + + // Get the profile properties + mlt_properties profile_properties = mlt_properties_get_data( filter_properties, "profile_properties", NULL ); + + // Obtain the profile_properties if we haven't already + if ( profile_properties == NULL ) + { + char temp[ 512 ]; + + // Get the profile requested + char *profile = mlt_properties_get( filter_properties, "resource" ); + + // If none is specified, pick up the default for this normalisation + if ( profile == NULL ) + sprintf( temp, "%s/feeds/%s/data_fx.properties", mlt_factory_prefix( ), mlt_environment( "MLT_NORMALISATION" ) ); + else if ( strchr( profile, '%' ) ) + sprintf( temp, "%s/feeds/%s/%s", mlt_factory_prefix( ), mlt_environment( "MLT_NORMALISATION" ), strchr( profile, '%' ) + 1 ); + else + strcpy( temp, profile ); + + // Load the specified profile or use the default + profile_properties = mlt_properties_load( temp ); + + // Store for later retrieval + mlt_properties_set_data( filter_properties, "profile_properties", profile_properties, 0, ( mlt_destructor )mlt_properties_close, NULL ); + } + + if ( profile_properties != NULL ) + { + for ( i = 0; i < mlt_properties_count( profile_properties ); i ++ ) + { + char *name = mlt_properties_get_name( profile_properties, i ); + char *value = mlt_properties_get_value( profile_properties, i ); + + if ( result == NULL && !strcmp( name, type ) && result == NULL ) + result = mlt_factory_filter( value, NULL ); + else if ( result != NULL && !strncmp( name, type, type_len ) && name[ type_len ] == '.' ) + mlt_properties_set( MLT_FILTER_PROPERTIES( result ), name + type_len + 1, value ); + else if ( result != NULL ) + break; + } + } + + return result; +} + +/** Retrieve medatata value +*/ + +char* metadata_value(mlt_properties properties, char* name) +{ + if (name == NULL) return NULL; + char *meta = malloc( strlen(name) + 18 ); + sprintf( meta, "meta.attr.%s.markup", name); + char *result = mlt_properties_get( properties, meta); + free(meta); + return result; +} + +/** Convert frames to Timecode +*/ + +char* frame_to_timecode( int frames , int fps) +{ + if (fps == 0) return strdup("-"); + char *res = malloc(12); + int seconds = frames / (int) fps; + frames = frames % ((int) fps); + int minutes = seconds / 60; + seconds = seconds % 60; + int hours = minutes / 60; + minutes = minutes % 60; + sprintf(res, "%.2d:%.2d:%.2d:%.2d", hours, minutes, seconds, frames); + return res; +} + +/** Process the frame for the requested type +*/ + +static int process_feed( mlt_properties feed, mlt_filter filter, mlt_frame frame ) +{ + // Error return + int error = 1; + + // Get the properties of the data show filter + mlt_properties filter_properties = MLT_FILTER_PROPERTIES( filter ); + + // Get the type requested by the feeding filter + char *type = mlt_properties_get( feed, "type" ); + + // Fetch the filter associated to this type + mlt_filter requested = mlt_properties_get_data( filter_properties, type, NULL ); + + // If it doesn't exist, then create it now + if ( requested == NULL ) + { + // Source filter from profile + requested = obtain_filter( filter, type ); + + // Store it on the properties for subsequent retrieval/destruction + mlt_properties_set_data( filter_properties, type, requested, 0, ( mlt_destructor )mlt_filter_close, NULL ); + } + + // If we have one, then process it now... + if ( requested != NULL ) + { + int i = 0; + mlt_properties properties = MLT_FILTER_PROPERTIES( requested ); + static char *prefix = "properties."; + int len = strlen( prefix ); + + // Determine if this is an absolute or relative feed + int absolute = mlt_properties_get_int( feed, "absolute" ); + + // Make do with what we have + int length = !absolute ? + mlt_properties_get_int( feed, "out" ) - mlt_properties_get_int( feed, "in" ) + 1 : + mlt_properties_get_int( feed, "out" ) + 1; + + // Repeat period + int period = mlt_properties_get_int( properties, "period" ); + period = period == 0 ? 1 : period; + + // Pass properties from feed into requested + for ( i = 0; i < mlt_properties_count( properties ); i ++ ) + { + char *name = mlt_properties_get_name( properties, i ); + char *key = mlt_properties_get_value( properties, i ); + if ( !strncmp( name, prefix, len ) ) + { + if ( !strncmp( name + len, "length[", 7 ) ) + { + mlt_properties_set_position( properties, key, ( length - period ) / period ); + } + else + { + char *value = mlt_properties_get( feed, name + len ); + if ( value != NULL ) + { + // check for metadata keywords in metadata markup if user requested so + if ( mlt_properties_get_int( filter_properties, "dynamic" ) == 1 && !strcmp( name + strlen( name ) - 6, "markup") ) + { + // Find keywords which should be surrounded by '#', like: #title# + char* keywords = strtok( value, "#" ); + char result[512] = ""; // XXX: how much is enough? + int ct = 0; + int fromStart = ( value[0] == '#' ) ? 1 : 0; + + while ( keywords != NULL ) + { + if ( ct % 2 == fromStart ) + { + // backslash in front of # suppresses substitution + if ( keywords[ strlen( keywords ) -1 ] == '\\' ) + { + // keep characters except backslash + strncat( result, keywords, strlen( keywords ) -1 ); + strcat( result, "#" ); + ct++; + } + else + { + strcat( result, keywords ); + } + } + else if ( !strcmp( keywords, "timecode" ) ) + { + // special case: replace #timecode# with current frame timecode + int pos = mlt_properties_get_int( feed, "position" ); + char *tc = frame_to_timecode( pos, mlt_profile_fps( NULL ) ); + strcat( result, tc ); + free( tc ); + } + else + { + // replace keyword with metadata value + char *metavalue = metadata_value( MLT_FRAME_PROPERTIES( frame ), keywords ); + strcat( result, metavalue ? metavalue : "-" ); + } + keywords = strtok( NULL, "#" ); + ct++; + } + mlt_properties_set( properties, key, (char*) result ); + } + else mlt_properties_set( properties, key, value ); + } + } + } + } + + // Set the original position on the frame + if ( absolute == 0 ) + mlt_frame_set_position( frame, mlt_properties_get_int( feed, "position" ) - mlt_properties_get_int( feed, "in" ) ); + else + mlt_frame_set_position( frame, mlt_properties_get_int( feed, "position" ) ); + + // Process the filter + mlt_filter_process( requested, frame ); + + // Should be ok... + error = 0; + } + + return error; +} + +void process_queue( mlt_deque data_queue, mlt_frame frame, mlt_filter filter ) +{ + if ( data_queue != NULL ) + { + // Create a new queue for those that we can't handle + mlt_deque temp_queue = mlt_deque_init( ); + + // Iterate through each entry on the queue + while ( mlt_deque_peek_front( data_queue ) != NULL ) + { + // Get the data feed + mlt_properties feed = mlt_deque_pop_front( data_queue ); + + if ( mlt_properties_get( MLT_FILTER_PROPERTIES( filter ), "debug" ) != NULL ) + mlt_properties_debug( feed, mlt_properties_get( MLT_FILTER_PROPERTIES( filter ), "debug" ), stderr ); + + // Process the data feed... + if ( process_feed( feed, filter, frame ) == 0 ) + mlt_properties_close( feed ); + else + mlt_deque_push_back( temp_queue, feed ); + } + + // Now put the unprocessed feeds back on the stack + while ( mlt_deque_peek_front( temp_queue ) ) + { + // Get the data feed + mlt_properties feed = mlt_deque_pop_front( temp_queue ); + + // Put it back on the data queue + mlt_deque_push_back( data_queue, feed ); + } + + // Close the temporary queue + mlt_deque_close( temp_queue ); + } +} + +/** Get the image. +*/ + +static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Pop the service + mlt_filter filter = mlt_frame_pop_service( frame ); + + // Get the frame properties + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + + // Track specific + process_queue( mlt_properties_get_data( frame_properties, "data_queue", NULL ), frame, filter ); + + // Global + process_queue( mlt_properties_get_data( frame_properties, "global_queue", NULL ), frame, filter ); + + // Need to get the image + return mlt_frame_get_image( frame, image, format, width, height, 1 ); +} + + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Push the filter + mlt_frame_push_service( frame, this ); + + // Register the get image method + mlt_frame_push_get_image( frame, filter_get_image ); + + // Return the frame + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_data_show_init( char *arg ) +{ + // Create the filter + mlt_filter this = mlt_filter_new( ); + + // Initialise it + if ( this != NULL ) + { + // Get the properties + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + // Assign the argument (default to titles) + mlt_properties_set( properties, "resource", arg == NULL ? NULL : arg ); + + // Specify the processing method + this->process = filter_process; + } + + return this; +} + diff --git a/src/modules/core/filter_gamma.c b/src/modules/core/filter_gamma.c new file mode 100644 index 0000000..e1a5197 --- /dev/null +++ b/src/modules/core/filter_gamma.c @@ -0,0 +1,89 @@ +/* + * filter_gamma.c -- gamma filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_gamma.h" + +#include + +#include +#include +#include + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + int error = mlt_frame_get_image( this, image, format, width, height, 1 ); + + if ( error == 0 && *format == mlt_image_yuv422 ) + { + // Get the gamma value + double gamma = mlt_properties_get_double( MLT_FRAME_PROPERTIES( this ), "gamma" ); + + if ( gamma != 1.0 ) + { + uint8_t *p = *image; + uint8_t *q = *image + *width * *height * 2; + + // Calculate the look up table + double exp = 1 / gamma; + uint8_t lookup[ 256 ]; + int i; + + for( i = 0; i < 256; i ++ ) + lookup[ i ] = ( uint8_t )( pow( ( double )i / 255.0, exp ) * 255 ); + + while ( p != q ) + { + *p = lookup[ *p ]; + p += 2; + } + } + } + + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + double gamma = mlt_properties_get_double( MLT_FILTER_PROPERTIES( this ), "gamma" ); + gamma = gamma <= 0 ? 1 : gamma; + mlt_properties_set_double( MLT_FRAME_PROPERTIES( frame ), "gamma", gamma ); + mlt_frame_push_get_image( frame, filter_get_image ); + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_gamma_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "gamma", arg == NULL ? "1" : arg ); + } + return this; +} diff --git a/src/modules/core/filter_gamma.h b/src/modules/core/filter_gamma.h new file mode 100644 index 0000000..c8066cc --- /dev/null +++ b/src/modules/core/filter_gamma.h @@ -0,0 +1,28 @@ +/* + * filter_gamma.h -- gamma filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_GAMMA_H_ +#define _FILTER_GAMMA_H_ + +#include + +extern mlt_filter filter_gamma_init( char *arg ); + +#endif diff --git a/src/modules/core/filter_greyscale.c b/src/modules/core/filter_greyscale.c new file mode 100644 index 0000000..c9f8d7a --- /dev/null +++ b/src/modules/core/filter_greyscale.c @@ -0,0 +1,63 @@ +/* + * filter_greyscale.c -- greyscale filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_greyscale.h" + +#include + +#include +#include + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + int error = mlt_frame_get_image( this, image, format, width, height, 1 ); + if ( error == 0 && *format == mlt_image_yuv422 ) + { + uint8_t *p = *image; + uint8_t *q = *image + *width * *height * 2; + while ( p ++ != q ) + *p ++ = 128; + } + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + mlt_frame_push_get_image( frame, filter_get_image ); + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_greyscale_init( void *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + this->process = filter_process; + return this; +} + diff --git a/src/modules/core/filter_greyscale.h b/src/modules/core/filter_greyscale.h new file mode 100644 index 0000000..bbea94a --- /dev/null +++ b/src/modules/core/filter_greyscale.h @@ -0,0 +1,28 @@ +/* + * filter_greyscale.h -- greyscale filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_GREYSCALE_H_ +#define _FILTER_GREYSCALE_H_ + +#include + +extern mlt_filter filter_greyscale_init( void *arg ); + +#endif diff --git a/src/modules/core/filter_luma.c b/src/modules/core/filter_luma.c new file mode 100644 index 0000000..f6b4582 --- /dev/null +++ b/src/modules/core/filter_luma.c @@ -0,0 +1,128 @@ +/* + * filter_luma.c -- luma filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_luma.h" + +#include +#include +#include +#include + +#include +#include +#include + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + int error = 0; + mlt_filter filter = mlt_frame_pop_service( this ); + mlt_properties properties = MLT_FILTER_PROPERTIES( filter ); + mlt_transition luma = mlt_properties_get_data( properties, "luma", NULL ); + mlt_frame b_frame = mlt_properties_get_data( properties, "frame", NULL ); + mlt_properties b_frame_props = b_frame ? MLT_FRAME_PROPERTIES( b_frame ) : NULL; + int out = mlt_properties_get_int( properties, "period" ); + + if ( out == 0 ) + out = 24; + + if ( luma == NULL ) + { + char *resource = mlt_properties_get( properties, "resource" ); + luma = mlt_factory_transition( "luma", resource ); + if ( luma != NULL ) + { + mlt_properties luma_properties = MLT_TRANSITION_PROPERTIES( luma ); + mlt_properties_set_int( luma_properties, "in", 0 ); + mlt_properties_set_int( luma_properties, "out", out ); + mlt_properties_set_int( luma_properties, "reverse", 1 ); + mlt_properties_set_data( properties, "luma", luma, 0, ( mlt_destructor )mlt_transition_close, NULL ); + } + } + + if ( b_frame == NULL || mlt_properties_get_int( b_frame_props, "width" ) != *width || mlt_properties_get_int( b_frame_props, "height" ) != *height ) + { + b_frame = mlt_frame_init( ); + mlt_properties_set_data( properties, "frame", b_frame, 0, ( mlt_destructor )mlt_frame_close, NULL ); + } + + if ( luma != NULL && + ( mlt_properties_get( properties, "blur" ) != NULL || + (int)mlt_frame_get_position( this ) % ( out + 1 ) != out ) ) + { + mlt_properties luma_properties = MLT_TRANSITION_PROPERTIES( luma ); + mlt_properties_pass( luma_properties, properties, "luma." ); + mlt_transition_process( luma, this, b_frame ); + } + + error = mlt_frame_get_image( this, image, format, width, height, 1 ); + + if ( error == 0 ) + { + mlt_properties a_props = MLT_FRAME_PROPERTIES( this ); + int size = 0; + uint8_t *src = mlt_properties_get_data( a_props, "image", &size ); + uint8_t *dst = mlt_pool_alloc( size ); + + if ( dst != NULL ) + { + mlt_properties b_props = MLT_FRAME_PROPERTIES( b_frame ); + memcpy( dst, src, size ); + mlt_properties_set_data( b_props, "image", dst, size, mlt_pool_release, NULL ); + mlt_properties_set_int( b_props, "width", *width ); + mlt_properties_set_int( b_props, "height", *height ); + mlt_properties_set_int( b_props, "format", *format ); + } + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Push the filter on to the stack + mlt_frame_push_service( frame, this ); + + // Push the get_image on to the stack + mlt_frame_push_get_image( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_luma_init( void *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + this->process = filter_process; + if ( arg != NULL ) + mlt_properties_set( properties, "resource", arg ); + } + return this; +} diff --git a/src/modules/core/filter_luma.h b/src/modules/core/filter_luma.h new file mode 100644 index 0000000..7293e63 --- /dev/null +++ b/src/modules/core/filter_luma.h @@ -0,0 +1,28 @@ +/* + * filter_luma.h -- luma filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_LUMA_H_ +#define _FILTER_LUMA_H_ + +#include + +extern mlt_filter filter_luma_init( void *arg ); + +#endif diff --git a/src/modules/core/filter_mirror.c b/src/modules/core/filter_mirror.c new file mode 100644 index 0000000..f3b5dc1 --- /dev/null +++ b/src/modules/core/filter_mirror.c @@ -0,0 +1,336 @@ +/* + * filter_mirror.c -- mirror filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_mirror.h" + +#include + +#include +#include +#include + +/** Do it :-). +*/ + +static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Pop the mirror filter from the stack + mlt_filter this = mlt_frame_pop_service( frame ); + + // Get the mirror type + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + // Get the properties + char *mirror = mlt_properties_get( properties, "mirror" ); + + // Determine if reverse is required + int reverse = mlt_properties_get_int( properties, "reverse" ); + + // Get the image + int error = mlt_frame_get_image( frame, image, format, width, height, 1 ); + + // Get the alpha + uint8_t *alpha = mlt_frame_get_alpha_mask( frame ); + + // If we have an image of the right colour space + if ( error == 0 && *format == mlt_image_yuv422 ) + { + // We'll KISS here + int hh = *height / 2; + + if ( !strcmp( mirror, "horizontal" ) ) + { + uint8_t *p = NULL; + uint8_t *q = NULL; + uint8_t *a = NULL; + uint8_t *b = NULL; + int i; + int uneven_w = ( *width % 2 ) * 2; + for ( i = 0; i < *height; i ++ ) + { + p = ( uint8_t * )*image + i * *width * 2; + q = p + *width * 2; + a = alpha + i * *width; + b = a + *width - 1; + if ( !reverse ) + { + while ( p < q ) + { + *p ++ = *( q - 2 ); + *p ++ = *( q - 3 - uneven_w ); + *p ++ = *( q - 4 ); + *p ++ = *( q - 1 - uneven_w ); + q -= 4; + *a ++ = *b --; + *a ++ = *b --; + } + } + else + { + while ( p < q ) + { + *( q - 2 ) = *p ++; + *( q - 3 - uneven_w ) = *p ++; + *( q - 4 ) = *p ++; + *( q - 1 - uneven_w ) = *p ++; + q -= 4; + *b -- = *a ++; + *b -- = *a ++; + } + } + } + } + else if ( !strcmp( mirror, "vertical" ) ) + { + uint16_t *end = ( uint16_t *)*image + *width * *height; + uint16_t *p = NULL; + uint16_t *q = NULL; + uint8_t *a = NULL; + uint8_t *b = NULL; + int i; + int j; + for ( i = 0; i < hh; i ++ ) + { + p = ( uint16_t * )*image + i * *width; + q = end - ( i + 1 ) * *width; + j = *width; + a = alpha + i * *width; + b = alpha + ( *height - i - 1 ) * *width; + if ( !reverse ) + { + while ( j -- ) + { + *p ++ = *q ++; + *a ++ = *b ++; + } + } + else + { + while ( j -- ) + { + *q ++ = *p ++; + *b ++ = *a ++; + } + } + } + } + else if ( !strcmp( mirror, "diagonal" ) ) + { + uint8_t *end = ( uint8_t *)*image + *width * *height * 2; + uint8_t *p = NULL; + uint8_t *q = NULL; + uint8_t *a = NULL; + uint8_t *b = NULL; + int i; + int j; + int uneven_w = ( *width % 2 ) * 2; + for ( i = 0; i < *height; i ++ ) + { + p = ( uint8_t * )*image + i * *width * 2; + q = end - i * *width * 2; + j = ( ( *width * ( *height - i ) ) / *height ) / 2; + a = alpha + i * *width; + b = alpha + ( *height - i - 1 ) * *width; + if ( !reverse ) + { + while ( j -- ) + { + *p ++ = *( q - 2 ); + *p ++ = *( q - 3 - uneven_w ); + *p ++ = *( q - 4 ); + *p ++ = *( q - 1 - uneven_w ); + q -= 4; + *a ++ = *b --; + *a ++ = *b --; + } + } + else + { + while ( j -- ) + { + *( q - 2 ) = *p ++; + *( q - 3 - uneven_w ) = *p ++; + *( q - 4 ) = *p ++; + *( q - 1 - uneven_w ) = *p ++; + q -= 4; + *b -- = *a ++; + *b -- = *a ++; + } + } + } + } + else if ( !strcmp( mirror, "xdiagonal" ) ) + { + uint8_t *end = ( uint8_t *)*image + *width * *height * 2; + uint8_t *p = NULL; + uint8_t *q = NULL; + int i; + int j; + uint8_t *a = NULL; + uint8_t *b = NULL; + int uneven_w = ( *width % 2 ) * 2; + for ( i = 0; i < *height; i ++ ) + { + p = ( uint8_t * )*image + ( i + 1 ) * *width * 2; + q = end - ( i + 1 ) * *width * 2; + j = ( ( *width * ( *height - i ) ) / *height ) / 2; + a = alpha + ( i + 1 ) * *width - 1; + b = alpha + ( *height - i - 1 ) * *width; + if ( !reverse ) + { + while ( j -- ) + { + *q ++ = *( p - 2 ); + *q ++ = *( p - 3 - uneven_w ); + *q ++ = *( p - 4 ); + *q ++ = *( p - 1 - uneven_w ); + p -= 4; + *b ++ = *a --; + *b ++ = *a --; + } + } + else + { + while ( j -- ) + { + *( p - 2 ) = *q ++; + *( p - 3 - uneven_w ) = *q ++; + *( p - 4 ) = *q ++; + *( p - 1 - uneven_w ) = *q ++; + p -= 4; + *a -- = *b ++; + *a -- = *b ++; + } + } + } + } + else if ( !strcmp( mirror, "flip" ) ) + { + uint8_t t[ 4 ]; + uint8_t *p = NULL; + uint8_t *q = NULL; + int i; + uint8_t *a = NULL; + uint8_t *b = NULL; + uint8_t c; + int uneven_w = ( *width % 2 ) * 2; + for ( i = 0; i < *height; i ++ ) + { + p = ( uint8_t * )*image + i * *width * 2; + q = p + *width * 2; + a = alpha + i * *width; + b = a + *width - 1; + while ( p < q ) + { + t[ 0 ] = p[ 0 ]; + t[ 1 ] = p[ 1 + uneven_w ]; + t[ 2 ] = p[ 2 ]; + t[ 3 ] = p[ 3 + uneven_w ]; + *p ++ = *( q - 2 ); + *p ++ = *( q - 3 - uneven_w ); + *p ++ = *( q - 4 ); + *p ++ = *( q - 1 - uneven_w ); + *( -- q ) = t[ 3 ]; + *( -- q ) = t[ 0 ]; + *( -- q ) = t[ 1 ]; + *( -- q ) = t[ 2 ]; + c = *a; + *a ++ = *b; + *b -- = c; + c = *a; + *a ++ = *b; + *b -- = c; + } + } + } + else if ( !strcmp( mirror, "flop" ) ) + { + uint16_t *end = ( uint16_t *)*image + *width * *height; + uint16_t *p = NULL; + uint16_t *q = NULL; + uint16_t t; + uint8_t *a = NULL; + uint8_t *b = NULL; + uint8_t c; + int i; + int j; + for ( i = 0; i < hh; i ++ ) + { + p = ( uint16_t * )*image + i * *width; + q = end - ( i + 1 ) * *width; + a = alpha + i * *width; + b = alpha + ( *height - i - 1 ) * *width; + j = *width; + while ( j -- ) + { + t = *p; + *p ++ = *q; + *q ++ = t; + c = *a; + *a ++ = *b; + *b ++ = c; + } + } + } + } + + // Return the error + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + // Push the service on to the stack + mlt_frame_push_service( frame, this ); + + // Push the filter method on to the stack + mlt_frame_push_service( frame, filter_get_image ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_mirror_init( void *arg ) +{ + // Construct a new filter + mlt_filter this = mlt_filter_new( ); + + // If we have a filter, initialise it + if ( this != NULL ) + { + // Get the properties + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + + // Set the default mirror type + mlt_properties_set_or_default( properties, "mirror", arg, "horizontal" ); + + // Assign the process method + this->process = filter_process; + } + + // Return the filter + return this; +} + diff --git a/src/modules/core/filter_mirror.h b/src/modules/core/filter_mirror.h new file mode 100644 index 0000000..7229aab --- /dev/null +++ b/src/modules/core/filter_mirror.h @@ -0,0 +1,28 @@ +/* + * filter_mirror.h -- mirror filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_MIRROR_H_ +#define _FILTER_MIRROR_H_ + +#include + +extern mlt_filter filter_mirror_init( void *arg ); + +#endif diff --git a/src/modules/core/filter_mono.c b/src/modules/core/filter_mono.c new file mode 100644 index 0000000..665dfdf --- /dev/null +++ b/src/modules/core/filter_mono.c @@ -0,0 +1,95 @@ +/* + * filter_mono.c -- mix all channels to a mono signal across n channels + * Copyright (C) 2003-2006 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_mono.h" + +#include + +#include +#include + +/** Get the audio. +*/ + +static int filter_get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + // Get the properties of the a frame + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + int channels_out = mlt_properties_get_int( properties, "mono.channels" ); + int i, j, size; + int16_t *new_buffer; + + // Get the producer's audio + mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples ); + + size = *samples * channels_out * sizeof( int16_t ); + new_buffer = mlt_pool_alloc( size ); + mlt_properties_set_data( properties, "audio", new_buffer, size, ( mlt_destructor )mlt_pool_release, NULL ); + + // Mix + for ( i = 0; i < *samples; i++ ) + { + int16_t mixdown = 0; + for ( j = 0; j < *channels; j++ ) + mixdown += (*buffer)[ ( i * *channels ) + j ] / *channels; + for ( j = 0; j < channels_out; j++ ) + new_buffer[ ( i * channels_out ) + j ] = mixdown; + } + + // Apply results + *buffer = new_buffer; + *channels = channels_out; + + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter this, mlt_frame frame ) +{ + mlt_properties properties = MLT_FILTER_PROPERTIES( this ); + mlt_properties frame_props = MLT_FRAME_PROPERTIES( frame ); + + // Propogate the parameters + mlt_properties_set_int( frame_props, "mono.channels", mlt_properties_get_int( properties, "channels" ) ); + + // Override the get_audio method + mlt_frame_push_audio( frame, filter_get_audio ); + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_mono_init( char *arg ) +{ + mlt_filter this = mlt_filter_new( ); + if ( this != NULL ) + { + this->process = filter_process; + if ( arg != NULL ) + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "channels", atoi( arg ) ); + else + mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "channels", 2 ); + } + return this; +} diff --git a/src/modules/core/filter_mono.h b/src/modules/core/filter_mono.h new file mode 100644 index 0000000..b109654 --- /dev/null +++ b/src/modules/core/filter_mono.h @@ -0,0 +1,28 @@ +/* + * filter_mono.h -- mix all channels to a mono signal across n channels + * Copyright (C) 2003-2006 Ushodaya Enterprises Limited + * Author: Dan Dennedy + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FILTER_MONO_H_ +#define _FILTER_MONO_H_ + +#include + +extern mlt_filter filter_mono_init( char *arg ); + +#endif diff --git a/src/modules/core/filter_obscure.c b/src/modules/core/filter_obscure.c new file mode 100644 index 0000000..5773933 --- /dev/null +++ b/src/modules/core/filter_obscure.c @@ -0,0 +1,310 @@ +/* + * filter_obscure.c -- obscure filter + * Copyright (C) 2003-2004 Ushodaya Enterprises Limited + * Author: Charles Yates + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "filter_obscure.h" + +#include + +#include +#include + +/** Geometry struct. +*/ + +struct geometry_s +{ + int nw; + int nh; + float x; + float y; + float w; + float h; + int mask_w; + int mask_h; +}; + +/** Parse a value from a geometry string. +*/ + +static inline float parse_value( char **ptr, int normalisation, char delim, float defaults ) +{ + float value = defaults; + + if ( *ptr != NULL && **ptr != '\0' ) + { + char *end = NULL; + value = strtod( *ptr, &end ); + if ( end != NULL ) + { + if ( *end == '%' ) + value = ( value / 100.0 ) * normalisation; + while ( *end == delim || *end == '%' ) + end ++; + } + *ptr = end; + } + + return value; +} + +/** Parse a geometry property string. +*/ + +static void geometry_parse( struct geometry_s *geometry, struct geometry_s *defaults, char *property, int nw, int nh ) +{ + // Assign normalised width and height + geometry->nw = nw; + geometry->nh = nh; + + // Assign from defaults if available + if ( defaults != NULL ) + { + geometry->x = defaults->x; + geometry->y = defaults->y; + geometry->w = defaults->w; + geometry->h = defaults->h; + geometry->mask_w = defaults->mask_w; + geometry->mask_h = defaults->mask_h; + } + else + { + geometry->x = 0; + geometry->y = 0; + geometry->w = nw; + geometry->h = nh; + geometry->mask_w = 20; + geometry->mask_h = 20; + } + + // Parse the geomtry string + if ( property != NULL ) + { + char *ptr = property; + geometry->x = parse_value( &ptr, nw, ',', geometry->x ); + geometry->y = parse_value( &ptr, nh, ':', geometry->y ); + geometry->w = parse_value( &ptr, nw, 'x', geometry->w ); + geometry->h = parse_value( &ptr, nh, ':', geometr