diff --git a/.arcconfig b/.arcconfig new file mode 100644 index 0000000000..b837cea390 --- /dev/null +++ b/.arcconfig @@ -0,0 +1,6 @@ +{ + "phabricator.uri" : "https://phabricator.freedesktop.org/", + "repository.callsign" : "GES", + "project": "GStreamer Editing Services", + "default-reviewers": "thiblahute,Mathieu_Du" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..a2e9b7282d --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/build/ +/b/ +/_build/ +*~ +core.* + +core +log + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..c61aa7a529 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1 @@ +include: "https://gitlab.freedesktop.org/gstreamer/gst-ci/raw/master/gitlab/ci_template.yml" diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000000..52047338b7 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Edward Hervey +Brandon Lewis diff --git a/COPYING b/COPYING new file mode 100644 index 0000000000..c87cfe8c99 --- /dev/null +++ b/COPYING @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin St, 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 library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + 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 Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, 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 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 a program 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. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, 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 companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + 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, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +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 compile 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) 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. + + c) 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. + + d) 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 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. + + 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 to +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 Library 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 + + Appendix: 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 Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin St, 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/COPYING.LIB b/COPYING.LIB new file mode 100644 index 0000000000..c87cfe8c99 --- /dev/null +++ b/COPYING.LIB @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin St, 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 library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + 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 Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, 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 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 a program 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. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, 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 companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + 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, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +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 compile 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) 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. + + c) 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. + + d) 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 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. + + 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 to +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 Library 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 + + Appendix: 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 Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin St, 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 0000000000..165f1ca2ed --- /dev/null +++ b/ChangeLog @@ -0,0 +1,28736 @@ +=== release 1.19.2 === + +2021-09-23 01:35:39 +0100 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * gst-editing-services.doap: + * meson.build: + Release 1.19.2 + +2021-08-10 17:10:43 -0400 Thibault Saunier + + * tests/check/meson.build: + * tools/ges-launcher.c: + * tools/ges-validate.c: + * tools/utils.h: + launch: Make enabling validate opt-in + Instead of opt-out. + Part-of: + +2021-08-12 23:37:59 +0200 Mathieu Duponchelle + + * ges/ges-uri-source.c: + ges-uri-source: fix object debug + Part-of: + +2021-08-10 23:54:47 +0200 Mathieu Duponchelle + + * docs/gst_plugins_cache.json: + * plugins/nle/nlecomposition.c: + * tools/ges-launcher.c: + * tools/utils.h: + ges-launcher: add option to forward tags + Part-of: + +2021-08-10 23:25:06 +0200 Mathieu Duponchelle + + * tools/ges-launcher.c: + * tools/utils.h: + ges-launcher: allow using a clip to determine the rendering format + This includes both topology and profile + Part-of: + +2021-08-10 23:23:39 +0200 Mathieu Duponchelle + + * tools/ges-launcher.c: + launcher: don't start the pipeline before we're done updating it + Since 70e3b8ae2a8d13b50f52305b71cfa4b590bb63f6 the CommandLineFormatter + also emit "loaded" so we ended up doing this twice, once + as before in `run_pipeline` and another time in the `project:loaded` + callback. + Part-of: + +2021-08-10 23:20:21 +0200 Mathieu Duponchelle + + * tools/ges-launcher.c: + ges-launcher: don't unref transfer none objects + Part-of: + +2021-07-21 19:31:53 +0200 Piotrek Brzeziński + + * ges/ges-clip.c: + * tests/check/ges/clip.c: + clip: Copy trackelement's metadata upon splitting + Part-of: + +2021-07-09 16:15:01 +0200 Piotrek Brzeziński + + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-xml-formatter.c: + xml-formatter: Add support for metadata on sources + Part-of: + +2021-07-09 16:14:19 +0200 Piotrek Brzeziński + + * ges/ges-marker-list.c: + * tests/check/ges/markerlist.c: + marker-list: Add flags (de)serialization + Part-of: + +2021-08-03 11:31:07 +0200 Stéphane Cerveau + + * ges/ges-pipeline.c: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + ges: freeze commit during render + In render mode, do not commit the timeline + as the position can be invalid and lead to + missing frames. + Fixes #136 + Part-of: + +2021-08-05 22:59:07 +0200 Piotrek Brzeziński + + * ges/ges-timeline-tree.c: + timeline: Check if metadata value holds object on marker snapping + Part-of: + +2021-06-20 23:51:02 +0200 Piotrek Brzeziński + + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges-internal.h: + * ges/ges-marker-list.c: + * ges/ges-marker-list.h: + * ges/ges-timeline-tree.c: + * tests/check/ges/markerlist.c: + * tests/check/ges/timelineedition.c: + timeline: Implement snapping to markers + Part-of: + +2021-06-16 17:12:11 +0200 François Laignel + + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + Check mandatory ClockTime arguments + Part-of: + +2021-05-22 18:41:08 +0100 Tim-Philipp Müller + + * ges/ges-pitivi-formatter.c: + * meson.build: + Use g_memdup2() where available and add fallback for older GLib versions + Size is constant here, so no problem in any case, but g_memdup() is + now deprecated and we don't want deprecation warnings. + Part-of: + +2021-06-01 15:29:10 +0100 Tim-Philipp Müller + + * meson.build: + Back to development + +=== release 1.19.1 === + +2021-06-01 00:16:05 +0100 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * gst-editing-services.doap: + * meson.build: + Release 1.19.1 + +2021-05-18 11:42:22 -0400 Thibault Saunier + + * ges/ges-uri-clip.c: + uriclip: Add an error message when creating a clip failed + Part-of: + +2021-05-18 11:31:19 -0400 Thibault Saunier + + * examples/c/simple1.c: + examples: c: Sensibly simplify the simple example + Part-of: + +2021-05-18 11:16:02 -0400 Thibault Saunier + + * examples/python/gst-player.py: + * examples/python/simple.py: + examples: python: Simplify the simple example + We shouldn't show assets usage in the simplest example we have + as it is useful for more advanced use cases. + Part-of: + +2021-05-21 15:26:03 -0400 Thibault Saunier + + * tests/check/scenarios/check_keyframes_in_compositor_two_sources/flow-expectations/log-videosink-sink-expected: + * tests/check/scenarios/complex_effect_bin_desc/flow-expectations/log-videosink-sink-expected: + * tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected: + * tests/check/scenarios/seek_with_stop.check_clock_sync/flow-expectations/log-videosink-sink-expected: + * tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected: + * tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected: + tests: Update expectation files with sorted structure fields + +2021-05-20 16:47:41 +0100 Philippe Normand + + * tests/check/ges/test-utils.c: + * tests/check/meson.build: + * tests/check/nle/complex.c: + * tests/check/nle/nlecomposition.c: + * tests/check/nle/nleoperation.c: + * tests/check/nle/nlesource.c: + * tests/check/nle/seek.c: + * tests/check/nle/simple.c: + * tests/check/nle/tempochange.c: + * tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario: + * tests/check/scenarios/check_layer_activness_gaps.scenario: + tests/check: Use fake{audio,video}sink + The tests already depend on -bad, so this should be OK. + Part-of: + +2021-05-20 16:45:43 +0100 Philippe Normand + + * tools/ges-launcher.c: + launcher: Switch to fake{audio,video}sink + Simplifies the code a bit, though introducing runtime dependency on -bad. + Part-of: + +2021-05-18 21:31:38 -0400 Thibault Saunier + + * ges/ges-command-line-formatter.c: + * ges/ges-structured-interface.c: + * tests/check/meson.build: + * tests/check/scenarios/set-layer-on-command-line.validatetest: + structure-interface: Convert fields type as much as possible + Since 60922c02889cf1ebcfaca4501936be689c342e01 we force string in the + command line parser which broke setting layers on clips for example + Part-of: + +2021-05-18 22:04:48 -0400 Thibault Saunier + + * ges/ges-validate.c: + validate: Add support to check properties of object properties + And recursively + Part-of: + +2021-04-23 16:08:48 +0900 Seungha Yang + + * ges/ges-smart-video-mixer.c: + * ges/ges-utils.c: + smart-mixer: Add support for d3d11compositor and glvideomixer + Some hardware compositor elements (d3d11compositor and glvideomixer) + consist of wrapper bin with internal mixer element. + So, we need special handling for such elements. + Part-of: + +2021-04-24 00:55:45 +0900 Seungha Yang + + * ges/gstframepositioner.c: + framepositioner: Install operator property only when compositor is used + Other compositor/mixer elements might not have the property. For instance, + d3d11compositor and glvideomixer define graphics API specific blending + properties, instead of simple "operator" one. + Part-of: + +2021-05-12 17:43:46 -0400 Doug Nazar + + * ges/ges-xml-formatter.c: + xml-formatter: Write xml directly to file + Skip allocation of temp buffer (which was undersized). + Part-of: + +2021-05-01 19:18:15 -0400 Doug Nazar + + * tests/check/meson.build: + tests: Run ges-launch tests non-interactively + It's not needed for the tests and fixes an occasional issue where + the terminal is left in -echo mode. + Part-of: + +2021-02-24 23:49:06 -0300 Thibault Saunier + + * ges/ges-track-element.h: + track-element: Fix and cleanup annotations + Making the class subclass able by bindings + Part-of: + +2021-02-24 23:37:28 -0300 Thibault Saunier + + * ges/ges-audio-source.c: + * ges/ges-audio-source.h: + * ges/ges-audio-test-source.c: + * ges/ges-audio-uri-source.c: + * ges/ges-image-source.c: + * ges/ges-multi-file-source.c: + * ges/ges-source.h: + * ges/ges-title-source.c: + * ges/ges-track-element.h: + * ges/ges-video-source.c: + * ges/ges-video-source.h: + * ges/ges-video-test-source.c: + * ges/ges-video-uri-source.c: + ges: Move GESVideo/AudioSource::create_source to GESSource + Deprecating the old variants which were not introspectable + and cleaning a bit the API. + Part-of: + +2021-04-21 10:47:51 +0200 François Laignel + + * docs/design/encoding.txt: + * ges/ges-effect-asset.c: + * ges/ges-pipeline.c: + * ges/ges-smart-video-mixer.c: + * ges/gstframepositioner.c: + * plugins/nle/nleoperation.c: + Use gst_element_request_pad_simple... + Instead of the deprecated gst_element_get_request_pad. + Part-of: + +2021-04-28 00:57:35 +0900 Seungha Yang + + * examples/c/concatenate.c: + * examples/c/ges-ui.c: + * examples/c/gessrc.c: + * examples/c/multifilesrc.c: + * examples/c/overlays.c: + * examples/c/play_timeline_with_one_clip.c: + * examples/c/simple1.c: + * examples/c/test2.c: + * examples/c/test3.c: + * examples/c/test4.c: + * examples/c/text_properties.c: + * examples/c/thumbnails.c: + * examples/c/transition.c: + * ges/ges-asset.c: + * ges/ges-timeline-tree.c: + * ges/ges-uri-asset.c: + * ges/ges.c: + * tests/benchmarks/timeline.c: + * tests/check/ges/test-utils.c: + * tools/ges-launcher.c: + * tools/ges-validate.c: + * tools/utils.c: + ges: Port to gst_print* + Sync with gst-launch, as g_print* will print broken string on Windows. + See also + https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/258 + Part-of: + +2021-04-23 16:42:26 +0900 Seungha Yang + + * ges/gstframepositioner.c: + framepositioner: Allow ANY caps features + framepositioner will not touch raw video data and therefore should + be able to accept ANY caps features + Part-of: + +2021-04-23 09:01:35 -0500 reed.lawrence + + * ges/gstframepositioner.c: + gstframepositioner: fix operator magic number + In gst_frame_positioner_init, there was the magic number 1 + when assigning the default value of the operator. Now it + has the default value for the operator pulled from the + compositor. + Part-of: + +2021-04-21 18:12:30 -0500 reed.lawrence + + * ges/ges-smart-video-mixer.c: + * ges/ges-video-source.c: + * ges/gstframepositioner.c: + * ges/gstframepositioner.h: + gstframepositioner: added 'operator' property + The 'operator' property was added to gstframepositioner so that + blending modes in the compositor could be accessed. This was done + by accessing the pad of the compositor class, and referencing the + 'operator' property in that pad. Getters and Setters were also + created so that the 'operator' could be accessed by software that + is based on GES, such as Pitivi. + Related to but does not close Issue + https://gitlab.gnome.org/GNOME/pitivi/-/issues/2313 + Part-of: + +2021-04-14 12:58:30 +0900 Seungha Yang + + * ges/gstframepositioner.c: + framepositioner: Fix runtime warning + GstCaps is not a GObject! + Part-of: + +2021-04-08 15:35:30 -0500 Adam Leppky + + * ges/ges-title-source.c: + titleclip: Expose draw-shadow child property + Part-of: + +2021-03-19 17:21:01 +1100 Matthew Waters + + * ges/ges-smart-video-mixer.c: + * ges/gstframepositioner.c: + * plugins/nle/nlecomposition.c: + * plugins/nle/nleobject.c: + gst: don't use volatile to mean atomic + volatile is not sufficient to provide atomic guarantees and real atomics + should be used instead. GCC 11 has started warning about using volatile + with atomic operations. + https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1719 + Discovered in https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/issues/868 + Part-of: + +2021-03-08 14:50:52 -0300 Thibault Saunier + + * ges/ges-clip.h: + ges: doc: Fix wrong vmethod links + +2021-03-08 09:56:49 -0300 Thibault Saunier + + * ges/ges-group.c: + group: Use proper group constructor + Otherwise we might en up having a group which is not backed by any asset + leading to possible assertion as this should never happen (see + https://gitlab.gnome.org/GNOME/pitivi/-/issues/2526) + Part-of: + +2021-02-17 21:34:22 +1100 Jan Schmidt + + * tests/check/scenarios/check_keyframes_in_compositor_two_sources.validatetest: + * tests/check/scenarios/check_keyframes_in_compositor_two_sources/flow-expectations/log-videosink-sink-expected: + Update check_keyframes_in_compositor_two_sources + Update the validate expectation for videoconvert caps changes in + https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/1033 + Part-of: + +2021-01-19 11:00:22 -0300 Thibault Saunier + + * ges/ges-project.c: + project: Plug a leak + Part-of: + +2021-01-19 10:29:09 -0300 Thibault Saunier + + * ges/ges-xml-formatter.c: + xml-formatter: Properly report error parsing restriction caps + Part-of: + +2021-01-15 15:29:47 -0300 Thibault Saunier + + * tests/check/meson.build: + * tests/check/scenarios/check-clip-positioning.validatetest: + test: Check clip positioning works when specifying track size + Make use of the new 'timeline specification' support in .validatetest + files. + Part-of: + +2021-01-15 15:28:34 -0300 Thibault Saunier + + * tools/ges-launcher.c: + tools: Fix some naming + Part-of: + +2021-01-15 15:28:17 -0300 Thibault Saunier + + * tools/ges-launcher.c: + tools: Reindent options + Part-of: + +2021-01-15 15:27:30 -0300 Thibault Saunier + + * tools/ges-launcher.c: + launch: Add encoding profiles to the project + So it is serialized on `--save` + Part-of: + +2021-01-15 15:26:36 -0300 Thibault Saunier + + * tools/ges-launcher.c: + * tools/ges-validate.c: + * tools/ges-validate.h: + * tools/utils.c: + validate: Handle passing timeline desc in .validatetest files + Part-of: + +2021-01-15 15:25:12 -0300 Thibault Saunier + + * plugins/ges/gesbasebin.c: + * plugins/ges/gesdemux.c: + plugin: Fix `is-ges-timeline` registration + We need to register it for all subclasses. + Part-of: + +2021-01-15 15:23:13 -0300 Thibault Saunier + + * ges/ges-command-line-formatter.c: + command-line-formatter: Stop uselessly looping over options + Part-of: + +2021-01-15 15:21:06 -0300 Thibault Saunier + + * ges/ges-command-line-formatter.c: + * ges/ges-command-line-formatter.h: + * ges/ges-internal.h: + * ges/ges-xml-formatter.c: + * plugins/ges/gessrc.c: + * tools/utils.c: + command-line-formatter: Add a way to format timelines using the format + Part-of: + +2021-01-15 15:03:20 -0300 Thibault Saunier + + * ges/ges-command-line-formatter.c: + * plugins/ges/gessrc.c: + * tools/ges-launcher.c: + * tools/ges-launcher.h: + * tools/utils.c: + * tools/utils.h: + ges: Use a `ges:` uri to define timeline from description + This way the command line formatter actually uses an URI and not + an ugly hack where were passing a random string instead of an URI. + This also allows the `gessrc` element to handle timelines described + in its URI meaning that you can now use, for example: + gst-play-1.0 "ges:+test-clip blue d=4.0 + Part-of: + +2021-01-15 09:27:31 -0300 Thibault Saunier + + * ges/ges-command-line-formatter.c: + * ges/ges-structure-parser.c: + * ges/parse.l: + ges: Add keyframe support to the command line formatter + Part-of: + +2021-01-15 09:25:11 -0300 Thibault Saunier + + * ges/ges-structured-interface.c: + * ges/ges-structured-interface.h: + * ges/ges-validate.c: + structured-interface: Move set_control_source from ges-validate + So it can be reused in the command line formatter. + Part-of: + +2021-01-15 09:13:59 -0300 Thibault Saunier + + * ges/ges-structured-interface.c: + structured-interface: Factor out method to get element to set property + Used to set properties or keyframes + Part-of: + +2021-01-15 08:49:20 -0300 Thibault Saunier + + * ges/ges-command-line-formatter.c: + command-line-formatter: Reindent command line options array + Part-of: + +2021-01-15 08:47:10 -0300 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-xml-formatter.c: + formatter: Use the new `GstEncodingProfile:element-properties` property + Cleaning up the code and making everything simpler. + Part-of: + +2021-01-14 08:05:59 -0300 Thibault Saunier + + * ges/ges-asset.c: + * ges/ges-base-xml-formatter.c: + * ges/ges-validate.c: + ges: Minor debug logging level and typo fixes + Part-of: + +2021-01-12 15:55:52 -0300 Thibault Saunier + + * ges/ges-command-line-formatter.c: + * ges/ges-structure-parser.c: + * ges/ges-structured-interface.c: + * ges/ges-structured-interface.h: + * ges/parse.l: + * tools/ges-launcher.c: + command-line-formatter: Add track management to timeline description + Instead of having it all handled by the tool, this way we can + set the restriction before clips are added to the timeline, + leading to better behavior in term of video images placement + in the scene. + Without that we would have the clips positioned before setting the + restriction caps which leads to weird behavior for the end users. + Part-of: + +2021-01-13 15:18:04 -0300 Thibault Saunier + + * ges/ges-video-source.c: + * ges/ges-video-test-source.c: + test-source: Respect asset natural size + We had cases where the frame positioner had the default natural size for + video test sources instead of the user provided one. + Part-of: + +2021-01-29 20:42:26 +0100 Mathieu Duponchelle + + * tools/ges-launcher.c: + ges-launcher: do not set rendering details too early + It looks like the _set_rendering_details call is superfluous + in _startup(), as it will get called in run_pipeline. + The problem with calling it before timeline_set_user_options + is that we are going to fail creating a smart profile if + the user selected eg --track-types=video, as the get_smart_profile + method compares the tracks in the asset with those on the timeline. + Reproduce with a video-only clip: + ges-launch-1.0 --track-types=video +clip file://$PWD/jelly.mp4 \ + inpoint=15.0 -o foo.mp4 --smart-rendering + Part-of: + +2019-10-29 17:03:14 +0000 Henry Wilkes + + * ges/ges-command-line-formatter.c: + * ges/ges-structure-parser.c: + * ges/ges-structure-parser.h: + * ges/ges-structured-interface.c: + * ges/parse.l: + ges-structure-parser: force string types + Force a string type for structure values obtained through parsing a + serialized timeline by inserting a (string) specifier after a '=', + rather than relying on gst_structure_from_string guessing the type. + As such, the functions that extract clocktimes and properties are + modified to accept string value types. + Part-of: + +2019-10-29 16:29:24 +0000 Henry Wilkes + + * ges/ges-command-line-formatter.c: + command-line-formatter: fix typos + Part-of: + +2019-10-18 23:23:10 +0100 Henry Wilkes + + * ges/ges-marker-list.c: + * tests/check/ges/markerlist.c: + marker-list: made deserialize reverse of serialize + Changed deserialize method to actually reverse the serialize method by + removing the edge quote marks and reversing g_strescape. + See https://gitlab.freedesktop.org/gstreamer/gstreamer/issues/452 + Part-of: + +2020-12-13 22:54:37 -0300 Thibault Saunier + + * ges/ges-audio-uri-source.c: + * ges/ges-source.c: + * ges/ges-source.h: + * ges/ges-uri-source.c: + * ges/ges-uri-source.h: + * ges/ges-video-uri-source.c: + uri-source: Respect stream-id even on streams muxed in raw + The issue is that we rely on `decodebin::autoplug-select` to `SKIP` + unwanted pads, that signal was first provided to select factories during + autoplugin, not totally thought to avoid exposing pads. For streams + muxed directly in raw, decodebin has nothing to plug after the demuxer + and the pad is exposed right away, meaning that we do not have any + chance to avoid that pad to be exposed. This patch takes that limitation + into account and checks the stream ID of the pads exposed by decodebin + before exposing them itself, so we end up using the right pad even if + more are uselessly exposed by decodebin. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/126 + Part-of: + +2021-01-12 15:50:27 -0300 Thibault Saunier + + * ges/ges-audio-track.c: + audio-track: Respect track restrictions in our gaps + Avoiding not negotiated errors in specific cases. + Part-of: + +2021-01-05 11:52:15 -0300 Thibault Saunier + + * tools/ges-launcher.c: + launch: Ensure to add required ref to profiles from project + We were unreffing something we were not owning + Part-of: + +2020-11-02 22:18:24 +1100 Jan Schmidt + + * tests/check/meson.build: + tests: fix meson test env setup to make sure we use the right gst-plugin-scanner + This is the same fix that was applied in gst-plugins-good in + https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/603 + and fixes the testsuite running in gst-build. + Part-of: + +2020-09-04 10:27:05 -0400 Thibault Saunier + + * tools/ges-launcher-kb.c: + * tools/ges-launcher-kb.h: + * tools/ges-launcher.c: + * tools/ges-launcher.h: + * tools/meson.build: + launch: Add an interactive mode where we can seek etc... + Part-of: + +2020-11-04 18:47:28 +0530 Nirbheek Chauhan + + * meson.build: + meson: Enable some MSVC warnings for parity with GCC/Clang + This makes it easier to do development with MSVC by making it warn + on common issues that GCC/Clang error out for in our CI configuration. + Continuation from https://gitlab.freedesktop.org/gstreamer/gst-build/-/merge_requests/223 + Part-of: + +2020-10-30 00:30:52 +1100 Jan Schmidt + + * ges/ges.c: + * tools/ges-launcher.c: + init: Fix initialisation crash + Fix a case where initialisation fails without setting + the passed-in GError and the caller assumes it will be + set, and add a guard to catch the condition in case it + happens again in the future. + Part-of: + +2018-11-04 13:04:45 -0500 Xavier Claessens + + * ges/meson.build: + * meson.build: + * pkgconfig/gst-editing-services-uninstalled.pc.in: + * pkgconfig/gst-editing-services.pc.in: + * pkgconfig/meson.build: + Meson: Use pkg-config generator + +2020-10-18 16:08:36 +0200 Fabrice Fontaine + + * tools/ges-launcher.c: + * tools/utils.c: + * tools/utils.h: + utils.c: fix static build + Static build fails since version 1.17.1 and + https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/commit/1e488d4311420b5ca193155ad8ab05509c9a4a37 + on: + FAILED: tools/ges-launch-1.0 + /srv/storage/autobuild/run/instance-2/output-1/host/bin/arm-linux-gcc -o tools/ges-launch-1.0 tools/ges-launch-1.0.p/ges-validate.c.o tools/ges-launch-1.0.p/ges-launch.c.o tools/ges-launch-1.0.p/ges-launcher.c.o tools/ges-launch-1.0.p/utils.c.o -Wl,--as-needed -Wl,--no-undefined -Wl,-O1 -Wl,-Bsymbolic-functions -static -Wl,--start-group ges/libges-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgstreamer-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgobject-2.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libglib-2.0.a -pthread /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libpcre.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libffi.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgmodule-2.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgstbase-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgstvideo-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgstpbutils-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgstaudio-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libz.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgsttag-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgstcontroller-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgio-2.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libmount.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libblkid.a -lm -Wl,--end-group + /srv/storage/autobuild/run/instance-2/output-1/host/opt/ext-toolchain/bin/../lib/gcc/arm-buildroot-linux-uclibcgnueabi/8.3.0/../../../../arm-buildroot-linux-uclibcgnueabi/bin/ld: /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libc.a(err.os): in function `warn': + err.c:(.text+0x1d8): multiple definition of `warn'; tools/ges-launch-1.0.p/utils.c.o:utils.c:(.text+0x9bc): first defined here + So rename warn function to ges_warn + Also prefix ok, print and printerr function by ges_ for consistancy and + run gst-indent on tools/ges-launcher.c + Fixes: + - http://autobuild.buildroot.org/results/2a528a1185644f5b23d26eb3f2b342e99aa1e493 + Signed-off-by: Fabrice Fontaine + Part-of: + +2020-10-18 20:11:33 +0200 Antonio Ospite + + * meson.build: + meson: actually check glib dependency version + Actually check the version constraint when looking for the glib + dependency. + The version check will make meson use the fallback dependency when the + one from the system is not recent enough, and eventually make the build + succeed even on some older systems like Ubuntu 16.04. + Part-of: + +2020-10-16 13:17:04 +0200 Stéphane Cerveau + + * ges/ges-asset.c: + * meson.build: + meson: update glib minimum version to 2.56 + In order to support the symbol g_enum_to_string in various + project using GStreamer ( gst-validate etc.), the glib minimum + version should be 2.56.0. + Remove compat code as glib requirement + is now > 2.56 + Version used by Ubuntu 18.04 LTS + Part-of: + +2020-09-03 23:32:23 -0400 Thibault Saunier + + * ges/ges-auto-transition.c: + * ges/ges-clip.c: + * ges/ges-internal.h: + * ges/ges-timeline-tree.c: + * ges/ges-timeline.c: + * ges/ges-uri-clip.c: + * tests/check/python/common.py: + * tests/check/python/test_assets.py: + * tests/check/python/test_timeline.py: + ges: Do not recreate auto-transitions when changing clip assets + Otherwise we loose the configuration of the auto transition, and + it is not required at all in any case. + Fixes https://gitlab.gnome.org/GNOME/pitivi/-/issues/2380 + Part-of: + +2020-09-08 11:39:10 -0300 Thibault Saunier + + * tests/check/meson.build: + ges: Fix a copy/paste mistake in meson file + Passed unnoticed because we built against GstValidate + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/119 + Part-of: + +2020-09-03 21:15:16 -0400 Thibault Saunier + + * ges/ges-video-transition.c: + * ges/ges-video-transition.h: + video-transition: Make smpte props children properties + And deprecate old style accessors. + Part-of: + +2020-09-08 17:30:53 +0100 Tim-Philipp Müller + + * .gitlab-ci.yml: + ci: include template from gst-ci master branch again + +2020-09-08 16:59:02 +0100 Tim-Philipp Müller + + * meson.build: + Back to development + +=== release 1.18.0 === + +2020-09-08 00:09:25 +0100 Tim-Philipp Müller + + * .gitlab-ci.yml: + * ChangeLog: + * NEWS: + * RELEASE: + * gst-editing-services.doap: + * meson.build: + Release 1.18.0 + +2020-09-04 10:43:05 -0400 Thibault Saunier + + * docs/gst_plugins_cache.json: + * plugins/ges/gesdemux.c: + demux: Fixate documentation caps + Part-of: + +2020-08-22 00:57:06 +1000 Jan Schmidt + + * tests/check/scenarios/complex_effect_bin_desc/flow-expectations/log-videosink-sink-expected: + complex_effect_bin_desc: Regenerate expectation for compositor change + Part of: https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/796 + +2020-08-20 21:09:31 -0400 Thibault Saunier + + * tests/check/meson.build: + tests: Fix running tests fully uninstalled + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/118 + Part-of: + +=== release 1.17.90 === + +2020-08-20 16:16:01 +0100 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * gst-editing-services.doap: + * meson.build: + Release 1.17.90 + +2020-07-31 22:02:01 -0400 Thibault Saunier + + * ges/ges-source.c: + ges:source: Handle missing elements in converters + Part-of: + +2020-07-22 12:02:10 -0400 Thibault Saunier + + * ges/ges-smart-video-mixer.c: + * ges/ges-video-transition.c: + * tests/check/scenarios/check_keyframes_in_compositor_two_sources/flow-expectations/log-videosink-sink-expected: + * tests/check/scenarios/complex_effect_bin_desc/flow-expectations/log-videosink-sink-expected: + * tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected: + * tests/check/scenarios/seek_with_stop.check_clock_sync/flow-expectations/log-videosink-sink-expected: + * tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected: + smart-mixer: Move the videoconvert to after the mixer + So that it tries to negotiate with alpha and the alpha channel is + dropped as late as possible in the pipeline. + The compositor is able to do video conversion internally in any case + so having a videoconvert before it is useless. + Part-of: + +2020-07-21 08:49:35 -0400 Thibault Saunier + + * ges/ges-video-transition.c: + transition: Enhance name of the elements + Making it simpler to debug + Part-of: + +2020-07-20 17:32:39 -0400 Thibault Saunier + + * ges/ges-source.c: + source: Handle missing elements in converter + Part-of: + +2020-07-14 00:09:32 -0400 Thibault Saunier + + * ges/ges-video-source.c: + video-source: Stop giving useless name to frame positioner + Part-of: + +2020-07-13 18:18:22 -0400 Thibault Saunier + + * ges/ges-smart-video-mixer.c: + * ges/ges-smart-video-mixer.h: + * ges/ges-utils.c: + * ges/ges-video-transition.c: + transition: Better document the way alpha is computed for transitions + Part-of: + +2020-07-12 13:51:42 -0400 Thibault Saunier + + * ges/ges-smart-video-mixer.c: + * ges/ges-timeline.c: + * ges/ges-utils.c: + * tests/check/meson.build: + * tests/check/scenarios/check_keyframes_in_compositor_two_sources.validatetest: + * tests/check/scenarios/check_keyframes_in_compositor_two_sources/flow-expectations/log-videosink-sink-expected: + smart-mixer: Use the new 'samples-selected' signal to handle queuing in aggregator pads + Since aggregator introduced queueing in its sinkpads the way we set + properties on the pads is incorrect as it doesn't take it into account. + This fixes the issue by using the newly introduced `samples-selected` + signal in aggregator to set the properties right before the compositing + is done. + Also require the compositor we use to be an aggregator. + And add a validate test for it. + Part-of: + +2020-07-12 13:49:36 -0400 Thibault Saunier + + * ges/ges-structured-interface.c: + * ges/ges-validate.c: + ges:validate: Allow setting keyframes using the clips directly + Part-of: + +2020-07-25 13:14:56 -0400 Thibault Saunier + + * ges/ges-uri-source.c: + ges-source: Ensure that we output stream with segments in time + Part-of: + +2020-07-09 11:10:41 -0400 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Restrict the presence only if the user didn't explicitly provided one + Part-of: + +2020-07-08 15:47:55 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Add a simplified version of track selection signal + Most user do not need to select several tracks for a single + TrackElement and this signal is not binding friendly so + this is adding a simpler, more user and binding friendly version + Part-of: + +2020-07-08 15:47:12 -0400 Thibault Saunier + + * ges/ges-uri-source.c: + uri-source: Respect user stream selection + Part-of: + +2020-07-08 08:02:27 -0400 Thibault Saunier + + * tools/ges-validate.c: + launch: Also print the position when disabling validate + Part-of: + +2020-07-08 08:01:58 -0400 Thibault Saunier + + * meson.build: + * tools/ges-launcher.c: + * tools/meson.build: + * tools/utils.c: + * tools/utils.h: + launch: Print more useful information to stdout + Part-of: + +2020-07-08 07:42:38 -0400 Thibault Saunier + + * meson_options.txt: + * tools/ges-launcher.c: + build: Add an option to disable examples + And make it yield as in other modules + Part-of: + +2020-07-03 18:21:22 -0400 Thibault Saunier + + * tools/ges-launcher.c: + launcher: Re activate smart rendering support + Trying to get the best encoding profile for smart rendering when + the user didn't specify anything. + Part-of: + +2020-07-03 18:16:13 -0400 Thibault Saunier + + * ges/ges-enums.h: + * ges/ges-internal.h: + * ges/ges-pipeline.c: + * ges/ges-source.c: + * ges/ges-timeline-tree.c: + * ges/ges-timeline-tree.h: + * ges/ges-timeline.c: + * ges/ges-track.c: + * ges/ges-uri-source.c: + ges: Fix smart rendering + Smart rendering has been broken since, mostly forever, but some code + was there pretending it was supported... let's try to stop pretending. + We now keep track of the smart rendering state in the timeline, track + and sources to be able to: + * tell decodebin to stop plugging more (decoding elements) as soon as + downstream supports the format. + * avoid plugging converters after the source element when smart + rendering. + Part-of: + +2020-07-03 18:00:39 -0400 Thibault Saunier + + * tests/validate/geslaunch.py: + validate: Pipe debug output to a file when discovering scenarios + Otherwise `gst-validate-launcher` can get veeery noisy + Part-of: + +2020-07-03 17:59:49 -0400 Thibault Saunier + + * plugins/nle/nlesource.c: + nle: Minor debug enhancement + Part-of: + +2020-07-03 17:58:16 -0400 Thibault Saunier + + * tests/check/ges/clip.c: + tests: Mark audio identity as audio + Otherwise GES fallbacks to video... + Part-of: + +2020-01-13 13:08:24 +0000 Henry Wilkes + + * ges/ges-internal.h: + * ges/ges-pipeline.c: + * ges/ges-track.c: + pipeline: stop setting the track caps + Stop setting the track 'caps' property. The previous code could + overwrite a users own setting of the caps for video and audio caps. + Moreover, the 'caps' property is listed as construct only, and users + will likely expect it to stay the same after a track has been added to a + timeline. + Part-of: + +2020-07-03 17:41:28 -0400 Thibault Saunier + + * tools/ges-launcher.c: + launcher: Delay setting rendering setting to right before rendering + So that user settings have been applied to the timeline taking into + account any `validatetest` arguments + Part-of: + +2020-07-03 17:18:51 -0400 Thibault Saunier + + * ges/ges-audio-source.c: + * ges/ges-internal.h: + * ges/ges-source.c: + * ges/ges-video-source.c: + * ges/ges-video-test-source.c: + source: Refactor the way we plug converter elements + Paving the way to skipping converters when rendering smartly + Part-of: + +2020-07-03 17:02:45 -0400 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Do not name urisink as `urisink` as it is useless + And actually harmful in case you are debugging several pipelines. + Part-of: + +2020-07-03 17:01:18 -0400 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Remove urisink from timeline instead of unrefing it + Doing what was suggested in the FIXME and avoiding to unref + something it while we do not actually own it ourself. + Part-of: + +2020-07-03 16:52:06 -0400 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Discard encoding profiles that don't match any track + Otherwise we get a 'not linked' error and we should just help + the user as we can here. + If the user adds a new track, he should set a new encoding profile + anyway. + Part-of: + +2020-07-03 16:34:21 -0400 Thibault Saunier + + * ges/ges-audio-uri-source.c: + * ges/ges-audio-uri-source.h: + * ges/ges-uri-source.c: + * ges/ges-uri-source.h: + * ges/ges-video-uri-source.c: + * ges/ges-video-uri-source.h: + * ges/meson.build: + uri*source: Factor out common logic into a GESUriSource private data + The two classes are *very* close but have different hierarchy so this + introduces a new GESUriSource structure that is used as private + structure by both subclasses and makes most of the logic shared this + way. + Part-of: + +2020-06-24 11:11:11 -0400 Thibault Saunier + + * ges/ges-audio-uri-source.c: + * ges/ges-video-uri-source.c: + *uri-source: Call free from the object ->finalize not ->dispose + Part-of: + +2020-07-25 19:16:06 +0100 Tim-Philipp Müller + + * meson.build: + * meson_options.txt: + * tools/meson.build: + meson: install bash completion helper for ges-launch-1.0 + Fixes #77 + Part-of: + +2020-07-25 19:09:30 +0100 Tim-Philipp Müller + + * meson.build: + * meson_options.txt: + meson: add 'tools' and 'examples' options + To optionally disable build of those. + Part-of: + +2020-07-24 07:43:05 +0530 AsociTon + + * ges/ges-base-xml-formatter.c: + * tests/check/python/test_assets.py: + Fix retrieving asset metadata on project reload. + Part-of: + +2020-01-21 16:02:56 +0530 yatinmaan1@gmail.com + + * tests/check/python/test_clip.py: + tests: Add test for ges_clip_get_top_effect_index + Part-of: + +2020-07-14 10:20:32 +0200 Guillaume Desmottes + + * tests/check/ges/clip.c: + tests: clip: fix test_rate_effects_duration_limit + Fix this assertion: + g_value_copy: assertion 'g_value_type_compatible (G_VALUE_TYPE (src_value), G_VALUE_TYPE (dest_value))' failed + 'tempo' is a float, not a double. + Part-of: + +2020-07-10 08:16:10 -0400 Thibault Saunier + + * ges/meson.build: + build: Add version.h to the headers list + So it is properly installed and the gir contains the required information + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/75 + Part-of: + +2020-07-09 21:42:50 -0400 Thibault Saunier + + * ges/ges-pitivi-formatter.h: + pitivi-formatter: Also skip the class + +2020-07-08 17:33:07 +0100 Tim-Philipp Müller + + * meson.build: + * scripts/extract-release-date-from-doap-file.py: + meson: set release date from .doap file for releases + Part-of: + +2020-07-08 10:03:43 -0400 Thibault Saunier + + * ges/ges-title-clip.h: + title: Make deprecated symbols visible API + Part-of: + +2020-07-03 02:04:08 +0100 Tim-Philipp Müller + + * meson.build: + Back to development + +=== release 1.17.2 === + +2020-07-03 00:35:20 +0100 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * gst-editing-services.doap: + * meson.build: + Release 1.17.2 + +2020-06-23 16:11:59 +0200 Mathieu Duponchelle + + * docs/libs/GESTimeOverlayClip-children-props.md: + * docs/libs/GESTitleSource-children-props.md: + * docs/libs/GESVideoTestSource-children-props.md: + * docs/libs/GESVideoUriSource-children-props.md: + * ges/ges-track.c: + * plugins/nle/nleoperation.c: + docs: fix links + +2020-06-23 00:05:13 +0200 Mathieu Duponchelle + + * docs/gst_plugins_cache.json: + plugins_cache: add base classes + +2020-06-23 00:04:52 +0200 Mathieu Duponchelle + + * docs/meson.build: + meson: mark plugins cache target as always stale + +2020-06-21 01:42:26 +0200 Mathieu Duponchelle + + * plugins/ges/gesbasebin.c: + * plugins/nle/nleobject.c: + docs: mark more types as plugin API + +2020-06-19 22:56:41 -0400 Thibault Saunier + + * docs/gst_plugins_cache.json: + doc: Stop documenting properties from parents + +2020-06-22 12:34:20 +0300 Sebastian Dröge + + * ges/ges-smart-video-mixer.c: + smart-video-mixer: Don't call gst_ghost_pad_construct() anymore + It's deprecated, unneeded and doesn't do anything anymore. + Part-of: + +2020-06-20 00:28:31 +0100 Tim-Philipp Müller + + * meson.build: + Back to development + +=== release 1.17.1 === + +2020-06-19 19:25:56 +0100 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * gst-editing-services.doap: + * meson.build: + Release 1.17.1 + +2020-06-19 11:13:24 -0400 Thibault Saunier + + * ges/ges-clip-asset.c: + * ges/ges-clip-asset.h: + * ges/ges-clip.c: + * ges/ges-enums.c: + * ges/ges-layer.c: + * ges/ges-marker-list.c: + * ges/ges-marker-list.h: + * ges/ges-meta-container.c: + * ges/ges-project.h: + * ges/ges-source-clip-asset.h: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-timeline.c: + * ges/ges-track-element-asset.c: + * ges/ges-track-element-asset.h: + * ges/ges-track-element.c: + * ges/ges-types.h: + * ges/ges-uri-asset.c: + * ges/ges-video-source.c: + ges: Add all missing Since markers from 1.16 onward + Part-of: + +2020-06-09 10:07:13 -0400 Thibault Saunier + + * ges/ges-asset.c: + asset: Do not try to update proxies when we are in a proxying loop + This is a regression introduced in + c12b84788d197c714ec32653e2b751079e377c46, this commit simply brings back + the previous behavior. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/113 + Part-of: + +2020-06-09 00:03:57 -0400 Thibault Saunier + + * ges/ges-command-line-formatter.c: + * ges/ges-effect-asset.c: + * ges/ges-effect-clip.c: + * ges/ges-effect.c: + * ges/ges-gerror.h: + * ges/ges-internal.h: + * tests/check/meson.build: + * tests/check/scenarios/complex_effect_bin_desc.validatetest: + * tests/check/scenarios/complex_effect_bin_desc/flow-expectations/log-videosink-sink-expected: + ges: Refactor the way we plug converters in effects + Stopping to do it at the bin description level but properly + plugging them where they are needed and cleanly ghosting the pads + where it makes most sense. + This introduces support for GES to request pads on the most upstream + element in case no static pad can be ghosted. + Part-of: + +2020-06-09 16:40:11 -0400 Thibault Saunier + + * ges/ges-command-line-formatter.c: + * ges/ges-structured-interface.c: + structured-interface: Add support for setting effects inpoint + Part-of: + +2020-06-09 16:35:44 -0400 Thibault Saunier + + * ges/ges-track-element.c: + * ges/ges-track-element.h: + track-element: Make set_has_internal_source return a boolean + Telling the user if it is legal to have an internal source in that + particular GESTrackElement. + Part-of: + +2020-06-15 13:09:39 -0400 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: doc: Add a note about trying to render before setting rendering settings + Part-of: + +2020-06-15 12:23:26 -0400 Thibault Saunier + + * ges/ges-asset.c: + * ges/ges-uri-clip.c: + uri-clip: Add a warning about synchronous uri discovery + Part-of: + +2020-06-09 15:22:30 -0400 Thibault Saunier + + * docs/gst_plugins_cache.json: + docs: Update plugins cache + +2020-06-08 10:58:43 -0400 Thibault Saunier + + * docs/gst_plugins_cache.json: + docs: Update plugins cache + +2020-06-05 15:56:00 +0200 Guillaume Desmottes + + * tests/check/scenarios/edit_while_seeked_with_stop.validatetest: + * tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected: + * tests/check/scenarios/seek_with_stop.check_clock_sync.validatetest: + * tests/check/scenarios/seek_with_stop.validatetest: + tests: enforce I420 format + Tests are assuming video is I420 with a specific chroma and colorimetry + but were not actually enforcing it. + Fixes needed as I420 will no longer be the first video format, see + gst-plugins-base!689 + Part-of: + +2020-06-04 23:14:59 +0200 Mathieu Duponchelle + + * docs/gst_plugins_cache.json: + * ges/ges-track.c: + * plugins/nle/nlecomposition.c: + track, composition: mark stream id properties as DOC_SHOW_DEFAULT + and update plugins cache + Part-of: + +2020-06-03 18:30:39 -0400 Thibault Saunier + + * docs/meson.build: + doc: Require hotdoc >= 0.11.0 + +2020-05-27 16:03:35 +0300 Sebastian Dröge + + * docs/gst_plugins_cache.json: + docs: Update gst_plugins_cache.json + +2020-06-03 09:57:06 +0200 Guillaume Desmottes + + * ges/ges-base-effect.c: + * ges/ges-base-effect.h: + * ges/ges-clip.c: + * ges/ges-enums.h: + * ges/ges-gerror.h: + * ges/ges-layer.c: + * ges/ges-time-overlay-clip.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-track-element.c: + * ges/ges-track.c: + add missing Since annotations on new API + Part-of: + +2020-05-27 19:44:29 -0400 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + formatter: Do not dereference NULL pointer + CID 1461701 + Part-of: + +2020-05-27 19:39:49 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + xml-formatter: Add an GST_ERROR when setting control sources fails + CID 1463853 + Part-of: + +2020-05-26 19:14:53 -0400 Thibault Saunier + + * ges/ges-validate.c: + validate: Wait for state change to consider commit as done + Part-of: + +2020-05-26 19:02:58 -0400 Thibault Saunier + + * tests/validate/geslaunch.py: + validate: Stop always muting + Part-of: + +2020-05-21 17:22:18 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-xml-formatter.c: + * tests/check/python/common.py: + * tests/check/python/test_timeline.py: + formatter: Fix saving/loading project with clip speed rate control + We need to ensure that clips duration is set after time effects are + added and we now need to serialize effects inpoints and max duration. + Part-of: + +2020-05-21 15:42:23 +0100 Henry Wilkes + + * docs/design/time_notes.md: + docs: add some notes on Time in GES + These notes cover time coordinates in GES, time effects, time + translations. + It also goes into why keyframes will not work with non-linear time + effects. + Part-of: + +2020-05-21 11:25:30 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-internal.h: + * ges/ges-uri-clip.c: + * tests/check/ges/asset.c: + uri-clip: don't assume duration needs to stay the same + ges_uri_clip_asset_get_duration does not tell us what the duration in + the timeline needs to be. Especially when we have time effects, or + effects with finite max-durations. So we should no longer expect the + duration to stay the same when replacing assets. Instead, we just check + that the new max-duration would be compatible with the current in-point + (which was not checked before), and the clip would not be totally + overlapped if its duration-limit changes. + This is based on the assumption that each source is replaced one-to-one + in its track. If a source is replaced with nothing in the same track, + this check may be a little too strong (but still mostly weaker than + before). However, problems could occur if track selection does + something unexpected, such as placing the new source in a track not + previously occupied. + Part-of: + +2020-05-20 21:23:03 +0100 Henry Wilkes + + * ges/ges-clip.c: + clip: provide an example of using time effects + Part-of: + +2020-05-20 21:20:10 +0100 Henry Wilkes + + * ges/ges-base-xml-formatter.c: + * ges/ges-clip.c: + * ges/ges-internal.h: + * ges/ges-timeline-tree.c: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + * ges/ges-xml-formatter.c: + * tests/check/ges/clip.c: + * tests/check/ges/project.c: + track-element: use out-point for updating control bindings + The out-point, which is an internal time, is used instead of the + duration for determining the control binding value at the end of the + element. + Also, allow the user to switch off the auto-clamping of control sources + if they are not desired. And allow them to clamp specific control sources + individually. + Also, fix a lot of memory leaks related to control sources. In + particular, releasing the extra ref gained by source in + g_object_get (binding, "control-source", &source, NULL); + Part-of: + +2020-05-15 18:09:50 +0100 Henry Wilkes + + * ges/ges-clip.c: + clip: test for layer in group + Make sure the layer exists before we try to remove the grouped clips + from it. + Part-of: + +2020-05-15 14:58:08 +0100 Henry Wilkes + + * ges/ges-internal.h: + * ges/ges-timeline-tree.c: + * ges/ges-timeline.c: + timeline-tree: make sure the layer priority refers to an existing layer + If a layer priority sits between the priorities of two layers in the + timeline, i.e. it references a gap in the timeline's layers, then + ges_timeline_append_layer will never fill this gap and create the + desired layer, so the edit in timeline-tree would loop forever. So a + check was added to avoid this. + This would be a usage error, but a user can reasonably end up with a gap + in their layers if they remove a layer from the timeline. + Part-of: + +2020-05-15 14:53:49 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-internal.h: + * ges/ges-timeline.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + clip: add method for adding top effects + Unlike ges_container_add, this lets you set the index and will check + that track selection did not fail. This is useful for time effects whose + addition would create an unsupported timeline configuration. + Also can use the clip add error in ges_timeline_add_clip to let the user + know when adding a clip to a layer that its in-point is set larger than + the max-duration of its core children. + Part-of: + +2020-05-15 14:47:15 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-enums.h: + * ges/ges-internal.h: + * ges/ges-timeline-tree.c: + * tests/check/python/test_timeline.py: + timeline-tree: take time effects into account when trimming + When trimming the start of a clip, we want to set the in-point of its + children such that whatever data was at the timeline time T still + remains at the timeline time T after the trim, where + T = MAX (prev_start, new_start) + Part-of: + +2020-05-15 14:41:58 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-internal.h: + * ges/ges-timeline-element.c: + * tests/check/ges/tempochange.c: + clip: use time translation for split + The new in-point should be the media position corresponding to the media + position. media_duration_factor is no longer needed. + Part-of: + +2020-05-18 17:34:01 +0100 Henry Wilkes + + * ges/ges-clip.c: + clip: fix warning when getting duration-limit + The duration-limit case was missing a 'break;' statement. + Part-of: + +2020-05-12 18:18:09 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-timeline-element.c: + * tests/check/ges/clip.c: + clip: add methods to convert between time coordinates + Add methods to convert between the timeline time coordinates and the + internal time coordinates of a track element in a clip, taking time + effects into account. + Part-of: + +2020-05-15 14:28:09 +0100 Henry Wilkes + + * ges/ges-base-effect-clip.c: + * ges/ges-base-effect.c: + * ges/ges-base-effect.h: + * ges/ges-clip.c: + * ges/ges-effect.c: + * ges/ges-internal.h: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-track-element.c: + * tests/check/ges/clip.c: + effect: Add support for time effects + Allow the user to register a child property of a base effect as a time + property. This can be used by GES to correctly calculate the + duration-limit of a clip when it has time effects on it. The existing + ges_effect_class_register_rate_property is now used to automatically + register such time effects for rate effects. + Part-of: + +2020-05-15 14:25:01 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-gerror.h: + * ges/ges-group.c: + * ges/ges-internal.h: + * ges/ges-layer.c: + * ges/ges-layer.h: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-timeline-tree.c: + * ges/ges-timeline-tree.h: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + * ges/ges-track.c: + * ges/ges-track.h: + * tests/check/ges/clip.c: + * tests/check/ges/test-utils.h: + * tests/check/python/common.py: + * tests/check/python/test_timeline.py: + errors: added edit errors + Added more errors to GES_ERROR for when edits fail (other than + programming or usage errors). Also promoted some GST messages if they + related to a usage error. + Also added explanation of timeline overlap rules in user docs. + Part-of: + +2020-05-15 12:19:16 -0400 Thibault Saunier + + * tests/check/scenarios/seek_with_stop.validatetest: + * tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected: + tests: Stop recording gaps in seek_with_stop + We have little control over those as they are generated by streamsynchronizer in a not reproducible way + Part-of: + +2020-05-15 11:53:10 -0400 Thibault Saunier + + * docs/libs/GESTitleSource-children-props.md: + * docs/libs/GESVideoTestSource-children-props.md: + docs: Remove reference to deinterlacing props in title and video test source + Part-of: + +2020-05-15 18:33:46 -0400 Thibault Saunier + + * ges/ges-validate.c: + validate: Fix setting ges properties + And fix typos. + Part-of: + +2020-04-22 13:39:21 -0400 Thibault Saunier + + * docs/libs/GESTimeOverlayClip-children-props.md: + * docs/libs/document-children-props.py: + * docs/sitemap.txt: + * ges/ges-internal.h: + * ges/ges-source-clip.c: + * ges/ges-test-clip.c: + * ges/ges-test-clip.h: + * ges/ges-time-overlay-clip.c: + * ges/ges-time-overlay-clip.h: + * ges/ges-video-source.c: + * ges/ges-video-test-source.c: + * ges/ges-video-test-source.h: + * ges/ges.h: + * ges/meson.build: + * tests/check/ges/clip.c: + * tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario: + * tests/check/scenarios/edit_while_seeked_with_stop.validatetest: + * tests/check/scenarios/seek_with_stop.check_clock_sync.validatetest: + * tests/check/scenarios/seek_with_stop.validatetest: + ges: Move TimeOverlayClip out of GESTestClip + This was complexifying the implementation for very little gain. + Each source type should ideally have its own API. + In that patch we make it so we do not have to subclass anything + but instead use GESAsset to pass information about how the pipeline + should look like. + Part-of: + +2020-05-14 00:56:40 -0400 Thibault Saunier + + * plugins/nle/nlecomposition.c: + nlecomposition: Add stack initialization action after setting our state + Otherwise there is a pretty rare race where we get the + _initialize_stack_func executed leading to the stack set up and + the source pushing buffers before the composition source pad is + activated, and a STREAM_ERROR is reported as we end up pushing a + buffer to a flushing pad. + Thanks rr chaos mode for showing that improbable race + Part-of: + +2020-05-13 17:11:24 -0400 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-timeline.c: + * ges/ges-track.c: + timeline: No thread checking while disposing + While this is not correct, we can't predict from what thread a + GstElement will be disposed as it might still be referenced by + a GstMessage somewhere which is freed by, any thread. + In this specific case we can assume that GES user will already have + let go his timeline reference and we should not avoid assert in that + specific case as it should be safe to let the timeline be destroyed + at that point. + Part-of: + +2020-05-01 23:05:44 -0400 Thibault Saunier + + * plugins/nle/nleobject.c: + nle: Use G_PARAM_DEPRECATED for media-duration-factor + Part-of: + +2020-05-18 08:49:53 -0400 Thibault Saunier + + * ges/ges-timeline.c: + ges: Ensure that assets are added to project before adding clip to timeline + It is the right ordering and in Pitivi we set the project size + when adding the first (relevant) asset, meaning that our code to + reposition clips would kick in (in the unit tests) if we do not respect + that ordering. + Part-of: + +2020-05-13 12:11:32 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-internal.h: + * ges/ges-timeline-tree.c: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + * ges/ges-uri-clip.c: + track-element: Add is_core method to API + Open up the method to the user, since they may need the information. + Also added more documentation on what a core track element is to a clip + and how they are treated. + Part-of: + +2020-05-01 12:40:58 +0100 Henry Wilkes + + * ges/ges-project.c: + * tests/check/ges/asset.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/clip.c: + * tests/check/ges/group.c: + * tests/check/ges/layer.c: + asset: unref requested assets + Prevent a few memory leaks in the tests. + Also mark ges_project_save as transfer full for the formatter asset. + Also make sure that ges_project_request_sync is transfer full on the + returned asset. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/104 + Part-of: + +2020-04-30 12:10:22 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-internal.h: + * ges/ges-track-element.c: + * tests/check/ges/clip.c: + * tests/check/ges/test-utils.h: + clip: enforce duration-limit + Prevent setting of properties or that of children, if the clip would not + be able to set the corresponding duration if the duration-limit would + drop below the currently set duration. + Part-of: + +2020-04-30 12:01:52 +0100 Henry Wilkes + + * ges/ges-clip.c: + * tests/check/ges/clip.c: + clip: make sure core child is active for non-core in same track + Each active non-core child must have a corresponding active core child + in the same track. Therefore, if we de-activate a core child, we also + need to de-activate all the non-core children in the same track. + Similarly, if we activate a non-core child, we need to activate the + corresponding core child as well. + Part-of: + +2020-04-30 11:50:08 +0100 Henry Wilkes + + * ges/ges-clip.c: + clip: be more robust in handling priority + Make less assumptions about the priority of effects and core elements so + that the code would still work if the priority of an element was set + directly. In particular, the index of a top effect will always be its + position in the effect ordering. + Part-of: + +2020-04-28 17:29:22 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-internal.h: + container: stop storing priority offset in child mapping + GESGroup no longer uses this, and GESClip can be made simpler without + it. + Part-of: + +2020-04-27 19:11:16 +0100 Henry Wilkes + + * ges/ges-auto-transition.c: + * ges/ges-auto-transition.h: + * ges/ges-clip.c: + * ges/ges-internal.h: + * ges/ges-timeline.c: + * tests/check/ges/clip.c: + clip: preserve auto-transition in split + When splitting a clip, keep the auto-transition at the end of the clip + alive and move its source to that of the corresponding split track + element. + Part-of: + +2020-04-21 12:55:34 +0100 Henry Wilkes + + * ges/ges-clip.c: + * tests/check/ges/clip.c: + clip: change order of split + We first change the duration of the splitted clip, then we add the new + clip to the layer and assign the tracks for its children. Normally, when + a clip is added to a layer it will have its track elements created, if + needed, and then assigned to their tracks. This will fail if any sources + would fully or triple overlap existing sources in the same track. + However, here we were adding the clip to the layer *and* avoiding the + track assignment process and instead setting the tracks explicitly. In + particular, the order was: + + add new clip to layer with no tracks assigned + + shrink the split clip + + assign the tracks for the new clip + This has been changed to: + + shrink the split clip + + add new clip to layer with no tracks assigned + + assign the tracks for the new clip + Thus, the order of events for any users connecting to object signals + will be close to that of adding another clip to the layer. + Part-of: + +2020-04-27 16:27:15 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-timeline.c: + timeline: create auto-transitions during track-element-added + Any time a track element is added to a track, we need to check whether + we need to create a new corresponding auto-transition. This simply moves + the code from ges-clip.c to ges-timeline.c, where it is more appropriate. + Moreover, it technically opens the possibility for creating + auto-transitions for track elements in the timeline that have no + corresponding clip. + Part-of: + +2020-04-27 16:05:54 +0100 Henry Wilkes + + * ges/ges-timeline-tree.c: + * tests/check/python/test_timeline.py: + timeline-tree: also trim non-core track elements + Also trim the in-point of non-core children of clips to ensure that + their content will appear in the timeline at the same position. + Part-of: + +2020-04-24 21:00:18 +0100 Henry Wilkes + + * ges/ges-timeline.c: + * tests/check/ges/basic.c: + timeline: make sure appended layer has lowest priority + Make sure that the priority of an appended layer is the lowest (highest + in value) when appending a layer to the timeline. This change is + important when appending a layer to a timeline, which can easily have a + gap in priorities if a layer has been removed. + Part-of: + +2020-04-23 17:34:52 +0100 Henry Wilkes + + * tests/check/python/common.py: + * tests/check/python/test_timeline.py: + tests: add tests for new editing behaviour + These tests expose some of the new editing behaviour in timeline + tree. In particular, we test: + + edits for clips within groups within a group + + that an edit can succeed if a snap allows it to + + that snapping occurs at a specific point, and that we alternate + between one call to snapping-started and one call to snapping-ended + with corresponding values + + that an edit can fail if a snap causes it to + + no snapping is released when an edit fails + + We tests for the expected changes, and otherwise check that the + configuration of the timeline has remained unchanged + + The timeline configuration remains the same when an edit fails + + That each clip overlap has a corresponding auto-transition + + That particular auto-transitions are created when a new overlap is + formed + + That particular auto-transitions are destroyed when an overlap ends + + That auto-transitions are not replaced when two clips move but + maintain their overlap + + That the timeline does not contain any unaccounted for clips + Part-of: + +2020-04-23 17:30:17 +0100 Henry Wilkes + + * ges/ges-layer.c: + * ges/ges-timeline.c: + layer: don't set timeline when moving clip + If a clip is moving we should not unset its timeline when it is removed + from the layer. Logic has been moved to ges_timeline_add_clip and + ges_timeline_remove_clip. + Part-of: + +2020-04-22 15:06:32 +0100 Henry Wilkes + + * ges/ges-auto-transition.c: + * ges/ges-auto-transition.h: + * ges/ges-internal.h: + * ges/ges-timeline-tree.c: + * ges/ges-timeline.c: + timeline-tree: freeze auto-transitions whilst editing + Freeze the auto-tranistions so they do not destroy themselves during an + edit. Once complete the auto-transitions can move themselves back into + position, or remove themselves if their sources are no longer + overlapping. + Part-of: + +2020-04-21 15:06:03 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-timeline-tree.c: + * ges/ges-timeline-tree.h: + clip: make auto-transitions less expensive when adding to track + Only check the overlaps with the actual track element that was just added + to the track. This reduces the tree traversal by one order. + Part-of: + +2020-04-21 14:05:55 +0100 Henry Wilkes + + * ges/ges-layer.c: + * tests/check/ges/clip.c: + clip: remove children if failed to add to layer + If adding to a layer fails during ges_timeline_add_clip, any new children + that were created during this process should be removed from the clip to + put it back into its previous state. + Part-of: + +2020-04-21 11:36:58 +0100 Henry Wilkes + + * ges/ges-group.c: + group: let timeline-tree handle layer priority + Since a group can only have its priority set whilst it is part of a + timeline, we can simply let the timeline-tree handle the move, which it + can already do, whilst checking that the move would be legal (not break + the timeline configuration). All the group has to do now if update its + priority value if the priority of any of its children changes. It + doesn't even need to keep track of the layer priority offsets. + Also, added a check to ensure added children belong to the same + timeline. + Also moved the sigids from the GObject data to a g_hash_table, which is + clearer. + Part-of: + +2020-04-20 14:56:55 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-group.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + timeline-element: stop using edit vmethods + These were all redirecting to essentially ges_timeline_element_edit + anyway. + Part-of: + +2020-04-20 13:13:48 +0100 Henry Wilkes + + * ges/ges-auto-transition.c: + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-group.c: + * ges/ges-internal.h: + * ges/ges-timeline-element.c: + * ges/ges-timeline-tree.c: + timeline-element: simplify check for being edited + It should be sufficient to set the edit flag only on the toplevel, which + allows all of its children to know they are being edited and should not + move in response. + Also, removed some unnecessary setting/checking of this. + Also, supplied the ges_timeline_element_peak_toplevel, which unlike + ges_timeline_element_get_toplevel_parent, does not add a reference to + the toplevel. Some corresponding leaks in auto-transition have been + fixed by using this instead. + Part-of: + +2020-04-27 14:05:38 +0100 Henry Wilkes + + * ges/ges-timeline.c: + * tests/check/python/test_timeline.py: + timeline: emit snapping-started with new valid time + Only emit snapping-ended if we have a valid snap time. Moreover, we + should emit a new snapping-started even if we are snapping at the same + location. This is because a new snap will always correspond to a new edit, + possibly involving different snapping elements, which a user would want + to know about. + Part-of: + +2020-04-27 13:58:38 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges-group.c: + * ges/ges-internal.h: + * ges/ges-timeline-element.c: + * ges/ges-timeline-tree.c: + * ges/ges-timeline-tree.h: + * ges/ges-timeline.c: + * ges/ges-track.c: + * tests/check/ges/layer.c: + * tests/check/ges/timelineedition.c: + * tests/check/python/test_timeline.py: + * tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario: + timeline-tree: simplify and fix editing + Editing has been simplified by breaking down each edit into a + combination of three basic single-element edits: MOVE, TRIM_START, and + TRIM_END. + Each edit follows these steps: + + Determine which elements are to be edited and under which basic mode + + Determine which track elements will move as a result + + Snap the edit position to one of the edges of the main edited element, + (or the edge of one of its descendants, in the case of MOVE), avoiding + moving elements. + NOTE: in particular, we can *not* snap to the edge of a neighbouring + element in a roll edit. This was previously possible, even though the + neighbour was moving! + + Determine the edit positions for clips (or track elements with no + parent) using the snapped value. In addition, we replace any edits of + a group with an edit of its descendant clips. If any value would be + out of bounds (e.g. negative start) we do not edit. + NOTE: this is now done *after* checking the snapping. This allows the + edit to succeed if snapping would cause it to go from being invalid to + valid! + + Determine whether the collection of edits would result in a valid + timeline-configuration which does not break the rules for sources + overlapping. + + If all this succeeds, we emit snapping-started on the timeline. + + We then perform all the edits. At this point they should all succeed. + The simplification/unification should make it easier to make other + changes. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/97 + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/98 + Part-of: + +2020-04-18 16:49:31 +0100 Henry Wilkes + + * ges/ges-group.c: + * tests/check/ges/group.c: + group: fix priority setting + Stop moving the group if a child clip is being edited by timeline-tree, + a child group is updating its own priority, or a layer that a clip is in + has changed priority. A group should only move if a descendant moves + layers outside of a timeline-tree edit, or the priority of the group is + set by the user. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/89 + Part-of: + +2020-04-18 16:34:56 +0100 Henry Wilkes + + * ges/ges-container.c: + * ges/ges-group.c: + * ges/ges-internal.h: + container: keep start and duration up to date + Simplified keeping the start and the duration of a container/group up to + date with the earliest start of the children and the last end of the + children. The previous logic was spread between ges-group and + ges-container, now all the position handling is in ges-container. + Part-of: + +2020-04-28 18:01:04 +0100 Henry Wilkes + + * ges/ges-uri-clip.c: + uri-clip: use duration-limit in set_max_duration + Use the duration-limit rather than max-duration - in-point, since the + former will be able to take other factors, such as effects, into + account. + Part-of: + +2020-04-13 17:42:22 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-clip.h: + * tests/check/ges/clip.c: + * tests/check/ges/test-utils.h: + clip: add the duration-limit property + The duration-limit is the maximum duration that can be set for the clip + given its current children and their properties. If a change in the + children properties causes this to drop below the current duration, it + is automatically capped by this limit. + Part-of: + +2020-05-04 10:35:25 -0400 Thibault Saunier + + * ges/ges-formatter.c: + * ges/python/gesotioformatter.py: + ges: Output otio formatter loading issues in debug logs + Instead of spamming the terminal with a python traceback + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/107 + Part-of: + +2020-05-05 23:03:36 -0400 Thibault Saunier + + * tests/check/scenarios/seek_with_stop.validatetest: + * tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected: + * tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected: + tests: Stop recording segment position in seek_with_stop + There are two valid timing in GstAggregator where the segment event + is pushed before GstAggregator sets its srcpad->segment.position in + gst_aggregator_pad_chain_internal. Segment.position is basically + a helper field for internal elements use so we should not require + a specific value here as we are not checking a particular element + behavior. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/106 + Part-of: + +2020-05-02 01:24:18 -0400 Thibault Saunier + + * tests/validate/geslaunch.py: + test: Add support for .validatetest in the launcher app + +2020-05-01 14:26:32 +0100 Henry Wilkes + + * ges/ges-container.c: + container: return TRUE if adding doesn't cause any errors + If `add_child` and `set_parent` succeed we want to return TRUE, even if + the added element is no longer a child by the end of the method. This is + because some users may call ges_container_remove during `child-added`. + This shouldn't be considered an error. + +2020-04-30 17:44:33 -0400 Thibault Saunier + + * tests/check/scenarios/edit_while_seeked_with_stop.validatetest: + * tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected: + * tests/check/scenarios/seek_with_stop.check_clock_sync.validatetest: + * tests/check/scenarios/seek_with_stop.check_clock_sync/flow-expectations/log-videosink-sink-expected: + * tests/check/scenarios/seek_with_stop.validatetest: + * tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected: + * tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected: + ges:tests: Fix the `ignore-fields` format in validatetests + They are needed as those are not 100% reproducible with GES. + Part-of: + +2020-04-30 13:23:05 -0400 Thibault Saunier + + * plugins/ges/gesbasebin.c: + plugin: Fix a race removing tracks from timeline from the wrong thread + The case was that the timeline state was being changed from the parent + composition's action thread before the timeline was committed, leading + to the SELECT_STREAM event to be pushed from the track to the nested + timeline from the wrong composition thread. + ``` + ** + GES:ERROR:../subprojects/gst-editing-services/ges/ges-track.c:1263:ges_track_remove_element: assertion failed: (track->priv->valid_thread == g_thread_self()) + Bail out! GES:ERROR:../subprojects/gst-editing-services/ges/ges-track.c:1263:ges_track_remove_element: assertion failed: (track->priv->valid_thread == g_thread_self()) + Thread 1 (Thread 0x7f6ec2d43700 (LWP 1228982)): + #0 0x00007f6ed85b2a25 in raise () at /lib64/libc.so.6 + #1 0x00007f6ed859b895 in abort () at /lib64/libc.so.6 + #2 0x00007f6ed899cb8c in g_assertion_message (domain=, file=0x7f6ed8d7fd58 "../subprojects/gst-editing-services/ges/ges-track.c", line=, func=, message=) at ../glib/gtestutils.c:2914 + #3 0x00007f6ed89fa9ff in g_assertion_message_expr (domain=domain@entry=0x7f6ed8d76875 "GES", file=file@entry=0x7f6ed8d7fd58 "../subprojects/gst-editing-services/ges/ges-track.c", line=line@entry=1263, func=func@entry=0x7f6ed8d805b0 <__func__.6> "ges_track_remove_element", expr=expr@entry=0x7f6ed8d801e8 "track->priv->valid_thread == g_thread_self()") at ../glib/gtestutils.c:2940 + #4 0x00007f6ed8d2658f in ges_track_remove_element (track=track@entry=0x7f6eb4119b20 [GESAudioTrack], object=object@entry=0x106f240 [GESAudioUriSource]) at ../subprojects/gst-editing-services/ges/ges-track.c:1263 + #5 0x00007f6ed8d10842 in ges_clip_empty_from_track (clip=0x7f6e7803ee80 [GESUriClip], track=track@entry=0x7f6eb4119b20 [GESAudioTrack]) at ../subprojects/gst-editing-services/ges/ges-clip.c:1086 + #6 0x00007f6ed8d01453 in ges_timeline_remove_track (timeline=timeline@entry=0x7f6e6c01ae50 [GESTimeline], track=0x7f6eb4119b20 [GESAudioTrack]) at ../subprojects/gst-editing-services/ges/ges-timeline.c:2460 + #7 0x00007f6ed8d0286b in ges_timeline_send_event (element=, event=) at ../subprojects/gst-editing-services/ges/ges-timeline.c:484 + #8 0x00007f6ed8bf466c in gst_element_send_event (element=0x7f6e6c01ae50 [GESTimeline], event=event@entry=0x7f6eb410f9f0) at ../subprojects/gstreamer/gst/gstelement.c:1934 + #9 0x00007f6ed8d242cd in ges_track_handle_message (bin=0xd846f0 [GESVideoTrack], message=0x7f6eb411ac90) at ../subprojects/gst-editing-services/ges/ges-track.c:477 + #10 0x00007f6ed8bc9128 in bin_bus_handler (bus=, message=, bin=) at ../subprojects/gstreamer/gst/gstbin.c:3286 + #11 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0xfdf440 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359 + #12 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0x10261d0 [NleComposition], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067 + #13 0x00007f6ed8bccbee in gst_bin_post_message (element=0x10261d0 [NleComposition], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376 + #14 0x00007f6ed8bf4b66 in gst_element_post_message (element=0x10261d0 [NleComposition], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110 + #15 0x00007f6ed8bc9128 in bin_bus_handler (bus=, message=, bin=) at ../subprojects/gstreamer/gst/gstbin.c:3286 + #16 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0xfdf2c0 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359 + #17 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0x1029110 [GstBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067 + #18 0x00007f6ed8bccbee in gst_bin_post_message (element=0x1029110 [GstBin], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376 + #19 0x00007f6ed8bf4b66 in gst_element_post_message (element=0x1029110 [GstBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110 + #20 0x00007f6ed8bc9128 in bin_bus_handler (bus=, message=, bin=) at ../subprojects/gstreamer/gst/gstbin.c:3286 + #21 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0xfdf500 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359 + #22 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0xd705e0 [NleSource], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067 + #23 0x00007f6ed8bccbee in gst_bin_post_message (element=0xd705e0 [NleSource], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376 + #24 0x00007f6ed8bf4b66 in gst_element_post_message (element=0xd705e0 [NleSource], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110 + #25 0x00007f6ed8bc9128 in bin_bus_handler (bus=, message=, bin=) at ../subprojects/gstreamer/gst/gstbin.c:3286 + #26 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0x1042400 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359 + #27 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0x1029450 [GstBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067 + #28 0x00007f6ed8bccbee in gst_bin_post_message (element=0x1029450 [GstBin], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376 + #29 0x00007f6ed8bf4b66 in gst_element_post_message (element=0x1029450 [GstBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110 + #30 0x00007f6ed8bc9128 in bin_bus_handler (bus=, message=, bin=) at ../subprojects/gstreamer/gst/gstbin.c:3286 + #31 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0x1042640 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359 + #32 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0x7f6eb42fc7a0 [GstURIDecodeBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067 + #33 0x00007f6ed8bccbee in gst_bin_post_message (element=0x7f6eb42fc7a0 [GstURIDecodeBin], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376 + #34 0x00007f6ed8bf4b66 in gst_element_post_message (element=0x7f6eb42fc7a0 [GstURIDecodeBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110 + #35 0x00007f6ed8bc9128 in bin_bus_handler (bus=, message=, bin=) at ../subprojects/gstreamer/gst/gstbin.c:3286 + #36 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0x7f6eb80a7130 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359 + #37 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0x7f6e6c02aa60 [GstDecodeBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067 + #38 0x00007f6ed8bccbee in gst_bin_post_message (element=0x7f6e6c02aa60 [GstDecodeBin], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376 + #39 0x00007f6ed8bf4b66 in gst_element_post_message (element=0x7f6e6c02aa60 [GstDecodeBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110 + #40 0x00007f6ec8f1e00d in gst_decode_bin_handle_message (bin=0x7f6e6c02aa60 [GstDecodeBin], msg=) at ../subprojects/gst-plugins-base/gst/playback/gstdecodebin2.c:5667 + #41 0x00007f6ed8bc9128 in bin_bus_handler (bus=, message=, bin=) at ../subprojects/gstreamer/gst/gstbin.c:3286 + #42 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0x7f6eb4139110 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359 + #43 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0x7f6e54038c70 [GESDemux], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067 + #44 0x00007f6ed8bccbee in gst_bin_post_message (element=0x7f6e54038c70 [GESDemux], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376 + #45 0x00007f6ed8bf4b66 in gst_element_post_message (element=0x7f6e54038c70 [GESDemux], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110 + #46 0x00007f6ed8bc9128 in bin_bus_handler (bus=, message=, bin=) at ../subprojects/gstreamer/gst/gstbin.c:3286 + #47 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0x7f6eb4139350 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359 + #48 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0x7f6e6c01ae50 [GESTimeline], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067 + #49 0x00007f6ed8bccbee in gst_bin_post_message (element=0x7f6e6c01ae50 [GESTimeline], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376 + #50 0x00007f6ed8bf4b66 in gst_element_post_message (element=element@entry=0x7f6e6c01ae50 [GESTimeline], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110 + #51 0x00007f6ed8cfa221 in ges_timeline_change_state (element=0x7f6e6c01ae50 [GESTimeline], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gst-editing-services/ges/ges-timeline.c:450 + #52 0x00007f6ed8bf5d1e in gst_element_change_state (element=element@entry=0x7f6e6c01ae50 [GESTimeline], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:3033 + #53 0x00007f6ed8bf6868 in gst_element_continue_state (element=element@entry=0x7f6e6c01ae50 [GESTimeline], ret=ret@entry=GST_STATE_CHANGE_SUCCESS) at ../subprojects/gstreamer/gst/gstelement.c:2741 + #54 0x00007f6ed8bf5d67 in gst_element_change_state (element=element@entry=0x7f6e6c01ae50 [GESTimeline], transition=transition@entry=GST_STATE_CHANGE_NULL_TO_READY) at ../subprojects/gstreamer/gst/gstelement.c:3072 + #55 0x00007f6ed8bf6368 in gst_element_set_state_func (element=0x7f6e6c01ae50 [GESTimeline], state=GST_STATE_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:2987 + #56 0x00007f6ed8bd2129 in gst_bin_element_set_state (next=GST_STATE_PAUSED, current=GST_STATE_PAUSED, start_time=0, base_time=0, element=0x7f6e6c01ae50 [GESTimeline], bin=0x7f6e54038c70 [GESDemux]) at ../subprojects/gstreamer/gst/gstbin.c:2615 + #57 gst_bin_change_state_func (element=0x7f6e54038c70 [GESDemux], transition=GST_STATE_CHANGE_PAUSED_TO_PAUSED) at ../subprojects/gstreamer/gst/gstbin.c:2957 + #58 0x00007f6ed8bf5d1e in gst_element_change_state (element=element@entry=0x7f6e54038c70 [GESDemux], transition=transition@entry=GST_STATE_CHANGE_PAUSED_TO_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:3033 + #59 0x00007f6ed8bf6368 in gst_element_set_state_func (element=0x7f6e54038c70 [GESDemux], state=GST_STATE_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:2987 + #60 0x00007f6ed8bd2129 in gst_bin_element_set_state (next=GST_STATE_PAUSED, current=GST_STATE_READY, start_time=0, base_time=0, element=0x7f6e54038c70 [GESDemux], bin=0x7f6e6c02aa60 [GstDecodeBin]) at ../subprojects/gstreamer/gst/gstbin.c:2615 + #61 gst_bin_change_state_func (element=0x7f6e6c02aa60 [GstDecodeBin], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstbin.c:2957 + #62 0x00007f6ec8f1e84f in gst_decode_bin_change_state (element=0x7f6e6c02aa60 [GstDecodeBin], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gst-plugins-base/gst/playback/gstdecodebin2.c:5482 + #63 0x00007f6ed8bf5d1e in gst_element_change_state (element=element@entry=0x7f6e6c02aa60 [GstDecodeBin], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:3033 + #64 0x00007f6ed8bf6868 in gst_element_continue_state (element=element@entry=0x7f6e6c02aa60 [GstDecodeBin], ret=ret@entry=GST_STATE_CHANGE_SUCCESS) at ../subprojects/gstreamer/gst/gstelement.c:2741 + #65 0x00007f6ed8bf5d67 in gst_element_change_state (element=element@entry=0x7f6e6c02aa60 [GstDecodeBin], transition=transition@entry=GST_STATE_CHANGE_NULL_TO_READY) at ../subprojects/gstreamer/gst/gstelement.c:3072 + #66 0x00007f6ed8bf6368 in gst_element_set_state_func (element=0x7f6e6c02aa60 [GstDecodeBin], state=GST_STATE_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:2987 + #67 0x00007f6ed8bf5ae8 in gst_element_sync_state_with_parent (element=0x7f6e6c02aa60 [GstDecodeBin]) at ../subprojects/gstreamer/gst/gstelement.c:2413 + #68 0x00007f6ed89f17a0 in g_slist_foreach (list=, func=0x7f6ed8bf5a50 , user_data=user_data@entry=0x0) at ../glib/gslist.c:880 + #69 0x00007f6ec8f37d45 in gst_uri_decode_bin_change_state (element=, transition=) at ../subprojects/gst-plugins-base/gst/playback/gsturidecodebin.c:2869 + #70 0x00007f6ed8bf5d1e in gst_element_change_state (element=element@entry=0x7f6eb42fc7a0 [GstURIDecodeBin], transition=transition@entry=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:3033 + #71 0x00007f6ed8bf6368 in gst_element_set_state_func (element=0x7f6eb42fc7a0 [GstURIDecodeBin], state=GST_STATE_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:2987 + #72 0x00007f6ed8bd2129 in gst_bin_element_set_state (next=GST_STATE_PAUSED, current=GST_STATE_READY, start_time=0, base_time=0, element=0x7f6eb42fc7a0 [GstURIDecodeBin], bin=0x1029450 [GstBin]) at ../subprojects/gstreamer/gst/gstbin.c:2615 + #73 gst_bin_change_state_func (element=0x1029450 [GstBin], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstbin.c:2957 + #74 0x00007f6ed8bf5d1e in gst_element_change_state (element=element@entry=0x1029450 [GstBin], transition=transition@entry=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:3033 + #75 0x00007f6ed8bf6368 in gst_element_set_state_func (element=0x1029450 [GstBin], state=GST_STATE_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:2987 + #76 0x00007f6ed8bd2129 in gst_bin_element_set_state (next=GST_STATE_PAUSED, current=GST_STATE_READY, start_time=0, base_time=0, element=0x1029450 [GstBin], bin=0xd705e0 [NleSource]) at ../subprojects/gstreamer/gst/gstbin.c:2615 + #77 gst_bin_change_state_func (element=0xd705e0 [NleSource], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstbin.c:2957 + #78 0x00007f6ec805533f in nle_object_change_state (element=0xd705e0 [NleSource], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gst-editing-services/plugins/nle/nleobject.c:748 + #79 0x00007f6ed8bf5d1e in gst_element_change_state (element=element@entry=0xd705e0 [NleSource], transition=transition@entry=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:3033 + #80 0x00007f6ed8bf6368 in gst_element_set_state_func (element=0xd705e0 [NleSource], state=GST_STATE_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:2987 + #81 0x00007f6ed8bd2129 in gst_bin_element_set_state (next=GST_STATE_PAUSED, current=GST_STATE_READY, start_time=0, base_time=0, element=0xd705e0 [NleSource], bin=0x1029110 [GstBin]) at ../subprojects/gstreamer/gst/gstbin.c:2615 + #82 gst_bin_change_state_func (element=0x1029110 [GstBin], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstbin.c:2957 + #83 0x00007f6ed8bf5d1e in gst_element_change_state (element=element@entry=0x1029110 [GstBin], transition=transition@entry=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:3033 + #84 0x00007f6ed8bf6368 in gst_element_set_state_func (element=0x1029110 [GstBin], state=GST_STATE_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:2987 + #85 0x00007f6ed8bf5ae8 in gst_element_sync_state_with_parent (element=0x1029110 [GstBin]) at ../subprojects/gstreamer/gst/gstelement.c:2413 + #86 0x00007f6ec8060356 in _activate_new_stack (toplevel_seek=, comp=0x10261d0 [NleComposition]) at ../subprojects/gst-editing-services/plugins/nle/nlecomposition.c:3117 + #87 update_pipeline (comp=comp@entry=0x10261d0 [NleComposition], currenttime=, seqnum=, update_reason=update_reason@entry=COMP_UPDATE_STACK_INITIALIZE) at ../subprojects/gst-editing-services/plugins/nle/nlecomposition.c:3396 + #88 0x00007f6ec80614f6 in _initialize_stack_func (comp=0x10261d0 [NleComposition], ucompo=0x108c800) at ../subprojects/gst-editing-services/plugins/nle/nlecomposition.c:732 + #89 0x00007f6ed893788a in g_closure_invoke (closure=, return_value=, n_param_values=, param_values=, invocation_hint=) at ../gobject/gclosure.c:810 + #90 0x00007f6ec805aaf6 in _execute_actions (comp=0x10261d0 [NleComposition]) at ../subprojects/gst-editing-services/plugins/nle/nlecomposition.c:412 + #91 0x00007f6ed8c4c1cf in gst_task_func (task=0x7f6e6c01c290 [GstTask]) at ../subprojects/gstreamer/gst/gsttask.c:328 + #92 0x00007f6ed89fc0f4 in g_thread_pool_thread_proxy (data=) at ../glib/gthreadpool.c:354 + #93 0x00007f6ed89fb7f2 in g_thread_proxy (data=0x7f6eb0017800) at ../glib/gthread.c:807 + #94 0x00007f6ed7e14432 in start_thread () at /lib64/libpthread.so.0 + #95 0x00007f6ed86779d3 in clone () at /lib64/libc.so.6 + ``` + Part-of: + +2020-04-14 10:22:09 +0100 Henry Wilkes + + * tests/check/nle/tempochange.c: + check: give nle_tempochange test more time + These test can take longer than most under valgrind, so give them a + little more time until they timeout. + Part-of: + +2020-04-13 11:40:55 +0100 Henry Wilkes + + * ges/ges-timeline.c: + * tests/check/ges/basic.c: + timeline: fix adding track when layers contains clips + Made sure that adding a new track only uses select-tracks-for-object for + core children to determine whether a track elements should be added to the + new track or not, and *not* any other track. In particular, there should + be *no* change in the existing tracks of the timeline when adding another + track. Moreover, a new track should not invoke the creation of track + elements for other tracks. + Part-of: + +2020-04-08 17:11:14 +0100 Henry Wilkes + + * ges/ges-effect.c: + * ges/ges-track-element.c: + * plugins/nle/nleghostpad.c: + * plugins/nle/nleobject.c: + * plugins/nle/nleobject.h: + * plugins/nle/nleoperation.c: + * tests/check/ges/tempochange.c: + * tests/check/nle/tempochange.c: + nleobject: stop using media-duration-factor + The property had been deprecated and is unused. + This property is not needed. Any internal time effect that an nleoperation + wraps is itself responsible for converting seek/segment timestamps. + Previously, the ghostpads were performing a rate conversion after the + rate element had already done so, essentially doubling their effect on + seeks and segment times. This was always unnecessary, but went unnoticed + by the tempochange test because it was using an identity element rather + than an actual rate-changing element. + Part-of: + +2020-04-08 17:08:41 +0100 Henry Wilkes + + * plugins/nle/nlecomposition.c: + * plugins/nle/nleoperation.c: + * plugins/nle/nleoperation.h: + nleoperation: stop setting next_base_time + This property was unused. + Part-of: + +2020-04-21 16:22:31 -0400 Thibault Saunier + + * plugins/nle/nlecomposition.c: + * tests/check/meson.build: + * tests/check/scenarios/edit_while_seeked_with_stop.validatetest: + * tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected: + * tests/check/scenarios/seek_with_stop.check_clock_sync.validatetest: + * tests/check/scenarios/seek_with_stop.check_clock_sync/flow-expectations/log-videosink-sink-expected: + * tests/check/scenarios/seek_with_stop.validatetest: + * tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected: + * tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected: + * tools/ges-launcher.c: + * tools/ges-launcher.h: + * tools/ges-validate.c: + nlecomposition: Fix seeking with stop + And add some tests + Part-of: + +2020-04-24 17:15:16 -0400 Thibault Saunier + + * tools/ges-launcher.c: + * tools/ges-launcher.h: + * tools/ges-validate.c: + launch: Add support for testfiles + Making it simpler to define a test in a single files, including the + configuration etc.. + Part-of: + +2020-04-24 16:46:50 -0400 Thibault Saunier + + * ges/ges-track.c: + track: Do not commit ourselves automatically when changing state from wrong thread + The user is responsible to commit the timeline from the right thread + in that case and in the case of gesdemux, the loaded timeline is filling + gaps automatically when the project is set loaded. + Part-of: + +2020-04-18 16:22:25 +0200 Andoni Morales Alastruey + + * meson.build: + macos: fix python's configure checks + +2020-04-17 12:35:26 -0400 Thibault Saunier + + * ges/ges-video-source.c: + * ges/ges-video-uri-source.c: + ges: Fix interlaced stream playback + Negotiation was failling as `videoflip` was not allowing not + progressive interlacing. + Also avoid adding a deinterlace element when it is useless. + +2020-04-16 20:27:30 -0400 Thibault Saunier + + * ges/gstframepositioner.c: + * ges/gstframepositioner.h: + * meson.build: + * tests/check/scenarios/check_video_track_restriction_scale.scenario: + framepositioner: Fix some source repositionning rounding issues + Avoid loosing (too much) precision when rescaling back and forth by + storing values in gdoubles. + Handle the fact that position values can be negative + Also fix debug category static variable + as it clashes with the instance variable name in a few methods. + +2020-04-16 12:53:00 -0400 Thibault Saunier + + * ges/ges-clip-asset.c: + * ges/ges-timeline.c: + timeline: Fix wrong usage of scale_int + We are multiplying the framerate by GST_SECOND and thus have no + guarantee that it won't overflow. + +2020-04-11 11:40:06 -0400 Thibault Saunier + + * ges/ges-image-source.c: + * ges/ges-video-uri-source.c: + ges: Place imagefreeze at right place + Negotiation fails when having the imagefreeze after videorate and + frame positioning won't happen after seeks if we do not put it + before the postioner + +2020-04-09 11:24:44 -0400 Thibault Saunier + + * ges/ges-project.c: + * ges/ges-timeline.c: + asset: Avoid dereferencing NULL pointer + CID 1461286 + +2020-04-09 11:20:34 -0400 Thibault Saunier + + * plugins/ges/gesbasebin.c: + basebin: Do not set stream_group if upstream didn't provide it + CID: 1461278 + +2020-04-09 11:17:59 -0400 Thibault Saunier + + * ges/gstframepositioner.c: + framepositionner: Fix wrong old size check condition + CID: 1461277 + +2020-04-09 11:16:34 -0400 Thibault Saunier + + * ges/ges-validate.c: + validate: Avoid dereferencing NULL pointer + CID: 1461266 + +2020-04-09 11:10:43 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Ensure setting framerate in timeline_get_framerate + CID: 1461250, 1461288 + +2020-04-09 11:07:04 -0400 Thibault Saunier + + * tests/check/nle/complex.c: + tests: Check that linking pads works + CID: 1456061 + +2020-04-09 11:02:26 -0400 Thibault Saunier + + * ges/ges-structured-interface.c: + structured-interface: Properly check that setting keyframe works + Fixes CID: 1455490 + +2020-04-09 10:59:40 -0400 Thibault Saunier + + * plugins/ges/gesdemux.c: + gesdemux: Check result of g_stat + CID: 1455489, 1455521 + +2020-04-09 10:54:26 -0400 Thibault Saunier + + * tests/check/ges/tempochange.c: + test: tempochange: Plug leak + CID: 1455448 + +2020-04-09 10:42:03 -0400 Thibault Saunier + + * ges/ges-timeline.c: + ges: Cast to signed int to compare agasint 0 + The check made sense but we were not casting to be able to check + signess of subtraction result. + CID: 1444923 + +2020-04-09 10:37:20 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Do not compare unsigned with 0 + Layer priorities are always positive the check was making no + sense in any case. + Fixes CID: 1444922, 1461284 + +2020-04-09 10:31:36 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-group.c: + * ges/ges-pitivi-formatter.c: + * plugins/ges/gesdemux.c: + * tests/check/ges/clip.c: + ges: Always check return value of `ges_container_add` + Making coverity happy + CIDs: 1461460, 1461461, 1461462, 1461463, 1461464, 1461465, 1461466, 1461468, + +2020-04-09 10:00:43 -0400 Thibault Saunier + + * ges/ges-video-test-source.c: + ges: Fix sending EOS on testclip when using timeoverlay + Basically when using timeoverlay we where waiting for input-selector + to receive EOS on its active on the output-selector streaming thread + but... EOS was being sent from that same thread waiting for input-selector + to unblock to send EOS on its other pad. + In our specific use case we want EOS to be sent only on the active pad. + Fixes: https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/103 + +2020-04-09 09:29:17 -0400 Thibault Saunier + + * docs/deprecated.md: + * docs/libs/GESAudioTestSource-children-props.md: + * docs/libs/GESAudioUriSource-children-props.md: + * docs/libs/GESTitleSource-children-props.md: + * docs/libs/GESTransitionClip-children-props.md: + * docs/libs/GESVideoTestSource-children-props.md: + * docs/libs/GESVideoUriSource-children-props.md: + * docs/libs/document-children-props.py: + * docs/sitemap.txt: + * ges/ges-audio-test-source.h: + * ges/ges-audio-uri-source.h: + * ges/ges-effect-asset.c: + * ges/ges-source-clip-asset.c: + * ges/ges-title-source.c: + * ges/ges-title-source.h: + * ges/ges-transition-clip.h: + * ges/ges-types.h: + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + * ges/ges-video-source.c: + * ges/ges-video-test-source.c: + * ges/ges-video-test-source.h: + * ges/ges-video-uri-source.h: + ges: Update documentation + And start generating TrackElement children property with a stupid + simple script + +2020-04-09 09:24:12 -0400 Thibault Saunier + + * ges/ges-video-test-source.c: + ges: Add the foreground color child property + +2020-04-07 10:53:15 -0400 Thibault Saunier + + * ges/ges-asset.c: + * tests/check/python/test_assets.py: + ges: Fix reloading UriClipAsset synchronously + And add tests for that + +2020-04-07 10:47:07 -0400 Thibault Saunier + + * bindings/python/gi/overrides/GES.py: + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-internal.h: + * ges/ges-track-element.c: + * ges/ges-transition-clip.c: + * ges/ges-uri-asset.c: + * tests/check/python/test_clip.py: + ges: Rework the way we ensure core elements are not wrongly moved between clips + Instead of focusing on the instances of the clips and their children, + we relax the check to allow moving track element clip between clips + that share a common asset. This makes it as correct conceptually but + more flexible, and the code becomes simpler. + +2020-04-02 11:58:18 +0100 Henry Wilkes + + * ges/ges-container.c: + * ges/ges-group.c: + * ges/ges-timeline.c: + group: tidied timeline membership in copy-paste + Previously, the GESContainer ->paste method and GESGroup ->paste methods + were unnecessarily setting the timeline of groups, even though this is + handled by the GESGroup ->child_added method. This could result in the + group being added multiple times. + +2020-04-01 21:34:48 +0100 Henry Wilkes + + * ges/ges-timeline-tree.c: + * tests/check/python/test_timeline.py: + timeline-tree: fix overlap check + Previously, the code was not able to detect that an element overlaps on + its end, nor could it detect that an element overlaps two elements that + already overlap. + +2020-04-06 12:44:30 +0100 Henry Wilkes + + * ges/ges-clip.c: + * tests/check/ges/clip.c: + clip: tidy grouping + Make the grouping of clips cleaner by checking that the clips share the + same asset. + +2020-04-06 12:42:03 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-layer.c: + clip: secure adding clip to layer + Add more checks when adding a clip to a layer, or moving a clip to a new + layer. Also, mark the "layer" property as explicit-notify. + +2020-04-06 12:28:13 +0100 Henry Wilkes + + * ges/ges-uri-clip.c: + uri-clip: match children by track + When the asset of a uri clip is reset, its core children are removed and + replaced by the new core children. When replacing, the `set_asset` + method attempts to copy children properties from the previous children + to the new children. However, the children were matched by track-type + only. This would not function as intended when a URI contains multiple + audio or video streams. Instead, we now match children by the tracks + themselves. This should work better, provided the user's + select-tracks-for-object is well behaved. + Also, fix a memory problem in `set_mute` for when a child is not in a + track. + +2020-04-06 12:26:11 +0100 Henry Wilkes + + * ges/ges-timeline-element.c: + * ges/ges-track-element.c: + timeline-element: only copy read-write properties + Only copy the properties that can be both read and written, and are not + construct only. Similarly for child properties when a track-element is + deep copied. + +2020-04-06 12:17:43 +0100 Henry Wilkes + + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + timeline: return sunk element on pasting + Technically, an element can still be floating on the return from + `->paste` (e.g. a clip not in a layer). Since the return of the `_paste` + methods are (return full) a non-floating object is probably expected in + all cases. + +2020-04-06 12:16:11 +0100 Henry Wilkes + + * ges/ges-timeline.c: + * tests/check/ges/basic.c: + auto-transition: select track directly + By-pass the select-tracks-for-object signal for auto-transitions since + their track element must land in the same track as the elements it is + the auto-transition for. + +2020-04-06 12:09:54 +0100 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-internal.h: + * ges/ges-layer.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + * ges/ges-track.c: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/test-utils.h: + timeline: re-handle clip children track selection + The way a clip's track elements are added to tracks was re-handled. This + doesn't affect the normal usage of a simple audio-video timeline, where + the tracks are added before any clips, but usage for multi-track + timelines has improved. The main changes are: + + We can now handle a track being selected for more than one track, + including a full copy of their children properties and bindings. + (Previously broken.) + + When a clip is split, we copy the new elements directly into the same + track, avoiding select-tracks-for-object. + + When a clip is grouped or ungrouped, we avoid moving the elements to + or from tracks. + + Added API to allow users to copy the core elements of a clip directly + into a track, complementing select-tracks-for-object. + + Enforced the rule that a clip can only contain one core child in a + track, and all the non-core children must be added to tracks that + already contains a core child. This extends the previous condition + that two sources from the same clip should not be added to the same + track. + + Made ges_track_add_element check that the newly added track element + does not break the configuration rules of the timeline. + + When adding a track to a timeline, we only use + select-tracks-for-object to check whether track elements should be + added to the new track, not existing ones. + + When removing a track from a timeline, we empty it of all the track + elements that are controlled by a clip. Thus, we ensure that a clip + only contains elements that are in the tracks of the same timeline, or + no track. Similarly, when removing a clip from a timeline. + + We can now avoid unsupported timeline configurations when a layer is + added to a timeline, and already contains clips. + + We can now avoid unsupported timeline configurations when a track is + added to a timeline, and the timeline already contains clips. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/84 + +2020-03-26 09:21:42 +0000 Henry Wilkes + + * ges/ges-timeline.c: + timeline: stop connecting to track-element-added + This was used to connect to the track element's notify::start signal in + order to update the duration of the timeline (it is not clear why the + notify::duration signal was not also connected to for the same reason). + However, this is already covered by the timeline_tree_move method, which + is always called to update the start of a track element, even if it is not + part of a clip (and similarly for timeline_tree_trim, which is called + when the duration is set). + +2020-03-25 19:35:11 +0000 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-uri-clip.c: + * tests/check/ges/clip.c: + clip: allow arbitrary max-duration when no core children + Before the max-duration could be set arbitrarily when the clip was empty, + to indicate what the max-duration would be once the core children were + created. Now, we can also do this whilst the clip only contains non-core + children. + +2020-03-25 18:49:16 +0000 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-internal.h: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + * tests/check/python/test_clip.py: + track-element: change owner to creator + Rename the private "owners" to "creators" to avoid confusing this with + the owner of the track element's memory. + Also made the ungroup method for GESClip symmetric by making all the + children of the resulting clips share their creators, which allows them + to be added to any of the other ungrouped clips. Once the clips are + grouped back together, the tracks loose these extra creators. + +2020-04-06 12:21:54 +0100 Henry Wilkes + + * ges/ges-container.c: + * tests/check/ges/clip.c: + container: change ownership when adding + Make sure we sink the child on adding, and keep it alive until the end + in case the method fails. + Also, since the child mappings hold a ref to the child, they should give + them up in their free method. This way, the ref will be given up on + disposing, even if ges_container_remove fails. + Also, reverse setting of the start of the container if adding fails. + +2020-04-06 23:06:29 +0530 Nirbheek Chauhan + + * ges/ges-uri-clip.c: + * ges/ges-video-source.c: + ges: Fix build with GCC 10 + gcc-10 defaults to -fno-common, which exposes a symbol conflict, so + use `static` correctly. Also we don't use `parent_extractable_iface` + in `ges-uri-clip.c`. + See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85678 + +2020-03-31 11:25:49 -0300 Thibault Saunier + + * ges/ges-container.c: + * tests/check/python/test_timeline.py: + ges: Fix trimming clip inside deeply nested groups + This broke in 6b7c658b6a551a5b9170987ba44592d1d819e1ae + +2020-03-24 22:47:01 -0300 Thibault Saunier + + * ges/ges-uri-clip.c: + uri-clip: Remove dead code + GES_TESTING_ASSETS_DIRECTORY is prehistoric and since then + new mechanism for asset relocation have been added, it makes + no sense to keep that unused code path + +2020-03-24 22:44:07 -0300 Thibault Saunier + + * ges/ges-uri-clip.c: + uri-clip: Remove ->create_track_element implementation + It is dead code + +2020-03-24 22:35:35 -0300 Thibault Saunier + + * ges/ges-image-source.c: + * ges/ges-multi-file-source.c: + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + * ges/ges-video-source.c: + * ges/ges-video-source.h: + * ges/ges-video-uri-source.c: + * tests/check/ges/uriclip.c: + * tests/check/python/test_clip.py: + ges: Deprecate GESImageSource and GESMultiFileSource + Refactoring GESVideoSource so that #GESUriVideoSource can handle + still image in a simple way + MultiFileSource has been replaced with the new `imagesequencesrc` + element, this was totally broken anyway as `multifilesrc` can not seek + properly. + +2020-03-24 22:30:38 -0300 Thibault Saunier + + * ges/ges-audio-uri-source.c: + * ges/ges-track-element.c: + * ges/ges-video-source.c: + * ges/ges-video-uri-source.c: + track-element: Create nleobject on GESExtractable::set_asset + This means that we have all the information about the asset + when constructing the underlying GstElements. + This also allows to cleanup some code all around + +2020-03-24 22:25:47 -0300 Thibault Saunier + + * ges/ges-timeline-element.c: + timeline:element: Refactor the way we 'copy' + Simplifying the implementation and making sure assets are set asap + +2020-03-24 22:23:16 -0300 Thibault Saunier + + * ges/ges-audio-source.c: + * ges/ges-audio-test-source.c: + * ges/ges-audio-transition.c: + * ges/ges-audio-transition.h: + * ges/ges-effect-clip.c: + * ges/ges-group.c: + * ges/ges-text-overlay.c: + * ges/ges-text-overlay.h: + * ges/ges-title-source.c: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + * ges/ges-video-source.c: + * ges/ges-video-test-source.c: + * ges/ges-video-transition.c: + * ges/ges-video-transition.h: + ges: Use assets to instantiate track elements/group + And deprecate all GESTrackElement constructors, but the GESEffect one. + Those should **never** be created by users and should become internal + in the future. + Stop having docstring for the constructors that were internal. + +2020-03-18 16:24:08 -0300 Thibault Saunier + + * tests/check/assets/audio_only.ogg: + * tests/check/assets/audio_video.ogg: + * tests/check/assets/image.png: + * tests/check/assets/test-auto-transition.xges: + * tests/check/assets/test-project.xges: + * tests/check/assets/test-properties.xges: + * tests/check/ges/test-utils.c: + * tests/check/meson.build: + * tests/check/python/test_clip.py: + tests: Cleanup test files handling + +2020-03-13 15:03:17 -0300 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-layer.h: + * ges/ges-xml-formatter.c: + formatter: Serialize source properties + This way we ensure that the TrackElement 'active' property is + properly serialized + +2020-03-06 18:56:52 -0300 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-layer.c: + * ges/ges-layer.h: + * ges/ges-timeline-tree.c: + * ges/ges-timeline-tree.h: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + * ges/ges-track.c: + * ges/ges-validate.c: + * ges/ges-xml-formatter.c: + * tests/check/meson.build: + * tests/check/python/common.py: + * tests/check/python/test_timeline.py: + * tests/check/scenarios/check_layer_activness_gaps.scenario: + ges: Add a way to set layer activeness by track + a.k.a muting layers. + Adding unit tests and making sure serialization works properly + +2020-03-23 21:21:10 -0300 Thibault Saunier + + * ges/ges-timeline-element.c: + element: Add API safe guard against invalid position in edit() + +2020-03-23 21:11:45 -0300 Thibault Saunier + + * ges/ges-validate.c: + validate: Refactor actions implementation + Making them simpler to read and avoiding leaks + +2020-03-23 15:14:13 -0300 Thibault Saunier + + * ges/ges-structured-interface.c: + structured-interface: Fix adding clip to layer error reporting + +2020-03-17 11:53:47 -0300 Thibault Saunier + + * ges/ges-clip-asset.c: + * ges/ges-source-clip-asset.c: + * ges/ges-source-clip-asset.h: + * ges/ges-source-clip.c: + * ges/ges-test-clip.c: + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + * ges/meson.build: + ges: Add a SourceClipAsset class + Cleaning up the way we use the default framerate for natural + frame rate. + +2020-03-10 16:10:12 -0300 Thibault Saunier + + * tools/ges-launcher.c: + * tools/ges-launcher.h: + * tools/ges-validate.c: + launch: Add a way to disable validate at runtime + Also avoid to add useless bin in our sinks + +2020-03-09 15:38:58 -0300 Thibault Saunier + + * ges/ges-structured-interface.c: + * ges/ges-timeline.c: + * ges/ges-uri-asset.c: + * ges/ges-validate.c: + * tests/check/meson.build: + * tools/ges-launch.c: + * tools/ges-launcher.c: + * tools/ges-validate.c: + ges: Plug some leaks + +2020-02-28 11:56:22 -0300 Thibault Saunier + + * ges/ges-validate.c: + * tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario: + validate: Add support to seek in frames + +2020-02-28 11:47:25 -0300 Thibault Saunier + + * ges/ges-command-line-formatter.c: + * ges/ges-extractable.c: + * ges/ges-extractable.h: + * ges/ges-internal.h: + * ges/ges-structure-parser.c: + * ges/ges-test-clip.c: + * ges/ges-video-test-source.c: + * tests/check/python/test_timeline.py: + ges: support test clips assets natural size/framerate + This way we can test this kind of behaviour without requiring + real sources. + Also add simple tests. + +2020-02-21 09:17:11 -0300 Thibault Saunier + + * ges/ges-clip-asset.c: + * ges/ges-clip-asset.h: + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-command-line-formatter.c: + * ges/ges-gerror.h: + * ges/ges-internal.h: + * ges/ges-structured-interface.c: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-types.h: + * ges/ges-utils.c: + * ges/ges-validate.c: + * tests/check/meson.build: + * tests/check/scenarios/check_edit_in_frames.scenario: + * tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario: + ges: Add APIs to have a sens of frame numbers + APIs: + - ges_timeline_get_frame_time + - ges_timeline_get_frame_at + - ges_clip_asset_get_frame_time + - ges_clip_get_timeline_time_from_source_frame + Extracting ges_util_structure_get_clocktime to internal utilities adding + support for specifying timing values in frames with the special + f synthax. + +2019-10-29 16:52:52 +0000 Henry Wilkes + + * tools/utils.c: + utils: fix argument sanitization + _sanitize_argument is supposed to wrap arguments in '"' quote marks such + that they can be parsed and copied into a GstStructure string. This + purpose is now supported more directly, which fixes some bugs, e.g.: + arguments before fix + +title my=title +title my="title" +title "my=title" + +title abc n=my=name +title abc n="my="name" +title abc n="my=name" + +title my"title +title "my"title" +title "my\"title" + +title my\title +title "my\title" +title "my\\title" + +2020-02-28 11:52:38 -0300 Thibault Saunier + + * tools/ges-launcher.c: + * tools/utils.c: + launch: Fix memory management issue with the rendering format + +2020-02-25 17:42:47 -0300 Thibault Saunier + + * ges/ges-validate.c: + validate: Rename edit-container to edit + Keeping the old version for backward compat + +2020-02-21 17:17:10 -0300 Thibault Saunier + + * ges/ges-source.c: + * ges/ges-video-test-source.c: + ges: Add a timeoverlay to video test sources + This is often very useful to have a timeoverlay inside test sources. + We do not want to use it as an effect as segments are not the sames + in GES when it comes to nleoperations. + +2020-02-25 18:39:47 -0300 Thibault Saunier + + * ges/ges-timeline-element.c: + element: Handle using own property as child property + Avoiding ref cycles + +2020-02-21 17:16:01 -0300 Thibault Saunier + + * ges/ges-clip.c: + ges: Ensure GESClips assets are always ClipAssets + +2020-02-18 15:21:38 -0300 Thibault Saunier + + * ges/ges-audio-uri-source.c: + * ges/ges-clip-asset.c: + * ges/ges-clip-asset.h: + * ges/ges-clip.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-track-element-asset.c: + * ges/ges-track-element-asset.h: + * ges/ges-track-element.c: + * ges/ges-uri-asset.c: + * ges/ges-video-uri-source.c: + ges: Add API to retrieve the natural framerate of an element + +2020-02-28 17:53:55 -0300 Thibault Saunier + + * ges/ges-track.c: + ges: Some memory management fixes setting track mixing + Also fix 'mixing' property notifies + +2020-02-28 17:50:05 -0300 Thibault Saunier + + * ges/ges-enums.c: + ges: Cleanup GESEdge and GESEditMode GEnum values + By duplicating the registered values, so that bindings have + better values to use + +2020-03-02 14:35:33 -0300 Thibault Saunier + + * tools/ges-launcher.c: + launch: Make command line provided sinks override scenario defined ones + +2020-02-28 11:58:30 -0300 Thibault Saunier + + * ges/gstframepositioner.c: + framepositioner: Avoid dereferencing NULL pointer + +2020-03-04 16:03:30 -0300 Thibault Saunier + + * ges/ges-validate.c: + validate: Initialize GValue before calling g_object_get_value + This is required with GLib < 2.60 + +2020-03-17 18:13:51 -0300 Thibault Saunier + + * ges/ges-asset.h: + * ges/ges-audio-source.h: + * ges/ges-audio-test-source.h: + * ges/ges-audio-track.h: + * ges/ges-audio-transition.h: + * ges/ges-audio-uri-source.h: + * ges/ges-auto-transition.h: + * ges/ges-base-effect-clip.h: + * ges/ges-base-effect.h: + * ges/ges-base-transition-clip.h: + * ges/ges-base-xml-formatter.h: + * ges/ges-clip-asset.h: + * ges/ges-clip.h: + * ges/ges-command-line-formatter.h: + * ges/ges-container.h: + * ges/ges-effect-asset.h: + * ges/ges-effect-clip.h: + * ges/ges-effect.h: + * ges/ges-enums.h: + * ges/ges-extractable.h: + * ges/ges-formatter.h: + * ges/ges-gerror.h: + * ges/ges-group.h: + * ges/ges-image-source.h: + * ges/ges-internal.h: + * ges/ges-layer.h: + * ges/ges-marker-list.h: + * ges/ges-meta-container.h: + * ges/ges-multi-file-source.h: + * ges/ges-operation-clip.h: + * ges/ges-operation.h: + * ges/ges-overlay-clip.h: + * ges/ges-pipeline.h: + * ges/ges-pitivi-formatter.h: + * ges/ges-prelude.h: + * ges/ges-project.h: + * ges/ges-screenshot.h: + * ges/ges-smart-adder.h: + * ges/ges-smart-video-mixer.h: + * ges/ges-source-clip.h: + * ges/ges-source.h: + * ges/ges-structure-parser.h: + * ges/ges-structured-interface.h: + * ges/ges-test-clip.h: + * ges/ges-text-overlay-clip.h: + * ges/ges-text-overlay.h: + * ges/ges-timeline-element.h: + * ges/ges-timeline-tree.h: + * ges/ges-timeline.h: + * ges/ges-title-clip.h: + * ges/ges-title-source.h: + * ges/ges-track-element-asset.h: + * ges/ges-track-element.h: + * ges/ges-track.h: + * ges/ges-transition-clip.h: + * ges/ges-transition.h: + * ges/ges-types.h: + * ges/ges-uri-asset.h: + * ges/ges-uri-clip.h: + * ges/ges-utils.h: + * ges/ges-version.h.in: + * ges/ges-video-source.h: + * ges/ges-video-test-source.h: + * ges/ges-video-track.h: + * ges/ges-video-transition.h: + * ges/ges-video-uri-source.h: + * ges/ges-xml-formatter.h: + * ges/ges.h: + * plugins/ges/gesbasebin.h: + * tests/check/ges/test-utils.h: + * tools/ges-launcher.h: + * tools/ges-validate.h: + ges: Use #pragma once everywhere + +2020-03-17 15:51:39 -0300 Thibault Saunier + + * ges/ges-asset.h: + * ges/ges-audio-source.h: + * ges/ges-audio-test-source.h: + * ges/ges-audio-track.h: + * ges/ges-audio-transition.h: + * ges/ges-audio-uri-source.h: + * ges/ges-auto-transition.h: + * ges/ges-base-effect-clip.h: + * ges/ges-base-effect.h: + * ges/ges-base-transition-clip.h: + * ges/ges-base-xml-formatter.h: + * ges/ges-clip-asset.h: + * ges/ges-clip.h: + * ges/ges-command-line-formatter.h: + * ges/ges-container.h: + * ges/ges-effect-asset.h: + * ges/ges-effect-clip.h: + * ges/ges-effect.h: + * ges/ges-extractable.h: + * ges/ges-formatter.h: + * ges/ges-group.h: + * ges/ges-image-source.h: + * ges/ges-internal.h: + * ges/ges-layer.h: + * ges/ges-meta-container.h: + * ges/ges-multi-file-source.h: + * ges/ges-operation-clip.h: + * ges/ges-operation.c: + * ges/ges-operation.h: + * ges/ges-overlay-clip.h: + * ges/ges-pipeline.h: + * ges/ges-pitivi-formatter.h: + * ges/ges-project.h: + * ges/ges-smart-video-mixer.c: + * ges/ges-source-clip.h: + * ges/ges-source.h: + * ges/ges-test-clip.h: + * ges/ges-text-overlay-clip.h: + * ges/ges-text-overlay.h: + * ges/ges-timeline-element.h: + * ges/ges-timeline.h: + * ges/ges-title-clip.h: + * ges/ges-title-source.h: + * ges/ges-track-element-asset.h: + * ges/ges-track-element.h: + * ges/ges-track.h: + * ges/ges-transition-clip.h: + * ges/ges-transition.c: + * ges/ges-transition.h: + * ges/ges-types.h: + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + * ges/ges-uri-clip.h: + * ges/ges-video-source.h: + * ges/ges-video-test-source.h: + * ges/ges-video-track.h: + * ges/ges-video-transition.h: + * ges/ges-video-uri-source.h: + * ges/ges-xml-formatter.h: + * tools/ges-launcher.h: + ges: Cleanup the way we declare object types + We create our own _DECLARE_ macro because we have instance structures + +2020-03-19 09:15:07 -0300 Thibault Saunier + + * ges/ges-timeline-element.c: + ges: Stop using hash_table_steal_extended + This appeard in GLib 2.58 + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/99 + +2020-03-18 13:36:47 -0300 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-uri-clip.c: + * tests/check/assets/30frames.ogv: + * tests/check/ges/clip.c: + * tests/check/python/test_clip.py: + clip: Allow setting max-duration clips without TrackElements + Otherwise this breaks quite a few assumption in user code, several + pitivi tests broke because of that. + +2020-03-18 12:56:06 -0300 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-internal.h: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + * tests/check/assets/30frames.ogv: + * tests/check/python/test_clip.py: + ges: Make it so core elements can be re added to their 'owners' + The user might want to add/remove/add core children to clips and be able + to regroup ungrouped clip. This is needed for undo/redo in Pitivi for + example + +2020-03-18 11:12:55 -0300 Thibault Saunier + + * ges/ges-container.c: + container: Let subclass know adding child was interrupted + When the `child-added` signal emission was called, the + `GESContainer->child_added` vmethod was called (the signal is + `G_SIGNAL_RUN_FIRST`) so we need to call `GESContainer->child_removed` + ourself so subclasses know they do not control the child anymore. + +2020-03-10 16:01:02 +0000 Henry Wilkes + + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-track-element.c: + timeline-element: make start and duration EXPLICIT_NOTIFY + The properties will only have their signal emitted when they change in + value, even when g_object_set, etc, methods are used. + The _set_start method already did this, but start was missing the + EXPLICIT_NOTIFY flag. There should be no need to check that the property + has changed in ->set_start or ->set_duration + +2020-03-10 15:27:20 +0000 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-internal.h: + * ges/ges-timeline-element.c: + * ges/ges-track-element.c: + * tests/check/ges/clip.c: + timeline-element: make max-duration cap in-point + Do not allow the in-point to exceed the max-duration of any timeline + element. + +2020-03-10 11:53:09 +0000 Henry Wilkes + + * ges/ges-clip.c: + * tests/check/ges/clip.c: + clip: only allow children with the same timeline + Refuse the addition of children whose timeline is neither NULL nor the + clip's timeline. + +2020-03-10 11:38:58 +0000 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-transition-clip.c: + * ges/ges-uri-clip.c: + * tests/check/ges/clip.c: + * tests/check/ges/overlays.c: + * tests/check/ges/test-utils.h: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + clip: re-handle child in-point and max-duration + The in-point of a clip is kept in sync with its core children, unless they + have no has-internal-source. + The max-duration is defined as the minimum max-duration amongst the + clip's core children. If it is set to a new value, this sets the + max-duration of its core children to the same value if they have + has-internal-source set as TRUE. + Non-core children (such as effects on a source clip) do not influence + these values. + As part of this, we no longer track in-point in GESContainer. Unlike start + and duration, the in-point of a timeline element does not refer to its + extent in the timeline. As such, it has little meaning for most + collections of timeline-elements, in particular GESGroups. As such, there + is no generic way to relate the in-point of a container to its children. + +2020-03-10 11:35:23 +0000 Henry Wilkes + + * ges/ges-group.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + timeline-element: make in-point and max-duration EXPLICIT_NOTIFY + As such, they only emit a signal if their value changes, either through + their _set_inpoint or _set_max_duration methods, or through + g_object_set, etc. + Also, we now require the ->set_max_duration method to be implemented. + This was added to GESGroup, which will only allow the max-duration to be + set to GST_CLOCK_TIME_NONE. + +2020-03-10 11:29:40 +0000 Henry Wilkes + + * ges/ges-image-source.c: + * ges/ges-source.c: + * ges/ges-title-clip.c: + * ges/ges-title-source.c: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + track-element: add has-internal-source property + Unless this property is set to TRUE, the in-point must be 0 and the + max-duration must be GST_CLOCK_TIME_NONE. + Also added EXPLICIT_NOTIFY flags to the active and track-type + properties such that their notifies are emitted only if the property + changes, even when the g_object_set, etc, methods are used. + Also added a missing notify signal to the set_active method. + +2020-03-03 18:00:51 +0000 Henry Wilkes + + * ges/ges-clip.c: + * tests/check/ges/clip.c: + clip: copy and paste control bindings + Previously the control bindings were not properly copied into the pasted + clip. Also changed the order so that elements are added to the clip + before the clip is added to the timeline. + +2020-03-03 14:31:10 +0000 Henry Wilkes + + * ges/ges-container.c: + * ges/ges-internal.h: + * ges/ges-timeline-element.c: + * tests/check/ges/clip.c: + * tests/check/ges/group.c: + * tests/check/ges/test-utils.c: + * tests/check/ges/test-utils.h: + timeline-element: add signals for child properties + Add the child-property-added and child-property-removed signals to + GESTimelineElement. + GESContainer is able to use this to keep their child properties in sync + with their children: if they are added or removed from the child, they + are also added or removed from the container. + +2020-03-02 12:23:07 +0000 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-group.c: + * tests/check/ges/clip.c: + container: freeze notifies during add and remove + Hold the notify signals for the container and the children until after + the child has been fully added or removed. + After the previous commit, this was used to ensure that the + notify::priority signal was sent for children of a clip *after* the + child-removed signal. This stopped being the case when the code in + ->child_removed was moved to ->remove_child (the latter is called before + the child-removed signal is emitted, whilst the former is called + afterwards). Rather than undo this move of code, which was necessary to + ensure that ->add_child was always reversed, the notify::priority signal + is now simply delayed until after removing the child has completed. This + was done for all notify signals, as well as in the add method, to ensure + consistency. + This allows the test_clips.py test_signal_order_when_removing_effect to + pass. + Also make subclasses take a copy of the list of the children before + setting the start and duration, since this can potentially re-order the + children (if they have the SET_SIMPLE flag set). + +2020-03-02 13:35:20 +0000 Henry Wilkes + + * ges/ges-clip.c: + clip: make remove_child a reverse of add_child + Previously, we relied on ->child_removed to reverse the priority changes + that occured in ->add_child. However, ->child_removed is not always + called (the signal child-removed is not always emitted) when a + ->add_child needs to be removed. However, ->remove_child is always + called to reverse ->add_child, so the code was moved here. Otherwise, we + risk that the priorities of the clip will contain gaps, which will cause + problems when another child is added to the clip. + +2020-03-02 13:25:21 +0000 Henry Wilkes + + * ges/ges-clip.c: + clip: tidy handling of child priorities + Handle the child priorities in a way that keeps the container children + list sorted by priority at all times. Also, no longer rely on the + control_mode of the container, since we have less control over its value, + compared to private variables. + Also fixed the changing of priorities in set_top_effect_index: + previously *all* children whose priority was above or below the new + priority were shifted, when we should have been only shifting priorities + for the children whose priority lied *between* the old and the new + priority of the effect. E.g. + effect: A B C D E F + index: 0 1 2 3 4 5 + After moving effect E to index 1, previously, we would get + effect: A B C D E F + index: 0 2 3 4 1 6 + (this would have also shifted the priority for the core children as + well!). Whereas now, we have the correct: + effect: A B C D E F + index: 0 2 3 4 1 5 + +2020-03-02 12:56:03 +0000 Henry Wilkes + + * ges/ges-base-effect-clip.c: + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-container.c: + * ges/ges-internal.h: + * ges/ges-source-clip.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/test-utils.h: + clip: only allow core elements as children + Only allow elements that were created by ges_clip_create_track_elements + (or copied from such an element) to be added to a clip. This prevents + users from adding arbitrary elements to a clip. + As an exception, a user can add GESBaseEffects to clips whose class + supports it, i.e. to a GESSourceClip and a GESBaseEffectClip. + This change also introduces a distinction between the core elements of a + clip (created by ges_clip_create_track_elements) and non-core elements + (currently, only GESBaseEffects, for some classes). In particular, + GESBaseEffectClip will now distinguish between its core elements and + effects added by the user. This means that the core elements will always + have the lowest priority, and will not be listed as top effects. This is + desirable because it brings the behaviour of GESBaseEffectClip in line + with other clip types. + +2020-03-11 19:38:19 -0300 Thibault Saunier + + * plugins/nle/nlecomposition.c: + * plugins/nle/nleobject.c: + nle: Delay marking object as not in composition + Instead of doing it at the time of resetting `object->in_composition` + when user calls `gst_bin_remove` do it after we actually removed + it from the object thread, and do it in the `nle_object_reset` + method where it belongs + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/96 + +2020-03-10 21:54:56 +0000 Henry Wilkes + + * ges/ges-auto-transition.c: + auto-transition: fix setting of SET_SIMPLE flag + Previously, the SET_SIMPLE flag was non unset for auto-transitions after + it had been set. + +2020-03-11 13:42:50 +0200 Sebastian Dröge + + * meson.build: + Fix build with Python 3.8 by also checking for python-3.X-embed.pc + Since Python 3.8 the normal checks don't include the Python libraries + anymore and linking of the Python formatters would fail. + See also https://github.com/mesonbuild/meson/issues/5629 + and https://gitlab.freedesktop.org/gstreamer/gst-python/issues/28 + +2020-03-09 11:49:33 -0300 Thibault Saunier + + * ges/ges-validate.c: + validate: Handle checking/setting subprojects ges properties + +2020-03-09 11:49:02 -0300 Thibault Saunier + + * ges/ges-project.c: + project: Do not warn when resetting URI to the same one + +2020-03-05 15:56:28 -0300 Thibault Saunier + + * ges/ges-auto-transition.c: + * ges/ges-clip.c: + * ges/ges-source-clip.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline-tree.c: + * ges/ges-timeline.c: + * tests/check/ges/group.c: + * tests/check/python/common.py: + * tests/check/python/test_timeline.py: + ges: Make setting start/duration move or trim generic + We were implementing the logic for moving/trimming elements specific + to SourceClip but this was not correct ass the new timeline tree allows + us to handle that for all element types in a generic and nice way. + This make us need to have groups trimming properly implemented in the + timeline tree, leading to some fixes in the group tests. + This adds tests for the various cases known to not be handled properly + by the previous code. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/92 + +2020-03-04 17:42:46 -0300 Thibault Saunier + + * ges/ges-group.c: + * tests/check/python/common.py: + * tests/check/python/test_group.py: + group: Update priority when a child is removed + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/93 + +2020-03-04 17:16:18 -0300 Thibault Saunier + + * ges/ges-clip.c: + * tests/check/python/test_timeline.py: + clip: Don't split clips at illegal position + Make sure that when we split a clip, the resulting timeline would + not be in an illegal state. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/94 + +2020-03-05 19:00:20 +0000 Henry Wilkes + + * ges/ges-pipeline.c: + pipeline: don't link tracks unnecessarily + Unless the pipeline is in certain modes, we do not want to try and link + every track. The previous debug message implied this, but the method did + not actually end early. + Also, we always end early if we receive a track that is neither video + nor audio. + +2020-03-05 18:15:41 +0000 Henry Wilkes + + * ges/ges-asset.c: + * tests/check/ges/asset.c: + asset: fix handling of proxies + Previous usage of the property proxy-target seemed to alternate between + the two definitions: + + The asset we are the default proxy of + + The asset we are in the proxy list of + Now, the latter definition is used, which seems more useful to a user + since knowing the latter can easily allow you to find out the former. + The previous behaviour of ges_asset_set_proxy (asset, NULL) was not very + clear. It is now defined so that it clears all the proxies for 'asset'. + This means that after this call, the GESAsset:proxy property will indeed + be NULL. + Also fixed: + + We can call ges_asset_set_proxy (asset, proxy) when 'proxy' is already + in the proxy list of 'asset'. + + Handling of removing the default proxy in ges_asset_unproxy. This was + sending out the wrong notifies. + + Prohibiting circular proxying. Before we could only prevent one case, + we should now be able to prevent all cases. This will prevent a hang + in ges_asset_request. + +2020-03-04 17:00:46 +0000 Henry Wilkes + + * tests/check/ges/asset.c: + test: remove asset test that needs internal method + The test_proxy_asset test needs the internal method + ges_asset_finish_proxy. The test also uses the associated internal methods + ges_asset_try_proxy and ges_asset_cache_lookup. However, these are + marked with GES_API in ges-internal.h, which allows us access to them + here. + The new method is not marked as GES_API because it would not allow us to + remove the method in the future without removing it from the symbols list. + We do not want to add to the problem. + The test was simply commented out since we may wish to support tests + that access internal methods in the future using meson. + +2020-03-04 13:05:58 +0000 Henry Wilkes + + * ges/ges-asset.c: + asset: fix ownership in ges_asset_request + Fix the ownership in ges_asset_request. This should be transfer-full, + but for proxies it would fail to add a reference. Also, + ges_asset_cache_put was leaking memory if the asset already existed. + +2020-03-04 11:31:32 +0000 Henry Wilkes + + * ges/ges-asset.c: + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-project.c: + * tests/check/ges/asset.c: + asset: move set_proxy (NULL, proxy) behaviour to new method + We should not be accepting ges_asset_set_proxy (NULL, proxy) as part of + the API! This behaviour was used internally in combination with + ges_asset_try_proxy, which is called on a still loading asset, so it was + moved to ges_asset_finish_proxy. + +2020-03-04 10:34:45 +0000 Henry Wilkes + + * ges/ges-asset.c: + * ges/ges-asset.h: + asset: deprecate ->proxied method + This method was no longer called, so it has been deprecated. + +2020-03-04 09:59:33 +0000 Henry Wilkes + + * ges/ges-asset.c: + asset: make proxy-target read only + We should not be able to set this property. + +2020-02-27 16:08:45 +0000 Henry Wilkes + + * ges/ges-timeline.c: + timeline: fix layer priority argument in trim + Previously, we tested that the given priority was `>0`, when it seems + that `>=0` was intended. A priority of `-1` means leave the priority + unchanged, whilst a priority of 0, or more, means move to this layer + priority. + +2020-02-21 09:23:34 +0000 Henry Wilkes + + * ges/ges-container.c: + * ges/ges-timeline-element.h: + * ges/ges-track-element.c: + timeline-element: use default ->list_children_properties + Stop overwriting the ->list_children_properties virtual method in + subclasses because the timeline element class handles everything itself + anyway. + Note that containers already automatically add the children properties of + their child elements in ges_container_add. + +2020-02-25 08:16:58 +0000 Henry Wilkes + + * ges/ges-group.c: + group: fix memory leak in child layer callback + We were leaking the sigids->layer argument because gst_clip_get_layer + returns a new reference. + +2020-02-24 20:19:12 +0000 Henry Wilkes + + * ges/ges-container.c: + container: fix child duration callback + Previously, we were setting the inpoint_offset using the start offset in + the duration callback! + Also added a notify for when the duration is changed in the child start + callback. + +2020-02-24 18:58:55 +0000 Henry Wilkes + + * ges/ges-group.c: + group: fix max layer priority + The maximum priority is `height - prio - 1`. Previously missing the -1. + Related to, but does not completely fix, + https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/91 + +2020-02-18 18:02:08 +0000 Henry Wilkes + + * ges/ges-clip.c: + clip: allow for neither track nor type in search + Previously, either the track or track_type arguments had to be specified + in order to find **any** track elements. Now, you can specify neither, + which will match any track element, of the specified type. + +2020-02-18 12:17:50 +0000 Henry Wilkes + + * tests/check/python/test_timeline.py: + pythontests: change num layers in timeline to 1 + In the test_timeline.test_auto_transition, the corresponding xges only + has one layer, so we should only expect one layer when we extract the + timeline. This fixes a change that was missing from commit + d3e2cf55e3ad6258ff09220ee6393655fdd833f1 + +2020-02-18 12:14:25 +0000 Henry Wilkes + + * ges/ges-extractable.c: + extractable: check extractable-type of set asset + When setting the asset of a GESExtractable object, first make sure that + the asset's extractable-type matches the type of the object. + +2020-02-18 09:17:09 +0000 Henry Wilkes + + * ges/ges-layer.c: + layer: fix ownership when failing to add clip + If a clip is already part of a layer, then adding it to another layer + should fail. Previously, in this case, `ges_layer_add_clip` was adding a + reference to the clip instead, without subsequently giving up ownership. + This meant that the clip would be left with an unowned reference. + This has now been corrected by also calling `unref` after the + `ref_sink`. + Note that, since `clip` is already part of `current_layer`, it should + already be non-floating, so the `ref_sink`-`unref` should do nothing + overall. But we keep both to make the ownership (transfer floating/none) + explicit. + +2020-02-12 22:23:38 +0000 Henry Wilkes + + * ges/ges-audio-track.c: + * ges/ges-video-track.c: + docs: update GESAudioTrack and GESVideoTrack + +2020-01-21 12:01:41 +0000 Henry Wilkes + + * ges/ges-enums.h: + * ges/ges-pipeline.c: + docs: update GESPipeline + +2020-01-17 20:10:23 +0000 Henry Wilkes + + * ges/ges-meta-container.c: + * ges/ges-meta-container.h: + docs: update GESMetaContainer + +2020-01-17 15:27:29 +0000 Henry Wilkes + + * ges/ges-extractable.c: + * ges/ges-extractable.h: + docs: update GESExtractable + +2020-01-17 12:20:11 +0000 Henry Wilkes + + * ges/ges-asset.c: + * ges/ges-asset.h: + docs: update GESAsset + +2020-01-15 14:46:02 +0000 Henry Wilkes + + * ges/ges-track-element.c: + * ges/ges-track-element.h: + docs: update GESTrackElement + +2020-01-15 14:44:38 +0000 Henry Wilkes + + * ges/ges-track.c: + * ges/ges-track.h: + docs: update GESTrack + +2020-01-09 12:11:35 +0000 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-clip.h: + docs: update GESClip + +2020-01-09 12:09:15 +0000 Henry Wilkes + + * ges/ges-container.c: + * ges/ges-group.c: + docs: update GESGroup + +2020-01-08 09:26:07 +0000 Henry Wilkes + + * ges/ges-container.c: + * ges/ges-container.h: + docs: update GESContainer + +2020-01-07 17:40:53 +0000 Henry Wilkes + + * ges/ges-enums.h: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + docs: update GESTimelineElement + +2019-12-20 12:30:54 +0000 Henry Wilkes + + * ges/ges-validate.c: + validate: unref copied and pasted + +2019-12-20 11:20:49 +0000 Henry Wilkes + + * ges/ges-timeline.c: + timeline: fix paste ownership + The method steals ownership of `copied_from`, so should be responsible + for unreffing it. Also make sure we fail when `layer != -1`, since this + functionality is not supported. + +2019-12-18 20:33:45 +0000 Henry Wilkes + + * ges/ges-layer.c: + * ges/ges-layer.h: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-utils.c: + docs: update GESTimeline and GESLayer + +2020-03-03 18:07:32 -0300 Thibault Saunier + + * bindings/python/gi/overrides/GES.py: + python: Cleanup overrides using monkey patching + Following the PyGObject guidelines[0], this starts monkey patching + overridden elements instead of subclassing them. + [0]: https://pygobject.readthedocs.io/en/latest/devguide/override_guidelines.html#python-override-guidelines + +2018-11-29 19:12:24 +0100 Jens Göpfert + + * examples/c/concatenate.c: + add assets to layer and adjust position and duration (closes #45) + +2020-03-02 19:06:17 -0300 Thibault Saunier + + * bindings/python/gi/overrides/GES.py: + * tests/check/python/test_timeline.py: + python: Add a Timeline.iter_clips() helper to iterate clips + +2020-02-24 12:21:11 -0300 Thibault Saunier + + * meson.build: + * meson_options.txt: + meson: Add an option to enable/disable validate integration + +2020-02-22 14:23:45 -0300 Thibault Saunier + + * ges/gstframepositioner.c: + * tests/check/meson.build: + * tests/check/scenarios/check_video_track_restriction_scale.scenario: + * tests/check/scenarios/check_video_track_restriction_scale_with_keyframes.scenario: + framepositioner: Reposition source when the user positioned them + Keeping the same proportion in the size and position and only if + the aspect ratio is conserved. + +2020-02-24 08:50:04 -0300 Thibault Saunier + + * tools/ges-launcher.c: + * tools/ges-launcher.h: + * tools/ges-validate.c: + * tools/ges-validate.h: + ges:launch: Handle setting playback information in scenarios + This way we can avoid real sinks when implementing scenarios + +2020-02-24 08:47:11 -0300 Thibault Saunier + + * ges/ges-structured-interface.c: + * ges/ges-track-element.c: + * ges/ges-validate.c: + validate: Handle absolute control binding support when setting keyframes + And minor fix in set-control-source + +2020-02-19 18:09:19 -0300 Thibault Saunier + + * ges/ges-video-source.c: + * ges/ges-video-uri-source.c: + * ges/gstframepositioner.c: + * ges/gstframepositioner.h: + * tests/check/scenarios/check_video_track_restriction_scale.scenario: + ges: Properly position video sources in the scene by default + We try to do our best to have the video frames scaled the best way + to fill most space on the final frames, keeping aspect ratio. The user + can later on rescale or move the sources as usual but it makes the + default behaviour a better and more natural especially now that we + set default restriction caps to the video tracks. + And fix the unit test to take that change into account + +2020-02-19 18:06:26 -0300 Thibault Saunier + + * ges/ges-image-source.c: + * ges/ges-internal.h: + * ges/ges-video-source.c: + * ges/ges-video-source.h: + * ges/ges-video-test-source.c: + * ges/ges-video-uri-source.c: + ges: Add a method to retrieve the 'natural' size of VideoSource + This way the user can easily know how the clip would look like + if no scaling was applied to the clip, this is useful to be able + to properly position the clips with the framepositionner element. + +2020-02-19 15:31:28 -0300 Thibault Saunier + + * bindings/python/gi/overrides/GES.py: + * ges/ges-container.c: + * ges/ges-internal.h: + * ges/ges-timeline-element.c: + ges: Call the right ->set_child_property vmethod + We used to always call the `->set_child_property` virtual method + of the object that `ges_timeline_element_set_child_property` was called + from, but that means that, in the case of referencing GESContainer + children properties from its children, the children wouldn't know + what child property have been set, and the children override wouldn't + be takent into account, in turns, it means that the behaviour could be + different in the setter depending on parent the method was called, + which is totally unexpected. + We now make sure that the vmethod from the element that introduced the + child property is called whatever parent method is called, making the + behaviour more uniform. + Fix the python override to make sure that new behaviour is respected. + +2020-02-18 16:31:15 -0300 Thibault Saunier + + * ges/ges-timeline.h: + ges: Deprecate the GESTimeline::track field + It is not MT safe to access it, and user should use the proper getter + +2020-02-18 16:09:55 -0300 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-video-track.c: + ges: Set default caps for GESVideoTrack + By default, video track output full HD@30fps, this makes the behaviour + of clip position much more understandable and guarantess that we + always have a framerate. + The user can modify the values whenever he wants + +2020-02-20 12:28:59 -0300 Thibault Saunier + + * ges/gstframepositioner.c: + * tests/check/ges/timelineedition.c: + * tests/check/meson.build: + * tests/check/scenarios/check_video_track_restriction_scale.scenario: + * tools/meson.build: + framepositioner: Stop lying about the source size + Basically we were advertising that the source size would be the + size of the track if it hadn't been defined by end user, but since + we started to let scaling happen in the compositor, this is not true + as the source size is now the natural size of the underlying video + stream. + Remove the unit test and reimplemented using a validate scenario which + make the test much simpler to read :=) + +2020-02-20 12:27:37 -0300 Thibault Saunier + + * ges/ges-validate.c: + validate: Add action types to set/check various child properties at once + And add a way to take into account control bindings. + +2020-02-20 12:22:19 -0300 Thibault Saunier + + * ges/ges-timeline-element.c: + ges: Allow setting children property using the set_object_arg format + This make it much simpler for the user to set enum values and should not cause any issue + +2020-02-20 17:13:46 -0300 Thibault Saunier + + * ges/ges-project.c: + * ges/ges-validate.c: + * tools/ges-launcher.c: + * tools/ges-launcher.h: + * tools/ges-validate.c: + * tools/utils.c: + * tools/utils.h: + ges: Plug leaks in new ges-launch and related + +2020-02-25 17:38:15 -0300 Thibault Saunier + + * ges/ges-validate.c: + validate: Port to the new REPORT_ACTION API + +2020-02-18 23:08:53 -0300 Thibault Saunier + + * plugins/nle/nlesource.c: + nlesource: When standalone consider object.duration==0 as not set + nleobject.duration defaults to 0, but this is pretty unintuitive for + end user in the case nlesource is use standalone, just consider + duration=0 equivalent to duration=GST_CLOCK_TIME_NONE as it makes + the element much simpler to use, we could actually forbid 0 as a value + in the future. + Also take into account potential CLOCK_TIME_NONE + +2020-02-10 18:05:38 -0300 Thibault Saunier + + * ges/ges-audio-source.c: + * ges/ges-internal.h: + * ges/ges-source.c: + * ges/ges-video-source.c: + * ges/ges-video-source.h: + * ges/ges-video-test-source.c: + * ges/ges-video-uri-source.c: + ges: Avoid adding unnecessary converters for nested timelines + Basically we know that if we are using mixing, compositor will be + able to do video conversion and scaling for us, so avoid adding those + usless elements. + This optimizes a lot caps negotiation for deeply nested timelines. + +2020-02-10 18:00:33 -0300 Thibault Saunier + + * plugins/ges/gesbasebin.c: + * plugins/ges/gesdemux.c: + plugins:ges: Fix pushing tags after e8c782d119eccf364fa24812cdc90c40f60d65d6 + Basically the tags we send before STREAM_START are now ignored, meaning + that we could not detect nested timelines anymore, this commits makes + sure that we send our tag event after getting pushing STREAM_START. + +2020-02-06 16:42:25 -0300 Thibault Saunier + + * plugins/nle/nlecomposition.c: + nlecomposition: Optimize prerolling when using nested compositions + When a composition is nested into anotherone, we *know* that the + toplevel composition is going to send a stack initializing seek, + we can thus avoid sending it on the subcomposition itself when + prerolling. This avoid seeking roundtrips and we now have one and + only one seek travelling in the overall pipeline (after it has + prerolled). + +2020-02-06 12:43:57 -0300 Thibault Saunier + + * plugins/nle/nlesource.c: + nlesource: Fix seeks when used standalone + The 'start' of nleobject is in the 'composition' scale, inpoint is in + the media scale, when outside a composition, a nleobject->start value + doesn't mean anything. + +2020-02-06 12:39:12 -0300 Thibault Saunier + + * plugins/nle/nlecomposition.c: + * plugins/nle/nlesource.c: + nle: Seek the whole stack on initialization + Instead of seeking each nleobject separately to setup new stack, wait + for the whole stack to preroll and then seek that newly setup stack, + leading to the same code path and seek 'tweaking' as when processing + a seek on the composition (without stack changes). + This is mandatory to properly handle filter that tweak segments to handle + time remapping for example. + +2020-02-06 12:37:37 -0300 Thibault Saunier + + * plugins/nle/nlecomposition.c: + * plugins/nle/nleghostpad.c: + nle: Minor typo fixes + +2020-02-04 17:07:39 -0300 Thibault Saunier + + * tools/ges-launcher.c: + * tools/ges-launcher.h: + * tools/ges-validate.c: + * tools/ges-validate.h: + validate: Allow overriding ges-launch options through scenarios + In 99c45d42cfd1cafb658b63abf0b506db20167499 we allowed setting + track-types but in the end we could do it generically using the + following synthax in the scenario 'properties' metadata: + `ges-options={--track-types=video,--disable-mixing}` + +2020-02-07 09:39:39 -0300 Thibault Saunier + + * ges/ges-asset.c: + * ges/ges-effect-clip.c: + * ges/ges-effect.c: + * ges/ges-extractable.c: + * ges/ges-extractable.h: + * ges/ges-internal.h: + * ges/ges-timeline-element.c: + * ges/ges-transition-clip.c: + * ges/ges-uri-clip.c: + ges: Ignore deprecation of GParameter + GParameter is part of our API, and for GLib < 2.54 we do not even have + a way around avoiding it (namely `g_object_new_with_properties`). + We should stop using GParameter once we depend on GLib 2.54. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/86 + +2019-08-20 17:46:09 -0400 Thibault Saunier + + * ges/ges-container.h: + * ges/ges-formatter.c: + * ges/ges-formatter.h: + * ges/ges-layer.h: + * ges/ges-prelude.h: + * ges/ges-screenshot.h: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-title-clip.c: + * ges/ges-title-clip.h: + * ges/ges-track-element-deprecated.h: + * ges/ges-track-element.h: + * ges/meson.build: + ges: Use G_DEPRECATE to mark deprecated methods + Cleanup a few things on the way. + And move ges-track-element deprecations to a dedicated header file + +2019-12-14 17:04:54 +0000 Henry Wilkes + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-source-clip.c: + * ges/ges-timeline-element.c: + * tests/check/ges/clip.c: + ges-source-clip: fixed return of duration setter + In general, brought the behaviour of the `start`, `duration` and + `inpoint` setters in line with each other. In particular: + 1. fixed return value the GESSourceClip `duration` setter + 2. changed the GESClip `start` setter + 3. fixed the inpoint callback for GESContainer + 4. changed the type of `res` in GESTimelineElement to be gint to + emphasise that the GES library is using the hack that a return of -1 + from klass->set_duration means no notify signal should be sent out. + Also added a new test for clips to ensure that the setters work for + clips within and outside of timelines, and that the `start`, `inpoint` + and `duration` of a clip will match its children. + +2019-12-05 14:23:04 -0300 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Ensure that encodebin enforces a single segment sent to encoders + +2019-10-04 09:58:17 -0300 Thibault Saunier + + * tests/validate/geslaunch.py: + validate: Move to the new GstValidateEncodingTestInterface API + +2019-11-20 07:52:56 +0100 Edward Hervey + + * ges/ges-xml-formatter.c: + xml-formatter: Free structure after usage + CID: 1416901 + CID: 1439518 + CID: 1439527 + +2019-11-20 07:46:47 +0100 Edward Hervey + + * ges/ges-pitivi-formatter.c: + formatter: Free path object after usage + As it's done everywhere else + CID: 1455511 + +2019-11-07 16:54:32 +0530 Nirbheek Chauhan + + * meson.build: + meson: Fix disabling of the python support + Cannot call python.dependency() if the python module was not found. + +2019-08-29 07:45:45 +0200 Niels De Graef + + * ges/ges-container.c: + * ges/ges-layer.c: + * ges/ges-marker-list.c: + * ges/ges-meta-container.c: + * ges/ges-project.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + * ges/ges-track.c: + * plugins/nle/nlecomposition.c: + * plugins/nle/nleoperation.c: + Don't pass default GLib marshallers for signals + By passing NULL to `g_signal_new` instead of a marshaller, GLib will + actually internally optimize the signal (if the marshaller is available + in GLib itself) by also setting the valist marshaller. This makes the + signal emission a bit more performant than the regular marshalling, + which still needs to box into `GValue` and call libffi in case of a + generic marshaller. + Note that for custom marshallers, one would use + `g_signal_set_va_marshaller()` with the valist marshaller instead. + +2019-10-16 19:26:55 +0100 Henry Wilkes + + * ges/ges-marker-list.c: + * ges/ges-meta-container.h: + * tests/check/ges/markerlist.c: + marker: add color meta + Support optionally coloring markers by reserving GES_META_MARKER_COLOR + for an ARGB guint. + +2019-10-16 13:40:57 +0100 Henry Wilkes + + * ges/ges-meta-container.c: + * ges/ges-meta-container.h: + meta-container: add register_static_meta + Allows us to register a static meta without having to set a value. + +2019-10-16 11:37:23 +0100 Henry Wilkes + + * ges/ges-meta-container.c: + meta-container: move comment + The comment that was above _register_meta is actually meant for + _set_value. + +2019-10-23 16:04:01 +0200 Thibault Saunier + + * ges/ges-command-line-formatter.c: + * tools/ges-launch.c: + * tools/ges-launcher.c: + ges-launch: Document timeline description format under --help + Making it simpler for user to get the documentation + +2019-10-22 22:51:41 +0200 Rico Tzschichholz + + * ges/ges-marker-list.c: + marker-list: Use proper parameters names even in the docs + Otherwise there will be parameters with hyphen in their name in the GIR. + +2019-10-22 13:30:36 +0200 Thibault Saunier + + * plugins/nle/nlecomposition.c: + nlecomposition: Enhance dumping stack output + +2019-10-22 12:21:04 +0200 Thibault Saunier + + * ges/ges-marker-list.c: + ges: Handle empty marker lists + +2019-10-22 11:53:36 +0200 Thibault Saunier + + * ges/ges-validate.c: + validate: Tear down pipeline when openning a new project + Avoiding potential deadlock when we remove tracks on a playing pipeline + +2019-10-22 11:50:02 +0200 Thibault Saunier + + * ges/ges-structured-interface.c: + ges: Fix setting GError when adding children to containers + We were misusing assertion and not properly setting the GError value + +2019-10-22 11:31:04 +0200 Thibault Saunier + + * ges/ges-smart-video-mixer.c: + smart-video-mixer: Handle segment updates + We were basically ignoring any segment update which could potentially + lead to setting a wrong stream time leading to wrong alpha value + being used. + +2019-10-17 16:30:49 +0200 Thibault Saunier + + * tools/ges-launcher.c: + * tools/utils.c: + * tools/utils.h: + launcher: Enhance printed output + +2019-10-17 16:21:28 +0200 Thibault Saunier + + * tools/ges-launcher.c: + * tools/utils.c: + * tools/utils.h: + launcher: Use the output URI extension to set encoding format + And print a description of the encoding profile. + +2019-10-17 16:19:11 +0200 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Be smarter about how we match encoding profiles and tracks + +2019-10-18 00:50:16 +0100 Tim-Philipp Müller + + * meson.build: + meson: build gir even when cross-compiling if introspection was enabled explicitly + This can be made to work in certain circumstances when + cross-compiling, so default to not building g-i stuff + when cross-compiling, but allow it if introspection was + enabled explicitly via -Dintrospection=enabled. + See gstreamer/gstreamer#454 and gstreamer/gstreamer#381 + +2019-10-16 16:40:27 +0100 Henry Wilkes + + * ges/ges-marker-list.c: + * tests/check/ges/markerlist.c: + marker-list: add prev position to ::marker-moved + Additionally give the previous marker position in the + GESMarkerList::marker-moved signal, since a user may want to know + where a move was from. + Also, fixed the documentation for GESMarkerList::marker-added + https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/78 + +2019-10-13 13:37:11 +0100 Tim-Philipp Müller + + * .gitignore: + * .gitmodules: + * Makefile.am: + * autogen.sh: + * bindings/Makefile.am: + * bindings/python/Makefile.am: + * bindings/python/gi/Makefile.am: + * bindings/python/gi/overrides/Makefile.am: + * common: + * configure.ac: + * examples/.gitignore: + * examples/Makefile.am: + * examples/c/Makefile.am: + * ges/.gitignore: + * ges/Makefile.am: + * m4/Makefile.am: + * pkgconfig/.gitignore: + * pkgconfig/Makefile.am: + * plugins/Makefile.am: + * plugins/ges/Makefile.am: + * plugins/nle/.gitignore: + * plugins/nle/Makefile.am: + * tests/.gitignore: + * tests/Makefile.am: + * tests/benchmarks/Makefile.am: + * tests/check/Makefile.am: + * tests/check/ges/.gitignore: + * tests/validate/Makefile.am: + * tests/validate/scenarios/Makefile.am: + * tools/Makefile.am: + Remove autotools build system + Todo: + - hook up data/completions/ges-launch-1.0 in Meson (#77) + +2019-10-01 18:02:27 +0300 Sebastian Dröge + + * ges/ges-internal.h: + * ges/ges.c: + ges: Hide internal debug category behind a GOnce + Otherwise it might be used (e.g. by the plugin loader via the GES + plugin!) before ges_init() is called. + +2019-10-01 18:01:21 +0300 Sebastian Dröge + + * plugins/ges/gesdemux.c: + gesdemux: Initialize debug category before first using it + Prevents critical warnings during class_init() + +2019-09-23 16:10:59 -0300 Thibault Saunier + + * ges/ges-project.c: + project: Add missing safe guard when listing assets + +2019-09-23 16:07:58 -0300 Thibault Saunier + + * ges/ges-command-line-formatter.c: + * ges/ges-structured-interface.c: + * tools/ges-launcher.c: + launch: Add an option to embed nested timelines when saving + +2019-08-19 14:38:12 +0100 Henry Wilkes + + * ges/ges-xml-formatter.c: + xml-formatter: increase xges version to 0.6 + Increase minor_version to 6 if a sub-project is saved under an asset or an asset includes a child stream-info element. + +2019-08-23 17:26:51 -0400 Thibault Saunier + + * ges/ges-timeline.c: + * tests/check/python/test_timeline.py: + tests: Fix transition project tests + Basically the test project was plain broken as it had fully overlapping + clips is prohibited since the timeline edition API was reimplemented. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/62 + +2019-08-21 14:41:46 -0400 Thibault Saunier + + * ges/ges-asset.c: + asset: Fix asset cache for CLips and TrackElement with same ID + We clearly uniquely identify assets by both their IDs and their + extractable type, and we should make sure that you can have a + TrackElement and a Clip with the same ID. + There is one exception in our implementation which is GESFormatter + because we treat their subclasses as 1 type with different IDs. + +2019-08-17 11:59:38 -0400 Thibault Saunier + + * ges/ges-effect.c: + * ges/ges.c: + ges: Expose ges mixer to be used as effects + +2019-08-17 11:59:02 -0400 Thibault Saunier + + * ges/ges-effect-clip.c: + * ges/ges-layer.c: + ges: Add support for EffectClip assets + +2019-08-28 18:13:06 +1000 Matthew Waters + + * plugins/ges/gesdemux.c: + * plugins/ges/gessrc.c: + build: also suppress unused-function warnings about g_autoptr + ../plugins/ges/gesdemux.c:50:1: error: unused function 'glib_autoptr_cleanup_GESDemux' [-Werror,-Wunused-function] + G_DECLARE_FINAL_TYPE (GESDemux, ges_demux, GES, DEMUX, GESBaseBin); + ^ + /home/matt/Projects/cerbero/build/dist/android_universal/x86_64/include/glib-2.0/gobject/gtype.h:1401:3: note: expanded from macro 'G_DECLARE_FINAL_TYPE' + _GLIB_DEFINE_AUTOPTR_CHAINUP (ModuleObjName, ParentName) \ + ^ + /home/matt/Projects/cerbero/build/dist/android_universal/x86_64/include/glib-2.0/glib/gmacros.h:451:22: note: expanded from macro '_GLIB_DEFINE_AUTOPTR_CHAINUP' + static inline void _GLIB_AUTOPTR_FUNC_NAME(ModuleObjName) (ModuleObjName **_ptr) { \ + ^ + /home/matt/Projects/cerbero/build/dist/android_universal/x86_64/include/glib-2.0/glib/gmacros.h:441:43: note: expanded from macro '_GLIB_AUTOPTR_FUNC_NAME' + #define _GLIB_AUTOPTR_FUNC_NAME(TypeName) glib_autoptr_cleanup_##TypeName + ^ + :81:1: note: expanded from here + glib_autoptr_cleanup_GESDemux + ^ + ../plugins/ges/gessrc.c:56:1: error: unused function 'glib_autoptr_cleanup_GESSrc' [-Werror,-Wunused-function] + G_DECLARE_FINAL_TYPE (GESSrc, ges_src, GES, SRC, GESBaseBin); + ^ + /home/matt/Projects/cerbero/build/dist/android_universal/x86_64/include/glib-2.0/gobject/gtype.h:1401:3: note: expanded from macro 'G_DECLARE_FINAL_TYPE' + _GLIB_DEFINE_AUTOPTR_CHAINUP (ModuleObjName, ParentName) \ + ^ + /home/matt/Projects/cerbero/build/dist/android_universal/x86_64/include/glib-2.0/glib/gmacros.h:451:22: note: expanded from macro '_GLIB_DEFINE_AUTOPTR_CHAINUP' + static inline void _GLIB_AUTOPTR_FUNC_NAME(ModuleObjName) (ModuleObjName **_ptr) { \ + ^ + /home/matt/Projects/cerbero/build/dist/android_universal/x86_64/include/glib-2.0/glib/gmacros.h:441:43: note: expanded from macro '_GLIB_AUTOPTR_FUNC_NAME' + #define _GLIB_AUTOPTR_FUNC_NAME(TypeName) glib_autoptr_cleanup_##TypeName + ^ + :158:1: note: expanded from here + glib_autoptr_cleanup_GESSrc + ^ + +2019-08-27 10:02:04 -0400 Thibault Saunier + + * plugins/ges/gesbasebin.h: + * plugins/ges/gesdemux.c: + * plugins/ges/gessrc.c: + ges: fix G_DECLARE_FINAL_TYPE -Werror with clang + Also fix wrong casing the `G_DECLARE` for GESDemux. + ../subprojects/gst-editing-services/plugins/ges/gessrc.c:56:1: warning: unused function 'GES_SRC' [-Wunused-function] + G_DECLARE_FINAL_TYPE (GESSrc, ges_src, GES, SRC, GESBaseBin); + ^ + /usr/include/glib-2.0/gobject/gtype.h:1405:33: note: expanded from macro 'G_DECLARE_FINAL_TYPE' + static inline ModuleObjName * MODULE##_##OBJ_NAME (gpointer ptr) { \ + ^ + :39:1: note: expanded from here + GES_SRC + ^ + ../subprojects/gst-editing-services/plugins/ges/gessrc.c:56:1: warning: unused function 'GES_IS_SRC' [-Wunused-function] + /usr/include/glib-2.0/gobject/gtype.h:1407:26: note: expanded from macro 'G_DECLARE_FINAL_TYPE' + static inline gboolean MODULE##_IS_##OBJ_NAME (gpointer ptr) { \ + ^ + :42:1: note: expanded from here + GES_IS_SRC + ^ + ../subprojects/gst-editing-services/plugins/ges/gesdemux.c:50:1: warning: unused function 'GES_Demux' [-Wunused-function] + G_DECLARE_FINAL_TYPE (GESDemux, ges_demux, GES, Demux, GESBaseBin); + ^ + /usr/include/glib-2.0/gobject/gtype.h:1405:33: note: expanded from macro 'G_DECLARE_FINAL_TYPE' + static inline ModuleObjName * MODULE##_##OBJ_NAME (gpointer ptr) { \ + ^ + :72:1: note: expanded from here + GES_Demux + ^ + ../subprojects/gst-editing-services/plugins/ges/gesdemux.c:50:1: warning: unused function 'GES_IS_Demux' [-Wunused-function] + /usr/include/glib-2.0/gobject/gtype.h:1407:26: note: expanded from macro 'G_DECLARE_FINAL_TYPE' + static inline gboolean MODULE##_IS_##OBJ_NAME (gpointer ptr) { \ + ^ + :75:1: note: expanded from here + GES_IS_Demux + ^ + +2019-08-27 13:52:52 +1000 Matthew Waters + + * ges/ges-timeline.c: + ges/timeline: remove unused function get_toplevel_container + Fixes -Werror build with clang: + ../subprojects/gst-editing-services/ges/ges-timeline.c:695:1: warning: unused function 'get_toplevel_container' [-Wunused-function] + get_toplevel_container (gpointer element) + ^ + +2019-08-23 12:36:38 -0400 Thibault Saunier + + * docs/gst_plugins_cache.json: + * ges/ges-enums.h: + doc: Update cache and fix usage of + +2019-08-22 18:50:00 +0200 Millan Castro + + * ges/Makefile.am: + * ges/ges-internal.h: + * ges/ges-marker-list.c: + * ges/ges-marker-list.h: + * ges/ges-meta-container.c: + * ges/ges-meta-container.h: + * ges/ges-types.h: + * ges/ges.c: + * ges/ges.h: + * ges/meson.build: + * tests/check/ges/layer.c: + * tests/check/ges/markerlist.c: + * tests/check/meson.build: + markerlist: implement GESMarkerList + Co-authored by Mathieu Duponchelle + +2019-08-20 15:29:12 -0400 Thibault Saunier + + * plugins/ges/gesdemux.c: + gesdemux: Fix querying if we need stack reloading + We are probing upstream queries, not downstream ones + This was clearly a small test that slipt into previous commit + +2019-08-16 17:41:17 +0100 Henry Wilkes + + * ges/ges-xml-formatter.c: + xml-formatter: strip "caps" from the "properties" attribute of a track element + We already have the separate "caps" attribute for xges track + elements, which is actually used in parsing. + +2019-08-19 16:35:49 +0100 Henry Wilkes + + * ges/ges-xml-formatter.c: + xml-formatter: fix cb of ::error-loading-asset + Corrected typo that attached project_loaded_cb, rather than error_loading_asset_cb, to ::error-loading-asset, which meant data.error would be left unset if an error occurred in loading. + +2019-08-15 17:32:12 +0100 Henry Wilkes + + * ges/ges-base-xml-formatter.c: + Test that gst_structure_get succeeds to ensure gchar *restriction is actually set before reading it. Warn if no caps are returned by gst_caps_from_string. + +2019-08-14 15:48:46 -0400 Thibault Saunier + + * ges/ges-project.c: + * ges/ges-structured-interface.c: + structured-interface: Properly error out when a child property could not be set + +2019-08-12 17:37:39 -0400 Thibault Saunier + + * tests/check/nle/nlecomposition.c: + tests:nle: Unref the bus before unrefing the pipeline + Aiming at fixing a rare race condition where we get: + ../subprojects/gstreamer/libs/gst/check/gstcheck.c:1258:F:nlecomposition:test_seek_on_nested:0: nested_src0_0x1a1a310 is not destroyed, 1 refcounts left! + The idea is that there might have a remaining GstMessage + with the nested_src as `message.src` on the bus that has + yet to be processed in some conditions leading to a reference + still existing when unrefing the pipeline. + +2019-08-12 17:17:53 +0300 Sebastian Dröge + + * ges/ges-xml-formatter.c: + ges-xml-formatter: Use g_filename_to_uri() instead of deprecated gst_uri_construct() + ges-xml-formatter.c: In function ‘_parse_asset’: + ges-xml-formatter.c:357:7: error: ‘gst_uri_construct’ is deprecated: Use 'gst_uri_new' instead [-Werror=deprecated-declarations] + 357 | id = gst_uri_construct ("file", subproj_data->filename); + | ^~ + +2019-08-12 17:16:44 +0300 Sebastian Dröge + + * ges/ges-asset.c: + * ges/ges-uri-asset.c: + Fix old-style C function declarations + ges-uri-asset.c: In function ‘create_discoverer’: + ges-uri-asset.c:53:1: error: old-style function definition [-Werror=old-style-definition] + 53 | create_discoverer () + | ^~~~~~~~~~~~~~~~~ + ges-uri-asset.c: In function ‘get_discoverer’: + ges-uri-asset.c:67:1: error: old-style function definition [-Werror=old-style-definition] + 67 | get_discoverer () + | ^~~~~~~~~~~~~~ + CC libges_1.0_la-ges-auto-transition.lo + ges-asset.c: In function ‘_get_type_entries’: + ges-asset.c:489:1: error: old-style function definition [-Werror=old-style-definition] + 489 | _get_type_entries () + | ^~~~~~~~~~~~~~~~~ + +2019-08-12 09:49:45 -0400 Thibault Saunier + + * ges/ges-track.c: + * ges/ges-uri-asset.c: + * plugins/ges/gesbasebin.c: + doc: Add some missing Since: + +2019-08-11 21:20:21 -0400 Thibault Saunier + + * ges/ges-project.c: + project: Properly handle NULL project asset ID + +2019-07-30 18:24:07 -0700 Thibault Saunier + + * ges/ges-structured-interface.c: + structured: Enhance error message when no clip duration set + +2019-07-30 18:22:18 -0700 Thibault Saunier + + * ges/ges-structured-interface.c: + structured-interface: Avoid setting invalid clip duration + +2019-07-16 21:51:10 -0400 Thibault Saunier + + * ges/ges-track.c: + * ges/ges-track.h: + track: Add a getter for restriction_caps + +2019-07-13 21:27:46 -0400 Thibault Saunier + + * tools/ges-launcher.c: + launch: Set user restriction caps even when loading projects + +2019-07-13 21:26:35 -0400 Thibault Saunier + + * ges/ges-track.c: + track: Enhance restriction capsfilter name + +2019-07-13 13:25:48 -0400 Thibault Saunier + + * ges/ges-xml-formatter.c: + xml-formatter: Serialize DiscovererStreamInfo + We do not use it yet but it gives interesting information to + users + +2019-07-12 16:15:35 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-xml-formatter.c: + * tests/check/ges/project.c: + formatter: Plug lists of TimedValue leak + +2019-07-05 09:40:57 -0400 Thibault Saunier + + * ges/ges-formatter.c: + * ges/ges-formatter.h: + * ges/ges-xml-formatter.c: + formatter: Better document metadata registration + And fix xges mimetype to match typefind mimetype + +2019-07-04 16:51:54 -0400 Thibault Saunier + + * docs/meson.build: + doc: Do not require the GStreamer cache generator + +2019-07-04 15:58:44 -0400 Thibault Saunier + + * ges/ges-asset.c: + * ges/ges-formatter.c: + * ges/ges-internal.h: + * ges/ges.c: + * ges/python/gesotioformatter.py: + * plugins/ges/gesdemux.c: + gesdemux: Compute sinkpad caps based on formatter mimetypes + Implement lazy loading asset cache so gesdemux use the formatters + assets while GES hasn't been initialized. + And set extensions to temporary files as some formatters require + the information (otio) + +2019-07-03 20:15:23 -0400 Thibault Saunier + + * ges/ges-formatter.c: + * ges/ges-formatter.h: + * ges/ges-project.c: + formatter: Add a method to retrieve the best formatter for a givent URI + Uses the file extension as hint falling back to the default formatter + if none is found + Make use of that function in when saving a project and not formatter + is specified. + +2019-02-05 15:46:49 -0300 Thibault Saunier + + * ges/ges-formatter.c: + * ges/ges.resource: + * ges/meson.build: + * ges/python/gesotioformatter.py: + * meson.build: + * meson_options.txt: + Implement a formatter based on [OpenTimelineIO] + [OpenTimelineIO]: http://opentimeline.io/ + +2019-04-19 09:07:44 -0400 Thibault Saunier + + * ges/ges-formatter.c: + formatter: Handle coma separated extensions in formatter metas + +2019-03-11 19:25:23 -0300 Thibault Saunier + + * ges/ges-formatter.c: + * ges/ges-formatter.h: + formatter: Duplicate const gchar* for metadatas + +2019-02-05 16:08:10 -0300 Thibault Saunier + + * ges/ges-project.c: + * ges/ges-project.h: + project: Expose the ges_project_add_formatter method + This method is useful when implementing a formatter outside + GES that end up converting to xges and uses the default formatter + to finally load the timeline. + +2019-07-11 16:23:47 -0400 Thibault Saunier + + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + * ges/ges-uri-clip.c: + * plugins/ges/gesdemux.c: + Mark nested timeline assets as such + Adding a property to let the application know + Also make sure that the duration of nested timeline assets is reported + as CLOCK_TIME_NONE as those are extended as necessary. + And make a difference between asset duration and their max duration + As nested timelines can be extended 'infinitely' those max duration + is GST_CLOCK_TIME_NONE, but their duration is the real duration of + the timeline. + +2019-07-11 15:54:27 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-pitivi-formatter.c: + * ges/ges-project.c: + * plugins/ges/gesdemux.c: + * tools/ges-launcher.c: + formatter: Enhance error reporting + And add a "loading-error" signal in GESProject so we can report + issue when loading async elements for the timeline. + +2019-07-11 15:43:47 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * tests/check/ges/test-properties.xges: + xml-formatter: Fix loading sources + And fix the project file which couldn't be load now that we + properly check clips coherency + +2019-07-10 19:36:21 -0400 Thibault Saunier + + * plugins/ges/gesdemux.c: + gesdemux: Add a testsrc to timelines if parent nleobject duration is too long + +2019-07-10 12:06:01 -0400 Thibault Saunier + + * ges/ges-validate.c: + ges:validate: Properly error when editing container fails + +2019-07-10 11:02:07 -0400 Thibault Saunier + + * ges/ges-structured-interface.c: + * plugins/ges/gesdemux.c: + * plugins/nle/nlecomposition.c: + nle: Handle nested timelines update when file changes + When we have nested timelines, we need to make sure the underlying + formatted file is reloaded when commiting the main composition to + take into account the new timeline. + In other to make the implementation as simple as possible we make + sure that whenever the toplevel composition is commited, the decodebin + holding the gesdemux is torn down so that a new demuxer is created + with the new content of the timeline. + To do that a we do a NleCompositionQueryNeedsTearDown query to which + gesdemux answers leading to a full nlecomposition stack + deactivation/activation cycle. + +2019-07-10 10:15:31 -0400 Thibault Saunier + + * ges/ges-structured-interface.c: + * ges/ges-structured-interface.h: + * ges/ges-validate.c: + ges:validate: Add a way to execute actions on serialized timelines + This way we can modify nested timelines. + +2019-07-09 01:03:56 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-project.c: + * ges/ges-timeline-element.c: + * ges/ges-utils.c: + ges: Implement our own idle_add which uses the thread local maincontext + +2019-07-09 00:28:29 -0400 Thibault Saunier + + * ges/ges-validate.c: + validate: Allow passing 'uri' to 'load-project' + The action type was thought to allow that but it wasn't implemented. + +2019-07-09 00:07:16 -0400 Thibault Saunier + + * ges/ges-xml-formatter.c: + xml-formatter: Lower down borring debug to _LOG + +2019-07-09 00:05:21 -0400 Thibault Saunier + + * ges/ges-project.c: + project: Use asset ID as URI if possible + It was making no sense to consider it an empty timeline when the user + had passed the project URI when requesting the asset. Usually user + use `ges_project_new` with the URI but it is also valid to use + `ges_asset_request` with the uri as ID so let's handle that properly. + +2019-07-08 19:25:32 -0400 Thibault Saunier + + * bindings/python/gi/overrides/GES.py: + python: Add a better asset __repr__ + +2019-07-07 20:55:53 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-base-xml-formatter.h: + * ges/ges-internal.h: + * ges/ges-project.c: + * ges/ges-xml-formatter.c: + * ges/ges.c: + * tests/check/python/common.py: + * tests/check/python/test_timeline.py: + ges: Implement subprojects + Subprojects simply consist of adding the GESProject + to the main project asset list. Then those are recursively + serialized in the main project in the not, when deserializing, + temporary files are created and those will be used in clips + as necessary + +2019-07-07 20:35:14 -0400 Thibault Saunier + + * ges/ges-project.c: + project: Fix our asset cache + It was not talking into account the fact that you can have + several assets with a same ID but different exactractable types. + +2019-07-14 16:28:23 -0400 Thibault Saunier + + * ges/ges-asset.c: + asset: Handle trying to proxy an asset to itself + And avoid infinite recursion + +2019-07-03 12:10:24 -0400 Thibault Saunier + + * plugins/ges/gesdemux.c: + gesdemux: Detect recursively loading the same project file + And error out when it is the case. + +2019-07-03 12:09:23 -0400 Thibault Saunier + + * plugins/ges/gesdemux.c: + gesdemux: Create proper stream-ids + +2019-07-03 10:10:42 -0400 Thibault Saunier + + * tests/check/nle/nlecomposition.c: + nle: Check seeking on deeply nested composition + +2019-06-28 20:19:49 -0400 Thibault Saunier + + * ges/ges-track.c: + track: Disable last gap by default + And let the GESPipeline logic handle that + +2019-06-28 20:19:20 -0400 Thibault Saunier + + * plugins/nle/nlecomposition.c: + nlecomposition: Drop all group-done but the last one + +2019-06-28 17:35:40 -0400 Thibault Saunier + + * tools/ges-launcher.c: + * tools/ges-validate.c: + * tools/ges-validate.h: + validate: Allow scenarios to set track types + +2019-06-19 15:52:21 +0530 Swayamjeet + + * tests/validate/geslaunch.py: + tests: Add ges-sample-path-recurse with projects location + So that project files are found when using nested timelines + +2019-06-23 13:03:54 -0400 Thibault Saunier + + * tests/validate/geslaunch.py: + * tools/ges-validate.c: + validate: Add a way to use validate configs with scenarios + Config files should have the-scenario-name.scenario.config to be picked automatically + +2019-06-23 13:03:04 -0400 Thibault Saunier + + * tests/validate/geslaunch.py: + validate: Use proper sink and give them good names + +2019-06-23 12:42:21 -0400 Thibault Saunier + + * ges/ges-validate.c: + validate: Create folders as needed when serializing timelines + +2019-06-22 23:49:50 -0400 Thibault Saunier + + * plugins/nle/nlesource.c: + nlesource: Wait for the seek to actualy happen before removing the probe + Make sure that an event resulting from the seek happens before removing + the pad probe, dropping anything while it is not the case. + This guarantees that the seek happens before `nlesource` outputs + anything. This was not necessary as with decodebin or usual source + flushing seeks lead to synchronous flush_start/flush_stop and we could + safely assume that once the seek is sent, it was happenning. + With nested `nlecomposition` this assumption is simply not true as + in the composition seeks are basically cached and happen later in + the composition updating thread. + This fixes races where we ended up removing the blocking probe before + the seek actually started to be executed in the nlecomposition + nested inside an nlesource which leaded to data from *before* the seek + to be outputed which means we could display wrong frames, + and it was leading to interesting deadlocks. + +2019-06-22 23:25:57 -0400 Thibault Saunier + + * plugins/nle/nlecomposition.c: + nlecomposition: Minor debugging enhancements + +2019-06-21 11:45:20 -0400 Thibault Saunier + + * ges/ges-uri-asset.c: + * tests/check/python/test_assets.py: + uri-asset: Fix retrieving a relocated asset sync twice + Add a simple test for that. + +2019-06-21 10:47:34 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Make adding/removing track MT safe + It was almost the case already so make it happen fully + +2019-06-19 18:14:52 -0400 Thibault Saunier + + * plugins/nle/nlecomposition.c: + nlecomposition: Ensure flushes after seek have the right seqnum + Seeks that lead to a stack change lead to deactivating the current + stack. At that point we explicitely flush downstream as a reaction to + the flushing seek. Until now those flushes had a random seqnum, this + fails if we are a nested compostion as the parent composition will end + up dropping that flush which in turns might lead to deadlocks. For + example, the flush goes through a `compositor` which wants to flush + downstream to stop its srcpad task, but that flush wouldn't have + "released" its srcpad thread if the composition srcpad drops it, meaning + it won't be able to stop the task ever. + +2019-06-17 18:23:43 -0400 Thibault Saunier + + * plugins/nle/nlecomposition.c: + * tests/check/nle/nlecomposition.c: + nlecomposition: Shutdown children when setting state to NULL + Otherwise if we shutdown a composition whith an nested composition + (inside a source in the test) and leak it, we end up with the nested + composition task still running (in READY) which is bad. + Add a test for that which leaks the pipeline on purpose. + +2019-06-17 18:23:07 -0400 Thibault Saunier + + * plugins/nle/nlecomposition.c: + nle: Parent the GstTask to ourself + This allows accessing the nlecomposition in gdb when a task is + 'dangling' making debugging easier. + +2019-06-11 23:51:14 +0530 Swayamjeet + + * tests/validate/geslaunch.py: + tests: Implement nested timelines tests + +2019-06-16 23:03:44 -0400 Thibault Saunier + + * plugins/nle/nlecomposition.c: + nlecomposition: Properly set seqnum on flush events + +2019-06-16 23:00:31 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Drop ASYNC_/START/DONE messages + When we have nested timelines, we do not want those messages to pop + to the parent timelines as we handle the sequence ourself in the + timeline. + +2019-06-14 23:48:20 -0400 Thibault Saunier + + * plugins/ges/gesdemux.c: + demux: Create timeline from the streaming thread + First marshilling it to the main thread is dangerous as it is a blocking + operation and it should never happen there. + The asset cache is MT safe now so it is possible to load the timeline + from that thread directly + +2019-06-16 21:27:47 -0400 Thibault Saunier + + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + * plugins/ges/gesdemux.c: + uri-asset: Implement multi threading support + Making sure to have 1 GstDiscoverer per thread. + Use that new feature in gesdemux by loading the timeline directly from + the streaming thread. Modifying the timeline is not supported allowed + anyway. + +2019-06-09 19:35:21 -0400 Thibault Saunier + + * tests/check/nle/nlecomposition.c: + nle: Add a seeking test for nested composition + +2019-06-07 16:12:26 -0400 Thibault Saunier + + * ges/ges-uri-asset.c: + * plugins/ges/gesdemux.c: + Use the new GstDiscoverer caching feature + +2019-06-07 16:06:39 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Do not post upstream translated composition update messages + In the case of nested timeline in the toplevel timeline we ended up + with CompositionUpdate for seeks sent by our own composition to + granchildren composition. This was not causing essential issues + if all tracks where containing nested timelines but in cases + where one of the tracks only had a nested timelines, then we + were waiting forever for a `CompositionUpdateDone`. + CompositionUpdate translated into ASYNC_START/ASYNC_DONE should + be kept inside the GESTimeline and not travel up (possibly to some + parent GESTimeline). + +2019-06-07 09:10:53 -0400 Thibault Saunier + + * ges/ges-structured-interface.c: + structured-interface: Handle track-types in clip addition + The field was already expected in the launcher + +2019-06-06 23:19:38 -0400 Thibault Saunier + + * ges/ges-timeline.c: + * ges/ges-track.c: + * plugins/ges/gesbasebin.c: + * plugins/ges/gesdemux.c: + * plugins/nle/nlecomposition.c: + Implement and use the GstStream API + +2019-06-06 17:21:01 -0400 Thibault Saunier + + * ges/ges-timeline.c: + * ges/ges-track.c: + * plugins/nle/nlecomposition.c: + timeline: Create stable stream IDs + +2019-06-06 15:40:57 -0400 Thibault Saunier + + * docs/meson.build: + * docs/plugins/index.md: + * docs/plugins/nle.md: + * docs/plugins/sitemap.txt: + * meson.build: + * plugins/ges/gessrc.c: + * plugins/nle/nleoperation.c: + docs: Generate ges plugin doc + +2019-06-06 13:51:45 -0400 Thibault Saunier + + * docs/gst_plugins_cache.json: + * plugins/ges/gesbasebin.c: + * plugins/ges/gesbasebin.h: + * plugins/ges/gesdemux.c: + * plugins/ges/gessrc.c: + * plugins/ges/meson.build: + plugins:ges: Factor out a GESBaseBin class + And use it in both gesdemux and gessrc + +2019-06-06 13:02:33 -0400 Thibault Saunier + + * plugins/ges/gesdemux.c: + gesdemux: Emit no-more-pad as required + +2019-06-06 12:46:08 -0400 Thibault Saunier + + * plugins/nle/nlecomposition.c: + nlecomposition: Respect seek seqnum in output EOS/SEGMENT + Allowing a proper seek EOS handling with nested compositions + +2019-06-06 11:26:45 -0400 Thibault Saunier + + * plugins/ges/gesdemux.c: + gesdemux: Properly combine flows + +2019-06-06 10:16:50 -0400 Thibault Saunier + + * plugins/ges/gesdemux.c: + * plugins/ges/gesdemux.h: + * plugins/ges/gesplugin.c: + * plugins/ges/gessrc.c: + * plugins/ges/gessrc.h: + plugin: Make use of G_DECLARE + And remove useless .h files + +2019-06-16 11:09:46 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-xml-formatter.c: + xml-formatter: Plug some leaks + +2019-06-15 16:44:50 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + xml-formatter: Refactor the way we handle loading state + +2019-06-15 15:11:38 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + xml-formatter: Cleanup removing all now useless pending fields + +2018-06-23 11:26:03 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-layer.c: + * ges/ges-uri-asset.c: + * tests/check/python/test_clip.py: + xml-formatter: Load assets before their proxies + Paving the way to removing pending fields to make the code + simpler to follow. + +2019-06-15 01:33:49 -0400 Thibault Saunier + + * ges/ges-asset.c: + assets: Recurse in the chain of proxies + When linking loaded proxies and trying to setup their targets + +2019-06-06 09:48:32 -0400 Thibault Saunier + + * docs/gst_plugins_cache.json: + * plugins/ges/meson.build: + docs: Add gstges plugin + +2019-05-26 09:55:03 -0400 Thibault Saunier + + * ges/ges-validate.c: + validate: Add action type to copy/paste clips + +2019-05-25 20:20:07 -0400 Thibault Saunier + + * ges/ges-container.c: + * tests/check/python/test_timeline.py: + container: Handle children pasting failures + +2019-05-25 18:51:08 -0400 Thibault Saunier + + * ges/ges-clip.c: + * tests/check/python/test_timeline.py: + clip: Fix layer managament when copying a clip that was pasted + +2019-05-25 16:05:00 -0400 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-timeline-element.c: + * tests/check/python/test_timeline.py: + element: Properly handle the fact that pasting can return NULL + And fix paste annotation + +2019-05-31 23:13:48 +0200 Niels De Graef + + * configure.ac: + * meson.build: + meson: Bump minimal GLib version to 2.44 + This means we can use some newer features and get rid of some + boilerplate code using the G_DECLARE_* macros. + As discussed on IRC, 2.44 is old enough by now to start depending on it. + +2019-05-29 23:12:11 +0200 Mathieu Duponchelle + + * plugins/nle/nleobject.c: + * plugins/nle/nleoperation.c: + doc: remove xml from comments + +2019-05-17 19:54:51 -0400 Thibault Saunier + + * ges/ges-video-transition.c: + video-transition: When using non crossfade effect use 'over' operations + For smptealph element to work as expected the following compositing + element should mix with the default "over" operator, as described + in its documentation. + +2019-05-23 18:43:06 -0400 Thibault Saunier + + * tools/ges-launcher.c: + launcher: Remove duplicated track types option + +2019-05-23 18:42:34 -0400 Thibault Saunier + + * ges/ges-layer.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + docs: Minor documentation fixes + +2019-05-23 17:20:56 -0400 Thibault Saunier + + * bindings/python/gi/overrides/GES.py: + overrides: Make sure overrides are in hierarchy order + Otherwise method order resolution will not be correct + +2019-01-24 19:39:48 -0300 Thibault Saunier + + * ges/ges-timeline-element.c: + ges: Minor reorganisation of timeline-element.c + +2019-01-24 08:43:00 -0300 Thibault Saunier + + * ges/ges-timeline-element.h: + ges: Cleanup timeline-element.h indentation + +2019-05-01 18:20:42 -0400 Thibault Saunier + + * ges/ges-project.c: + * ges/ges-project.h: + project: Add a signal to notify when a new timeline is starting to load + +2019-05-23 16:58:25 -0400 Thibault Saunier + + * tools/ges-launcher.c: + tools: Initialize GStreamer before parsin options + We need it to be initialized to be able to parse our options + +2019-05-01 17:28:26 -0400 Thibault Saunier + + * tools/ges-launcher.c: + * tools/utils.c: + * tools/utils.h: + tools: Use a proper implementation of get_flags_from_string + +2019-05-01 17:26:51 -0400 Thibault Saunier + + * tests/check/ges/test-utils.h: + tests: Simply include ges-internal.h instead of redefining the same macros + +2019-05-16 09:07:03 -0400 Thibault Saunier + + * docs/gst_plugins_cache.json: + * docs/meson.build: + docs: Stop building the doc cache by default + And update the cache + Fixes https://gitlab.freedesktop.org/gstreamer/gst-docs/issues/36 + +2019-05-16 15:09:51 +0300 Sebastian Dröge + + * ges/ges-timeline-element.c: + timeline-element: Mark edit() as Since: 1.18 + +2019-05-16 15:06:14 +0300 Sebastian Dröge + + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + * ges/ges-uri-asset.c: + * ges/ges.c: + ges: Sprinkle around some Since: 1.16 markers + +2019-05-01 13:19:42 -0400 Thibault Saunier + + * docs/sitemap.txt: + * ges/ges-pipeline.c: + * ges/ges-screenshot.c: + ges: Deprecate ges_play_sink_convert_frame + It has nothing to do in our namespace/API + +2019-05-01 12:56:44 -0400 Thibault Saunier + + * ges/ges-audio-source.c: + * ges/ges-title-source.c: + * ges/ges-types.h: + * ges/ges-video-source.c: + More porting to markdown + +2019-05-01 11:53:07 -0400 Thibault Saunier + + * ges/ges-title-clip.c: + title-clip: Enhance documentation + +2018-10-22 08:22:52 +0200 Thibault Saunier + + * Makefile.am: + * configure.ac: + * docs/Makefile.am: + * docs/base-classes.md: + * docs/gst_plugins_cache.json: + * docs/images/layer_track_overview.png: + * docs/index.md: + * docs/libs/.gitignore: + * docs/libs/Makefile.am: + * docs/libs/architecture.xml: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * docs/libs/meson.build: + * docs/low_level.md: + * docs/meson.build: + * docs/nle-index.md: + * docs/nle-sitemap.txt: + * docs/nle.md: + * docs/sitemap.txt: + * ges/meson.build: + * meson.build: + * meson_options.txt: + * plugins/meson.build: + * plugins/nle/meson.build: + doc: Build documentation with hotdoc + +2018-10-22 11:39:03 +0200 Thibault Saunier + + * ges/ges-asset.c: + * ges/ges-pitivi-formatter.h: + * ges/ges-project.c: + * ges/ges-track-element-asset.c: + * ges/ges-track-element.c: + * ges/ges-uri-asset.c: + * ges/ges.c: + * ges/meson.build: + docs: Minor fixes + +2019-05-07 13:33:09 -0400 Nicolas Dufresne + + * docs/libs/ges-sections.txt: + doc: ges-track: Add ges_track_set_create_element_for_gap_func + +2019-05-05 11:38:28 -0400 Thibault Saunier + + * tools/ges-launcher.c: + launch: Fix caps restriction short names + +2019-05-04 10:47:07 -0400 Thibault Saunier + + * bindings/python/gi/overrides/GES.py: + * tests/check/python/test_timeline.py: + python: Avoid warning about using deprecated methods + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/69 + +2019-05-02 11:41:10 -0400 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-group.c: + * ges/ges-source-clip.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-track-element.c: + element: Make return value of setters mean something + Setters return values should return %FALSE **only** when the value + could not be set, not when unchanged or when the subclass handled + it itself! + This patches makes it so the return value is meaningul by allowing + subclasses return anything different than `TRUE` or `FALSE` (convention + is -1) to let the subclass now that it took care of everything and + no signal should be emited. + +2019-05-01 12:09:45 -0400 Thibault Saunier + + * bindings/python/gi/overrides/GES.py: + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-container.h: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-track-element.c: + ges: Move `ges_container_edit` to GESTimelineElement + Now that the notion of layer has been moved down to #GESTimelineElement + (through the new #ges_timeline_element_get_layer_priority method), this + method make much more sense directly in the base class. + +2019-04-20 01:36:10 +0530 Nirbheek Chauhan + + * plugins/ges/meson.build: + meson: Generate a pkgconfig file for the GES plugin + This was missing due to a typo. + +2019-04-19 10:41:39 +0100 Tim-Philipp Müller + + * RELEASE: + * configure.ac: + * meson.build: + Back to development + +=== release 1.16.0 === + +2019-04-19 00:35:57 +0100 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + * meson.build: + Release 1.16.0 + +2019-04-18 16:44:31 -0400 Thibault Saunier + + * ges/ges-asset.c: + asset: Avoid unrefing a task we do not own + +2019-04-17 23:53:14 +0200 Alexandru Băluț + + * ges/ges-clip.c: + clip: Optimize set_top_effect_index by checking parent sooner + +2019-04-17 23:51:13 +0200 Alexandru Băluț + + * ges/ges-clip.c: + clip: Return TRUE when the the effect index does not change + +2019-04-13 20:03:52 +0200 Alexandru Băluț + + * ges/ges-clip.c: + clip: Remove obsolete FIXME + +2019-04-11 23:58:48 +0200 Alexandru Băluț + + * ges/ges-container.c: + container: Call _remove_child when cannot set parent + ges_container_add removes the child being added if the call to + ges_timeline_element_set_parent fails. In this case, subclasses should + be given the chance to revert the effects of the add_child vmethod which + has just been called. + +2019-04-11 23:45:13 +0200 Alexandru Băluț + + * ges/ges-container.c: + ges: Remove unused nb_effects field + +2019-04-12 17:30:14 +0300 Mart Raudsepp + + * ges/ges-track.c: + track: Avoid various sorting operations before timeline commit + These are showing up in performance profile of 1000+ clips looped addition. + All this is done at commit time as well, so let that do only one update and + sorting. + +2019-04-15 17:03:49 -0400 Thibault Saunier + + * ges/ges-asset.c: + asset: Do not take an extra ref on asset when already initialized + The task already has a ref so this one doesn't make sense and leads to leaks + +2019-04-12 18:31:07 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-project.c: + * tests/check/ges/project.c: + xml-formatter: Fix some asset leaks + +2019-04-09 08:58:24 -0400 Thibault Saunier + + * tests/check/ges/clip.c: + * tests/check/ges/group.c: + * tests/check/ges/layer.c: + * tests/check/ges/mixers.c: + * tests/check/ges/project.c: + * tests/check/ges/timelineedition.c: + tests: Plug misc leaks + +2019-04-09 08:56:49 -0400 Thibault Saunier + + * ges/gstframepositioner.c: + framepositioner: Plug caps leak + +2019-04-09 08:56:08 -0400 Thibault Saunier + + * ges/ges-xml-formatter.c: + xml-formatter: Plug some leaks + +2019-04-08 16:25:59 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Plug leak of the auto transition asset + +2019-04-08 16:25:44 -0400 Thibault Saunier + + * ges/ges-timeline-tree.c: + tree: Plug a GList leak + +2019-04-08 16:25:29 -0400 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Plug pad leak + +2019-04-08 16:23:18 -0400 Thibault Saunier + + * ges/ges-title-source.c: + * tests/check/ges/titles.c: + title: Deprecate method that return newly allocated `const gchar*` + This is just plain broken 190643508f14a64e36f085a69de819505e79dadb + but we can't do anything about it. + +2019-04-05 11:24:39 -0300 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-project.c: + Plug some GError leaks when loading assets + +2019-04-01 11:52:43 -0300 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + xml-formatter: Free pending clips on disposal + +2019-03-28 13:51:36 -0300 Thibault Saunier + + * ges/ges-asset.c: + asset: Plug a GError leak + +2019-03-28 13:08:55 -0300 Thibault Saunier + + * tests/check/ges/mixers.c: + tests: Avoid random timeout and let the launcher set it up for us + +2019-03-28 13:08:01 -0300 Thibault Saunier + + * ges/ges-asset.c: + asset: s/unsure/ensure + +2019-03-28 13:06:37 -0300 Thibault Saunier + + * ges/ges-project.c: + asset: Plug a leak of EncodingProfiles + +2019-03-28 13:06:16 -0300 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * tests/check/ges/group.c: + xml-formatter: Plug leaks of pending groups + +2019-03-28 13:05:45 -0300 Thibault Saunier + + * ges/ges-asset.c: + asset: plug a GTask leak + +2019-03-28 11:29:05 -0300 Thibault Saunier + + * ges/ges-track-element.c: + * tests/check/ges/clip.c: + Fix splitting control bindings leaks + +2019-03-28 11:09:13 -0300 Thibault Saunier + + * tests/check/ges/asset.c: + tests: Fix a leak in the 'asset' test + +2019-03-28 11:08:58 -0300 Thibault Saunier + + * ges/ges-timeline-tree.c: + tree: Fixup some GList leaks + +2019-04-15 18:37:58 +0900 Yeongjin Jeong + + * ges/ges-uri-asset.c: + uri-asset: Ensure that the discoverer stops on deinit. + Discoverer maintain a referernce on the discoverer object while + the async timeout callback is alive to prevent a potential crash + if the object is freed while the callback is pending. + But if g_main_context is released before calling the timeout callback, + the discoverer pointer which was weak referenced from GESUriClipAssetClass + will not be disposed because the discoverer object is not finalized. + +=== release 1.15.90 === + +2019-04-11 00:37:00 +0100 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + * meson.build: + Release 1.15.90 + +2019-03-23 19:21:31 +0000 Tim-Philipp Müller + + * meson.build: + g-i: pass --quiet to g-ir-scanner + This suppresses the annoying 'g-ir-scanner: link: cc ..' output + that we get even if everything works just fine. + We still get g-ir-scanner warnings and compiler warnings if + we pass this option. + +2019-03-19 16:39:20 +0100 Jakub Adam + + * ges/ges-video-source.c: + videosource: Expose video-direction child property + +2019-03-15 16:24:16 +0100 Jakub Adam + + * ges/ges-video-source.c: + videosource: auto-flip the image according to image-orientation tag + If there's image-orientation tag, make sure the image is correctly + oriented before we scale it. + +2019-03-16 15:04:29 +0000 Tim-Philipp Müller + + * ges/Makefile.am: + Fix autotools build + +2019-03-08 17:45:27 -0300 Thibault Saunier + + * ges/ges-clip.c: + clip: Make sure to set the pasted clip start before adding to layer + And handle the fact that adding to a layer can fail. + Also plug some leaks in the dispose method (and use the dispose + vmethod instead of finalize as appropriate). + +2019-03-08 12:28:31 -0300 Thibault Saunier + + * ges/ges-clip.c: + clip: Emit signals while splitting in a way the operation is undoable + Basically if we do not emit a "duration" change of the clip being + splitted first when executing the 'reverse' operations would lead + to fully overallaping clips. + +2019-03-01 19:32:19 -0300 Thibault Saunier + + * ges/ges-auto-transition.c: + * ges/ges-auto-transition.h: + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-container.c: + * ges/ges-group.c: + * ges/ges-internal.h: + * ges/ges-layer.c: + * ges/ges-source-clip.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline-tree.c: + * ges/ges-timeline-tree.h: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + * ges/ges-uri-clip.c: + * ges/meson.build: + * tests/check/ges/asset.c: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/group.c: + * tests/check/ges/layer.c: + * tests/check/ges/test-utils.h: + * tests/check/ges/timelineedition.c: + * tests/check/ges/uriclip.c: + * tests/check/python/common.py: + * tests/check/python/test_group.py: + * tests/check/python/test_timeline.py: + Reimplement the timeline editing API + This is implemented on top of a Tree that represents the whole timeline. + SourceClips can not fully overlap anymore and the tests have been + updated to take that into account. Some new tests were added to verify + that behaviour in greater details + +2019-03-03 21:18:53 -0300 Thibault Saunier + + * examples/c/gessrc.c: + * plugins/ges/gesdemux.c: + Some copyright fixing + +2019-03-03 20:59:12 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Rename group_id to stream_start_group_id + +2019-03-01 19:30:41 -0300 Thibault Saunier + + * tests/check/ges/test-utils.c: + * tests/check/ges/test-utils.h: + * tests/check/python/common.py: + tests: Add utilities to print the timeline + Making debugging tests simpler + +2019-03-01 19:08:39 -0300 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-group.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-track-element.c: + * tests/check/ges/group.c: + * tests/check/ges/timelineedition.c: + timeline-element: Add a method to retrieve layer priority + Each timeline element is in a layer (potentially spanning + over several), it is very often useful to retrieve an element + layer priority (from an app perspective more than the element + priority itself as that is a bit of an implementation detail + in the end). + Port tests to it + +2019-02-11 20:30:31 -0300 Thibault Saunier + + * bindings/python/gi/overrides/GES.py: + python: Implement TimelineElement.__repr__ + +2019-02-28 13:56:50 -0300 Thibault Saunier + + * ges/ges-enums.c: + * ges/ges-enums.h: + Add API to get the GESEdge names + +2019-02-09 18:59:08 -0300 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-internal.h: + * ges/ges-timeline-element.c: + ges: Move GESClipFlags to GESTimelineElementFlags + Keeping it internal + And add an internal method to get layer priority for GESTimelineElements + (dirty implementation to make it simple for now) + +2019-02-08 17:50:04 -0300 Thibault Saunier + + * tests/check/python/test_timeline.py: + tests:python: assertEquals is deprecated, use assertEqual + +2019-02-08 17:48:26 -0300 Thibault Saunier + + * ges/ges-layer.c: + layer: factor out a method to remove an object without signaling it + +2019-02-08 17:47:48 -0300 Thibault Saunier + + * tests/check/python/common.py: + * tests/check/python/test_timeline.py: + tests: python: Move assertTimelineTopology to the baseclass + +2019-02-08 17:46:31 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: No error when moving an object as part of the context + It will just happen from the context + +2019-02-08 17:44:40 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Not being able to trim and object is an error + So error out when that happens. + +2019-02-08 17:43:34 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Setting duration to the same value is valid + And should not be advertised as if the operation failed. + +2019-02-08 17:37:39 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Do not ripple if resulting duration would be 0 + +2019-02-08 16:44:39 -0300 Thibault Saunier + + * ges/ges-auto-transition.c: + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-internal.h: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + * tests/check/ges/group.c: + clip: Add a method to get the priority of the layer it is in + Just an helper method to get the 'priority of a the clip' + +2019-02-08 16:05:18 -0300 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-source-clip.c: + * tests/check/ges/timelineedition.c: + clip: Rollback moving clips when moving a contained TrackElement fails + And fix unit tests to match the correct behaviour + +2019-02-09 00:07:08 -0300 Thibault Saunier + + * ges/ges-internal.h: + * tests/check/ges/test-utils.h: + Shorten GES_FORMAT output + +2019-02-21 17:24:51 -0300 Thibault Saunier + + * ges/ges-clip.c: + * tests/check/python/test_clip.py: + clip: Make sure to remove and re add effects when adding clips to layer + And make re add them in the same order. + And enhance tests to check that + +2019-03-01 22:57:48 -0300 Thibault Saunier + + * ges/ges-source.c: + source: No checks when linking default elements + +2019-03-15 18:31:30 -0300 Thibault Saunier + + * plugins/nle/nlesource.c: + nlesource: Use gst_element_call_async as appropriate + +2019-03-15 17:07:06 -0300 Thibault Saunier + + * plugins/nle/nlesource.c: + nlesource: Protect seeks from tear down + Otherwise there is a race where we trigger the seek at the exact + same time the composition is being teared down potentially leading + to basesrc restarting its srcpad task which ends up being leaked. + Fixes ges.playback.scrub_backward_seeking.test_title.audio_video.vorbis_theora_ogg + and probably all its friends timeouting with the following stack trace: + (gdb) t a a bt + Thread 4 (Thread 0x7f5962acd700 (LWP 19997)): + #0 0x00007f5976713efd in syscall () at ../sysdeps/unix/sysv/linux/x86_64/syscall.S:38 + #1 0x00007f5976a9d3f3 in g_cond_wait (cond=cond@entry=0x7f5938125410, mutex=mutex@entry=0x7f59381253c8) at gthread-posix.c:1402 + #2 0x00007f5976c9e26b in gst_task_func (task=0x7f59381253b0 [GstTask]) at ../subprojects/gstreamer/gst/gsttask.c:313 + #3 0x00007f5976a7ecb3 in g_thread_pool_thread_proxy (data=) at gthreadpool.c:307 + #4 0x00007f5976a7e2aa in g_thread_proxy (data=0x7f5954071d40) at gthread.c:784 + #5 0x00007f59767ea58e in start_thread (arg=) at pthread_create.c:486 + #6 0x00007f59767196a3 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95 + Thread 3 (Thread 0x7f5963fff700 (LWP 19995)): + #0 0x00007f597670e421 in __GI___poll (fds=0xe32da0, nfds=2, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29 + #1 0x00007f5976a553a6 in g_main_context_poll (priority=, n_fds=2, fds=0xe32da0, timeout=, context=0xe31ff0) at gmain.c:4221 + #2 0x00007f5976a553a6 in g_main_context_iterate (context=0xe31ff0, block=block@entry=1, dispatch=dispatch@entry=1, self=) at gmain.c:3915 + #3 0x00007f5976a55762 in g_main_loop_run (loop=0xe32130) at gmain.c:4116 + #4 0x00007f59768db10a in gdbus_shared_thread_func (user_data=0xe31fc0) at gdbusprivate.c:275 + #5 0x00007f5976a7e2aa in g_thread_proxy (data=0xe1b8a0) at gthread.c:784 + #6 0x00007f59767ea58e in start_thread (arg=) at pthread_create.c:486 + #7 0x00007f59767196a3 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95 + Thread 2 (Thread 0x7f5968dcc700 (LWP 19994)): + #0 0x00007f597670e421 in __GI___poll (fds=0xe1bcc0, nfds=1, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29 + #1 0x00007f5976a553a6 in g_main_context_poll (priority=, n_fds=1, fds=0xe1bcc0, timeout=, context=0xe1b350) at gmain.c:4221 + #2 0x00007f5976a553a6 in g_main_context_iterate (context=context@entry=0xe1b350, block=block@entry=1, dispatch=dispatch@entry=1, self=) at gmain.c:3915 + #3 0x00007f5976a554d0 in g_main_context_iteration (context=0xe1b350, may_block=may_block@entry=1) at gmain.c:3981 + #4 0x00007f5976a55521 in glib_worker_main (data=) at gmain.c:5861 + #5 0x00007f5976a7e2aa in g_thread_proxy (data=0xe1b800) at gthread.c:784 + #6 0x00007f59767ea58e in start_thread (arg=) at pthread_create.c:486 + #7 0x00007f59767196a3 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95 + Thread 1 (Thread 0x7f5975df4fc0 (LWP 19993)): + #0 0x00007f5976713efd in syscall () at ../sysdeps/unix/sysv/linux/x86_64/syscall.S:38 + #1 0x00007f5976a9d3f3 in g_cond_wait (cond=cond@entry=0xe34020, mutex=0xe39b80) at gthread-posix.c:1402 + #2 0x00007f5976a7f41c in g_thread_pool_free (pool=0xe34000, immediate=0, wait_=) at gthreadpool.c:776 + #3 0x00007f5976c9f1ca in default_cleanup (pool=0xe256b0 [GstTaskPool]) at ../subprojects/gstreamer/gst/gsttaskpool.c:89 + #4 0x00007f5976c9e32d in init_klass_pool (klass=) at ../subprojects/gstreamer/gst/gsttask.c:161 + #5 0x00007f5976c9e502 in gst_task_cleanup_all () at ../subprojects/gstreamer/gst/gsttask.c:381 + #6 0x00007f5976c214f4 in gst_deinit () at ../subprojects/gstreamer/gst/gst.c:1095 + #7 0x000000000040394f in main (argc=6, argv=) at ../subprojects/gst-editing-services/tools/ges-launch.c:94 + +2019-02-08 18:26:19 -0300 Thibault Saunier + + * tests/benchmarks/meson.build: + * tests/meson.build: + meson: Build benchmarks + +2019-03-11 19:56:09 -0300 Thibault Saunier + + * ges/ges-uri-asset.c: + asset-uri: Create a specific discoverer when discovering sync + To allow 'reintrancy'. + This was a 'regression' introduced in bad64296d9b497a13f5f7fe91d568d85ed236265 + Fixes https://gitlab.gnome.org/GNOME/pitivi/issues/2278 + +2019-02-22 17:31:06 -0800 Pat DeSantis + + * ges/ges-formatter.h: + Mark ges_timeline_load_from_uri as deprecated + +2019-02-20 20:17:55 -0800 Pat DeSantis + + * ges/ges-formatter.h: + Update deprecation warning to match GTK style + +2019-02-20 17:17:14 -0800 Pat DeSantis + + * ges/ges-formatter.h: + Mark ges_formatter_save_to_uri as deprecated + +2019-01-29 13:45:49 +0900 Seungha Yang + + * tests/check/Makefile.am: + * tests/check/ges/negative.c: + * tests/check/meson.build: + tests: Add inconsistent init/deinit test case + +2019-01-28 20:45:11 +0900 Seungha Yang + + * ges/ges-asset.c: + * ges/ges.c: + ges: Enhance ges_{init/deinit} documentation + Add some init/deinit related comment and make assertion when + ges_deinit() is called from unexpected thread. + +2019-02-06 19:49:14 -0300 Thibault Saunier + + * tests/check/python/common.py: + tests:python: Use proper GES.Project constructor + Avoiding a g_critical + +2019-02-08 13:54:06 +0900 Seungha Yang + + * plugins/nle/nlecomposition.c: + nlecomposition: Suppress error from child during sync state with parent + This commit is to ensure cleanup internal elements on state change failure. + nlecomposition posts its own error message after cleanup child. + If we don't suppress child error, meanwhile, an application + triggered downward state change (resulting from child error message) + might be able to reach nlecomposition before internal cleaning child up. + That eventually results to downward state change failure. + +2019-02-05 17:29:00 +0900 Seungha Yang + + * plugins/nle/nlesource.c: + nlesource: Don't leak pending seek event on dispose + +2019-02-01 15:37:42 +0900 Seungha Yang + + * plugins/nle/nlecomposition.c: + nlecomposition: Don't keep sync state of child on activation failure + This will result in downward state change failure eventually + when user is finalizing top level (i.g., gespipeline) bin. + +2019-03-04 11:09:33 +0000 Tim-Philipp Müller + + * examples/.gitignore: + examples: add gessrc example binary to .gitignore + +2019-03-04 11:07:51 +0000 Tim-Philipp Müller + + * plugins/ges/gesdemux.c: + gesdemux: don't use deprecated gst_uri_construct() + Fixes #64 + +2019-03-04 09:14:25 +0000 Tim-Philipp Müller + + * NEWS: + * RELEASE: + * configure.ac: + * meson.build: + Back to development + +2019-02-28 13:09:38 +0200 Sebastian Dröge + + * plugins/ges/Makefile.am: + ges: Link ges plugin to libgstpbutils + /usr/bin/ld: .libs/libgstges_la-gesdemux.o: in function `ges_timeline_new_from_uri_from_main_thread': + ./plugins/ges/gesdemux.c:279: undefined reference to `gst_discoverer_new' + /usr/bin/ld: ./plugins/ges/gesdemux.c:288: undefined reference to `gst_discoverer_start' + +=== release 1.15.2 === + +2019-02-26 11:59:49 +0000 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + * meson.build: + Release 1.15.2 + +2019-02-26 14:12:13 +0000 Tim-Philipp Müller + + * examples/c/Makefile.am: + examples: add new gessrc example, so sourcefile gets disted + +2019-02-26 13:57:17 +0000 Tim-Philipp Müller + + * configure.ac: + * plugins/Makefile.am: + * plugins/ges/Makefile.am: + plugins: add autotools build for new ges plugin + +2019-02-20 22:11:54 -0300 Thibault Saunier + + * examples/python/keyframes.py: + examples: Add an example about using keyframes in python + +2019-02-11 18:26:04 +0900 Seungha Yang + + * ges/ges-meta-container.c: + ges-meta-container: Fix g-i annotation + ges-meta-container.c:516: Warning: GES: invalid "allow-none" annotation: + only valid for pointer types and out parameters + +2019-02-06 00:30:35 +0530 Nirbheek Chauhan + + * plugins/ges/gesdemux.c: + misc: Fix warnings on Cerbero's ancient MinGW + gesdemux.c:297:3: error: value computed is not used [-Werror=unused-value] + +2019-01-23 09:07:58 -0300 Thibault Saunier + + * ges/ges-group.c: + Fix segfault when adding clips to group outside a timeline + Making sure that objects are inside a timeline before adding/removing them from it + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/35 + +2019-01-30 15:58:33 -0300 Thibault Saunier + + * tests/check/python/test_timeline.py: + tests: Fix usage of undefined class + +2019-01-28 19:09:03 -0300 Thibault Saunier + + * tools/ges-launcher.c: + launcher: Add options to set tracks restriction caps + +2019-01-28 00:55:27 +0900 Yeongjin Jeong + + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + tests: ges: Fix various leak + +2019-01-26 19:50:48 +0900 Yeongjin Jeong + + * ges/ges-layer.c: + layer: Fix asset leak + +2019-01-26 16:44:09 +0900 Yeongjin Jeong + + * ges/ges-effect.c: + effect: Fix string leak + +2019-01-29 11:52:43 +0900 Yeongjin Jeong + + * ges/ges-uri-asset.c: + uri-asset: Implement dispose vmethod for GESUriSourceAsset + ... and fix DiscovererStreamInfo leak + +2019-01-26 16:27:27 +0900 Yeongjin Jeong + + * ges/ges-uri-asset.c: + uri-asset: Don't forget to unref DiscovererInfo on dispose + Dispose() must unref DiscovererInfo ownership + taken by ges_uri_clip_asset_set_info(). + +2019-01-25 18:21:43 +0900 Yeongjin Jeong + + * ges/ges-video-transition.c: + video-transition: Fix GstPad leak + Returned Gstpad by link_element_to_mixer_with_smpte() + has increased refcount in ges_smart_mixer_get_mixer_pad(). + +2018-11-27 04:55:17 +0100 Alexandru Băluț + + * ges/ges-timeline.c: + * tests/check/python/common.py: + * tests/check/python/test_timeline.py: + timeline: Better handle loading inconsistent timelines + Auto transition when having 3 overlapping clips in a same point in the + timeline is not supported as we can't handle it in a nice way. Before we + to avoid creating 2 overlapping transitions (which is plain broken in + NLE) were completely disabling `auto-transition` and removing all + auto-transitions in the timeline but this is pretty weird for the end + user. This commit changes and now makes sure 2 transitions are not + created in the same place. + Also cleanup previous test case. + +2019-01-18 17:25:11 -0300 Thibault Saunier + + * ges/ges-asset.c: + * ges/ges-project.c: + * ges/ges-transition-clip.c: + * ges/ges-xml-formatter.c: + s/accured/occurred/g + +2019-01-18 17:12:42 -0300 Thibault Saunier + + * ges/ges-xml-formatter.c: + xml-formatter: Minor debug enhancement + +2019-01-18 09:52:47 -0300 Thibault Saunier + + * plugins/ges/gesdemux.c: + * plugins/ges/gesdemux.h: + * plugins/ges/gesplugin.c: + plugins: Add an a gesdemux element to 'demux' serialized timelines + +2015-03-14 20:52:47 +0000 Thibault Saunier + + * examples/c/gessrc.c: + * examples/c/meson.build: + * examples/python/gst-player.py: + * plugins/ges/gesplugin.c: + * plugins/ges/gessrc.c: + * plugins/ges/gessrc.h: + * plugins/ges/meson.build: + * plugins/meson.build: + plugins: implement a gessrc element useable from playbin + This is a new simple GstBin that can handle the ges:// uris + and will directly expose the srcppads of the tracks present in the + timeline. + +2019-01-18 15:45:39 -0300 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-project.c: + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + * ges/ges.c: + * tests/check/python/test_assets.py: + uri-asset: Use the same code path for sync discovery as the async one + And start handling relocated assets. + Also expose the discoverer callback as a vmethod so that we can + overridde the discoverer when necessary (to handle discovering of + timeline through gesdemux for example) + +2019-01-17 15:12:42 -0300 Thibault Saunier + + * ges/ges-pipeline.c: + * plugins/nle/nlecomposition.c: + * tests/check/nle/nlecomposition.c: + nlecomposition: Get overall pipeline position by recursing up + And handle NLEComposition inside NLEComposition + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/39 + +2018-09-30 17:22:13 -0300 Thibault Saunier + + * ges/ges-smart-video-mixer.c: + videomixer: Drop allocation query after the compositor + Working around https://gitlab.freedesktop.org/gstreamer/gstreamer/issues/310 + +2019-01-28 18:59:40 -0300 Thibault Saunier + + * ges/ges-layer.c: + * tests/check/python/test_timeline.py: + layer: Resort clips before syncing priorities + We set the priorities making the assumption that `start_clips` is properly + ordered by start! + Fixes https://gitlab.gnome.org/GNOME/pitivi/issues/2254 + +2019-01-28 12:58:06 +0900 Seungha Yang + + * tests/check/ges/asset.c: + tests: asset: Add test async asset request with custom GMainContext + ... and test call ges_{init/deinit} multiple times in a unit test. + +2019-01-28 17:22:10 +0900 Seungha Yang + + * ges/ges.c: + Revert "ges: Add missing type unref on deinit" + This reverts commit e939cfebaf4deeabf21ba799ddc3eeaa87e7cf9a. + Class might not be initialized if they were already registered + when ges_init() was called, but were not created until ges_deinit() called. + +2019-01-28 17:12:54 +0900 Seungha Yang + + * ges/ges-internal.h: + * ges/ges-uri-asset.c: + * ges/ges.c: + ges: Add check sync/async discoverer + To support ges_{init/deinit} multiple times in a process, + there should be a method for setting up internal object/table of + GESUriClipAssetClass. because *_class_init() will be called + only once in process lifecycle. + +2019-01-28 17:15:19 +0900 Seungha Yang + + * ges/ges-uri-asset.c: + uri-asset: Add missing GHashTable cleanup + ... and use g_object_unref() for GFile object, it's not a GstObject. + +2019-01-28 16:34:15 +0900 Seungha Yang + + * ges/ges.c: + ges: Print initialize error reasons + +2019-01-28 12:19:30 +0900 Seungha Yang + + * tests/check/ges/asset.c: + tests: asset: Remove out-of-date comment + +2019-01-28 12:17:00 +0900 Seungha Yang + + * tests/check/ges/asset.c: + tests: asset: Remove pointless gst_init() + It's done by GST_CHECK_MAIN() already + +2019-01-28 11:24:29 +0900 Seungha Yang + + * tests/check/ges/asset.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/group.c: + * tests/check/ges/layer.c: + * tests/check/ges/mixers.c: + * tests/check/ges/overlays.c: + * tests/check/ges/project.c: + * tests/check/ges/tempochange.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/titles.c: + * tests/check/ges/track.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + * tests/check/nle/complex.c: + * tests/check/nle/nlecomposition.c: + * tests/check/nle/nleoperation.c: + * tests/check/nle/simple.c: + tests: init/deinit per test case + ... in order to verify init/deinit pair. + +2019-01-28 11:07:36 +0900 Seungha Yang + + * ges/ges.c: + ges: Simplify init/deinit flag + In theory, GES can be init/deinit multiple times in a process. + To simplify that use-case, let's trace only "ges_initialized" flag. + +2019-01-21 11:53:44 +0100 Corentin Noël + + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + uri-asset: Add ges_uri_clip_asset_finish to get better introspection + Vala requires a matching _finish function to correctly bind the method with the right finish method. + +2019-01-21 14:14:06 +0100 Corentin Noël + + * ges/ges-timeline.c: + timeline: fix two issues in the documentation + +2019-01-15 09:59:59 -0300 Thibault Saunier + + * ges/ges-xml-formatter.c: + xml-formatter: Do not forget to serialize clips metadata + +2019-01-15 09:38:14 -0300 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-xml-formatter.c: + xml-formatter: Serialize groups metadatas + +2019-01-15 00:15:28 +0900 Seungha Yang + + * ges/ges-uri-asset.c: + uri-asset: Don't leak GstDiscovererInfo + +2019-01-15 00:13:24 +0900 Seungha Yang + + * ges/ges-uri-asset.c: + uri-asset: Impl. dispose vfunc + ... and fix GList/GESAsset leak + +2019-01-14 23:38:19 +0900 Seungha Yang + + * ges/ges-transition-clip.c: + transition-clip: Don't leak GESAsset + Returned GESAsset from ges_asset_request should be freed since + ges_extractable_set_asset doesn't take ownership + +2019-01-14 22:14:18 +0900 Seungha Yang + + * ges/gstframepositioner.c: + framepositioner: Fix invalid memory access + The GstFramePositioner might be finalized before the notify callback + Without this commit, + gst-editing-services / ges_basic / test_ges_timeline_remove_track + can reproduce the case. + +2019-01-14 15:06:26 +0900 Seungha Yang + + * plugins/nle/nleoperation.c: + nleoperation: Fix GstPad leak + Returned GstPad by nle_object_remove_ghost_pad() has increased + refcount. + +2019-01-14 14:10:32 +0900 Seungha Yang + + * plugins/nle/nlecomposition.c: + * plugins/nle/nleghostpad.c: + nleghostpad: Fix GstEvent leak + +2019-01-14 12:52:47 +0900 Seungha Yang + + * plugins/nle/nlecomposition.c: + nlecomposition: Don't leak GNode + Clear the last node before update + +2019-01-14 13:22:13 +0900 Seungha Yang + + * tests/check/nle/complex.c: + * tests/check/nle/nlecomposition.c: + * tests/check/nle/tempochange.c: + tests: nle: Fix various leak + Don't leak GError and GstPad object + +2019-01-14 11:34:20 +0900 Seungha Yang + + * tests/check/nle/common.c: + * tests/check/nle/common.h: + * tests/check/nle/complex.c: + * tests/check/nle/nleoperation.c: + * tests/check/nle/simple.c: + * tests/check/nle/tempochange.c: + tests: nle: Fix GList leak + +2019-01-13 00:12:42 +0900 Seungha Yang + + * tests/check/ges/asset.c: + * tests/check/ges/basic.c: + * tests/check/ges/layer.c: + tests: ges: Fix various leak + +2019-01-12 22:24:55 +0900 Seungha Yang + + * ges/ges-asset.c: + asset: Fix various leak + +2019-01-12 21:59:20 +0900 Seungha Yang + + * ges/ges-enums.c: + enums: Add missing unref + +2019-01-12 21:52:16 +0900 Seungha Yang + + * plugins/nle/nlecomposition.c: + nlecomposition: Clear all members before chaining up to parent on finalize + +2019-01-12 21:51:36 +0900 Seungha Yang + + * ges/ges-structure-parser.c: + structure-parser: Add missig chain up code + +2019-01-12 20:53:38 +0900 Seungha Yang + + * ges/ges.c: + ges: Add missing type unref on deinit + +2019-01-12 20:23:50 +0900 Seungha Yang + + * ges/ges-asset.c: + * ges/ges-internal.h: + * ges/ges.c: + ges: Cleanup internal hash table on deinit + System-wide once allocated but it makes tracing leak hard + +2019-01-12 19:57:37 +0900 Seungha Yang + + * ges/ges.c: + ges: Make init/deinit thread safe + Although it might be uncommon use case, init/deinit could be called + in non-main thread. + +2019-01-12 19:23:25 +0900 Seungha Yang + + * ges/ges-asset.c: + asset: Use static lock + The mutex life cycle follows processs. + +2019-01-14 10:16:18 +0900 Seungha Yang + + * tests/check/meson.build: + tests: Increase timeout value to 360 sec + Use consistent timeout value with core and other plugins. + Otherwise, valgrind sometimes timed out with default timeout 30sec. + +2019-01-14 12:45:29 +0900 Seungha Yang + + * plugins/nle/nlecomposition.c: + nlecomposition: Don't try dump null stack + Fixes following assertion + Unexpected critical/warning: g_node_traverse: assertion 'root != NULL' failed + +=== release 1.15.1 === + +2019-01-17 02:30:06 +0000 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + * meson.build: + Release 1.15.1 + +2019-01-14 18:32:23 -0300 Thibault Saunier + + * ges/ges-formatter.c: + ges: Register formatters during meta registration + So that formatters implemented outside GES itself are registered + +2019-01-14 18:30:38 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges.c: + * ges/ges.h: + ges: Add a ges_is_initialized function + +2019-01-14 18:28:52 -0300 Thibault Saunier + + * ges/ges-formatter.c: + formatter: sink ref of the temporary GESFormatter + To accomodate formatters implemented with bindings/in python + +2019-01-09 17:11:37 +0900 Seungha Yang + + * ges/ges-pipeline.c: + pipeline: Ensure timeline state to be NULL on dispose + The GESTimeline's state might not be synced with parent + +2019-01-09 16:23:54 +0900 Seungha Yang + + * plugins/nle/nlecomposition.c: + nlecomposition: Handle state change failure + Whatever the reason for failure, try cleanup child elements + and internal thread. + +2019-01-05 00:23:20 +0100 Alexandru Băluț + + * ges/ges-meta-container.c: + ges-meta-container: Fix warning message + +2019-01-04 05:31:39 +0100 Alexandru Băluț + + * ges/ges-meta-container.c: + ges-meta-container: Minor documentation fixes + +2019-01-04 12:36:20 +0100 Thibault Saunier + + * ges/ges-track-element.c: + track-element: Ignore writability for whitlisted children props + If the property was explicitely whitelisted, we should expose it + in any case. + This was a regression from 835d69374978208bc73a8f823b899f624dda9479 + +2018-12-30 19:49:44 +0000 Tim-Philipp Müller + + * ges/ges-smart-video-mixer.c: + ges: avoid use of G_DECLARE_FINAL_TYPE which requires GLib 2.44 + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/48 + +2018-12-27 10:54:28 +0900 Seungha Yang + + * ges/ges-container.c: + container: Fix GHashTable leak + +2018-12-27 00:15:30 +0900 Seungha Yang + + * ges/ges-container.c: + container: Fix wrong finalize() usage + finalize must chain up to parent's finalize(), not dispose() + +2018-12-27 00:14:03 +0900 Seungha Yang + + * ges/ges-timeline-element.c: + timeline-element: Chain up to parent impl. on dispose() + ... as documented in glib + +2018-09-24 15:41:24 +0100 Tim-Philipp Müller + + * configure.ac: + * ges/Makefile.am: + * ges/ges-asset.c: + * ges/ges-audio-source.c: + * ges/ges-audio-test-source.c: + * ges/ges-audio-track.c: + * ges/ges-audio-transition.c: + * ges/ges-audio-uri-source.c: + * ges/ges-auto-transition.c: + * ges/ges-base-effect-clip.c: + * ges/ges-base-effect.c: + * ges/ges-base-transition-clip.c: + * ges/ges-base-xml-formatter.c: + * ges/ges-clip-asset.c: + * ges/ges-clip.c: + * ges/ges-command-line-formatter.c: + * ges/ges-container.c: + * ges/ges-effect-asset.c: + * ges/ges-effect-clip.c: + * ges/ges-effect.c: + * ges/ges-enums.c: + * ges/ges-extractable.c: + * ges/ges-formatter.c: + * ges/ges-group.c: + * ges/ges-image-source.c: + * ges/ges-layer.c: + * ges/ges-meta-container.c: + * ges/ges-multi-file-source.c: + * ges/ges-operation-clip.c: + * ges/ges-operation.c: + * ges/ges-overlay-clip.c: + * ges/ges-pipeline.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-prelude.h: + * ges/ges-project.c: + * ges/ges-screenshot.c: + * ges/ges-smart-adder.c: + * ges/ges-smart-video-mixer.c: + * ges/ges-source-clip.c: + * ges/ges-source.c: + * ges/ges-structure-parser.c: + * ges/ges-structured-interface.c: + * ges/ges-test-clip.c: + * ges/ges-text-overlay-clip.c: + * ges/ges-text-overlay.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + * ges/ges-title-clip.c: + * ges/ges-title-source.c: + * ges/ges-track-element-asset.c: + * ges/ges-track-element.c: + * ges/ges-track.c: + * ges/ges-transition-clip.c: + * ges/ges-transition.c: + * ges/ges-uri-asset.c: + * ges/ges-uri-clip.c: + * ges/ges-utils.c: + * ges/ges-video-source.c: + * ges/ges-video-test-source.c: + * ges/ges-video-track.c: + * ges/ges-video-transition.c: + * ges/ges-video-uri-source.c: + * ges/ges-xml-formatter.c: + * ges/meson.build: + * meson.build: + WIP: ges: fix API export/import and 'inconsistent linkage' on MSVC + Export GES library API in headers when we're building the + library itself, otherwise import the API from the headers. + This fixes linker warnings on Windows when building with MSVC. + Fix up some missing config.h includes when building the lib which + is needed to get the export api define from config.h + Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/42 + +2018-12-10 13:28:16 +1100 Matthew Waters + + * ges/meson.build: + build: also allow building static libraries for e.g. Android/iOS + +2018-12-05 17:25:04 -0300 Thibault Saunier + + * common: + Automatic update of common submodule + From ed78bee to 59cb678 + +2018-11-30 12:41:04 -0300 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + * ges/ges.c: + * ges/ges.h: + * ges/meson.build: + * meson.build: + * meson_options.txt: + Add a way to disable xptv support + This formatter is in very bad shape and is generally not useful. + It has been deprecated since 1.0... and I bet noone uses it. + +2018-11-28 05:48:37 +0200 Jordan Petridis + + * examples/c/play_timeline_with_one_clip.c: + Run gst-indent through the files + This is required before we enabled an indent test in the CI. + https://gitlab.freedesktop.org/gstreamer/gstreamer-project/issues/33 + +2018-11-27 12:09:20 -0300 Thibault Saunier + + * ges/ges-track-element.c: + track: Fix documentation about "binding_type" + +2018-11-26 17:18:25 -0300 Thibault Saunier + + * meson.build: + Revert "meson: Fix the reference to libxml2 path" + It seemed to be what the wrap file expected but in the end it is + just a bug in meson which is now fixed. + This reverts commit cc5d74d0be30dab92d1540ed749eaf4dcedd9171. + +2018-11-26 15:57:30 -0300 Thibault Saunier + + * meson.build: + meson: Fix the reference to libxml2 path + +2018-11-26 14:50:29 -0300 Thibault Saunier + + * meson.build: + meson: Add a fallback for libxml2 + +2018-11-26 14:50:03 -0300 Thibault Saunier + + * ges/ges-validate.c: + validate: cleanup the playback-time from validate structures + Otherwise we might fail on them in the ges-structure-interface + +2018-11-23 11:22:03 -0300 Thibault Saunier + + * ges/ges-effect.c: + effect: Create ghost pads ourself + As we can have effects with several pads and the default ghosting + doesn't allow that. + This way we also filter the pads to ghost to match our track type. + +2018-11-23 11:20:00 -0300 Thibault Saunier + + * ges/ges-effect-asset.c: + effect: Consider the "Filter" classification to determine effect media type + +2018-11-12 12:47:02 +0200 Jordan Petridis + + * .gitlab-ci.yml: + Add Gitlab CI configuration + This commit adds a .gitlab-ci.yml file, which uses a feature + to fetch the config from a centralized repository. The intent is + to have all the gstreamer modules use the same configuration. + The configuration is currently hosted at the gst-ci repository + under the gitlab/ci_template.yml path. + Part of https://gitlab.freedesktop.org/gstreamer/gstreamer-project/issues/29 + +2018-10-09 00:45:29 +0200 Alexandru Băluț + + * ges/ges-clip.c: + * ges/ges-container.c: + * tests/check/python/common.py: + * tests/check/python/test_clip.py: + * tests/check/python/test_group.py: + clip: Emit additional signals after child-removed + When removing an effect from a clip, first the notify::priority signals + were being emitted for the remaining effects which changed priority, and only + at the end the child-removed signal. Now the child-removed signal is emitted + first. + +2018-11-05 13:57:25 +0100 Víctor Manuel Jáquez Leal + + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + timeline-element: Fix compilation errors + There were some code errors introduced in commit 6b738b7a + +2018-11-04 20:47:01 +1100 Matthew Waters + + * meson.build: + * plugins/nle/meson.build: + nle: install pkg-config file for plugin + +2018-11-05 11:00:58 +0100 Corentin Noël + + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + timeline-element: Align virtual methods and invokers prototypes + +2018-11-05 05:51:47 +0000 Matthew Waters + + * .gitmodules: + * gst-editing-services.doap: + Update git location to gitlab + +2018-11-02 14:32:04 -0300 Thibault Saunier + + * ges/ges-pipeline.c: + * ges/ges-timeline.c: + * ges/ges-track.c: + ges: Check the thread from which our API is used + And add some missing API guards + +2018-11-02 09:30:28 -0300 Thibault Saunier + + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + ges: Mark GValue in child property setters as const + We can't change the vmethod at this point so just cast. This makes + the API more explicit so it is better in all cases. + +2018-10-31 10:38:59 -0300 Thibault Saunier + + * ges/Makefile.am: + * ges/ges.h: + * ges/meson.build: + Keep GESSmartVideoMixer out of the Gir and add geseffectasset.h to ges.h + Fixing gstreamer-sys rust bindings. + +2018-10-31 10:06:08 -0300 Thibault Saunier + + * ges/Makefile.am: + * ges/meson.build: + Fix the `package` name in the gir to match the `.pc` filename + +2018-10-28 15:55:23 +0000 Thibault Saunier + + * ges/ges-smart-video-mixer.c: + * ges/ges-video-transition.c: + * ges/gstframepositioner.c: + video-transition: Port to the new 'operator' API in compositor + Now subclassing a ghostpad with an alpha property so that + we can multiply the alpha of the frame positioning meta + and the alpha of that pad, setting it on the compositor pad. + https://bugzilla.gnome.org/show_bug.cgi?id=797169 + +2018-10-28 15:33:31 +0000 Thibault Saunier + + * ges/ges-smart-video-mixer.c: + * ges/ges-video-transition.c: + Revert "video-transition: Make use of the new `compositor::crossfade-ratio` property" + This reverts commit 57be9b67998bf5fef81a61c645b167c3857ed35b. + +2018-10-28 13:29:43 +0000 Thibault Saunier + + * bindings/python/gi/__init__.py: + python: Remove __init__.py + It is not needed with latest python + +2018-09-30 17:44:08 -0300 Thibault Saunier + + * ges/ges-source.c: + * plugins/nle/nlecomposition.c: + nlecomposition: Add a function that prints stacks as debug info + +2018-10-28 11:05:38 +0000 Philippe Normand + + * bindings/python/gi/overrides/__init__.py: + python: Remove debug print + +2018-10-22 08:13:07 +0100 Sebastian Dröge + + * ges/ges-base-xml-formatter.c: + * ges/ges-xml-formatter.c: + ges: Fix compilation with latest GLib + g_clear_pointer() is now preserving the type of its arguments for the + free function. + ges-xml-formatter.c: In function ‘_dispose’: + ges-xml-formatter.c:1635:7: error: function called through a non-compatible type [-Werror] + (GDestroyNotify) g_hash_table_unref); + /usr/include/glib-2.0/glib/gmem.h:121:8: note: in definition of macro ‘g_clear_pointer’ + (destroy) (_ptr); \ + ^~~~~~~ + https://bugzilla.gnome.org/show_bug.cgi?id=797310 + +2018-10-08 23:25:21 +0100 Tim-Philipp Müller + + * meson.build: + meson: use 'python' module to find python instead of deprecated 'python3' one + https://github.com/mesonbuild/meson/pull/4169 + +2018-09-05 22:55:02 -0300 Thibault Saunier + + * ges/ges-asset.c: + * ges/ges-audio-source.c: + * ges/ges-audio-test-source.c: + * ges/ges-audio-track.c: + * ges/ges-audio-transition.c: + * ges/ges-audio-uri-source.c: + * ges/ges-base-effect-clip.c: + * ges/ges-base-effect.c: + * ges/ges-base-transition-clip.c: + * ges/ges-base-xml-formatter.c: + * ges/ges-clip-asset.c: + * ges/ges-clip.c: + * ges/ges-command-line-formatter.c: + * ges/ges-container.c: + * ges/ges-effect-asset.c: + * ges/ges-effect-clip.c: + * ges/ges-effect.c: + * ges/ges-formatter.c: + * ges/ges-group.c: + * ges/ges-image-source.c: + * ges/ges-layer.c: + * ges/ges-multi-file-source.c: + * ges/ges-operation-clip.c: + * ges/ges-operation.c: + * ges/ges-overlay-clip.c: + * ges/ges-pipeline.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-project.c: + * ges/ges-source-clip.c: + * ges/ges-source.c: + * ges/ges-test-clip.c: + * ges/ges-text-overlay-clip.c: + * ges/ges-text-overlay.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + * ges/ges-title-clip.c: + * ges/ges-title-source.c: + * ges/ges-track-element-asset.c: + * ges/ges-track-element.c: + * ges/ges-track.c: + * ges/ges-transition-clip.c: + * ges/ges-transition.c: + * ges/ges-uri-asset.c: + * ges/ges-uri-clip.c: + * ges/ges-video-source.c: + * ges/ges-video-test-source.c: + * ges/ges-video-track.c: + * ges/ges-video-transition.c: + * ges/ges-video-uri-source.c: + * ges/ges-xml-formatter.c: + * plugins/nle/nlecomposition.c: + * plugins/nle/nlesource.c: + * tools/ges-launcher.c: + Update for g_type_class_add_private() deprecation in recent GLib + +2018-09-05 21:49:09 -0300 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-container.c: + * tests/check/python/test_clip.py: + clip: Resync priorities when removing an effect + When removing a top effect in the list of top effects, other + effects priorities need to take that into account to avoid + holes in the indices. + +2018-08-03 14:02:58 -0400 Thibault Saunier + + * ges/ges.c: + ges: Check that nle is avalaible when initializing + +2018-09-01 12:17:08 +0530 Nirbheek Chauhan + + * meson.build: + * meson_options.txt: + * tests/meson.build: + meson: Add a feature option for tests + This autodetection is needed on iOS inside Cerbero where + gstreamer-check-1.0 is not available. + +2018-08-31 14:44:58 +0530 Nirbheek Chauhan + + * ges/meson.build: + * meson.build: + meson: Maintain macOS ABI through dylib versioning + Requires Meson 0.48, but the feature will be ignored on older versions + so it's safe to add it without bumping the requirement. + Documentation: + https://github.com/mesonbuild/meson/blob/master/docs/markdown/Reference-manual.md#shared_library + +2018-08-15 19:14:30 +0530 Nirbheek Chauhan + + * tests/check/meson.build: + meson: There is no gstreamer-plugins-good-1.0.pc + There is no installed version of that, only an uninstalled version. + +2018-07-29 16:20:50 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-xml-formatter.c: + formatter: Fix mixup in variable check + +2018-07-28 14:29:11 -0400 Thibault Saunier + + * ges/ges-xml-formatter.c: + xml-formatter: Bump format version + Previous commit makes the format not forward compat. + +2018-07-28 12:16:36 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-container.c: + * ges/ges-internal.h: + * ges/ges-transition-clip.c: + * ges/ges-xml-formatter.c: + * tests/check/python/test_clip.py: + formatter: Serialize Transition border and invert properties + Marking them as children properties and properly allow serializing + clips children properties. + This doesn't handle several TrackElement of a same type with + different property values but this require more worked already + marked as fixme to allow specifying full path of elements in the + children properties API. + See https://gitlab.gnome.org/GNOME/pitivi/issues/1687 + +2018-07-27 22:11:33 -0400 Thibault Saunier + + * ges/ges-project.c: + project: Compute relocation URIs in missing-uri signal + Until know we were doing it outside of the signal and subclasses didn't + have a chance to know that some assets was relocated. + This is required so that Pitivi can handle proxy delation and relocated + assets. + Required for https://gitlab.gnome.org/GNOME/pitivi/issues/2203 + +2018-07-25 17:20:02 +0530 Nirbheek Chauhan + + * docs/libs/meson.build: + * meson.build: + * meson_options.txt: + meson: Convert common options to feature options + The remaining automagic options are in tests and examples. + https://bugzilla.gnome.org/show_bug.cgi?id=795107 + +2018-07-23 00:07:07 -0400 Thibault Saunier + + * tests/validate/geslaunch.py: + validate: Let testsuites define scenarios path + The code was not taking into account the fact that testsuite could be + located in a different folder that the default one. + Now the testsuite is responsible for providing a path if it wants + to set extra scenarios or the user can set one by hand. + +2018-07-14 09:00:51 -0400 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + pitivi-formatter: Do not g_file_test on a NULL pointer + +2018-07-18 12:38:04 -0400 Thibault Saunier + + * plugins/nle/nlecomposition.c: + nlecomposition: Rename outside_segment to seek_segment + This segment is representing the last seek received + inside the composition. Or a simply initialized segment + if need seek occurred. + +2018-07-18 12:52:59 -0400 Thibault Saunier + + * tests/check/nle/tempochange.c: + tests: Minor assertion enahncements + +2018-07-19 10:55:31 -0400 Thibault Saunier + + * ges/ges-project.c: + project: Do not emit 'error-loading-asset' when we are trying to update the ID + +2018-07-19 22:06:54 -0400 Thibault Saunier + + * tests/validate/geslaunch.py: + validate: Handle scenario only based tests + Meaning tests that do not need project at all + +2018-07-12 13:53:44 -0400 Thibault Saunier + + * ges/ges-track.c: + track: Set restriction caps when update_restriction before caps being set + And stop leaking intermediary restriction caps. + https://bugzilla.gnome.org/show_bug.cgi?id=796802 + +2018-07-08 16:09:46 -0400 Thibault Saunier + + * bindings/python/gi/overrides/GES.py: + python:overrides: Remove spurious print + +2018-07-08 10:36:36 -0400 Thibault Saunier + + * bindings/python/gi/overrides/GES.py: + * bindings/python/meson.build: + * meson.build: + * meson_options.txt: + * tests/check/python/overrides_hack.py: + * tests/check/python/test_clip.py: + * tests/check/python/test_group.py: + * tests/check/python/test_timeline.py: + python: Fix GES.Timelineset_child_property + Implementing it in the overrides as PyGObject won't be able to properly + convert python values to GValues in some cases. Using + g_object_set_property works as some logic is implemented inside + PyGObject for that particular case. This is a "regression" due + to https://bugzilla.gnome.org/review?bug=769789&attachment=348766 were + we end up with an OverflowError while setting G_TYPE_UINT children + properties. + +2018-04-01 16:22:16 +0200 Bastian Köcher + + * ges/meson.build: + meson: fix install dir for configure files + Nixos configures a custom includedir. + https://bugzilla.gnome.org/show_bug.cgi?id=794856 + +2018-07-01 16:22:24 -0400 Thibault Saunier + + * ges/Makefile.am: + * meson.build: + Set GLib log domain to GES + +2018-07-01 12:21:54 -0400 Thibault Saunier + + * ges/ges-group.c: + group: Handle clips that get readded to a layer and inside a group + +2018-06-26 16:21:22 +0200 Mathieu Duponchelle + + * ges/ges-asset.c: + asset: documentation fix + +2018-06-15 16:49:55 -0400 Thibault Saunier + + * configure.ac: + * tests/check/Makefile.am: + * tests/check/meson.build: + tests: Use gst-validate-launcher to run python tests + +2018-06-14 17:07:10 -0400 Thibault Saunier + + * meson.build: + * meson_options.txt: + meson: Rename the gtkdoc option to gtk_doc + This is what other modules use + +2018-05-20 23:48:39 +0100 Tim-Philipp Müller + + * examples/c/Makefile.am: + examples: override -Werror + Don't want to error out on deprecated API warnings and such. + Just drop -Werror for the examples until someone updates them + to recent gtk3 API. Maybe showing the warnings will motivate + someone. + https://bugzilla.gnome.org/show_bug.cgi?id=796243 + +2018-05-20 23:47:14 +0100 Tim-Philipp Müller + + * configure.ac: + examples: always build against gtk3 + Drop gtk2 option. + https://bugzilla.gnome.org/show_bug.cgi?id=796243 + +2018-05-20 23:46:42 +0100 Tim-Philipp Müller + + * examples/c/ges-ui.c: + examples: ges-ui: fix some gtk2-ism + Still lots of deprecated API to update. + https://bugzilla.gnome.org/show_bug.cgi?id=796243 + +2018-05-13 21:12:35 -0400 Thibault Saunier + + * ges/ges-clip.c: + * tests/check/python/test_timeline.py: + clip: Make sure to never snap when splitting clips + It makes no sense to snap in that context. + https://gitlab.gnome.org/GNOME/pitivi/issues/2193 + +2018-05-13 16:37:08 -0400 Thibault Saunier + + * tests/validate/geslaunch.py: + validate: Run IQA tests when possible + Meaning that a reference file has to be present on disk with a + `.expected_result` extension. + +2018-04-20 17:56:15 -0300 Thibault Saunier + + * tests/validate/geslaunch.py: + validate: Stop forcing I420 in profiles restriction caps + This was a workaround for encoders bad behavior in the reconfigure case. + https://bugzilla.gnome.org/show_bug.cgi?id=795420 + +2018-05-05 19:34:14 +0530 Nirbheek Chauhan + + * meson.build: + * meson_options.txt: + meson: Update option names to omit disable_ and with- prefixes + Also yield common options to the outer project (gst-build in our case) + so that they don't have to be set manually. + +2018-04-25 11:01:01 +0100 Tim-Philipp Müller + + * meson.build: + meson: use -Wl,-Bsymbolic-functions where supported + Just like the autotools build. + +2018-04-20 18:45:19 -0300 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Properly error out when linking fails + In the rendering case we were getting random issues and often the + pipeline was not be able to preroll as some pad were not linked inside + encodebin. + https://bugzilla.gnome.org/show_bug.cgi?id=795422 + +2018-04-20 17:54:12 -0300 Thibault Saunier + + * ges/ges-track-element.c: + track-element: Fix the way we look for properties on simple elements + Refactor so that the same code is used to add children properties from + bin children and when inspecting a single element. + +2018-04-20 17:36:55 -0300 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Update caps only when rendering as comment suggests + We used to update caps for any more because of missing brackets. + +2018-04-20 17:35:06 -0300 Thibault Saunier + + * ges/ges-effect.c: + effect: Allow setting properties on any element specified by the user + Those are the elements he cares about and we should expose their APIs + as is, event if they are not classified as effects. For example if + the user want to use a capsfilter as effect, he should be able to set + its caps. + +2018-04-20 17:34:17 -0300 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + xml-formatter: Print error if an effect can't be set when deserializing + +2018-04-16 10:53:57 +0100 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From 3fa2c9e to ed78bee + +2018-03-31 13:39:54 -0300 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-layer.c: + * ges/ges-timeline.c: + * tests/check/ges/layer.c: + Deprecate ges_layer_set_priority + Keep old behaviour but deprecate the method and property as + ges_timeline_move_layer should be used instead. + +2015-12-12 11:29:50 +0000 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + timeline: Add a method to move layers around + summary_: + This way the timeline can handle all priorities for the user + making the API simpler to use. + API: + + ges_timeline_move_layer + reviewers_: Mathieu_Du + Differential Revision: https://phabricator.freedesktop.org/D232 + +2018-03-31 11:24:23 -0300 Thibault Saunier + + * ges/ges-timeline-element.h: + timeline-element: Fix ABI breakage + New fields in structure should be added in place of the padding + +2018-03-31 10:38:19 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + docs: Move timeline related doc to the timeline section + It wrongly was in the layers + +2018-03-30 18:17:13 -0300 Thibault Saunier + + * ges/ges-command-line-formatter.c: + * ges/ges-structure-parser.c: + * ges/ges-structured-interface.c: + * ges/parse.l: + ges-launcher: Add support for titles + +2018-03-30 17:41:49 -0300 Thibault Saunier + + * ges/ges-command-line-formatter.c: + command-line-formatter: Refactor to generate the documentation automatically + https://bugzilla.gnome.org/show_bug.cgi?id=794837 + +2018-03-26 12:13:25 -0300 Thibault Saunier + + * ges/ges-effect.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + ges: Update the media-duration-factor each time a child property is set + Otherwise the changes won't be reflected in the NLE backend. + This makes speed changes working inside ges-launch-1.0 + ges-launch-1.0 +clip /path/to/file i=10 d=5 +effect videorate set-rate 5.0 + https://bugzilla.gnome.org/show_bug.cgi?id=794699 + +2018-03-26 18:56:03 +0530 Suhas Nayak + + * ges/ges-effect.c: + ges: Register videorate::rate as a rate changing property + https://bugzilla.gnome.org/show_bug.cgi?id=794699 + +2018-03-20 10:24:35 +0000 Tim-Philipp Müller + + * NEWS: + * RELEASE: + * configure.ac: + * meson.build: + Back to development + +=== release 1.14.0 === + +2018-03-19 20:28:10 +0000 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + * meson.build: + Release 1.14.0 + +2018-03-19 08:57:47 -0300 Thibault Saunier + + * ges/ges-video-source.c: + doc: Remove documentation about GESVideoSource::zorder as it doesn't exist + The zorder is controled through the GESLayer priority API, not directly + on the sources. + +2018-03-18 11:03:00 -0300 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-internal.h: + * ges/ges-timeline.c: + * tests/check/python/common.py: + * tests/check/python/test_timeline.py: + clip: Make sure to create transition after a clip is splitted + In the (now tested) scenario where we have a transition on the right + side of a clip we are splitting, auto transitions can't be created + because we resize the clip after adding the new one, meaning that + there are 3 elements in the "transition zone", we need to force + auto transition creation after the splitting. + Fixes https://gitlab.gnome.org/GNOME/pitivi/issues/2142 + +2018-03-14 20:59:04 -0300 Thibault Saunier + + * ges/ges-group.c: + group: Handle clips being removed from their layers + +=== release 1.13.91 === + +2018-03-13 19:29:44 +0000 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + * meson.build: + Release 1.13.91 + +2018-03-13 14:14:57 +0000 Tim-Philipp Müller + + * ges/ges-asset.h: + * ges/ges-audio-source.h: + * ges/ges-audio-test-source.h: + * ges/ges-audio-track.h: + * ges/ges-audio-transition.h: + * ges/ges-audio-uri-source.h: + * ges/ges-base-effect-clip.h: + * ges/ges-base-effect.h: + * ges/ges-base-transition-clip.h: + * ges/ges-base-xml-formatter.h: + * ges/ges-clip-asset.h: + * ges/ges-clip.h: + * ges/ges-command-line-formatter.h: + * ges/ges-container.h: + * ges/ges-effect-asset.h: + * ges/ges-effect-clip.h: + * ges/ges-effect.h: + * ges/ges-enums.h: + * ges/ges-extractable.h: + * ges/ges-formatter.h: + * ges/ges-group.h: + * ges/ges-image-source.h: + * ges/ges-internal.h: + * ges/ges-layer.h: + * ges/ges-meta-container.h: + * ges/ges-multi-file-source.h: + * ges/ges-operation-clip.h: + * ges/ges-operation.h: + * ges/ges-overlay-clip.h: + * ges/ges-pipeline.h: + * ges/ges-pitivi-formatter.h: + * ges/ges-prelude.h: + * ges/ges-project.h: + * ges/ges-screenshot.h: + * ges/ges-smart-adder.h: + * ges/ges-source-clip.h: + * ges/ges-source.h: + * ges/ges-test-clip.h: + * ges/ges-text-overlay-clip.h: + * ges/ges-text-overlay.h: + * ges/ges-timeline-element.h: + * ges/ges-timeline.h: + * ges/ges-title-clip.h: + * ges/ges-title-source.h: + * ges/ges-track-element-asset.h: + * ges/ges-track-element.h: + * ges/ges-track.h: + * ges/ges-transition-clip.h: + * ges/ges-transition.h: + * ges/ges-uri-asset.h: + * ges/ges-uri-clip.h: + * ges/ges-utils.h: + * ges/ges-video-source.h: + * ges/ges-video-test-source.h: + * ges/ges-video-track.h: + * ges/ges-video-transition.h: + * ges/ges-video-uri-source.h: + * ges/ges-xml-formatter.h: + * ges/ges.h: + GST_GES_API -> GES_API + +2018-03-13 13:45:24 +0000 Tim-Philipp Müller + + * docs/libs/meson.build: + * ges/Makefile.am: + * ges/ges-asset.h: + * ges/ges-audio-source.h: + * ges/ges-audio-test-source.h: + * ges/ges-audio-track.h: + * ges/ges-audio-transition.h: + * ges/ges-audio-uri-source.h: + * ges/ges-base-effect-clip.h: + * ges/ges-base-effect.h: + * ges/ges-base-transition-clip.h: + * ges/ges-base-xml-formatter.h: + * ges/ges-clip-asset.h: + * ges/ges-clip.h: + * ges/ges-command-line-formatter.h: + * ges/ges-container.h: + * ges/ges-effect-asset.h: + * ges/ges-effect-clip.h: + * ges/ges-effect.h: + * ges/ges-enums.h: + * ges/ges-extractable.h: + * ges/ges-formatter.h: + * ges/ges-group.h: + * ges/ges-image-source.h: + * ges/ges-internal.h: + * ges/ges-layer.h: + * ges/ges-meta-container.h: + * ges/ges-multi-file-source.h: + * ges/ges-operation-clip.h: + * ges/ges-operation.h: + * ges/ges-overlay-clip.h: + * ges/ges-pipeline.h: + * ges/ges-pitivi-formatter.h: + * ges/ges-prelude.h: + * ges/ges-project.h: + * ges/ges-screenshot.h: + * ges/ges-smart-adder.h: + * ges/ges-source-clip.h: + * ges/ges-source.h: + * ges/ges-test-clip.h: + * ges/ges-text-overlay-clip.h: + * ges/ges-text-overlay.h: + * ges/ges-timeline-element.h: + * ges/ges-timeline.h: + * ges/ges-title-clip.h: + * ges/ges-title-source.h: + * ges/ges-track-element-asset.h: + * ges/ges-track-element.h: + * ges/ges-track.h: + * ges/ges-transition-clip.h: + * ges/ges-transition.h: + * ges/ges-types.h: + * ges/ges-uri-asset.h: + * ges/ges-uri-clip.h: + * ges/ges-utils.h: + * ges/ges-video-source.h: + * ges/ges-video-test-source.h: + * ges/ges-video-track.h: + * ges/ges-video-transition.h: + * ges/ges-video-uri-source.h: + * ges/ges-xml-formatter.h: + * ges/ges.h: + * ges/meson.build: + ges: GST_EXPORT -> GST_GES_API + We need different export decorators for the different libs. + For now no actual change though, just rename before the release, + and add prelude headers to define the new decorator to GST_EXPORT. + +2018-03-11 11:13:05 -0300 Thibault Saunier + + * ges/ges-clip.c: + clip: Snapping should happen with one and only one TrackElement + This was leading to clip with TrackElements that were not at the + same position in their container, and weird bugs, see: + https://gitlab.gnome.org/GNOME/pitivi/issues/2133 + +=== release 1.13.90 === + +2018-03-03 23:09:36 +0000 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + * meson.build: + Release 1.13.90 + +2018-02-26 04:01:33 +0530 Harish Fulara + + * ges/ges-internal.h: + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + Added paste functionality to GESTimeline class + https://bugzilla.gnome.org/show_bug.cgi?id=793820 + +2018-03-01 18:56:05 +0100 Mathieu Duponchelle + + * meson.build: + meson: enable more warnings + +2018-02-27 10:00:32 -0300 Thibault Saunier + + * tests/check/ges/layer.c: + test: Plug minor leaks + +2018-02-27 15:26:29 +0530 Harish Fulara + + * ges/ges-layer.c: + ges: Fix ges_layer_get_clips_in_interval(start, end) refcount handling + The documentation states that it returns a (transfer full) list + of GESClip but it was returning a (transfer container) list. Make + sure to actually make it (transfer full). + https://bugzilla.gnome.org/show_bug.cgi?id=793874 + +2018-01-29 17:46:06 -0300 Thibault Saunier + + * ges/meson.build: + meson: Explicitely include GObject-2.0 in the gir + +2018-02-21 19:42:19 +0000 Tim-Philipp Müller + + * meson.build: + meson: simplify GST_DISABLE_GST_DEBUG check some more + +2018-02-21 19:20:56 +0000 Tim-Philipp Müller + + * meson.build: + meson: don't use add_global_arguments() + .. and tighten check for disabled gst debugging sytem. + add_global_arguments() can't be used in subprojects. It's + entirely possible that ges is a subproject but gstreamer + is picked up from an installed location, so we should + really use add_project_arguments() in both cases. + +2018-02-15 19:44:30 +0000 Tim-Philipp Müller + + * configure.ac: + * meson.build: + Back to development + +=== release 1.13.1 === + +2018-02-15 17:20:22 +0000 Tim-Philipp Müller + + * NEWS: + * configure.ac: + * gst-editing-services.doap: + * meson.build: + Release 1.13.1 + +2018-02-08 19:16:26 +0000 Tim-Philipp Müller + + * meson.build: + meson: make version numbers ints and fix int/string comparison + WARNING: Trying to compare values of different types (str, int). + The result of this is undefined and will become a hard error + in a future Meson release. + +2018-02-04 12:26:48 +0100 Tim-Philipp Müller + + * configure.ac: + autotools: use -fno-strict-aliasing where supported + https://bugzilla.gnome.org/show_bug.cgi?id=769183 + +2018-01-30 20:35:33 +0000 Tim-Philipp Müller + + * meson.build: + meson: use -fno-strict-aliasing where supported + https://bugzilla.gnome.org/show_bug.cgi?id=769183 + +2018-01-11 10:57:30 +0100 Edward Hervey + + * ges/ges-asset.c: + * ges/ges-timeline-element.c: + ges: Fix sizeof() usage + The entries of the array are "gchar *" and not "gchar **" + CID #1427091 + CID #1427120 + +2017-12-20 14:28:33 +0100 Edward Hervey + + * tests/check/ges/asset.c: + check: Fix minor leak in test + +2017-12-19 23:28:53 +0100 Mathieu Duponchelle + + * ges/ges-smart-adder.c: + * ges/ges-smart-adder.h: + ges-smart-adder: use capsfilter instead of GstAudioMixer:caps + The property has been removed, and using a capsfilter instead + is the appropriate solution. + +2017-12-14 14:53:41 +1100 Matthew Waters + + * common: + Automatic update of common submodule + From e8c7a71 to 3fa2c9e + +2017-11-27 11:49:04 +0100 Edward Hervey + + * ges/ges-asset.c: + * ges/ges-base-xml-formatter.c: + * ges/ges-effect-asset.c: + * ges/ges-effect.c: + * ges/ges-timeline-element.c: + * ges/ges-uri-asset.c: + * ges/ges-xml-formatter.c: + ges: Fix a bunch of leaks + There are definitely more left, but don't have time for more debugging + +2017-11-27 20:18:55 +1100 Matthew Waters + + * common: + Automatic update of common submodule + From 3f4aa96 to e8c7a71 + +2017-11-26 13:31:02 +0000 Tim-Philipp Müller + + * configure.ac: + configure: remove c++ compiler bits that are unused + +2017-11-26 13:29:33 +0000 Tim-Philipp Müller + + * Makefile.am: + * ges/meson.build: + * meson.build: + * win32/MANIFEST: + * win32/common/libges.def: + win32: remove .def file with exports + They're no longer needed, symbol exporting is now explicit + via GST_EXPORT in all cases (autotools, meson, incl. MSVC). + +2017-11-26 13:25:06 +0000 Tim-Philipp Müller + + * configure.ac: + autotools: stop controlling symbol visibility with -export-symbols-regex + Instead, use -fvisibility=hidden and explicit exports via GST_EXPORT. + This should result in consistent behaviour for the autotools and + Meson builds. + +2017-11-26 13:26:13 +0000 Tim-Philipp Müller + + * .gitignore: + .gitignore: ignore test registry + +2017-11-25 15:56:36 -0300 Thibault Saunier + + * ges/ges-uri-clip.c: + uri-clip: Copy previous track elements bindings when setting a new asset + Fixes https://phabricator.freedesktop.org/T7862 + +2017-11-23 15:49:48 +0100 Edward Hervey + + * tests/check/Makefile.am: + check: Actually define a registry to use for tests + Otherwise every single run of every single test would recreate + a registry + +2017-11-07 12:04:03 +0530 Ashish Kumar + + * ges/ges-asset.c: + * ges/ges-layer.c: + GESAsset, GESLayer: add some function guards + https://bugzilla.gnome.org/show_bug.cgi?id=789521 + +2017-11-07 11:26:58 -0300 Thibault Saunier + + * ges/ges-auto-transition.c: + auto-transition: Fix debug printf format + +2017-11-07 10:15:58 -0300 Thibault Saunier + + * ges/ges-auto-transition.c: + * ges/ges-timeline.c: + timeline: Do not snap object within the moving context + Reviewed-by: Alex Băluț <> + Differential Revision: https://phabricator.freedesktop.org/D1873 + +2017-10-31 12:05:08 -0300 Thibault Saunier + + * ges/gstframepositioner.c: + * ges/gstframepositioner.h: + ges: Sync 'par' to track restriction caps in the frame positionner + Allowing GES users to have control over how compositing is done + +2017-09-20 12:59:40 +0300 Sebastian Dröge + + * ges/ges-source.c: + ges-source: Fix caps memory leak and compiler warnings when compiling without debug logging + +2017-09-07 12:08:40 -0400 Nicolas Dufresne + + * plugins/nle/nlecomposition.c: + nlecomposition: Always execute seeks + We have an optiominisation to avoid double seeks when a seek is passed + the end of the current stack. The problem, is that we no longer flush + the pipeline when this code is reached. This patch comments out this + optimization adding a FIXME. As mention, flushing the stack instead of + seeking would work, but does not seem trivial considering all the + mechanic inplace to forward or not the events. + https://bugzilla.gnome.org/show_bug.cgi?id=787405 + +2017-09-07 12:08:40 -0400 Nicolas Dufresne + + * plugins/nle/nlecomposition.c: + nlecomposition: Also start task on allocation query + The allocation query may block on the sink when in pause. As a side effect, we + may never get a buffer now that tee does forward the allocation query. + This would often lead in a pipeline stall. + https://bugzilla.gnome.org/show_bug.cgi?id=787405 + +2017-09-07 12:08:40 -0400 Nicolas Dufresne + + * tests/validate/scenarios/Makefile.am: + make: Fix validate scenario install directory + +2017-09-07 12:07:03 -0400 Nicolas Dufresne + + * tests/meson.build: + * tests/validate/meson.build: + * tests/validate/scenarios/meson.build: + meson: Install validate helpers and scenarios + This fixes the usage of gst-validate-launcher ges with an installed + version of GES. + +2017-08-17 07:28:46 +0000 Stefan Popa + + * ges/ges-track-element.c: + track_element: Always emit "control-binding-removed" signal. + When setting a new control binding on a track element, the old control + binding (if any) is going to be removed. Make sure the + "control-binding-removed" signal is emitted in this case. + Fixes https://phabricator.freedesktop.org/T7340#95666 + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1842 + +2017-08-29 22:23:57 -0300 Thibault Saunier + + * ges/ges-timeline.c: + * tests/check/ges/backgroundsource.c: + Revert "timeline: Return FALSE when commiting an empty timeline" + This commit means that we do not get ASYNC_DONE anymore when commiting + an empty timeline, which means that we need to special case that. + This actually broke some code and does not bring in much. + Fixes https://phabricator.freedesktop.org/T7802 + Fixes https://phabricator.freedesktop.org/T7797 + This reverts commit e570d1e08009992a0dd6a24bb4cda4427b2b460f. + Thanks @stefanzzz for investigating! + +2017-08-22 14:23:45 +0000 Stefan Popa + + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-xml-formatter.c: + xml-formatter: Save encoder and muxer advanced settings + Added support for saving/loading encoder and muxer advanced settings. + Differential Revision: https://phabricator.freedesktop.org/D1837 + +2017-08-19 11:42:57 -0300 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Allow up to 2 seconds queueing in the playsink video queue + In playsink the default video queue max size is 3 buffers, which is + sometimes not enough for our use case. + Allow up to 2 seconds of buffered data, giving us more time to do + the transition between clips, and thus avoiding dropping frames in + the sink when bringing up new clip takes too much time. + Differential Revision: https://phabricator.freedesktop.org/D1854 + +2017-08-18 23:39:38 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Add from first element with wanted start to the move context + We need to iterate over the previous element from trackelement_iter + to find the first element that is at the moving point. Several + elements can have the same start as the one initiating the move, + and we need to take all of them into account. + Fixes https://phabricator.freedesktop.org/T7819 + +2017-08-18 23:18:10 -0300 Thibault Saunier + + * ges/ges-validate.c: + validate: Add an action type to ungroup containers + +2017-08-17 12:26:24 +0100 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From 48a5d85 to 3f4aa96 + +2017-08-12 10:04:03 +0100 Tim-Philipp Müller + + * ges/ges-smart-video-mixer.h: + * win32/common/libges.def: + Hide ges_smart_mixer_* API + The header file isn't installed anyway. + +2017-08-12 10:01:51 +0100 Tim-Philipp Müller + + * configure.ac: + configure: bump gst-validate requirement to 1.12.1 + For gst_validate_scenario_get_pipeline(). + +2017-08-11 22:27:48 +0100 Tim-Philipp Müller + + * meson.build: + meson: hide symbols by default unless explicitly exported + +2017-08-11 21:41:52 +0100 Tim-Philipp Müller + + * ges/ges-internal.h: + * tests/check/ges/asset.c: + tests: don't use private debug category in asset test + That will lead to undefined symbol errors once it no + longer gets exported. + +2017-08-11 21:40:14 +0100 Tim-Philipp Müller + + * ges/ges-smart-adder.h: + * ges/ges-smart-video-mixer.h: + * ges/ges-timeline-element.h: + * ges/ges-video-track.h: + ges: sprinkle more GST_EXPORT + +2017-08-10 15:05:09 -0400 Thibault Saunier + + * ges/ges-smart-video-mixer.c: + * plugins/nle/nlecomposition.c: + smartmixer: Give a unique name to each compositor instances + +2017-08-10 21:38:04 -0400 Thibault Saunier + + * tests/validate/geslaunch.py: + validate: Check subprocess return code in rendering tests + +2017-08-10 15:18:22 +0100 Tim-Philipp Müller + + * tests/validate/geslaunch.py: + validate: fix error message + +2017-08-10 13:46:03 +0100 Tim-Philipp Müller + + * docs/libs/meson.build: + * docs/meson.build: + meson: fix a few warnings + +2017-08-07 15:35:58 -0400 Thibault Saunier + + * ges/ges-asset.h: + * ges/ges-audio-source.h: + * ges/ges-audio-test-source.h: + * ges/ges-audio-track.h: + * ges/ges-audio-transition.h: + * ges/ges-audio-uri-source.h: + * ges/ges-base-effect-clip.h: + * ges/ges-base-effect.h: + * ges/ges-base-transition-clip.h: + * ges/ges-base-xml-formatter.h: + * ges/ges-clip-asset.h: + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-command-line-formatter.h: + * ges/ges-container.h: + * ges/ges-effect-asset.h: + * ges/ges-effect-clip.h: + * ges/ges-effect.h: + * ges/ges-enums.h: + * ges/ges-extractable.h: + * ges/ges-formatter.h: + * ges/ges-group.h: + * ges/ges-image-source.h: + * ges/ges-internal.h: + * ges/ges-layer.h: + * ges/ges-meta-container.h: + * ges/ges-multi-file-source.h: + * ges/ges-operation-clip.h: + * ges/ges-operation.h: + * ges/ges-overlay-clip.h: + * ges/ges-pipeline.h: + * ges/ges-pitivi-formatter.h: + * ges/ges-project.h: + * ges/ges-screenshot.h: + * ges/ges-smart-video-mixer.h: + * ges/ges-source-clip.h: + * ges/ges-source.h: + * ges/ges-test-clip.h: + * ges/ges-text-overlay-clip.h: + * ges/ges-text-overlay.h: + * ges/ges-timeline-element.h: + * ges/ges-timeline.h: + * ges/ges-title-clip.h: + * ges/ges-title-source.h: + * ges/ges-track-element-asset.h: + * ges/ges-track-element.h: + * ges/ges-track.h: + * ges/ges-transition-clip.h: + * ges/ges-transition.h: + * ges/ges-uri-asset.h: + * ges/ges-uri-clip.c: + * ges/ges-uri-clip.h: + * ges/ges-utils.h: + * ges/ges-video-source.h: + * ges/ges-video-test-source.h: + * ges/ges-video-track.h: + * ges/ges-video-transition.h: + * ges/ges-video-uri-source.h: + * ges/ges-xml-formatter.h: + * ges/ges.h: + * win32/common/libges.def: + Mark symbols explicitly for export with GST_EXPORT + With two exceptions: + * ges_clip_create_track_elements_func + * ges_uri_clip_set_uri + which were never declared in headers and should always have been static. + +2017-08-03 17:03:31 -0400 Thibault Saunier + + * ges/ges-video-transition.c: + transition: Fix usage of transition types != crossfade + We need to make sure the crossfade ratio is disabled in that case. + +2017-07-10 11:43:11 -0400 Thibault Saunier + + * ges/ges-smart-video-mixer.c: + * ges/ges-video-transition.c: + video-transition: Make use of the new `compositor::crossfade-ratio` property + To achieve a real transition about to handle several level of layers. + https://bugzilla.gnome.org/show_bug.cgi?id=784827 + +2017-07-31 14:52:20 -0400 Thibault Saunier + + * win32/common/libges.def: + win32: Update .def file + +2017-07-31 12:54:25 -0400 Thibault Saunier + + * ges/ges-asset.c: + * ges/ges-asset.h: + asset: Add a function to stop proxying an asset + And remove any reference as it beeing a proxy. + +2017-07-31 12:55:53 -0400 Thibault Saunier + + * ges/ges-asset.c: + asset: Clear loading error when reload is requested + +2017-07-27 21:15:34 -0400 Thibault Saunier + + * plugins/nle/nlecomposition.c: + nlecomposition: Rename segment_start to current_stack_start + It is still not exactly precise, but gives a much better understanding + of what it is. + +2016-01-12 17:05:48 +0000 Thibault Saunier + + * plugins/nle/nlecomposition.c: + nlecomposition: Properly update segment->start/stop on commit + Otherwise they will just be the ones from the previous seek event/ + stack setup and be meaningless. + Also document the priv->segment meaning. + Fixes https://phabricator.freedesktop.org/T7796 + +2017-07-27 15:57:31 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Remove trackelements initating move from the moving context + They are handled specially when moving the context and having them + part of the context can lead to weird behaviours. + Fixes https://phabricator.freedesktop.org/T7693 + +2017-07-21 16:41:26 -0400 Thibault Saunier + + * ges/ges-xml-formatter.c: + xml-formatter: Serialize encoding profiles in reverse order + So they are reloaded in the right order. + +2017-07-24 10:32:47 -0400 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Make sure tracks are unlinked on NULL->NULL state changes + +2017-07-24 10:34:48 -0400 Thibault Saunier + + * ges/ges-pipeline.c: + * ges/ges-source.c: + Enhance some pad linking issue debug logging. + +2017-07-13 16:38:04 -0400 Thibault Saunier + + * tests/validate/geslaunch.py: + validate: Fix test names now that the launcher handles adding manager name + +2017-07-11 11:40:55 -0400 Thibault Saunier + + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/group.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/tempochange.c: + * tests/check/ges/titles.c: + * tests/check/ges/track.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + tests: Initialize GES only once in the main process + Fixing calling deinit in a process where init was not called + when libcheck is forking. + +2017-07-10 21:42:21 -0400 Nicolas Dufresne + + * ges/ges.c: + ges: Ref the GES class to avoid later deadlock + This ensure that that all class are initialized from the main thread, + avoid class initialization in random thread, which may cause deadlocks. + https://bugzilla.gnome.org/show_bug.cgi?id=784769 + +2017-07-07 12:27:16 +0100 Tim-Philipp Müller + + * meson.build: + meson: find python3 via python3 module + https://bugzilla.gnome.org/show_bug.cgi?id=783198 + +2017-07-03 18:33:39 +0300 Stefan Popa + + * ges/ges-timeline-element.c: + "deep-notify" signal gets emitted only from the main thread + https://bugzilla.gnome.org/show_bug.cgi?id=784414 + +2017-06-30 16:18:17 -0400 Thibault Saunier + + * ges/ges-video-transition.c: + video-transition: Make sure crossfade output never contains alpha + Otherwise it would get mixed with lower layers, which is totally + unexpected. + Fixes T7773 + Differential Revision: https://phabricator.freedesktop.org/D1764 + +2017-06-23 16:18:36 -0400 Thibault Saunier + + * meson.build: + meson: Allow using glib as a subproject + +2017-06-23 16:04:01 -0400 Thibault Saunier + + * ges/ges-asset.c: + * ges/ges-timeline-element.c: + ges: Handle g_object_newv deprecation in latest GLib + +2017-06-09 20:15:26 -0400 Nicolas Dufresne + + * Makefile.am: + Don't dist config.meson.h as it no longer exist + +2017-06-09 21:37:48 +0100 Tim-Philipp Müller + + * config.h.meson: + * meson.build: + meson: remove config.h.meson + +2017-06-07 12:08:00 -0400 Thibault Saunier + + * tests/check/meson.build: + meson: Do not use path separator in test names + Avoiding warnings like: + WARNING: Target "elements/audioamplify" has a path separator in its name. + +2017-04-28 16:41:42 -0300 Thibault Saunier + + * ges/ges-validate.c: + validate: Port to new GstValidate API for pipeline retrieval + +2017-05-15 09:13:38 +0200 Sebastian Dröge + + * ges/ges-layer.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + * ges/ges-track.c: + ges: Correctly handling floating references + If we ref_sink() a parameter, it must be marked as (transfer floating) + and it also has to be handled consistently between error and normal cases. + See https://bugzilla.gnome.org/show_bug.cgi?id=782499 + https://bugzilla.gnome.org/show_bug.cgi?id=782652 + +2017-05-16 14:37:03 -0400 Nicolas Dufresne + + * configure.ac: + * plugins/nle/Makefile.am: + Remove plugin specific static build option + Static and dynamic plugins now have the same interface. The standard + --enable-static/--enable-shared toggle are sufficient. + +2017-05-04 18:59:14 +0300 Sebastian Dröge + + * configure.ac: + * meson.build: + Back to development + +=== release 1.12.0 === + +2017-05-04 15:43:12 +0300 Sebastian Dröge + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + * meson.build: + Release 1.12.0 + +=== release 1.11.91 === + +2017-04-27 17:47:16 +0300 Sebastian Dröge + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + * meson.build: + Release 1.11.91 + +2017-04-24 20:30:46 +0100 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From 60aeef6 to 48a5d85 + +2017-04-10 23:51:18 +0100 Tim-Philipp Müller + + * autogen.sh: + * common: + Automatic update of common submodule + From 39ac2f5 to 60aeef6 + +=== release 1.11.90 === + +2017-04-07 16:35:23 +0300 Sebastian Dröge + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + * meson.build: + Release 1.11.90 + +2017-04-04 16:27:33 -0400 Nicolas Dufresne + + * data/completions/ges-launch-1.0: + completion: Fix previous commit + +2017-04-04 16:20:17 -0400 Nicolas Dufresne + + * data/completions/ges-launch-1.0: + completion: Fix listing commands + Executing a single string does not work in this context. Fixed using + a bash function instead. + +2017-04-04 15:48:05 -0400 Nicolas Dufresne + + * data/completions/ges-launch-1.0: + completion: Remove incorrect fixmes + In fact the fixmes are incorrect since these are options that are + available when built against gst-validate, which I didn't. There is + reference to these options in the HELP_SUMMARY that refers to these + options stating "if ges-launch is built with gst-validate ..." and these + get picked by the regex that list the options. + +2017-04-04 14:55:18 -0400 Nicolas Dufresne + + * data/completions/ges-launch-1.0: + completion: Update to new gstreamer core helpers + Also fix regressions, and mark lost features and problems with fixmes. + +2017-04-02 23:03:18 +0200 Corentin Noël + + * ges/ges-layer.c: + * ges/ges-pipeline.c: + * ges/ges-timeline.c: + Tiny fixes in the documentation + https://bugzilla.gnome.org/show_bug.cgi?id=780854 + +2017-03-30 19:57:06 -0400 Nicolas Dufresne + + * win32/common/libges.def: + Add missing win32 definition + This should fix dist check. ges_layer_get_clips_in_interval() was + added recently but missing from the list. + +2017-03-28 14:25:06 -0300 Thibault Saunier + + * tests/check/meson.build: + meson: Use get_pkgconfig_variable instead of calling pkg-config ourself + It is avalaible in meson 0.36 which is now are requirement + Nothing happens on not found dependencies. + +2017-03-25 10:47:16 -0300 Thibault Saunier + + * tests/check/meson.build: + meson: test: Fix environment object usage + And make sure to bring -good plugins in. + +2017-03-17 19:02:56 +0000 suhas2go + + * ges/ges-layer.c: + * ges/ges-layer.h: + * tests/check/ges/layer.c: + layer: Add ability to get clips in a given interval + Reviewed-by: Alex Băluț <> + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1689 + +2017-03-13 09:30:39 -0300 Thibault Saunier + + * ges/ges-audio-source.c: + * ges/ges-title-source.c: + * ges/ges-transition.c: + * ges/ges-video-source.c: + docs: Fix generation using markdown for titles around tables + +2017-03-10 19:46:33 -0300 Thibault Saunier + + * docs/hotdoc/images/layer_track_overview.png: + * docs/hotdoc/index.md: + * docs/hotdoc/meson.build: + * docs/hotdoc/sitemap.txt: + * docs/meson.build: + Revert "doc: Build documentation with hotdoc" + This reverts commit 8857e004f78ea009e1c87a93da5cf3e25dbde07f. + This was not meant to be pushed yet. + +2017-03-10 19:46:24 -0300 Thibault Saunier + + * docs/hotdoc/index.md: + * docs/hotdoc/meson.build: + * ges/meson.build: + * meson.build: + Revert "Fhotdoc" + This reverts commit 220618ecc7c061a2146e00e0063123b8dbaeb734. + This was not meant to be pushed. + +2017-03-10 19:46:09 -0300 Thibault Saunier + + * docs/hotdoc/base-classes.md: + * docs/hotdoc/low_level.md: + * docs/hotdoc/meson.build: + * docs/hotdoc/sitemap.txt: + Revert "HOTDOC" + This reverts commit 5e251483ee6777b6a74a7988b5969bf95f6ecab6. + This was not meant to be pushed. + +2017-03-10 19:34:21 -0300 Thibault Saunier + + * ges/ges-timeline.c: + ges: Minor GESTimeline documentation fix + +2017-03-09 17:49:44 -0300 Thibault Saunier + + * docs/hotdoc/base-classes.md: + * docs/hotdoc/low_level.md: + * docs/hotdoc/meson.build: + * docs/hotdoc/sitemap.txt: + HOTDOC gi + +2017-03-09 13:11:37 -0300 Thibault Saunier + + * ges/ges-asset.h: + * ges/ges-extractable.h: + extractable: Typedef only in its own .h + +2017-03-08 18:26:42 -0300 Thibault Saunier + + * docs/hotdoc/index.md: + * docs/hotdoc/meson.build: + * ges/meson.build: + * meson.build: + Fhotdoc + +2017-03-08 18:13:48 -0300 Thibault Saunier + + * ges/ges-asset.c: + * ges/ges-audio-source.c: + * ges/ges-audio-test-source.c: + * ges/ges-audio-track.c: + * ges/ges-audio-transition.c: + * ges/ges-audio-uri-source.c: + * ges/ges-base-effect-clip.c: + * ges/ges-base-effect.c: + * ges/ges-base-transition-clip.c: + * ges/ges-clip-asset.c: + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-container.h: + * ges/ges-effect-clip.c: + * ges/ges-effect.c: + * ges/ges-enums.c: + * ges/ges-extractable.c: + * ges/ges-formatter.c: + * ges/ges-gerror.h: + * ges/ges-group.c: + * ges/ges-image-source.c: + * ges/ges-layer.c: + * ges/ges-layer.h: + * ges/ges-multi-file-source.c: + * ges/ges-operation-clip.c: + * ges/ges-operation.c: + * ges/ges-overlay-clip.c: + * ges/ges-pipeline.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-project.c: + * ges/ges-source-clip.c: + * ges/ges-source.c: + * ges/ges-test-clip.c: + * ges/ges-test-clip.h: + * ges/ges-text-overlay-clip.c: + * ges/ges-text-overlay-clip.h: + * ges/ges-text-overlay.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-timeline.c: + * ges/ges-title-clip.c: + * ges/ges-title-source.c: + * ges/ges-title-source.h: + * ges/ges-track-element-asset.c: + * ges/ges-track-element.c: + * ges/ges-track.c: + * ges/ges-transition-clip.c: + * ges/ges-transition.c: + * ges/ges-uri-asset.c: + * ges/ges-uri-clip.c: + * ges/ges-utils.c: + * ges/ges-video-source.c: + * ges/ges-video-test-source.c: + * ges/ges-video-track.c: + * ges/ges-video-transition.c: + * ges/ges-video-uri-source.c: + docs: Port all docstring to gtk-doc markdown + +2017-03-08 18:02:47 -0300 Thibault Saunier + + * docs/hotdoc/images/layer_track_overview.png: + * docs/hotdoc/index.md: + * docs/hotdoc/meson.build: + * docs/hotdoc/sitemap.txt: + * docs/meson.build: + doc: Build documentation with hotdoc + +2017-03-06 08:53:00 -0300 Thibault Saunier + + * ges/ges-timeline.c: + * tests/check/ges/backgroundsource.c: + timeline: Return FALSE when commiting an empty timeline + Meaning that ASYNC_DONE/COMMITED is always emited when TRUE is returned + +2017-02-28 15:39:27 +0200 Sebastian Dröge + + * ges/gstframepositioner.c: + framepositioner: Prevent division by zero + CID 1369046 + CID 1369047 + +2017-02-28 15:36:46 +0200 Sebastian Dröge + + * ges/ges-timeline.c: + ges-timeline: Document intentional case-fall-through + CID 1364754 + +2017-02-28 13:02:44 +0200 Sebastian Dröge + + * examples/c/ges-ui.c: + ges-ui: Ensure that string is \0-terminated + CID 1320699 + +2017-02-28 12:59:35 +0200 Sebastian Dröge + + * ges/ges-project.c: + ges-project: Check for set/unset error correctly by dereferencing + ... or simply calling g_clear_error() on it which does that for us. + CID 1257630 + +2017-02-28 12:50:31 +0200 Sebastian Dröge + + * examples/c/ges-ui.c: + examples/ges-ui: Remove useless NULL check + g_new0() will abort if allocation fails. + CID 1139842 + +2017-02-24 15:44:36 -0300 Thibault Saunier + + * ges/ges-container.h: + * ges/ges-internal.h: + * ges/ges-uri-asset.h: + * win32/common/libges.def: + ges: Keep internal symbols internal + The following implementation details where exposed as public symbols: + - _ges_container_get_priority_offset + - _ges_container_set_height + - _ges_container_set_priority_offset + - _ges_uri_asset_cleanup + but it was not correct and that should never have been used outside + GES. + Moving those declarations to the internal header and marking as + internal. + +2017-02-24 16:00:09 +0200 Sebastian Dröge + + * meson.build: + meson: Update version + +2017-02-24 15:37:55 +0200 Sebastian Dröge + + * configure.ac: + Back to development + +=== release 1.11.2 === + +2017-02-24 15:10:01 +0200 Sebastian Dröge + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.11.2 + +2017-02-15 12:52:24 -0300 Thibault Saunier + + * meson.build: + * meson_options.txt: + meson: Add an option to disable doc generation + +2017-02-15 12:51:51 -0300 Thibault Saunier + + * ges/ges-meta-container.c: + Minor documentation fix + +2017-02-15 00:58:52 +0000 Tim-Philipp Müller + + * Makefile.am: + meson: dist meson build files + Ship meson build files in tarballs, so people who use tarballs + in their builds can start playing with meson already. + +2017-02-07 11:18:58 +0100 Edward Hervey + + * win32/common/libges.def: + win32: Update def file + +2017-02-06 13:18:32 +0100 Guillaume Desmottes + + * tests/check/ges/mixers.c: + mixers: fix leaks in tests + - GstMessage and GstBus references were lost + - Need to call gst_bus_remove_signal_watch() for each + gst_bus_add_signal_watch_full() call + https://bugzilla.gnome.org/show_bug.cgi?id=778248 + +2017-02-06 10:05:11 -0300 Thibault Saunier + + * ges/ges-meta-container.c: + ges-meta: Minor documenation fix + +2017-02-06 12:07:26 +0100 Guillaume Desmottes + + * docs/libs/ges-sections.txt: + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + * ges/ges.c: + * ges/ges.h: + * tests/check/ges/asset.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/group.c: + * tests/check/ges/layer.c: + * tests/check/ges/mixers.c: + * tests/check/ges/overlays.c: + * tests/check/ges/project.c: + * tests/check/ges/tempochange.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/titles.c: + * tests/check/ges/track.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + * tests/check/nle/complex.c: + * tests/check/nle/nlecomposition.c: + * tests/check/nle/nleoperation.c: + * tests/check/nle/simple.c: + * tests/check/nle/tempochange.c: + introduce ges_deinit() + GstDiscoverer objects were leaked by tests making the leaks detector + unusable. + Introduce ges_deinit(), similiar to gst_deinit(), doing some cleanup + before exiting the process. + https://bugzilla.gnome.org/show_bug.cgi?id=776805 + +2017-02-04 20:15:55 +0000 namanyadav12 + + * ges/ges-uri-asset.c: + uri-clip-asset: Add file-size metadata + Add file-size metadata to GESUriClipAsset. + Reviewed-by: Thibault Saunier + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1645 + +2017-02-03 12:50:11 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Cleanup the moved_clip list before rolling back + Otherwise we might end up using an already freed pointer + Differential Revision: https://phabricator.freedesktop.org/D1640 + +2017-01-13 12:41:51 +0000 Tim-Philipp Müller + + * meson.build: + Revert "meson: don't use subproject fallback for gst-validate if it won't work" + This reverts commit 6760e5e0b1b2f28fb04e9c430506af56c15432b9. + This was not supposed to be pushed and should not be needed any more. + +2017-01-13 12:39:42 +0000 Tim-Philipp Müller + + * meson.build: + meson: bump version + +2016-10-29 16:24:53 +0100 Tim-Philipp Müller + + * meson.build: + meson: don't use subproject fallback for gst-validate if it won't work + gst-validate has a hard-dep on json-glib-1.0 so maintain optionality of + it all by only dragging it in as a fallback if we know we can satisfy + the dependencies. + +2017-01-12 16:33:06 +0200 Sebastian Dröge + + * configure.ac: + Back to development + +=== release 1.11.1 === + +2017-01-12 16:20:08 +0200 Sebastian Dröge + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.11.1 + +2017-01-09 12:12:34 -0300 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-timeline-element.c: + * tests/check/ges/timelineedition.c: + clip: Make sure that clip start change is notified before children changes + Fixes https://phabricator.freedesktop.org/T7577 + Differential Revision: https://phabricator.freedesktop.org/D1600 + +2016-10-19 15:36:49 +0000 Alexandru Băluț + + * tests/check/python/test_timeline.py: + tests_: Check the order of signals when a transition is created + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1391 + +2016-12-21 12:22:31 +0100 Alexandru Băluț + + * ges/ges-asset.c: + asset: Fix set_proxy to abort when an error happens + Differential Revision: https://phabricator.freedesktop.org/D1574 + +2016-12-21 11:39:12 +0100 Alexandru Băluț + + * ges/ges-asset.c: + asset: Reuse local variable + Differential Revision: https://phabricator.freedesktop.org/D1573 + +2017-01-04 15:55:36 +0100 Guillaume Desmottes + + * pkgconfig/Makefile.am: + * pkgconfig/gst-editing-services-uninstalled.pc.in: + * pkgconfig/meson.build: + meson: generate pkg-config -uninstalled pc files + Generating those files is useful for users building the GStreamer stack + using meson and having to link it to another project which is still + using the autotools. + https://bugzilla.gnome.org/show_bug.cgi?id=776810 + +2016-12-23 15:08:06 -0300 Thibault Saunier + + * tools/utils.c: + ges-launch: Use standard GstEncodingProfile deserialization function + +2016-12-22 10:00:06 -0300 Thibault Saunier + + * tests/validate/geslaunch.py: + validate: Remove space breaking muting ges-launch + +2016-12-22 09:48:58 -0300 Thibault Saunier + + * ges/ges-smart-video-mixer.c: + * ges/ges-video-transition.c: + video-mixer: Fix the way we release mixer pads + We were using the actual mixer pad to release the smart mixer + pad, which seemed to be on purpose, but was not properly handle, + moreover, it is now forbiden to pass a pad not inside a GstElement + when releasing it. + Also properly remove ghost pads from Smart mixer, we were planly + failling at it. + +2016-12-22 08:44:07 -0300 Thibault Saunier + + * tools/meson.build: + meson: Install ges-launch + +2016-12-16 17:29:59 +0000 Tim-Philipp Müller + + * .gitignore: + * Makefile.am: + * configure.ac: + * gst-editing-services.spec.in: + Remove generated .spec file + Likely extremely bitrotten, and we should not ship this anyway. + +2016-12-16 14:04:06 -0300 Thibault Saunier + + * tests/check/Makefile.am: + * tests/check/ges/text_properties.c: + * tests/check/meson.build: + tests: Remove now meaningless empty testsuite + +2016-12-13 16:05:17 +0100 Antonio Ospite + + * tools/Makefile.am: + * tools/ges-launch-1.0.1: + * tools/meson.build: + ges: add a basic unix man page for ges-launch-1.0 + Do not list all the possible options in the man page but only the help + options. + This is in order to avoid duplication and prevent the man page from + becoming obsolete in case the options change in the code but do not get + updated in the man page. + https://bugzilla.gnome.org/show_bug.cgi?id=776063 + +2016-12-13 15:10:26 +0100 Antonio Ospite + + * ges/ges.c: + ges: fix the description of the --help-GES command line option + Use "Show GES Options" which is more appropriate and avoids duplication + with --help-gst which already says "Show GStreamer Options". + https://bugzilla.gnome.org/show_bug.cgi?id=776063 + +2016-12-12 16:59:08 -0300 Thibault Saunier + + * tests/check/ges/uriclip.c: + tests: Make sure tests can be listed + Initializing GstCheck before creating the testsuite + +2016-12-13 23:26:23 -0300 Thibault Saunier + + * tools/ges-launcher.c: + ges-launch: Make sure GStreamer is always initialized + https://bugzilla.gnome.org/show_bug.cgi?id=776064 + +2016-12-09 17:50:28 -0300 Thibault Saunier + + * meson.build: + meson: Support building without Gst debug + +2016-12-03 08:21:07 +0100 Edward Hervey + + * common: + Automatic update of common submodule + From 1416c16 to 39ac2f5 + +2016-12-01 17:08:43 -0300 Thibault Saunier + + * ges/ges-timeline-element.c: + element: Rework set_child_property_by_pspec + It was making no sense to loose the information about the pspec itself + to retrieve the child associated to it and was failling when we were + forcing the AssociateType::prop synthax + +2016-12-01 15:46:51 -0300 Thibault Saunier + + * tests/check/nle/nleoperation.c: + test:nle: Soften check on refcount + The composition might already have taken a new ref processing the + source. + +2016-11-30 09:53:38 -0300 Thibault Saunier + + * plugins/nle/nlecomposition.c: + nlecomposition: Deactivate current stack in PAUSED_READY state + To avoid a race when tearing down the composition (PAUSED_TO_READY), + we should make sure to tear down the current stack and let the GstBin + class handle the remaining thing to do during the change state. + We should still ignore any error happening when tearing down the + bin state just in case. + https://bugzilla.gnome.org/show_bug.cgi?id=775051 + +2016-11-30 09:53:21 -0300 Thibault Saunier + + * tools/ges-launch.c: + tools: Deinit Gst before exiting + +2016-11-29 10:37:11 -0300 Thibault Saunier + + * meson.build: + meson: Do not print error logs when building the gir + +2016-11-28 17:07:39 -0800 Scott D Phillips + + * examples/c/assets.c: + examples: remove #include from assets.c + It is not needed and pulling it in is causing a link problem with msvc. + Including ges-internal.h sets the default debug category in assets.c to + _ges_debug. Because _ges_debug is marked as DATA in the libges.def, it + will only be linked from libges.dll if it is marked in the source with + dllimport. Instead of messing with that we can just remove this include. + https://bugzilla.gnome.org/show_bug.cgi?id=775295 + +2016-11-26 11:25:41 +0000 Tim-Philipp Müller + + * .gitmodules: + common: use https protocol for common submodule + https://bugzilla.gnome.org/show_bug.cgi?id=775110 + +2016-11-23 18:42:27 +0200 Sebastian Dröge + + * ges/ges-timeline.c: + ges-timeline: Properly calculate absolute diff of two unsigned integers + CID 1394491. + +2016-11-23 18:28:35 +0200 Sebastian Dröge + + * ges/ges.c: + ges: Add NULL check before dereferencing + CID 1394494. + +2016-11-20 15:34:46 +0100 Philippe Renon + + * ges/ges-uri-asset.c: + ges-uri-asset: fix compile error 'timeout' may be used uninitialized + https://bugzilla.gnome.org/show_bug.cgi?id=774751 + +2016-11-18 10:21:45 -0800 Scott D Phillips + + * Makefile.am: + * win32/MANIFEST: + * win32/common/libges.def: + make: include common/win32.mak + With the addition of the .def file for libges we need to make + sure the check-export script from common gets executed so that the + .def stays up to date. + https://bugzilla.gnome.org/show_bug.cgi?id=774641 + +2016-11-18 16:55:17 -0300 Thibault Saunier + + * common: + Update common submodule + +2016-11-17 10:31:50 -0800 Scott D Phillips + + * ges/meson.build: + * meson.build: + * win32/common/libges.def: + Enable building with MSVC + https://bugzilla.gnome.org/show_bug.cgi?id=774641 + +2016-11-17 10:40:05 -0800 Scott D Phillips + + * ges/gstframepositioner.c: + Cast away const from GstMetaInfo in *_get_meta_info() functions + MSVC warns about the const in the implicit argument conversion in the + calls to g_once_init_{enter,leave}. It's OK so explicitly cast it. + https://bugzilla.gnome.org/show_bug.cgi?id=774641 + +2016-11-17 10:39:01 -0800 Scott D Phillips + + * ges/ges-formatter.c: + * ges/ges-validate.c: + Pass gint/guint pointers instead of enum pointers + The underlying integer type for enums are implementation defined and may + not be the same size as gint/guint. So implicitly casting from pointers- + to-enum-types to pointers-to-int-types is unsafe. MSVC warns on these. + https://bugzilla.gnome.org/show_bug.cgi?id=774641 + +2016-11-17 10:35:50 -0800 Scott D Phillips + + * ges/ges-command-line-formatter.c: + * ges/parse.l: + parse: Don't #include + It isn't needed and isn't present in non-posix environments like windows + with MSVC or mingw. + https://bugzilla.gnome.org/show_bug.cgi?id=774641 + +2016-11-17 09:40:38 +0200 Sebastian Dröge + + * plugins/nle/nlecomposition.c: + Revert "nlecomposition: Start task and initialize the stack after chaining up to parent's change state function" + This reverts commit 57d40bec1a3c5048baaad08403d7b7e641a9c55c. + Apparently it causes timeouts in the unit tests on Jenkins and + Thibault's machine, and in the gst-validate tests. + Caused by elements staying in PAUSED and waiting to be set to PLAYING. + Needs further investigation. + +2016-11-17 09:40:33 +0200 Sebastian Dröge + + * plugins/nle/nleobject.c: + Revert "nleobject: Start up in NULL->READY->PAUSED after the parent class did" + This reverts commit 5f7943c59d9def8c2dc9983936463462c1cdf63f. + +2016-11-16 18:11:00 +0200 Sebastian Dröge + + * plugins/nle/nleobject.c: + nleobject: Start up in NULL->READY->PAUSED after the parent class did + This keeps everything in a more consistent order and makes sure that the + base class is already set up completely before we start doing anything. + It also prevents from doing any setup if the base class fails, and + possibly not shutting things down again then. + https://bugzilla.gnome.org/show_bug.cgi?id=774480 + +2016-11-15 17:56:00 +0200 Sebastian Dröge + + * plugins/nle/nlecomposition.c: + nlecomposition: Start task and initialize the stack after chaining up to parent's change state function + Otherwise we could set the state of the children to PAUSED already (i.e. + start dataflow) from the composition's task, while the composition + itself is currently chaining up to the parent class' change state + function and did not activate the pads yet. This causes buffers and + events to be discarded, and everything to stop with a not-negotiated + error. + https://bugzilla.gnome.org/show_bug.cgi?id=774480 + +2016-11-15 18:34:44 -0300 Thibault Saunier + + * tests/check/meson.build: + * tests/check/nose2-junit-xml.cfg.in: + tests: Fix running python unit tests + Adding missing nose2-junit-xml.cfg.in file and minor fixes in + the way we call nose2 also making sure the .xunit files end + up in the right place. + +2016-11-15 15:09:10 -0300 Thibault Saunier + + * ges/ges-uri-asset.c: + * ges/ges.c: + ges: Check if GstDiscoverer could be created at init time + And fail initialization if it is not the case, we make the assumption + it worked all around the codebase so we should really concider it fatal. + +2016-11-10 15:17:50 +0200 Sebastian Dröge + + * plugins/nle/nlecomposition.c: + nlecomposition: Fix small remaining race in previous commit + The seek action might currently be handled (in which case it is not in + the actions list and the action lock is not locked), but not actually + handled completely yet (the seqnum is not stored yet). + To prevent this, we remember what the current action is that is being + handled, and also compare to that. + https://bugzilla.gnome.org/show_bug.cgi?id=774149 + +2016-10-19 16:34:56 +0200 Alexandru Băluț + + * configure.ac: + * tests/check/Makefile.am: + * tests/check/meson.build: + tests_: Use nose2 instead of nosetests + Differential Revision: https://phabricator.freedesktop.org/D1394 + +2016-10-19 12:36:45 +0200 Alexandru Băluț + + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + ges: Fix documentation and debug comments + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1393 + +2016-01-12 14:51:55 +0000 Mathieu Duponchelle + + * ges/ges-timeline.c: + * tests/check/ges/timelineedition.c: + * tests/check/python/test_clip.py: + * tests/check/python/test_timeline.py: + timeline: reimplement snap_to_position a bit more appropriately. + It could yet be made be simpler, but it would require + touching the rest of the timeline editing code. + Fixes https://phabricator.freedesktop.org/T7587 + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D657 + +2016-11-09 17:14:19 +0200 Sebastian Dröge + + * plugins/nle/nlecomposition.c: + nlecomposition: De-duplicate seek events based on their sequence number + If there are e.g. multiple video sinks, we would get the same seek event + multiple times. But we only want to handle it once. + https://bugzilla.gnome.org/show_bug.cgi?id=774149 + +2016-11-07 18:01:51 -0300 Thibault Saunier + + * tests/validate/geslaunch.py: + test:validate: Port to python3 + +2016-11-09 11:48:09 +0200 Sebastian Dröge + + * ges/ges-timeline.c: + ges-timeline: Fix typo in debug messages + +2016-10-08 10:43:07 +0200 Thibault Saunier + + * ges/ges-timeline.c: + * tests/check/python/test_timeline.py: + timeline: Avoid creating extra transition when rippling clips + In some cases when rippling clip we could get the algo lost because + a transition existed between two clips (for example at the end of c1 + and at the begining of c2) but while rippling it would have required + a transition at the end of c2 and beginning of c1, and we were properly + not destroying the old one (as the two clips were in the moving context) + but we were still creating the other transition in the end... + Reviewed-by: Alex Băluț + Differential Revision: https://phabricator.freedesktop.org/D1362 + +2016-10-07 15:31:40 +0200 Thibault Saunier + + * ges/ges-timeline.c: + * tests/check/python/test_timeline.py: + timeline: Make sure transitions between rippled clips are never deleted + Reviewed-by: Alex Băluț + Differential Revision: https://phabricator.freedesktop.org/D1361 + +2016-10-06 19:14:57 +0200 Thibault Saunier + + * ges/ges-layer.c: + * ges/ges-timeline.c: + * tests/check/python/test_timeline.py: + timeline: Destroy transition if a neighbor is not being moved to a layer + And make sure that we move the transition to the right layer, not trying + to figure it out. + Differential Revision: https://phabricator.freedesktop.org/D1360 + +2016-10-06 14:00:23 +0200 Alexandru Băluț + + * tests/check/python/test_clip.py: + * tests/check/python/test_timeline.py: + tests_: Check transition is gone when editing clip to another layer + Differential Revision: https://phabricator.freedesktop.org/D1359 + +2016-11-04 14:41:13 -0300 Thibault Saunier + + * meson.build: + meson: Unset the plugin paths to generate the .gir files + Avoiding problems when using subproject: + 'Failed to load plugin something.so file too short' + +2016-11-01 18:10:47 +0000 Tim-Philipp Müller + + * meson.build: + meson: update version + +=== release 1.11.0 === + +2016-11-01 18:53:15 +0200 Sebastian Dröge + + * configure.ac: + Back to development + +=== release 1.10.0 === + +2016-11-01 18:12:35 +0200 Sebastian Dröge + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.10.0 + +2016-10-25 08:54:11 -0700 Scott D Phillips + + * meson.build: + meson: Don't depend on gstreamer-check-1.0 on windows + https://bugzilla.gnome.org/show_bug.cgi?id=773114 + +2016-10-25 11:48:35 +0530 Nirbheek Chauhan + + * meson.build: + * tests/check/meson.build: + Revert "meson: move gstreamer-check-1.0 dependency to tests/check" + This reverts commit 5665c2bfc9cae531c6dd9a75766d06a4af25ab9a. + Does not actually work. See: + https://bugzilla.gnome.org/show_bug.cgi?id=773114#c31 + +2016-10-21 05:49:18 -0300 Thibault Saunier + + * meson.build: + * tests/check/meson.build: + meson: move gstreamer-check-1.0 dependency to tests/check + +2016-10-17 09:34:27 -0700 Scott D Phillips + + * meson.build: + meson: mark gstreamer-check-1.0 as required: false + +2016-10-15 22:21:24 +0530 Nirbheek Chauhan + + * meson.build: + meson: Don't set c_std to gnu99 + Use the default for each compiler on every platform instead. This + improves our compatibility with compilers that don't have gnu99 as + a c_std. + +2016-10-03 17:44:04 -0300 Thibault Saunier + + * meson.build: + * tests/check/getpluginsdir: + * tests/check/meson.build: + meson: Use environment object to setup test environment variables + Bump meson requirement to 0.35 + +2016-10-11 00:59:47 +0200 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-track-element.c: + track-element: Avoid dereferencing NULL pointer + We set TrackElement track type very early when creating effects + so it now uses that information to find TrackElement in clips + by track type. + Reviewed-by: Alex Băluț + Differential Revision: https://phabricator.freedesktop.org/D1370 + +2016-09-13 12:31:54 -0300 Thibault Saunier + + * tests/check/meson.build: + meson: Add python tests + +2016-09-30 11:35:42 -0300 Thibault Saunier + + * hooks/pre-commit.hook: + * meson.build: + * tests/check/getpluginsdir: + meson: Setup pre commit hook and fix getpluginsdir for standalone case + +2016-09-30 14:56:48 +0100 Tim-Philipp Müller + + * meson.build: + meson: update version + +=== release 1.9.90 === + +2016-09-30 13:04:39 +0300 Sebastian Dröge + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.9.90 + +2016-09-23 20:41:04 -0300 Thibault Saunier + + * docs/libs/meson.build: + * docs/meson.build: + * ges/meson.build: + meson: Fix gtkdoc using new meson features + +2016-09-21 16:41:31 -0300 Thibault Saunier + + * ges/meson.build: + meson: Fix installing configured files + +2016-08-13 19:54:22 -0400 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/ges-uri-clip.c: + uriclip: Remove some filesource leftovers + Differential Revision: https://phabricator.freedesktop.org/D1329 + +2016-08-13 21:09:53 -0400 Thibault Saunier + + * tests/check/python/__init__.py: + * tests/check/python/common.py: + * tests/check/python/test_clip.py: + * tests/check/python/test_group.py: + * tests/check/python/test_timeline.py: + tests_:python: Factor out common code + Differential Revision: https://phabricator.freedesktop.org/D1328 + +2016-09-17 09:46:59 -0300 Thibault Saunier + + * plugins/nle/nlecomposition.c: + nle: Drop tags getting out of the composition + Those tag are meaningless in for the new stream created by the composition + First step toward fixing T3070 + Differential Revision: https://phabricator.freedesktop.org/D1327 + +2016-08-11 15:12:07 -0400 Thibault Saunier + + * ges/ges-timeline.c: + * tests/check/python/test_group.py: + timeline: Properly compute the end of groups when checking snapping + Computation was not taking into account the fact that the start of + the element being moved could be at the middle of a group and not + necessarily at the start! + Fixes T7544 + Reviewed-by: Alex Băluț + Differential Revision: https://phabricator.freedesktop.org/D1282 + +2016-08-11 13:19:44 -0400 Thibault Saunier + + * ges/ges-auto-transition.c: + * tests/check/python/test_group.py: + ges: Handle moving groups with effects inside + We were only concidering that we should let the group handle moving + transitions when changing transitions but in fact as soon as a + transition is happenning between two clips that are in a same group + the group properly handles moving the transition, so let the + group do its job. + Fixes T7543 + Differential Revision: https://phabricator.freedesktop.org/D1281 + +2016-08-11 10:54:08 -0400 Thibault Saunier + + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/layer.c: + ges: Deprecate GESTimelineElement::priority writability + GESLayer is now responsible for setting clips priorites. Also + GESClip top effects priorities are now set by the + ges_clip_set_top_effect_index method, the user should never call + ges_timeline_element_set_priority as it will anyway be overriden + by GES itself. + Differential Revision: https://phabricator.freedesktop.org/D1280 + +2016-08-11 10:36:44 -0400 Thibault Saunier + + * ges/ges-layer.c: + layer: Handle operation priorities + All operations should have higher priorites and sources should be + on top of those. We now first set the operations priorities in + a first pass and then stack sources on top of those. + Differential Revision: https://phabricator.freedesktop.org/D1279 + +2016-08-11 09:53:58 -0400 Thibault Saunier + + * ges/ges-video-transition.c: + ges: transition: Make crossfade fade out at the same time as it fade in + Until now fade out was just fading in the new clip, but this is not + correct and crossfade should at the same time fade out while fading + in. + Fixes https://phabricator.freedesktop.org/T3451 + Differential Revision: https://phabricator.freedesktop.org/D1278 + +2016-08-11 09:42:32 -0400 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-layer.c: + * ges/ges-timeline.c: + layer: Make sure to resync priorities on commit + In case effects have been added priorites might become wrong, + but until the timeline is not commited, it does not matter. + Make sure all priorities are correct before commiting compositions + Differential Revision: https://phabricator.freedesktop.org/D1277 + +2016-08-11 09:14:42 -0400 Thibault Saunier + + * ges/ges-auto-transition.c: + * ges/ges-layer.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/project.c: + * tests/check/ges/test-utils.h: + * tests/check/ges/titles.c: + * tests/check/ges/uriclip.c: + * tests/check/python/test_clip.py: + Finally move clip priority handling to GESLayer. + Fix all tests as we now have 1 priority inside the layer + dedicated to transitions (basically no source clip will + ever have a priority of 0 inside a layer). + Differential Revision: https://phabricator.freedesktop.org/D1276 + +2016-08-11 08:54:23 -0400 Thibault Saunier + + * ges/ges-clip.c: + * tests/check/ges/effects.c: + clip: Make top effect priority inside the clip priority range + And simplify the way we start computing children priority + making min_priority already relative to the clip itself. + Differential Revision: https://phabricator.freedesktop.org/D1275 + +2016-08-11 07:54:42 -0400 Thibault Saunier + + * tests/check/ges/timelineedition.c: + tests_: timelineedition: Fix test now that we ripple from start and not from end + Differential Revision: https://phabricator.freedesktop.org/D1274 + +2016-09-22 11:28:21 -0400 Sebastian Dröge + + * plugins/nle/nlecomposition.c: + nlecomposition: Don't try to seek on an empty stack + We would seek on a NULL pad then, which gives ugly assertions. + https://bugzilla.gnome.org/show_bug.cgi?id=771843 + +2016-09-22 11:25:18 -0400 Sebastian Dröge + + * plugins/nle/nleurisource.c: + * plugins/nle/nleurisource.h: + nleurisource: Always provide a srcpad + By putting uridecodebin into a bin with a ghostpad. Without this, + nlesource tries to get a srcpad too early (before uridecodebin added + one) and everything fails miserably. + This has to be fixed properly in nlesource at some point, by properly + handling dynamically added pads. Currently they can only work if they + are added in states <= READY, which is not the usual case. + https://bugzilla.gnome.org/show_bug.cgi?id=771843 + +2016-09-21 18:23:56 -0400 Sebastian Dröge + + * plugins/nle/nlesource.c: + nlesource: Fail prepare() if no valid source pad is found + https://bugzilla.gnome.org/show_bug.cgi?id=771792 + +2016-09-14 14:32:19 -0300 Thibault Saunier + + * tools/utils.c: + ges-launch: Be a bit more agressive sanitizing arguments + Otherwise GstStructure might fail parsing some fields + containing brackets + https://bugzilla.gnome.org/show_bug.cgi?id=771434 + +2016-09-14 11:31:23 +0200 Sebastian Dröge + + * configure.ac: + configure: Depend on gstreamer 1.9.2.1 + +2016-09-06 14:27:38 +0200 Alexandru Băluț + + * ges/ges-base-xml-formatter.c: + * ges/ges-group.c: + * ges/ges-internal.h: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-xml-formatter.c: + * tests/check/python/common.py: + * tests/check/python/test_group.py: + * tests/check/python/test_timeline.py: + timeline: Make get_groups public + Had to separate timeline_emit_group_added from timeline_add_group + to avoid emitting group-added when the project is being loaded. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1302 + +2016-09-06 15:49:49 +0200 Alexandru Băluț + + * tests/check/python/test_group.py: + tests_: Make sure child-removed is emitted when ungrouping + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1301 + +2016-09-06 13:03:11 +0200 Alexandru Băluț + + * ges/ges-timeline.c: + timeline: Fix documentation + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1300 + +2016-09-05 12:23:30 +0200 Alexandru Băluț + + * README: + Remove obsolete dependency + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1299 + +2016-09-10 20:52:38 +1000 Jan Schmidt + + * autogen.sh: + * common: + Automatic update of common submodule + From b18d820 to f980fd9 + +2016-09-10 09:58:37 +1000 Jan Schmidt + + * autogen.sh: + * common: + Automatic update of common submodule + From f49c55e to b18d820 + +2016-09-09 17:14:43 -0300 Thibault Saunier + + * tests/check/Makefile.am: + tests: Move -DGES_TEST_FILES_PATH to common_cflags + As it is needed to build the utils lib + +2016-09-09 16:42:13 -0300 Thibault Saunier + + * tests/check/Makefile.am: + * tests/check/ges/test-utils.c: + * tests/check/meson.build: + tests: Fix the way we get tests assets + Using __FILE__ won't work properly with meson. + +2016-09-09 08:52:32 -0300 Thibault Saunier + + * ges/meson.build: + * tests/validate/geslaunch.py: + test:validate: Handle new expected_failures Test argument + +2016-09-07 16:53:06 -0300 Thibault Saunier + + * meson.build: + * tests/check/meson.build: + meson: Do not build libges against libcheck + +2016-09-05 17:55:42 -0300 Thibault Saunier + + * tests/check/getpluginsdir: + * tests/check/meson.build: + meson: Properly find where other GStreamer plugins are when using subprojects + +2016-09-05 14:54:53 -0300 Thibault Saunier + + * tests/validate/geslaunch.py: + Revert "validate: Blacklist racy tests" + This reverts commit ce35412ff260fbd6e07b374bc3ca677053c277e0. + https://bugzilla.gnome.org/show_bug.cgi?id=769894 has been fixed + +2016-08-26 19:55:33 -0300 Thibault Saunier + + * ges/meson.build: + * meson.build: + * meson_options.txt: + meson: Handle building the gir file when used as subproject + Add support for building GIR when used as subproject + Add an option to disable GIR generation + And bump version to 1.9.2 + +2016-09-01 12:33:22 +0300 Sebastian Dröge + + * configure.ac: + Back to development + +=== release 1.9.2 === + +2016-09-01 12:33:13 +0300 Sebastian Dröge + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.9.2 + +2016-08-25 15:04:54 -0300 Thibault Saunier + + * docs/libs/meson.build: + * meson.build: + meson: doc: Fix building documentation when using subprojects + And check the presence of gtk-doc before building the documentation + +2016-08-25 10:06:51 +0300 Sebastian Dröge + + * .gitignore: + * ges/Makefile.am: + ges: Rename parse_lex.h to ges-parse-lex.h + Fixes the build and makes it consistent with the meson build system. + +2016-08-04 17:33:55 -0400 Thibault Saunier + + * .gitignore: + * config.h.meson: + * docs/libs/meson.build: + * docs/meson.build: + * examples/c/meson.build: + * examples/meson.build: + * ges/ges-command-line-formatter.c: + * ges/meson.build: + * meson.build: + * pkgconfig/meson.build: + * plugins/meson.build: + * plugins/nle/meson.build: + * tests/check/meson.build: + * tests/meson.build: + * tools/meson.build: + Add support for Meson as alternative/parallel build system + https://github.com/mesonbuild/meson + +2016-08-18 18:43:08 +0200 Philippe Renon + + * ges/ges-track-element.c: + ges-track-element: fix typos in control_binding_removed signal declaration + https://bugzilla.gnome.org/show_bug.cgi?id=770101 + +2016-08-14 17:45:16 +0200 Edward Hervey + + * tests/validate/geslaunch.py: + validate: Blacklist racy tests + See https://bugzilla.gnome.org/show_bug.cgi?id=769894 + +2016-08-14 17:44:36 +0200 Edward Hervey + + * ges/ges-timeline.c: + ges-timeline: Demote some debugging statements + locking should be in a lower level to avoid too many messages + +2016-08-13 11:08:34 +0200 Edward Hervey + + * ges/ges-timeline-element.c: + timeline-element: Reset pointer after freeing + dispose can be called multiple times, make sure we don't call functions + on free'd pointers. + +2016-08-03 11:40:30 -0400 Thibault Saunier + + * ges/gstframepositioner.c: + * tests/check/ges/timelineedition.c: + ges: Do not rescale videos if the track aspect ratio changes + Differential Revision: https://phabricator.freedesktop.org/D1242 + +2016-08-02 16:42:20 -0400 Thibault Saunier + + * ges/ges-title-source.c: + * ges/ges-video-source.c: + * ges/ges-video-source.h: + * ges/gstframepositioner.c: + * ges/gstframepositioner.h: + ges: Let the compositor do the scaling if mixing is enabled + Differential Revision: https://phabricator.freedesktop.org/D1241 + +2016-08-01 12:55:07 -0400 Thibault Saunier + + * tests/validate/geslaunch.py: + tests:validate: Also test opus and theora in OGG + +2016-07-29 15:48:28 -0400 Thibault Saunier + + * ges/ges-pipeline.c: + * tests/validate/geslaunch.py: + validate: Start also testing jpeg encoding + +2016-07-28 21:50:58 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Keep transitions when moving the moving context between layers + Differential Revision: https://phabricator.freedesktop.org/D1225 + +2015-10-15 22:13:30 +0000 Justin Kim + + * tools/ges-launcher.c: + ges-launcher: don't leak project uri string + ges_project_get_uri returns a cloned string so it should + be free'd after usage. + Reviewed-by: Thibault Saunier + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D381 + +2016-07-28 19:30:28 -0400 Thibault Saunier + + * ges/ges-timeline.c: + * tests/check/ges/layer.c: + timeline: Ripple from start of clips and not the end. + Fixes https://phabricator.freedesktop.org/T7503 + +2016-07-28 17:23:31 -0400 Thibault Saunier + + * ges/ges-enums.c: + * ges/ges-enums.h: + enums: Fix absolute text overlay alignment value + It needs to be in sync with GstBaseTextOverlayHAlign order. + +2016-07-28 16:04:42 -0400 Thibault Saunier + + * ges/ges-title-source.c: + title-source: Properly implement GESTimelineElement->lookup_child + GESTrackElement->lookup_child is deprecated and should be avoided + as much as possible. + +2016-07-28 16:02:05 -0400 Thibault Saunier + + * ges/ges-clip.c: + clip: Reimplement look_child and iterate over children if needed + Otherwise in the case where children reimplement lookup_child to + handle some property renaming lookup fails. + +2016-07-28 14:24:07 -0400 Thibault Saunier + + * ges/ges-command-line-formatter.c: + * ges/ges-command-line-formatter.h: + * ges/ges-internal.h: + * tools/ges-launch.c: + tools: Fix printing commands help + +2016-07-26 14:05:06 -0400 Thibault Saunier + + * tools/ges-validate.c: + ges:validate: Check that no extra decoding happens + +2016-07-26 11:59:39 -0400 Thibault Saunier + + * ges/ges-audio-uri-source.c: + * ges/ges-video-uri-source.c: + uri-sources: Make sure to set decodebin 'caps' property + Fixes a regression where we decode streams twice, + this was introduced when we started creating NLE + object at GESTrackElement construct time. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=769193 + +2016-07-24 08:32:06 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Emit snap-ended on commit if needed + Commiting the timeline means that the current operations on the clips are over, + so we should concider snapping as done at that point + Fixes T7499 + +2016-06-26 12:23:40 +0530 Mohan R + + * ges/Makefile.am: + fixed ges-version.h not found issue during out of tree build + +2016-07-22 07:32:51 -0400 Thibault Saunier + + * ges/ges-image-source.c: + image-source: Do not concider inpoints + We have no restriction on inpoint for Images + Differential Revision: https://phabricator.freedesktop.org/D1202 + +2016-07-15 08:57:28 -0400 Thibault Saunier + + * tests/validate/geslaunch.py: + tests:validate: Better choose tested rendering formats + +2016-07-11 21:16:11 +0200 Stefan Sauer + + * common: + Automatic update of common submodule + From f363b32 to f49c55e + +2016-07-06 13:51:18 +0300 Sebastian Dröge + + * configure.ac: + Back to development + +=== release 1.9.1 === + +2016-07-06 13:45:17 +0300 Sebastian Dröge + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.9.1 + +2016-06-29 00:39:02 +0000 Justin Kim + + * tools/ges-launch.c: + ges-launch: unref GApplication properly + Differential Revision: https://phabricator.freedesktop.org/D380 + +2016-06-29 00:38:36 +0000 Justin Kim + + * ges/ges-project.c: + project_: improve get_uri doc + The return value of ges_project_get_uri should be freed + after usage. + Differential Revision: https://phabricator.freedesktop.org/D1142 + +2016-06-21 11:49:14 -0400 Nicolas Dufresne + + * common: + Automatic update of common submodule + From ac2f647 to f363b32 + +2016-06-20 21:29:44 -0400 Thibault Saunier + + * tests/check/Makefile.am: + tests: Make sure to run python tests against the build in tree + +2016-06-18 16:16:00 -0400 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-timeline.c: + * ges/ges-uri-clip.c: + * tests/check/ges/basic.c: + * tests/check/python/test_clip.py: + ges: Don't remove track elements from clips when removing from layer + And reuse the same previously created element when adding the clip + back to a layer, avoiding losing all setting done on clip children + in that situation + This is a behaviour change but previous behaviour was actually totally + unexpected and people working around that weird behaviour will moste + probably not care about that change + Differential Revision: https://phabricator.freedesktop.org/D1094 + +2016-06-20 14:00:07 -0400 Thibault Saunier + + * ges/ges-title-clip.c: + * tests/check/python/test_clip.py: + title_: Do not forget to link up child_added/removed vmethod + Otherwise effect handling is broken + Differential Revision: https://phabricator.freedesktop.org/D1099 + +2016-06-19 20:02:06 -0400 Thibault Saunier + + * ges/ges-auto-transition.c: + * tests/check/python/test_group.py: + auto-transitions: Do not remove auto transitions when moving neighboor from the same group + Differential Revision: https://phabricator.freedesktop.org/D1097 + +2016-05-14 19:33:05 +0200 Aurélien Zanelli + + * ges/ges-container.c: + * ges/ges-timeline-element.c: + ges: fix various leaks with usage of ges_timeline_element_lookup_child + Some callers forgot to unref out child, pspec or both leading to leaks. + https://bugzilla.gnome.org/show_bug.cgi?id=766449 + +2016-05-14 19:02:57 +0200 Aurélien Zanelli + + * ges/ges-video-track.c: + video-track: don't leak restriction caps in _sync_capsfilter_with_track() + https://bugzilla.gnome.org/show_bug.cgi?id=766450 + +2016-06-06 17:44:15 +0300 Sebastian Dröge + + * ges/ges-uri-asset.c: + ges-uri-asset: GstDiscoverer can return a valid info but a non-OK result, consider this an error + The asynchronous case in ges_uri_clip_asset_request_async() already considered + it an error, do the same in ges_uri_clip_asset_request_sync(). + https://bugzilla.gnome.org/show_bug.cgi?id=767293 + +2016-05-31 12:09:44 +0300 Sebastian Dröge + + * plugins/nle/nlecomposition.c: + nlecomposition: Fix race condition in seek handling causing deadlocks + We might receive another seek from the application while the action task is + handling a previous seek (and thus setting seeking_itself to TRUE). To prevent + this seek to go through directly instead of being added as an action, also + check if the seek event was received from our action task thread or some other + thread. + https://bugzilla.gnome.org/show_bug.cgi?id=767053 + +2016-05-31 09:29:44 -0400 Thibault Saunier + + * ges/ges-meta-container.c: + ges: Emit GESMetontainer::notify-meta even if value is unset + +2016-05-30 10:51:51 -0400 Thibault Saunier + + * ges/ges-meta-container.c: + ges: Allow passing `NULL` as a value to ges_meta_container_set_meta + Fixes T7430 + +2016-05-25 10:32:46 +0100 Tim-Philipp Müller + + * ges/Makefile.am: + g-i: pass compiler env to g-ir-scanner + It's what introspection.mak does as well. Should + fix spurious build failures on gnome-continuous + (caused by g-ir-scanner getting compiler details + via python which is broken in some environments + so passing the compiler details bypasses that). + +2016-05-16 12:06:37 +0200 Aurélien Zanelli + + * ges/ges-uri-clip.c: + * ges/ges-uri-clip.h: + uri-clip: make uri parameter of ges_uri_clip_new () const + To avoid compiler warning when using const string to create a new + GESUriClip as string is not modified and only passed to functions which + take a const string. + https://bugzilla.gnome.org/show_bug.cgi?id=766523 + +2016-05-16 12:53:32 +0200 Aurélien Zanelli + + * ges/gstframepositioner.c: + framepositionner: add a weak ref on track element to know when it is finalized + Otherwise if frame positionner is disposed after track element has been + finalized, it will raise a critical message because we will try to + disconnect a signal handler on a freed track element object. + https://bugzilla.gnome.org/show_bug.cgi?id=766525 + +2016-05-16 17:36:36 +0200 Aurélien Zanelli + + * ges/ges-audio-source.c: + audio-source: unref private capsfilter reference on dispose + Otherwise a capsfilter reference will be leaked since it has been got + using gst_bin_get_by_name. + https://bugzilla.gnome.org/show_bug.cgi?id=766524 + +2016-05-16 17:35:29 +0200 Aurélien Zanelli + + * ges/ges-audio-source.c: + audio-source: fix indentation + https://bugzilla.gnome.org/show_bug.cgi?id=766524 + +2016-05-07 20:29:22 +0200 Aurélien Zanelli + + * plugins/nle/nlecomposition.c: + nlecomposition: ensure elements pending to be added are not leaked + When nlecomposition is finalized with pending add action or io, + associated elements are not unreffed as they should since caller gives + us the reference when calling gst_bin_add causing them to be leaked. + So to make sure we don't leak a reference on element when adding one to + the bin, each stage (action and pending_io) hold a reference on element + and release it when stage is done. + https://bugzilla.gnome.org/show_bug.cgi?id=766455 + +2016-05-14 18:06:56 +0200 Aurélien Zanelli + + * plugins/nle/nlecomposition.c: + nlecomposition: fix nle_composition_remove_object info message + We don't add internal bin, we remove it. + https://bugzilla.gnome.org/show_bug.cgi?id=766455 + +2016-05-15 01:04:17 +0200 Aurélien Zanelli + + * ges/ges-asset.c: + * ges/ges-audio-test-source.c: + * ges/ges-audio-track.c: + * ges/ges-audio-transition.c: + * ges/ges-audio-uri-source.c: + * ges/ges-clip.c: + * ges/ges-effect-clip.c: + * ges/ges-effect.c: + * ges/ges-extractable.c: + * ges/ges-group.c: + * ges/ges-image-source.c: + * ges/ges-layer.c: + * ges/ges-meta-container.c: + * ges/ges-multi-file-source.c: + * ges/ges-pipeline.c: + * ges/ges-project.c: + * ges/ges-test-clip.c: + * ges/ges-text-overlay-clip.c: + * ges/ges-text-overlay.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + * ges/ges-title-clip.c: + * ges/ges-title-source.c: + * ges/ges-track-element.c: + * ges/ges-track.c: + * ges/ges-transition-clip.c: + * ges/ges-uri-asset.c: + * ges/ges-uri-clip.c: + * ges/ges-utils.c: + * ges/ges-video-test-source.c: + * ges/ges-video-track.c: + * ges/ges-video-transition.c: + * ges/ges-video-uri-source.c: + ges: add some g-i annotations according to documentation + Mainly (transfer xxx) and (nullable). Also fix some typo. + https://bugzilla.gnome.org/show_bug.cgi?id=766459 + +2016-05-15 01:03:49 +0200 Aurélien Zanelli + + * ges/ges-asset.c: + asset: fix ges_asset_set_proxy() return value documentation + https://bugzilla.gnome.org/show_bug.cgi?id=766459 + +2016-05-06 22:28:26 -0300 Thibault Saunier + + * ges/ges-timeline-element.c: + element: Also accept GParamSpec.owner_type name as a child property prefix + Makes it simpler for python users to be able to retrieve children + properties iterating over them. + +2016-05-06 18:21:17 -0300 Thibault Saunier + + * ges/ges-track-element.c: + track-element: gi: skip now deprecated children property getter/setter + Those are implemented with the exact same API at the GESTimelineElement + level now, and user of those APIs with high level languages will get the + exact same API. + +2016-05-06 15:44:28 -0300 Thibault Saunier + + * ges/ges-xml-formatter.c: + formatter: Prefix all children properties in the XML formatter + Otherwise it will fail on properties that are mandatorily prefixed + like the newly added deinterlacing properties + +2016-05-06 15:18:50 -0300 Thibault Saunier + + * ges/ges-group.c: + * ges/ges-internal.h: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + ges: Remove timeline_emit_group_removed which slipped in the API by mistake + This is formally an API break but I am sure no one ever used that and + we should make sure the method is removed as soon as possible because + it has no reason to be exposed. + +2016-04-29 11:36:00 -0300 Thibault Saunier + + * ges/ges-container.c: + container: Handle setting children properties that need prefixing + +2016-04-29 10:29:00 -0300 Thibault Saunier + + * ges/ges-video-source.c: + video-source: Expose deinterlace-[fields, mode, tff] child properties + Letting some control over the deinterlacing to the users + +2016-04-30 18:38:33 +0100 Tim-Philipp Müller + + * ges/ges-effect.h: + * ges/ges-meta-container.c: + * ges/ges-timeline.c: + ges: fix misc g-i annotations + +2016-04-30 18:20:00 +0200 Aurélien Zanelli + + * ges/Makefile.am: + g-i: use only "ges/ges.h" as c-include for introspection + This is the only header which shall be included by user. Otherwise some + language using gir to generate binding, e.g Vala, will includes all + headers files in alphabetical order which causes compilation errors due + to incomplete type. + https://bugzilla.gnome.org/show_bug.cgi?id=765856 + +2016-04-30 16:43:26 +0200 Aurélien Zanelli + + * ges/ges-timeline.c: + timeline: rename "track-element" to "track_element" in select-tracks-for-object documentation + because "track-element" is not a valid identifier for a parameter and + will cause generated binding using GIR to be invalid. For instance in + Vala. + https://bugzilla.gnome.org/show_bug.cgi?id=765853 + +2016-04-29 10:05:10 -0300 Thibault Saunier + + * ges/ges-video-source.c: + video-source: Do not ever plugin avdeinterlace + It is not feature compatible with deinterlace and is not safe to use + +2016-04-28 13:39:41 +0300 Sebastian Dröge + + * ges/ges-types.h: + ges: #include glib.h for G_BEGIN_DECLS + +2016-04-28 13:39:27 +0300 Sebastian Dröge + + * ges/ges-multi-file-source.h: + ges-multi-file-source: Fix case of standard gobject macros + +2016-04-28 13:37:49 +0300 Sebastian Dröge + + * docs/libs/Makefile.am: + * docs/libs/ges-sections.txt: + ges: Add some more functions to the docs and don't scan internal headers + Someone still should look through the unused.txt for more things to add or + hide. + +2016-04-25 15:11:00 +0300 Sebastian Dröge + + * ges/ges-formatter.h: + * ges/ges-internal.h: + * ges/ges-pitivi-formatter.h: + * ges/ges-structured-interface.h: + * ges/ges-types.h: + ges: Add G_BEGIN_DECLS around all relevant declarations in headers + +2016-04-22 16:06:50 -0300 Thibault Saunier + + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + * ges/ges-title-clip.c: + * ges/ges-title-source.c: + * tests/check/ges/titles.c: + title: Do not concider inpoints + It does not make sense for titles + Handle element with no inpoint handling in the timeline + Fixes https://phabricator.freedesktop.org/T7319 + +2016-04-14 10:05:16 +0100 Julien Isorce + + * common: + Automatic update of common submodule + From 6f2d209 to ac2f647 + +2016-04-13 12:32:53 +0300 Sebastian Dröge + + * tests/check/ges/timelineedition.c: + tests: Rename positionner to positioner in the tests too + +2016-04-13 12:31:05 +0300 Sebastian Dröge + + * ges/Makefile.am: + * ges/ges-smart-video-mixer.c: + * ges/ges-source.c: + * ges/ges-video-source.c: + * ges/ges-video-transition.c: + * ges/ges.c: + * ges/gstframepositioner.c: + * ges/gstframepositioner.h: + ges: Fix typo by renaming positionner to positioner + It's fortunately private API + +2016-04-13 12:26:13 +0300 Sebastian Dröge + + * ges/gstframepositionner.c: + framepositionner: Initialize all fields of the meta during initialization + GstMetas are not allocated with all fields initialized to zeroes. + +2016-04-09 21:12:00 -0300 Thibault Saunier + + * ges/ges-track-element.c: + ges: Do not try to set read only properties + When copying and splitting clips + Fixes T7375 + +2016-04-09 18:13:33 -0300 Thibault Saunier + + * ges/ges-video-transition.c: + transition: Lower done some debug loggin level + +2016-03-27 23:40:16 +0200 Thibault Saunier + + * plugins/nle/nlecomposition.c: + Minor fix + +2016-04-01 10:09:39 +0200 Thibault Saunier + + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges-title-source.c: + titlesource: Add support for absolute positionning + +2016-04-04 10:53:13 +0300 Sebastian Dröge + + * tests/.gitignore: + * tests/check/ges/.gitignore: + tests: Add some things to .gitignore + +2016-04-03 18:06:00 +0200 Aurélien Zanelli + + * examples/.gitignore: + examples/gitignore: ignore assets and play_timeline_with_one_clip binaries + https://bugzilla.gnome.org/show_bug.cgi?id=764550 + +2016-03-30 09:26:18 +0200 Edward Hervey + + * ges/ges.c: + GES: Properly split the GOptionGroup initialization + Debugging must be configuring first (before any parsing), and then + the types are initialized at the end. + Fixes issues with debugging categories not being available at the + start + +2016-03-07 08:49:14 +0900 Vineeth TM + + * ges/ges-smart-adder.c: + * ges/ges-smart-video-mixer.c: + * ges/ges-track.c: + * ges/gstframepositionner.c: + * plugins/nle/nlecomposition.c: + * plugins/nle/nleoperation.c: + * plugins/nle/nlesource.c: + * plugins/nle/nleurisource.c: + editing-services: use new gst_element_class_add_static_pad_template() + https://bugzilla.gnome.org/show_bug.cgi?id=763195 + +2016-03-24 13:33:52 +0200 Sebastian Dröge + + * configure.ac: + Back to development + +=== release 1.8.0 === + +2016-03-24 13:05:16 +0200 Sebastian Dröge + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.8.0 + +=== release 1.7.91 === + +2016-03-15 12:33:13 +0200 Sebastian Dröge + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.7.91 + +2016-03-11 17:31:15 +0100 Thibault Saunier + + * examples/c/overlays.c: + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges-title-source.c: + * ges/ges-title-source.h: + Revert "titlesource: use x/yabsolute instead of x/ypos." + This reverts commit c4356db40c6e50f7314a75ea65d46f9f21ef0a5d. + This commit was not ready and was not support to be pushed + +2016-03-11 17:29:08 +0100 Thibault Saunier + + * ges/ges-group.c: + ges: Don't emit timeline::group-removed when ungrouping outside a timeline + +2016-03-11 12:45:37 +0100 Lubosz Sarnecki + + * examples/c/overlays.c: + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges-title-source.c: + * ges/ges-title-source.h: + titlesource: use x/yabsolute instead of x/ypos. + +2016-02-12 19:18:24 +0100 Justin Kim + + * tools/ges-launcher.c: + ges-launcher: don't leak sanitized_timeline string + Summary: + sanitized_timeline is created when parsing command line, + but it isn't free'd. + Reviewers: thiblahute + Differential Revision: https://phabricator.freedesktop.org/D382 + +=== release 1.7.90 === + +2016-03-01 19:09:52 +0200 Sebastian Dröge + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.7.90 + +2016-02-26 21:13:37 +0100 Sjors Gielen + + * plugins/nle/nleoperation.c: + nle: Set the NleOperation flags to NLE_OBJECT_OPERATION + Reviewed By: thiblahute + Differential Revision: https://phabricator.freedesktop.org/D770 + +2016-02-26 20:42:41 +0100 Thibault Saunier + + * ges/ges-effect.c: + ges: Register scaletempo::rate as a rate changing property + +2015-12-20 14:03:57 +0100 Sjors Gielen + + Handle changing playback rate + Before this patch, NLE and GES did not support NleOperations (respectively + GESEffects) that changed the speed/tempo/rate at which the source plays. For + example, the 'pitch' element can make audio play faster or slower. In GES 1.5.90 + and before, an NleOperation containing the pitch element to change the rate (or + tempo) would cause a pipeline state change to PAUSED after that stack; that has + been fixed in 1.5.91 (see #755012 [0]). But even then, in 1.5.91 and later, + NleComposition would send segment events to its NleSources assuming that one + source second is equal to one pipeline second. The resulting early EOS event + (in the case of a source rate higher than 1.0) would cause it to switch stacks + too early, causing confusion in the timeline and spectacularly messed up + output. + This patch fixes that by searching for rate-changing elements in + GESTrackElements such as GESEffects. If such rate-changing elements are found, + their final effect on the playing rate is stored in the corresponding NleObject + as the 'media duration factor', named like this because the 'media duration', + or source duration, of an NleObject can be computed by multiplying the duration + with the media duration factor of that object and its parents (this is called + the 'recursive media duration factor'). For example, a 4-second NleSource with + an NleOperation with a media duration factor of 2.0 will have an 8-second media + duration, which means that for playing 4 seconds in the pipeline, the seek + event sent to it must span 8 seconds of media. (So, the 'duration' of an + NleObject or GES object always refers to its duration in the timeline, not the + media duration.) + To summarize: + * Rate-changing elements are registered in the GESEffectClass (pitch::tempo and + pitch::rate are registered by default); + * GESTimelineElement is responsible for detecting rate-changing elements and + computing the media_duration_factor; + * GESTrackElement is responsible for storing the media_duration_factor in + NleObject; + * NleComposition is responsible for the recursive_media_duration_factor; + * The latter property finally fixes media time computations in NleObject. + NLE and GES tests are included. + [0] https://bugzilla.gnome.org/show_bug.cgi?id=755012 + Differential Revision: https://phabricator.freedesktop.org/D276 + +2016-02-26 12:42:55 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From b64f03f to 6f2d209 + +2016-02-16 12:49:57 +0000 Fabian Orccon + + * ges/ges-group.c: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + group-added and group-removed signals added + Differential Revision: https://phabricator.freedesktop.org/D619 + +2016-02-19 12:38:45 +0200 Sebastian Dröge + + * configure.ac: + Back to development + +=== release 1.7.2 === + +2016-02-19 12:26:27 +0200 Sebastian Dröge + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.7.2 + +2016-02-18 15:26:11 +0000 Julien Isorce + + * pkgconfig/gst-editing-services-uninstalled.pc.in: + uninstalled.pc: add support for non libtool build systems + Currently the .la path is provided which requires to use libtool as + mentioned in the GStreamer manual section-helloworld-compilerun.html. + It is fine as long as the application is built using libtool. + So currently it is not possible to compile a GStreamer application + within gst-uninstalled with CMake or other build system different + than autotools. + This patch allows to do the following in gst-uninstalled env: + gcc test.c -o test $(pkg-config --cflags --libs gstreamer-1.0 \ + gst-editing-services-1.0) + Previously it required to prepend libtool --mode=link + https://bugzilla.gnome.org/show_bug.cgi?id=720778 + +2016-02-09 12:31:10 +0100 Thibault Saunier + + * ges/ges-clip.c: + * tests/check/ges/effects.c: + Fix and test priority of TrackElement after splitting + And make sure we properly handle transitions in that case + +2016-02-09 12:14:15 +0100 Thibault Saunier + + * ges/ges-track-element.c: + ges: Give better names to nleobjects + +2016-02-05 20:02:40 -0300 Thiago Santos + + * tests/check/Makefile.am: + tests: extend the AM_TESTS_ENVIRONMENT from check.mak + To get the CK_DEFAULT_TIMEOUT defined for all tests + https://bugzilla.gnome.org/show_bug.cgi?id=761472 + +2016-02-05 18:11:59 -0300 Thiago Santos + + * autogen.sh: + * common: + Automatic update of common submodule + From 86e4663 to b64f03f + +2016-01-28 13:37:13 +0100 Lubosz Sarnecki + + * ges/ges-title-source.c: + titlesource: Add properties for text dimensions. + +2016-02-02 20:31:13 +0100 Lubosz Sarnecki + + * ges/ges-track-element.c: + trackelement: Make use of read-only children properties. + Read only properties will throw a GLib warning like this + when accessed with "set_child_property": + Warning: g_object_set_property: property 'text-x' of object class 'GstTextOverlay' is not writable + +2016-01-26 12:52:36 +0100 Thibault Saunier + + * plugins/nle/nlecomposition.c: + nle: Turn composition structural issue into ERROR on the bus + Those error are really critical and we are then enable to keep + working. Just post an ERROR message on the bus and let the + application deal with it. + Reviewed-by: Mathieu Duponchelle + Differential Revision: https://phabricator.freedesktop.org/D740 + +2016-01-25 16:11:14 +0100 Thibault Saunier + + * ges/ges-track-element.c: + track-element: Rely on nleobject to be created at construct time + Avoiding all the pending_xx dance and making the code simpler. + This is now possible thanks to the various recent refactoring. + Thanks to that the user is able to set_child_property on objects + that are not in GESTrack yet, as expected. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D739 + +2016-01-25 15:57:22 +0100 Thibault Saunier + + * ges/ges-effect-asset.c: + * ges/ges-effect.c: + * ges/ges-internal.h: + * tests/check/ges/asset.c: + * tests/check/ges/project.c: + effect: Determine the effect type as soon as possible + Making it possible to create the nleobject right at the creation + of the element. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D738 + +2016-01-25 15:51:26 +0100 Thibault Saunier + + * ges/ges-title-clip.c: + * ges/ges-title-source.c: + * tests/check/python/test_clip.py: + title-clip: Return default GESTitleSource value if no child set yet + In get_property we should return the default values if + we have not created any GESTitleSource yet + (instead of segfaulting). + And fix GESTitleSource default values! + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D737 + +2016-01-25 11:56:57 +0100 Thibault Saunier + + * ges/ges-track-element.c: + * ges/gstframepositionner.c: + ges: track-element: Try to create NleObject as soon as possible + This way we have informations about the content of the + children as soon as possible. + Most code paths where already ready to handle that as we use it for + copying clips. + Fix framepositionner to properly handle that (it would have broke + with copied clips before). + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D736 + +2016-01-19 11:22:57 +0100 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Avoid possible crash disposing the timeline + +2016-01-19 11:15:58 +0100 Thibault Saunier + + * ges/Makefile.am: + g-i: fix init section to avoid compiler warnings + +2016-01-06 17:20:20 +0100 Thibault Saunier + + * ges/ges-container.c: + container: Update start if adding a child that as a start < current start + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D629 + +2016-01-06 18:14:07 +0100 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Fix infinite loop on dispose + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D628 + +2016-01-01 11:56:27 +0100 Thibault Saunier + + * ges/ges-audio-source.c: + * ges/ges-audio-uri-source.c: + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-group.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-video-source.c: + * ges/ges-video-uri-source.c: + * ges/gstframepositionner.c: + * tests/check/python/test_clip.py: + * tests/check/python/test_group.py: + group: Make deep copying actually copy deep + Allowing pasting groups paste exactly what had been copied + And not the new version of the contained objects + This technically breaks the C API but this is a new API and I believe + and hope nobody is using it right now. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D616 + +2015-12-22 23:21:44 +0100 Thibault Saunier + + * configure.ac: + * tests/check/Makefile.am: + * tests/check/python/test_group.py: + tests_: Add a simple python copy/paste test for groups + Integrating python tests in the build system + And cleanup configure.ac + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D601 + +2016-01-02 16:15:02 +0100 Thibault Saunier + + * ges/Makefile.am: + Do not install ges-smart-video-mixer.h + it should always have been private + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D617 + +2016-01-06 09:50:39 +0100 Thibault Saunier + + * ges/ges-timeline-element.c: + Revert "timeline-element: Do not consider not serializable elements when getting top element" + This commit was causing issue where we were reporting the toplevel + element as an element but that element was actually in another + not serialized group. That is very tricky to handle for end users + as they are not guaranteed the toplevel clips were actually not + contained in another element. + This reverts commit ceb82ba3028332987d8d5251f98b4896120aa59b. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D627 + +2016-01-09 05:15:47 +0100 Mathieu Duponchelle + + * plugins/nle/nlecomposition.c: + nlecomposition: use correct type for flush_seqnum. + +2016-01-09 05:14:36 +0100 Mathieu Duponchelle + + * plugins/nle/nleghostpad.c: + nleghostpad: use GST_SEGMENT_FORMAT + This isn't 2005 anymore. + +2015-12-29 18:08:03 +0200 Sebastian Dröge + + * ges/ges-asset.c: + ges-asset: Don't dereference NULL proxy assets when resolving fails + CID 1346531 + +2015-12-26 09:43:11 +0100 Sebastian Dröge + + * ges/ges-asset.c: + * ges/ges-extractable.c: + * ges/ges-project.c: + * ges/ges-project.h: + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + ges: Fix various g-i warnings + +2015-12-26 09:43:19 +0100 Sebastian Dröge + + * ges/ges-track-element.c: + ges-track-element: Rename control-binding-reomved signal to control-binding-removed + Strictly speaking an API change but nobody on the Internet seemed to have used + the signal with the typo in the name. + +2015-12-24 15:30:23 +0100 Sebastian Dröge + + * configure.ac: + Back to development + +=== release 1.7.1 === + +2015-12-24 15:07:57 +0100 Sebastian Dröge + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.7.1 + +2015-12-22 09:58:06 +0100 Sebastian Dröge + + * ges/ges-validate.c: + ges-validate: Fix compiler warning caused by usage of wrong enum type + ges-validate.c:237:22: error: implicit conversion from enumeration type + 'GESEdge' to different enumeration type 'GESEditMode' + [-Werror,-Wenum-conversion] + GESEditMode edge = GES_EDGE_NONE; + ~~~~ ^~~~~~~~~~~~~ + ges-validate.c:277:41: error: implicit conversion from enumeration type + 'GESEditMode' to different enumeration type 'GESEdge' + [-Werror,-Wenum-conversion] + new_layer_priority, mode, edge, position))) { + ^~~~ + https://bugzilla.gnome.org/show_bug.cgi?id=759758 + +2015-12-18 13:32:22 +0100 Thibault Saunier + + * ges/ges-timeline-element.c: + timeline-element: Do not consider not serializable elements when getting top element + Those are temporary elements that should not be considered when dealing + with the hierarchy of objects. + Fixes T3455 + +2015-12-17 13:36:42 +0100 Thibault Saunier + + * ges/ges-uri-clip.c: + uri-clip: Copy sources child properties when resetting asset + +2015-03-12 13:57:28 +0100 Thibault Saunier + + * Makefile.am: + * bindings/python/Makefile.am: + * bindings/python/examples/Makefile.am: + * configure.ac: + * examples/.gitignore: + * examples/Makefile.am: + * examples/c/Makefile.am: + * examples/c/assets.c: + * examples/c/concatenate.c: + * examples/c/ges-ui.c: + * examples/c/ges-ui.glade: + * examples/c/multifilesrc.c: + * examples/c/overlays.c: + * examples/c/play_timeline_with_one_clip.c: + * examples/c/simple1.c: + * examples/c/test1.c: + * examples/c/test2.c: + * examples/c/test3.c: + * examples/c/test4.c: + * examples/c/text_properties.c: + * examples/c/thumbnails.c: + * examples/c/transition.c: + * examples/python/simple.py: + * tests/Makefile.am: + examples: Move all examples to the root dir and create foldersdir per language + + Add some markdown files to link between languages + + Add a simple 'play timeline with one clip" example in C and python + +2015-12-21 12:34:56 +0100 Sebastian Dröge + + * configure.ac: + configure: Use -Bsymbolic-functions if available + While this is more useful for libraries, some of our plugins with multiple + files and some internal API can also benefit from this. + +2015-12-11 15:20:53 +0100 Thibault Saunier + + * ges/ges-project.c: + Revert "project: Call asset_added in the first signal emition stage" + This reverts commit 08f927ca68f71530a32846b6da19eac9dc439a2c. + That commit was breaking the API and could break other people's code. + +2015-12-08 12:37:29 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-asset.c: + * ges/ges-asset.h: + asset: Add a way to set asset as "needing reload" + Allowing application to force the asset system to recheck if an + asset has been "fixed" and can be used again + API: + + ges_asset_needs_reload + Differential Revision: https://phabricator.freedesktop.org/D584 + +2015-12-02 11:04:10 +0100 Thibault Saunier + + * ges/ges-project.c: + project: Call asset_added in the first signal emition stage + Differential Revision: https://phabricator.freedesktop.org/D520 + +2015-11-20 23:33:12 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-asset.c: + * ges/ges-asset.h: + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-project.c: + * ges/ges-uri-clip.c: + * ges/ges-xml-formatter.c: + * tests/check/Makefile.am: + * tests/check/ges/asset.c: + Implement asset proxying support + API: + ges_asset_set_proxy + ges_asset_get_proxy + ges_asset_list_proxies + ges_asset_get_proxy_target + Differential Revision: https://phabricator.freedesktop.org/D504 + +2015-12-07 09:11:38 -0500 Nicolas Dufresne + + * autogen.sh: + * common: + Automatic update of common submodule + From b319909 to 86e4663 + +2015-11-26 23:11:36 +0530 Sebastian Dröge + + * Makefile.am: + bash-completion: Disable during "make distcheck" as this requires installing files outside the prefix + automake requires all files to be installed inside the prefix. bash-completion + requires the files to be in a specific directory given by a pkg-config file. + As such those two are having incompatible requirements and we just disable + bash-completion installation for the time being when running "make distcheck". + Nonetheless things like "make install" with e.g. a DESTDIR or a private + installation into a user's directory will fail as in both cases the + bash-completion data would be tried to be installed system-wide. + +2015-11-26 22:42:45 +0530 Sebastian Dröge + + * configure.ac: + Revert "build: fix make distcheck." + This reverts commit 462727d6d825b6e67119e6b8ea47d9e18cc22bdf. + This "fix" broke the build on Windows, where both prefix and datadir are + absolute paths and as such we would concatenate two absolute paths and fail. + +2015-11-21 00:23:02 +0100 Thibault Saunier + + * configure.ac: + * tests/check/Makefile.am: + tests: Properly setup GST_PLUGIN_PATH in test environement + +2015-11-15 00:31:21 +0100 Thibault Saunier + + * Makefile.am: + Dist gst-editing-services.doap + +2015-11-08 22:49:43 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-asset.c: + * ges/ges-asset.h: + asset: Add a method to retrieve the GError of an asset loaded with error + API: + ges_asset_get_error + +2015-11-07 18:21:53 +0100 Thibault Saunier + + * ges/ges-project.c: + * ges/ges-project.h: + project: Add a 'asset-loading' signal + +2015-11-05 11:16:31 +0100 Thibault Saunier + + * .arcconfig: + * ges/ges-audio-source.c: + * ges/ges-smart-adder.c: + ges: Set restriction caps in the audio source caps filter + Otherwise we could have not negotiated errors in audiomixer when + the channel/channel-mask do not match + Differential Revision: https://phabricator.freedesktop.org/D493 + Reviewed-by: Mathieu Duponchelle + +2015-11-04 20:20:10 +0100 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-xml-formatter.c: + formatter: Do not serialize top effect priorities + We just need to make sure they are always serialized in the right + order (which is the case) and de serializing them will lead to the + right behaviour. + We should not serialize the priority as the priority of the source + itself depends on the action having been done on the parent clip, + and we do not serialize the source priorities (and should not, GES + should just do the right thing). + Differential Revision: https://phabricator.freedesktop.org/D491 + +2015-11-04 18:37:34 +0100 Thibault Saunier + + * tools/ges-launcher.c: + launcher: Make sure to not activate validate twice when simply loading a scenario + +2015-10-30 10:52:12 +0100 Thibault Saunier + + * ges/ges-uri-clip.c: + uri-clip: Make sure to instantiate an asset to back GESUriClip-s + +2015-10-21 14:37:26 +0100 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From b99800a to b319909 + +2015-10-20 17:29:42 +0300 Sebastian Dröge + + * configure.ac: + Use new GST_ENABLE_EXTRA_CHECKS #define + https://bugzilla.gnome.org/show_bug.cgi?id=756870 + +2015-10-21 14:28:54 +0300 Sebastian Dröge + + * common: + Automatic update of common submodule + From 9aed1d7 to b99800a + +2015-10-02 22:27:37 +0300 Sebastian Dröge + + * configure.ac: + Update GLib dependency to 2.40.0 + +2015-10-02 16:51:56 +0200 Justin Kim + + * plugins/nle/nlecomposition.c: + nlecomposition: free closure actions when disposing + Summary: + After invoking GClosure, the item of action list becomes + orphan so it lost a chance to be freed. In addition, even + when disposing, the list of actions has few items so we + have to check the list. + Reviewers: thiblahute + Projects: #gstreamer_editing_services + Reviewed By: thiblahute + Differential Revision: https://phabricator.freedesktop.org/D324 + +2015-10-02 16:49:31 +0200 Justin Kim + + * plugins/nle/nlecomposition.c: + nlecomposition: fix wrong argument order of GClosureNotify + Summary: + _free_action should follow GClosureNotify type. + ``` + void + (*GClosureNotify) (gpointer data, + GClosure *closure); + ``` + Reviewers: thiblahute + Projects: #gstreamer_editing_services + Reviewed By: thiblahute + Differential Revision: https://phabricator.freedesktop.org/D323 + +2015-10-02 16:39:31 +0200 Justin Kim + + * ges/ges-track.c: + track: mixing_operation is handled by its parent + Summary: + Normally, mixing_operation is created and added to nlecomposition + as a child element so it will be freed when nlecomposition is removed + from a track. + Reviewers: thiblahute + Projects: #gstreamer_editing_services + Differential Revision: https://phabricator.freedesktop.org/D319 + +2015-10-02 16:11:33 +0200 Justin Kim + + * plugins/nle/nleoperation.c: + nleoperation: don't leak iterator + Summary: Once an iterator is created, it should be freed after usage. + Reviewers: thiblahute + Projects: #gstreamer_editing_services + Reviewed By: thiblahute + Differential Revision: https://phabricator.freedesktop.org/D318 + +2015-10-02 16:10:59 +0200 Justin Kim + + * ges/ges-asset.c: + asset: simplify if-statement in cache_set_loaded + Summary: + Manual iteration can be replaced with foreach function. + In addition, this patch fixes mismatched GFunc type for + g_list_foreach and adds debug cateory for gst-asset for + convenient debugging. + Reviewers: thiblahute + Reviewed By: thiblahute + Differential Revision: https://phabricator.freedesktop.org/D312 + +2015-10-02 16:08:03 +0200 Justin Kim + + * .arcconfig: + * ges/ges-uri-asset.c: + uri-asset: do not reuse a passed GError pointer + Summary: A passed GError is re-allocated when discoverer has no information. + Reviewers: thiblahute + Projects: #gstreamer_editing_services + Reviewed By: thiblahute + Differential Revision: https://phabricator.freedesktop.org/D302 + +2015-10-01 16:26:05 +0200 Justin Kim + + * ges/ges-xml-formatter.c: + xml-formatter: handle dispose properly + Summary: + To dispose properly, a child object should call same function + of parent class. + Reviewers: thiblahute + Differential Revision: https://phabricator.freedesktop.org/D311 + +2015-10-01 16:06:33 +0200 Justin Kim + + * ges/ges-base-xml-formatter.c: + base-xml-formatter: properly handle GFile from wrong uri + Summary: + g_file_new_for_uri never fails so GFile always has valid pointer. + And fix a bug of double unref from D303. + Reviewers: thiblahute + Differential Revision: https://phabricator.freedesktop.org/D310 + +2015-10-01 11:28:38 +0200 Justin Kim + + * ges/ges-audio-track.c: + * ges/ges-base-xml-formatter.c: + * ges/ges-extractable.c: + * ges/ges-multi-file-source.c: + * ges/ges-video-track.c: + * ges/ges-xml-formatter.c: + don't leaks caps and converted strings + Summary: + Valgrind reports trivial leakages related to handling + objects and their converted strings. + Reviewers: thiblahute + Differential Revision: https://phabricator.freedesktop.org/D303 + +2015-09-30 14:50:46 +0200 Mathieu Duponchelle + + * ges/ges-track.c: + track: add gaps when going from READY to PAUSED. + Summary: + The backend commits itself automatically in these cases, so track + needs to do so too. + Reviewers: thiblahute + Reviewed By: thiblahute + Differential Revision: https://phabricator.freedesktop.org/D94 + +2015-08-20 17:16:50 +0900 Vineeth TM + + * tests/examples/multifilesrc.c: + * tests/examples/overlays.c: + * tests/examples/simple1.c: + * tests/examples/test4.c: + * tests/examples/text_properties.c: + * tests/examples/thumbnails.c: + * tests/examples/transition.c: + * tools/ges-launcher.c: + editing-services: Fix memory leaks when context parse fails + When g_option_context_parse fails, context and error variables are not getting free'd + which results in memory leaks. Free'ing the same. + And replacing g_error_free with g_clear_error, which checks if the error being passed + is not NULL and sets the variable to NULL on free'ing. + https://bugzilla.gnome.org/show_bug.cgi?id=753864 + +2015-09-30 17:11:20 +0900 Justin Kim + + * plugins/nle/nleoperation.c: + nleoperation: don't leak srcpad + https://bugzilla.gnome.org/show_bug.cgi?id=755860 + +2015-09-30 17:27:26 +0900 Justin Kim + + * ges/ges-project.c: + project: fix a pointer for error message + https://bugzilla.gnome.org/show_bug.cgi?id=755862 + +2015-09-30 17:26:31 +0900 Justin Kim + + * ges/ges-project.c: + project: don't leak GFileInfo + https://bugzilla.gnome.org/show_bug.cgi?id=755862 + +2015-09-22 01:06:00 +0900 Justin Kim + + * ges/ges-timeline-element.c: + * ges/ges-track.c: + * ges/gstframepositionner.c: + timeline-element,track,framepositionner: don't leak internal object + https://bugzilla.gnome.org/show_bug.cgi?id=755247 + +2015-09-24 01:30:09 +0900 Justin Kim + + * ges/ges-structured-interface.c: + structured-interface: introduce TRY_GET_STRING + TRY_GET uses gst_structure_get. However, if boxed or + string pointer is retrieved by gst_structure_get, + it should be freed properly. + https://bugzilla.gnome.org/show_bug.cgi?id=755480 + +2015-09-24 13:41:30 +0900 Justin Kim + + * ges/ges-uri-asset.c: + uri-asset: don't leak uri string + https://bugzilla.gnome.org/show_bug.cgi?id=755505 + +2015-09-28 15:59:58 +0200 Thibault Saunier + + * ges/ges-title-source.c: + Revert "title-source: Force format with alpha channels out of videotestsrc" + This reverts commit 7d1e1010728a5348674bb9053de6b095cb824984. + This commit was never meant to be committed (at least *not* on master). + +2015-09-28 13:21:11 +0900 Justin Kim + + * ges/ges-structure-parser.c: + * ges/ges-structure-parser.h: + structure-parser: define GES_STRUCTURE_PARSER macro + And fix trivial leakages of internal list structure. + https://bugzilla.gnome.org/show_bug.cgi?id=755716 + +2015-09-27 15:15:10 +0200 Thibault Saunier + + * ges/ges-title-source.c: + title-source: Force format with alpha channels out of videotestsrc + Making sure the user can set a background of the title with an alpha + channel. + Working around https://bugzilla.gnome.org/show_bug.cgi?id=755482 for + the 1.6 branch. + +2015-09-25 12:30:29 +0200 Thibault Saunier + + * configure.ac: + Back to development + +=== release 1.6.0 === + +2015-09-25 12:29:40 +0200 Thibault Saunier + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.6.0 + +2015-09-24 13:21:15 +0200 Thibault Saunier + + * ges/ges-container.c: + * ges/ges-timeline.c: + ges: Avoid emitting 'child-added/removed' when signal emission stops addition + In the GESTimeline, TrackElement addition to a clip might get cancelled + (and thus the element gets removed), we need to make sure users do not + get wrong signals. + Also document the fact that user should connect to container::child-added + with g_signal_connect_after. + +2015-09-22 23:10:35 +0900 Justin Kim + + * plugins/nle/nlecomposition.c: + * plugins/nle/nleobject.c: + nle{composition,object}: remove unused allocation & trivial leakages + nlecomposition allocates unused 'UpdateCompositionData' and it + causes leakages. + https://bugzilla.gnome.org/show_bug.cgi?id=755417 + +2015-09-24 13:40:27 +0900 Justin Kim + + * ges/ges-pipeline.c: + pipeline: don't leak GstPad + https://bugzilla.gnome.org/show_bug.cgi?id=755505 + +2015-09-24 13:42:16 +0900 Justin Kim + + * ges/ges-pitivi-formatter.c: + pitivi-formatter: don't leak internal hash table + https://bugzilla.gnome.org/show_bug.cgi?id=755505 + +2015-09-23 21:23:13 +0200 Thibault Saunier + + * ges/ges-video-transition.c: + video-transition: Make compositor background transparent + Allowing further mixing downstream + +2015-09-23 21:12:33 +0200 Thibault Saunier + + * ges/ges-video-transition.c: + video-transition: Add a framepositioner at the end of the transitio + So downstream compositor knows the zorder of the various streams + +=== release 1.5.91 === + +2015-09-18 18:40:18 +0200 Thibault Saunier + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.5.91 + +2015-09-18 10:01:44 +0200 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-track.c: + * ges/ges-utils.c: + ges: Namespace NLE utils function into GES + Avoiding name clashes when built statically + +2015-09-15 12:17:19 +0200 Thibault Saunier + + * plugins/nle/nlesource.c: + nle: Avoid unsetting srcpad target after the srcpad is already freed + That leaded to segfaults + +2015-09-15 11:08:29 +0200 Thibault Saunier + + * plugins/nle/nleghostpad.c: + nle: Stop wrongly set operation segment base time + Inside the composition we actually do not need to have any notion + of what the timing outside the compositon as we already tweak the segment + base time outside the composition. This code was only there to work + around https://bugzilla.gnome.org/show_bug.cgi?id=753196 + https://bugzilla.gnome.org/show_bug.cgi?id=754893 + +2015-09-11 16:18:46 +0900 Justin Kim + + * plugins/nle/nlecomposition.c: + nlecomposition: don't leak internal hashtable + https://bugzilla.gnome.org/show_bug.cgi?id=754867 + +2015-09-11 16:13:19 +0900 Justin Kim + + * plugins/nle/nleobject.c: + nleobject: don't leak srcpad when disposing + https://bugzilla.gnome.org/show_bug.cgi?id=754867 + +2015-09-11 16:11:40 +0900 Justin Kim + + * ges/ges-timeline.c: + timeline: don't leak pad in private structure + https://bugzilla.gnome.org/show_bug.cgi?id=754867 + +2015-09-11 09:58:56 +0900 Justin Kim + + * tools/ges-launcher.c: + ges-launcher: don't leak GError + https://bugzilla.gnome.org/show_bug.cgi?id=754858 + +2015-09-09 23:32:19 +0900 Justin Kim + + * tools/ges-launcher.c: + ges-launcher: fix double free when argument is invalid + https://bugzilla.gnome.org/show_bug.cgi?id=754783 + +2015-09-04 12:01:16 +0200 Thibault Saunier + + * ges/ges-video-source.c: + video-source: Use the priority being set to compute zorder + +2015-09-02 23:27:16 +0200 Thibault Saunier + + * ges/ges-video-transition.c: + video:transition: Set mixer pad zorder + +2015-09-02 17:58:33 +0200 Thibault Saunier + + * ges/ges-smart-video-mixer.c: + * ges/ges-smart-video-mixer.h: + * ges/ges-video-source.c: + * ges/ges-video-transition.c: + video-source: Make sure to set framepositionner zorder when creating it + And fix a computation bug where we would be having mixing order + reversed between layers. + And make sure that the positionner does not mix up Transition handling + of the zorder + +2015-08-27 16:28:42 +0200 Thibault Saunier + + * ges/ges-video-source.c: + * ges/gstframepositionner.c: + video-source: Simply set framepositionner->zorder = self->priority + Summary: + Making the code simpler and handling the transition case + where elements are in the same layer (which was failing + /setting same zorders until now). + Reviewers: Mathieu_Du + Differential Revision: https://phabricator.freedesktop.org/D237 + +2015-08-23 01:35:18 +1000 Jan Schmidt + + * configure.ac: + Use standard GST_PLUGIN_LDFLAGS for the nle plugin + Add the standard GST_PLUGIN_LDFLAGS to the configure.ac file. + +2015-08-21 21:25:27 +0200 Thibault Saunier + + * configure.ac: + Add support for static plugins builds + +=== release 1.5.90 === + +2015-08-20 17:55:48 +0200 Thibault Saunier + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.5.90 + +2015-08-19 11:24:11 +0200 Thibault Saunier + + * .arcconfig: + * Makefile.am: + * configure.ac: + * ges/Makefile.am: + * ges/ges-internal.h: + * ges/ges-track.c: + * ges/ges-utils.c: + * ges/ges.c: + * plugins/Makefile.am: + * plugins/nle/.gitignore: + * plugins/nle/Makefile.am: + * plugins/nle/gnlmarshal.list: + * plugins/nle/gstnle.c: + * plugins/nle/nle.h: + * plugins/nle/nlecomposition.c: + * plugins/nle/nlecomposition.h: + * plugins/nle/nleghostpad.c: + * plugins/nle/nleghostpad.h: + * plugins/nle/nleobject.c: + * plugins/nle/nleobject.h: + * plugins/nle/nleoperation.c: + * plugins/nle/nleoperation.h: + * plugins/nle/nlesource.c: + * plugins/nle/nlesource.h: + * plugins/nle/nletypes.h: + * plugins/nle/nleurisource.c: + * plugins/nle/nleurisource.h: + Move NLE to a dedicated GstPlugin + Summary: Allowing external user to directly use it + Reviewers: Mathieu_Du + Differential Revision: https://phabricator.freedesktop.org/D231 + +2015-07-23 11:53:52 +0200 Thibault Saunier + + * ges/nle/nlecomposition.c: + nle: Enhance debug logging + +2015-07-23 11:42:48 +0200 Thibault Saunier + + * ges/ges-structured-interface.c: + * ges/ges-track.c: + ges: Do not leak and uselessly create errors + And avoid parenthesis in GstObject names + +2015-07-23 11:40:57 +0200 Thibault Saunier + + * ges/ges-structured-interface.c: + ges: Fix how we handle layer vs layer-priority in the structured interface + +2015-07-23 11:39:04 +0200 Thibault Saunier + + * tests/check/Makefile.am: + * tests/check/ges/clip.c: + tests: Do not use gst-structured-interface in the tests + It breaks $ make distcheck + +2015-07-16 17:26:04 +0100 Tim-Philipp Müller + + * gst-editing-services.doap: + Update mailing list in doap file + +2015-07-16 10:54:54 +0200 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Remove transitions that can no fit into an auto transition + When activating auto transition mode + +2015-07-16 10:53:17 +0200 Thibault Saunier + + * ges/ges-timeline-element.c: + ges; Minor debug enhancement + +2015-07-13 13:48:40 +0200 Thibault Saunier + + * ges/ges-asset.c: + assets: Avoid deadlock when done initialising asset + Avoid to hold the CACHE lock when setting the GTasks return values. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=752300 + +2015-07-08 18:59:33 +0200 Thibault Saunier + + * ges/nle/nlecomposition.c: + * ges/nle/nleobject.h: + nleobject: Concider objects as 'inactive' when they have a duration == 0 + +2015-07-08 18:59:00 +0200 Thibault Saunier + + * ges/ges-track-element.c: + track-element: Handle the case where we have only one keyframe set when interpollating keyframes + +2015-07-06 10:24:33 +0200 Thibault Saunier + + * ges/ges-asset.c: + * ges/ges-internal.h: + * tests/check/ges/uriclip.c: + asset: Port use of deprecated GSimpleAsyncResult to GTask + +2015-07-03 22:00:08 +0200 Stefan Sauer + + * common: + Automatic update of common submodule + From f74b2df to 9aed1d7 + +2015-07-03 13:49:57 +0200 Thibault Saunier + + * ges/ges-track-element.c: + * tests/check/Makefile.am: + * tests/check/ges/clip.c: + track-element: Fix splitting bindings and add unit tests + +2015-07-01 18:33:39 +0200 Thibault Saunier + + * ges/ges-structured-interface.c: + structured-interface: Better handle CLOCK_TIME type from GstStructures + +2015-06-29 18:04:32 +0200 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-group.c: + * ges/ges-internal.h: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-track-element.c: + element: Implement a paste method + Allowing user to copy paste clips very easily + +2015-06-23 16:11:26 +0200 Thibault Saunier + + * ges/ges-timeline.c: + * ges/gstframepositionner.c: + * tests/check/ges/layer.c: + timeline: Disable movement that lead to 2 transition at a position + We should never let 3 objects to overlap at a same position, for that + we introduce a "rollback" feature and whenever such an editing happens, + we rollback object position to whatever it was before the move. + +2015-06-23 19:19:29 +0200 Thibault Saunier + + * ges/ges-smart-video-mixer.c: + smart-video-mixer: Always keep a ref on the mixer pad + +2015-06-23 13:27:00 +0200 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-container.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-track-element.c: + timeline-element: Add a method to get the TrackType it interacts with + API: + + ges_timeline_element_get_track_types + +2015-06-19 11:08:25 +0200 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-timeline.c: + timeline: Never create transitions between rippled objects + In case of groups, we can have track elements that do not belong + directly to the moved_trackelements but will be moved as others. Never + create transition to all object that have a start > moving group start. + +2015-06-16 17:07:40 +0200 Thibault Saunier + + * ges/ges-smart-video-mixer.c: + * ges/ges-smart-video-mixer.h: + * ges/ges-video-transition.c: + video-transition: Use a SmartMixer as mixer + So that the frame position metas are parsed and taken into account + +2015-06-16 15:02:18 +0200 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-pipeline.c: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + track-element: Add method to remove control binding + API: + ges_track_element_remove_control_binding + +2015-06-16 13:25:32 +0200 Thibault Saunier + + * ges/ges-track-element.c: + * ges/ges-xml-formatter.c: + ges: Handle absolute GstDirectControlBindings + +2015-06-13 18:48:20 +0200 Thibault Saunier + + * ges/ges-smart-video-mixer.c: + * ges/gstframepositionner.c: + * ges/gstframepositionner.h: + framepositionner: Make use of the new CompositorPad.width/height + So that the scaling is done in the compositor and this way we can cleanly interpolate its value + +2015-07-03 09:19:30 +0200 Thibault Saunier + + * ges/ges-timeline.c: + * tests/check/ges/group.c: + * tests/check/ges/timelineedition.c: + timeline: Never change output media time when trimming start + + Fix testsuite + https://bugzilla.gnome.org/show_bug.cgi?id=638802 + +2015-07-03 09:16:50 +0200 Thibault Saunier + + * ges/ges-track-element.c: + * tests/check/ges/timelineedition.c: + track-element: Return right value when editing + We used to always return TRUE which was wrong + + Fix testsuite and remove randomness from the tests + +2015-07-01 17:28:52 +0200 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-pipeline.c: + * ges/ges-track.c: + ges: Do not add a final gap at the end of track while rendering + It is not correct to force a black frame at the end of the rendered + video and it also leads to rendering issue with vpX encoders. + https://bugzilla.gnome.org/show_bug.cgi?id=751510 + +2015-07-01 11:35:42 +0200 Thibault Saunier + + * ges/ges-clip.c: + * ges/nle/nlecomposition.c: + clip: Use container priority offset when setting children prios + Instead of trying to compute it ourself which might lead to wrong + behaviour when moving between layer. + + Make sure that when we reset clip children priority (to make space + for effects,) we update the container knowledge of priority offsets + +2015-06-30 23:13:28 +0200 Thibault Saunier + + * ges/ges-clip.c: + clip: Fix track element priority computation + We were computing the priority offset taking the global MIN_NLE_PRIO + (which is a constant == 2 to make space for the mixing elements) instead + of the layer 'track element' relative priority, leading to very big + offsets on layer with a prio > 0. In the end it leaded to effects having + the same priority as the sources which leads to an undefined behaviour + in NLE. + +2015-06-24 09:06:30 +0200 Thibault Saunier + + * ges/ges-uri-asset.c: + uri-asset: Bump the discoverer timeout to 1 minute + We should by default avoid false timeouts + +2015-06-22 01:52:39 +0200 Thibault Saunier + + * ges/ges-track.c: + track: Give usefull name to compositions + +2015-06-25 11:03:12 +0200 Thibault Saunier + + * ges/nle/nlecomposition.c: + * ges/nle/nleobject.c: + * tests/check/nle/common.c: + nle: Port tests to the "commit" action signals + Now that nle_object_commit symbol is hidden, we can't use it + in the tests. + +2015-06-25 10:32:46 +0200 Thibault Saunier + + * docs/libs/ges-sections.txt: + * docs/random/lifecycle: + * docs/random/scenarios: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + ges: Unbreeak API after renaming of GNL to NLE + +2015-06-25 10:28:41 +0200 Sebastian Dröge + + * ges/ges-auto-transition.h: + * ges/ges-internal.h: + * ges/ges-structure-parser.h: + * ges/gstframepositionner.h: + ges: Hide more symbols of headers that are not installed + +2015-06-25 10:25:48 +0200 Sebastian Dröge + + * ges/nle/nlecomposition.c: + nle: Remove unused function + nle/nlecomposition.c:2471:1: error: unused function '_parent_or_priority_changed' [-Werror,-Wunused-function] + _parent_or_priority_changed (NleObject * obj, GNode * oldnode, + ^ + +2015-06-25 10:24:13 +0200 Sebastian Dröge + + * configure.ac: + * ges/nle/nlecomposition.h: + * ges/nle/nleghostpad.h: + * ges/nle/nleobject.h: + * ges/nle/nleoperation.h: + * ges/nle/nlesource.h: + * ges/nle/nleurisource.h: + nle: Hide away symbols, they're supposed to be internal + +2015-06-24 17:55:22 +0200 Thibault Saunier + + * configure.ac: + Back to development + +=== release 1.5.2 === + +2015-06-24 17:44:04 +0200 Thibault Saunier + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.5.2 + +2015-06-23 09:41:01 +0100 Tim-Philipp Müller + + * ges/nle/nlesource.c: + nlesource: remove outdated comment + +2015-06-16 17:50:38 -0400 Nicolas Dufresne + + * common: + Automatic update of common submodule + From 6015d26 to f74b2df + +2015-06-10 17:54:20 +0200 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Handle rendering with disabled tracks + Summary: + The user might want to render only some media type of the timeline, + for example he wants to only render the audio part of the timeline. + It was failing as we were not connecting the track but were still trying + to 'render' it. + Depends on D153 + Reviewers: Mathieu_Du + Reviewed By: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D154 + +2015-06-09 21:00:44 +0200 Thibault Saunier + + * ges/ges-uri-asset.c: + * tools/ges-launcher.c: + ges: Raise an error when the discoverer returns != RESULT_OK + And do not try to run the pipeline when that happens + +2015-06-09 20:58:00 +0200 Thibault Saunier + + * ges/ges-uri-asset.c: + uri-asset: Add a way to control discoverer timeout through envvar + Making it possible to run ges-launch test under valgrind for example + +2015-06-09 12:23:59 +0100 Tim-Philipp Müller + + * tools/ges-validate.c: + ges-launch: don't print random position/duration values at startup + +2015-06-09 11:30:59 +0200 Edward Hervey + + * common: + Automatic update of common submodule + From d9a3353 to 6015d26 + +2015-06-08 23:08:40 +0200 Stefan Sauer + + * common: + Automatic update of common submodule + From d37af32 to d9a3353 + +2015-06-07 23:07:40 +0200 Stefan Sauer + + * common: + Automatic update of common submodule + From 21ba2e5 to d37af32 + +2015-06-07 17:32:34 +0200 Stefan Sauer + + * common: + Automatic update of common submodule + From c408583 to 21ba2e5 + +2015-06-07 17:16:53 +0200 Stefan Sauer + + * autogen.sh: + * common: + Automatic update of common submodule + From d676993 to c408583 + +2015-06-05 19:59:08 +0200 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Never snap end when rippling + http://phabricator.freedesktop.org/T74 + +2015-06-05 19:58:16 +0200 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Never create transition between elements inside the moving context + http://phabricator.freedesktop.org/T74 + +2015-06-05 18:49:51 +0200 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-group.c: + * ges/ges-group.h: + group: Disconnect from old layer notify::priority when a clip is moved to a NULL layer + This means we need to properly track the layer a clip was in. We now + keep track of the various signal IDs in a dedicated structure and + keep a ref on the layer an object is in. + http://phabricator.freedesktop.org/T88 + +2015-06-03 14:56:11 +0200 Thibault Saunier + + * tools/ges-launcher.c: + * tools/ges-validate.c: + tools: Exit the app as it is a simgle instance app + And force exiting GstValidate when wanted + +2015-06-01 13:05:25 +0100 Luis de Bethencourt + + * ges/ges-structured-interface.c: + ges: remove dead code + Summary: + No need to recheck if error exists since it has already been checked by the + conditional above. + Coverity CID #1302832 + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D200 + +2015-05-31 14:16:05 +0200 Thibault Saunier + + * ges/ges-auto-transition.c: + * ges/ges-auto-transition.h: + * ges/ges-clip.c: + * ges/ges-timeline.c: + * tests/check/ges/layer.c: + ges: Handle trimming auto transitions + Meaning trimming neighbors. + + And add a test + +2015-05-29 15:15:25 +0200 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Use a simple GList to track auto transitions + +2015-01-12 13:05:30 +0100 Thibault Saunier + + * ges/nle/nlecomposition.c: + nlecomposition: Do not fail when removing/adding child without commiting + Summary: + We use to end up removing the nleobject when the following case happened: + * add an object + * remove that object + * re add the object + * commit the composition + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D193 + +2015-05-19 18:18:30 +0200 Thibault Saunier + + * ges/ges-layer.c: + timeline: Minor documentation addition + +2015-05-18 21:24:25 +0200 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-formatter.c: + * ges/ges-meta-container.h: + * ges/ges-xml-formatter.c: + * tests/check/ges/project.c: + * tests/check/ges/test-project.xges: + * tests/check/ges/test-utils.c: + ges: Enhance xges format versioning + Summary: + Handle the fact that some new features can be added and that means + generated files will not be fully understandable by older versions of + the formatter. + Make sure that we set the format version to 0.2 when we serialize the + GstEncodingProfile.enabled property. + Add some tests around that. + + Fix a minor bug in the test-utils + + Add a meta on the projects to tell in what format version a project + has been serialized/parsed back + API: + GES_META_FORMAT_VERSION + Depends on D178 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D184 + +2015-05-14 11:12:20 +0200 Thibault Saunier + + * ges/ges-structured-interface.c: + ges: If last added clip is not in a layer, get the first layer + Summary: + In case we just removed it from its layer, make sure to + just use the first layer when none specified. + Depends on D177 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D178 + +2015-05-14 11:11:44 +0200 Thibault Saunier + + * ges/ges-structured-interface.c: + * ges/ges-validate.c: + ges: Fix some error settings + Summary: Depends on D176 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D177 + +2015-05-14 11:10:15 +0200 Thibault Saunier + + * ges/ges-structured-interface.c: + ges:structured-interface: Use GET_AND_CHECK in more places + Summary: + Giving more details about the issue to the user + Depends on D151 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D176 + +2015-05-07 10:52:18 +0200 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-xml-formatter.c: + xml-formatter: De/serialize whether encoding profiles are enabled or not + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D151 + +2015-04-26 18:22:40 +0100 Tim-Philipp Müller + + * Android.mk: + * ges/Makefile.am: + * tools/Makefile.am: + Remove obsolete Android build cruft + This is not needed any longer. + +2015-04-23 20:20:29 +0100 Tim-Philipp Müller + + * .gitignore: + Update .gitignore + +2015-04-22 15:07:58 +0200 Edward Hervey + + * tools/utils.c: + tools: Fix string leak + Only allocate the return string when we know we are going to return + it. + Coverity CID #1292292 + +2015-04-22 10:39:25 +0200 Sebastian Dröge + + * INSTALL: + Remove INSTALL file + autotools automatically generate this, and when using different versions + for autogen.sh there will always be changes to a file tracked by git. + +2015-04-21 11:24:38 +0200 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + ges:xml-formatter: Call g_markup_parse_context_end_parse + Summary: + Otherwise the parser context will never know that is all the XML it + will receive and fail out if the XML document is not valid (in that + case if it does not end) + https://bugzilla.gnome.org/show_bug.cgi?id=746354 + Reviewers: Mathieu_Du + Reviewed By: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D38 + +2015-04-20 17:42:44 +0200 Thibault Saunier + + * ges/ges-timeline.c: + ges: Add debug output when get_element returns NULL + +2015-04-15 12:18:15 +0200 Thibault Saunier + + * tools/ges-launcher.c: + tools:launch: Print out the timeline description as an INFO not an ERROR + +2015-04-15 12:18:15 +0200 Thibault Saunier + + * tools/ges-launcher.c: + tools:launch: clean user facing message on wrong timeline description + Summary: + Before: + $ ../gst-editing-services/tools/ges-launch-1.0 -p + 0:00:00.028629728 8155 0x17e1b60 ERROR default ges-launcher.c:214:_create_timeline: serialized timeline is -p + ** (lt-ges-launch-1.0:8155): ERROR **: Could not create timeline, error: Could not find a suitable formatter + [1] 8155 trace trap (core dumped) ../gst-editing-services/tools/ges-launch-1.0 -p + $ + After: + $ GST_DEBUG=0 ges-launch-1.0 -p + ERROR: Could not create timeline, error: Could not find a suitable formatter + $ + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D95 + +2015-04-08 23:33:27 +0200 Mathieu Duponchelle + + * ges/nle/nleobject.c: + * tests/check/nle/common.c: + nleobject: It is wrong to update object->stop in set_property. + Summary: It must only be done when the object is commited. + We can do that in constructed though, as the changes will + anyway be commited when the object is added to a composition. + Also update the tests, as we set properties spearately then + check the stop, we can commit the source at its creation without + removing meaning from the tests. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D84 + +2015-04-08 21:38:48 +0200 Mathieu Duponchelle + + * docs/libs/ges-sections.txt: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-track.c: + timeline, track: Emit commited at the correct moment. + Summary: + + [API] GESTrack::commited signal. + + [API] ges_track_commit_sync + We were emitting commited when timeline_commit was called, which + wasn't very helpful. This commit makes it so we emit commited once + all the compositions have actually been commited. + We also add a synchronous commit method to spare the user + the need to connect to the signal and wait, and update the + documentation. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D83 + +2015-04-07 22:48:27 +0200 Mathieu Duponchelle + + * ges/ges-layer.c: + layer: call timeline_element_set_timeline in layer_set_timeline. + Summary: + Otherwise if there was still a reference to the layer when it + is removed from the timeline, it fails when the last reference + is released, because timeline_element_set_timeline calls + timeline_remove_element, which tries to remove the element from + an already disposed hashtable. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D82 + +2015-04-08 17:05:19 +0200 Edward Hervey + + * common: + * tests/check/Makefile.am: + tests: Use AM_TESTS_ENVIRONMENT + Needed by the new automake test runner + +2015-04-03 17:38:53 +0200 Mathieu Duponchelle + + * data/completions/ges-launch-1.0: + * ges/ges-command-line-formatter.c: + * ges/ges-structure-parser.c: + * ges/ges-structured-interface.c: + * ges/parse.l: + * tools/ges-launch.c: + ges-launch: Add support for +test-clip + Summary: With the pattern as a mandatory argument. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D68 + +2015-04-03 16:48:03 +0200 Mathieu Duponchelle + + * ges/ges-project.c: + ges-project: Surface a meaningful error when no suitable formatter. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D67 + +2015-04-03 15:35:54 +0200 Mathieu Duponchelle + + * tools/ges-launcher.c: + ges-launch: Add a save-only option. + Summary: + Allows to serialize the timeline without playing it back. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D66 + +2015-04-03 18:58:32 +0100 Tim-Philipp Müller + + * autogen.sh: + * common: + Automatic update of common submodule + From bc76a8b to c8fb372 + +2015-03-31 14:26:19 +0200 Mathieu Duponchelle + + * ges/ges-timeline-element.c: + timeline-element: Disconnect child properties handlers. + Summary: + + And freeze notifies while doing so. + We had a race with GstController which isn't MT safe, we can + fix it by propertly disconnecting signals, and making sure + no notifies are emitted while doing so. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D64 + +2015-03-30 18:41:11 +0200 Mathieu Duponchelle + + * ges/ges-track.c: + track: Set any caps features on tmpcaps. + Summary: Before checking if we have a specific constructor for a track type. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D63 + +2015-03-31 15:29:49 +0200 Mathieu Duponchelle + + * ges/ges-pipeline.c: + pipeline: no reason to disconnect a pad that is NULL anyway. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D62 + +2015-03-25 15:43:16 +0100 Mathieu Duponchelle + + * tests/validate/geslaunch.py: + * tools/ges-launcher.c: + ges-launch: Better document options. + + Sort them by topic + + remove --sample-paths and --sample-paths-recurse. + http://phabricator.freedesktop.org/D58 + +2015-03-24 14:13:54 +0100 Mathieu Duponchelle + + * tools/Makefile.am: + * tools/ges-launch.c: + * tools/ges-launcher.c: + * tools/ges-launcher.h: + * tools/ges-validate.c: + * tools/ges-validate.h: + * tools/utils.c: + * tools/utils.h: + ges-launch: port to GApplication + Summary: + Extract some utility functions. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D55 + +2015-03-25 12:25:54 +0100 Mathieu Duponchelle + + * ges/Makefile.am: + build: no reason to introspect nodist sources. + Summary: g-ir-scanner was erroring like crazy on the generated sources. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D57 + +2015-03-25 12:22:43 +0100 Mathieu Duponchelle + + * ges/ges-pitivi-formatter.c: + ges-pitivi-formatter: Don't flood stdout with alarming conclusions. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D56 + +2015-03-23 12:27:56 +0100 Mathieu Duponchelle + + * data/completions/ges-launch-1.0: + completions: port to new base gst script + +2015-03-24 17:13:20 +0100 Mathieu Duponchelle + + * data/completions/ges-launch-1.0: + completions: Fix completions after the first command. + +2015-03-24 13:01:39 +0100 Mathieu Duponchelle + + * tools/ges-launch.c: + Revert "ges-launch: no need for a tmp string pointer" + This reverts commit 44a0924c1f6b07f0c91ee8bd03d3ae5d97da92d5. + There indeed is a need for a tmp string pointer. + +2015-03-24 11:21:08 +0000 Luis de Bethencourt + + * tools/ges-launch.c: + ges-launch: no need for a tmp string pointer + +2015-03-24 11:19:09 +0000 Luis de Bethencourt + + * tools/ges-launch.c: + ges-launch: free string before going out of scope + CID #1291632 + +2015-02-26 17:08:43 +0100 Mathieu Duponchelle + + * ges/Makefile.am: + build: fix make distcheck. + +2015-03-17 18:25:02 +0100 Thibault Saunier + + * ges/ges-project.c: + * ges/ges-structured-interface.c: + ges: Do not clear potentially NULL errors + And avoid dereferencing NULL errors + +2015-03-13 12:02:30 +0000 Thibault Saunier + + * data/completions/ges-launch-1.0: + * ges/ges-structure-parser.c: + * ges/parse.l: + ges-launch: Prefix clip, transition and effect instruction with a + + Slightly changing the CLI so that we have indicators of the timeline + commands adding new objects. + +2015-03-01 13:10:55 +0100 Thibault Saunier + + * ges/ges-structured-interface.c: + ges: Accept path as URI in the create clip structured interface + +2015-02-26 13:49:23 +0100 Mathieu Duponchelle + + * tools/ges-launch.c: + ges-launch: Remove useless options, rename some short options. + +2015-02-26 13:19:25 +0100 Mathieu Duponchelle + + * data/completions/ges-launch-1.0: + * tools/ges-launch.c: + bash-completion: Add support for new ges-launch commands. + +2015-02-25 18:01:38 +0100 Mathieu Duponchelle + + * ges/ges-structure-parser.c: + * ges/parse.l: + parse.l: Modify command arguments. + + --clip uri=file:// becomes clip file:// for example. + +2015-02-23 17:41:59 +0100 Thibault Saunier + + * ges/ges-command-line-formatter.c: + * ges/ges-structure-parser.c: + * ges/ges-structure-parser.h: + * ges/ges-structured-interface.c: + * ges/ges-structured-interface.h: + * ges/ges-validate.c: + * tools/ges-launch.c: + ges: command-line-formatter: Properly error out on invalid arguments + +2015-02-23 14:48:18 +0100 Thibault Saunier + + * ges/Makefile.am: + * ges/ges-command-line-formatter.c: + * ges/ges-command-line-formatter.h: + * ges/ges-formatter.c: + * ges/ges-formatter.h: + * ges/ges-internal.h: + * ges/ges-project.c: + * ges/ges-project.h: + * ges/ges-structure-parser.c: + * ges/ges-structure-parser.h: + * ges/ges-structured-interface.h: + * ges/ges.c: + * ges/ges.h: + * ges/parse.l: + * tools/Makefile.am: + * tools/ges-launch.c: + ges: Factor out a GESCommandLineFormatter class + This formatter will allow any user to deserialize a timeline using + the new ges-launch command line interface + +2015-02-23 00:53:14 +0100 Mathieu Duponchelle + + * ges/ges-structured-interface.c: + * ges/ges-validate.c: + structured-interface: Be clever when no layer priority specified. + And add the new element to the same layer as the last clip that + was added, insted of adding to the last layer of the timeline + (and with the current code, actually adding a new layer each time) + +2015-02-21 15:30:57 +0100 Mathieu Duponchelle + + * ges/ges-structured-interface.c: + ges-structured-interface: fix build + +2015-02-20 12:26:54 +0100 Thibault Saunier + + * ges/ges-structured-interface.c: + ges: Automatically put clips at the end of layer if no start specified + In the 'structured' interface we should add it at the end of the layer + And make use of the new ges_timeline_get_layer API + +2015-02-19 19:29:36 +0100 Mathieu Duponchelle + + * ges/ges-structured-interface.c: + * tools/ges-launch.c: + structure-interface: rename layer-priority to layer. + + And add a short name for it in ges-launch. + +2015-02-20 12:12:52 +0100 Thibault Saunier + + * ges/ges-structured-interface.c: + ges: Add layer up to the wanted layer priority in the structure interface + Making the thing easier to use + +2015-02-19 19:16:44 +0100 Mathieu Duponchelle + + * tools/ges-structure-parser.c: + * tools/parse.l: + ges-launch: parse property names longer than 1 char. + + And finish the previous structure when encountering a setter. + +2015-02-19 13:15:25 +0100 Mathieu Duponchelle + + * tools/ges-structure-parser.c: + * tools/ges-structure-parser.h: + * tools/parse.l: + ges-launch: Update lexer / parser to handle set-* + + cleanup of the lexer + +2015-02-19 18:28:41 +0100 Thibault Saunier + + * tools/ges-launch.c: + * tools/ges-structure-parser.c: + tools: Implement a new CLI interface for the timeline creation + +2015-02-19 12:34:21 +0100 Thibault Saunier + + * ges/ges-structured-interface.c: + ges: Handle setting child property on container directly in the structured based interface + +2015-02-19 11:28:48 +0100 Thibault Saunier + + * tools/parse.l: + launcher: Add support to --set-property in the parser + +2015-02-19 08:51:20 +0100 Mathieu Duponchelle + + * .gitignore: + * configure.ac: + * tools/Makefile.am: + * tools/ges-launch.c: + * tools/ges-structure-parser.c: + * tools/ges-structure-parser.h: + * tools/parse.l: + ges-launch: Implement a new parser for the commandline. + Summary: + flex-based lexing and manual simplistic parsing. + Test Plan: Use that stuff to make awesome things, see if it breaks. + +2015-02-17 23:48:12 +0100 Thibault Saunier + + * ges/Makefile.am: + * ges/ges-structured-interface.c: + * ges/ges-structured-interface.h: + * ges/ges-validate.c: + ges: Add an internal GstStructure based interface + To be use by GstValidate action and ges-launch + Reviewers: Mathieu_Du, thiblahute + Differential Revision: http://phabricator.freedesktop.org/D42 + +2015-03-19 09:32:25 +0100 Thibault Saunier + + * ges/ges-timeline-element.c: + ges: Fix build for older GLib + The return type of g_hash_table_insert changed from void to boolean + +2015-02-19 18:19:44 +0100 Thibault Saunier + + * .arcconfig: + * ges/ges-container.c: + container: implement children property handling + +2015-02-19 16:30:18 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + * tests/check/ges/effects.c: + * tests/check/ges/project.c: + * tests/check/ges/timelineedition.c: + ges: Move the notion of children properties to GESTimelineElement + Summary: + Deprecate the old GESTrackElement children property handling API. + New APIs: + * ges_timeline_element_list_children_properties + * ges_timeline_element_lookup_child + * ges_timeline_element_get_child_property_by_pspec + * ges_timeline_element_get_child_property_valist + * ges_timeline_element_get_child_properties + * ges_timeline_element_set_child_property_valist + * ges_timeline_element_set_child_property_by_pspec + * ges_timeline_element_set_child_properties + * ges_timeline_element_set_child_property + * ges_timeline_element_get_child_property + * ges_timeline_element_add_child_property + * ges_timeline_element_remove_child_property + Deprecated APIs: + * ges_track_element_list_children_properties + * ges_track_element_lookup_child + * ges_track_element_get_child_property_by_pspec + * ges_track_element_get_child_property_valist + * ges_track_element_get_child_properties + * ges_track_element_set_child_property_valist + * ges_track_element_set_child_property_by_pspec + * ges_track_element_set_child_properties + * ges_track_element_set_child_property + * ges_track_element_get_child_property + * ges_track_element_add_child_property + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D40 + +2015-02-20 12:24:49 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + timeline: Add API to find a layer with a specific priority in a timeline + Summary: + API: + * ges_timeline_get_layer + Test Plan: Nan + Reviewers: mathieu.duponchelle + +2015-02-20 12:24:49 +0100 Thibault Saunier + + * .arcconfig: + * docs/libs/ges-sections.txt: + * ges/ges-container.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + * tests/check/ges/effects.c: + * tests/check/ges/project.c: + * tests/check/ges/timelineedition.c: + Revert "ges: Move the notion of children properties to GESTimelineElement" + I got some trouble with + arc land + and I wanted to push the 3 commit coming after this revert as 3 + different commits but they ended up being all squash into one single + commit, which is clearly not cool for later bisecting and blaming. + Reverting that commit and re pushing those 3 commits as they were + supposed to be. + This reverts commit 9fe15ef4354dc1d878dbdec80908ac8541bc6131. + +2015-03-18 20:23:55 +0100 Thibault Saunier + + * .arcconfig: + * docs/libs/ges-sections.txt: + * ges/ges-container.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + * tests/check/ges/effects.c: + * tests/check/ges/project.c: + * tests/check/ges/timelineedition.c: + ges: Move the notion of children properties to GESTimelineElement + Summary: + Deprecate the old GESTrackElement children property handling API. + New APIs: + * ges_timeline_element_list_children_properties + * ges_timeline_element_lookup_child + * ges_timeline_element_get_child_property_by_pspec + * ges_timeline_element_get_child_property_valist + * ges_timeline_element_get_child_properties + * ges_timeline_element_set_child_property_valist + * ges_timeline_element_set_child_property_by_pspec + * ges_timeline_element_set_child_properties + * ges_timeline_element_set_child_property + * ges_timeline_element_get_child_property + * ges_timeline_element_add_child_property + * ges_timeline_element_remove_child_property + Deprecated APIs: + * ges_track_element_list_children_properties + * ges_track_element_lookup_child + * ges_track_element_get_child_property_by_pspec + * ges_track_element_get_child_property_valist + * ges_track_element_get_child_properties + * ges_track_element_set_child_property_valist + * ges_track_element_set_child_property_by_pspec + * ges_track_element_set_child_properties + * ges_track_element_set_child_property + * ges_track_element_get_child_property + * ges_track_element_add_child_property + Reviewers: Mathieu_Du + Reviewed By: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D40 + +2015-03-01 21:13:35 +0100 Thibault Saunier + + * ges/ges-types.h: + ges: Remove all reference to already dead GESSimpleLayer + +2015-03-05 13:53:15 +0000 Luis de Bethencourt + + * ges/ges-project.c: + project: remove unnecessary dereference + g_clear_error() already dereferences the error pointer, no need to manually + check and do it. + CID #1257630 + +2015-03-03 14:26:40 +0000 Luis de Bethencourt + + * tests/examples/test4.c: + examples: check argument is valid + +2015-02-27 01:26:24 +0000 Tim-Philipp Müller + + * ges/ges-base-xml-formatter.c: + ges-base-xml-formatter: fix setting of child properties + Make sure all child properties get set. GstStructureForeachFunc + takes a gboolean return value that decides whether to + continue or not. + +2015-02-27 01:22:39 +0000 Tim-Philipp Müller + + * ges/ges-meta-container.c: + ges-meta-container: fix ges_meta_container_foreach() + Really call function on all metadata inside the container + instead of stopping randomly. GstStructureForeachFunc + takes a gboolean return value. + +2015-02-26 20:14:31 +0000 Tim-Philipp Müller + + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + ges-base-xml-formatter: fix property setting + GstStructureForeachFunc has a gboolean return value, + and the foreach function will stop unless we return + TRUE here. This meant it was potluck whether all + properties in the structure got set or not. + Fixes setting of text overlay clip text property + in particular. + https://bugzilla.gnome.org/show_bug.cgi?id=743874 + +2015-02-24 18:00:34 +0100 Mathieu Duponchelle + + * configure.ac: + build: fix make distcheck. + And install bash-completions in the supplied prefix. + +2015-02-20 15:22:25 +0100 Mathieu Duponchelle + + * Makefile.am: + * configure.ac: + * data/completions/ges-launch-1.0: + ges-launch: enable auto-completion. + Summary: + And be a little smart about it. + Test Plan: New feature, working, not testing bash completion + Reviewers: tsaunier + Differential Revision: http://internal.opencreed.com:8888/D25 + +2015-02-20 13:51:47 +0100 Thibault Saunier + + * ges/ges-track.c: + ges: Always set ANY capsfeatures on tracks caps property + Summary: + We should not restrict the CapsFeatures on the track caps. + If someone want to do such a restriction he should add it to + the restriction caps directly + Test Plan: Run testsuite + Reviewers: mathieu.duponchelle + +2015-02-19 17:33:12 +0100 Mathieu Duponchelle + + * ges/ges-project.c: + ges-project: no need to commit an empty timeline. + Summary: Can lead to deadlocks if the user commits at the same time. + Test Plan: Ran make check, it worked + Reviewers: tsaunier + +2015-02-10 10:29:39 +0000 Luis de Bethencourt + + * ges/ges-track.c: + ges: initialize timeline_duration value + If priv->timeline is False the function does not set any value for + timeline_duration before using it in gap_new (). Initialize the value to aviod + unexpected behaviour. + CID #1268405 + +2015-02-06 10:01:14 +0100 Thibault Saunier + + * configure.ac: + configure: Bump our Gst related dependencies to 1.5.0.1 + +2015-02-04 15:21:55 +0100 Thibault Saunier + + * ges/nle/nlecomposition.c: + nlecomposition: Properly protect the children task + +2015-02-03 12:02:42 +0100 Mathieu Duponchelle + + * ges/ges-track.c: + * ges/nle/nlecomposition.c: + * tests/check/nle/common.c: + * tests/check/nle/nlecomposition.c: + Cleanly handle removing the last object in a composition + The strategy here is to seek at the new end of the composition. And in + GES we always add a 1ns long gap at the end of the tracks so that all + track have the exact same duration, and we have black frames when the + timeline is empty + +2015-02-02 11:57:19 +0100 Thibault Saunier + + * ges/ges-validate.c: + validate: Do not wrongly set clip duration for UriClips + That was making no sense at all.... + +2015-01-12 13:04:16 +0100 Thibault Saunier + + * ges/ges-validate.c: + validate: Properly expose the commit action as ASYNC + +2015-01-27 21:16:05 +0100 Thibault Saunier + + * ges/ges-layer.c: + layer: Remove child from children list before emitting "child-removed" + +2015-01-26 18:25:02 +0000 Luis de Bethencourt + + * ges/nle/nlecomposition.c: + ges: remove useless gpointer variable + gpointer useless is indeed useless since we can use GST_DEBUG_REGISTER_FUNCPTR + to avoid having to store the return of the GST_DEBUG_FUNCPTR registration. + CID #1265771 + +2015-01-26 17:46:36 +0000 Luis de Bethencourt + + * ges/ges-timeline.c: + ges: merge MIN() and MAX() into CLAMP() + Merge the usage of MIN() and MAX() into one CLAMP() function. + CID #1265770 + +2015-01-24 10:54:13 +0100 Thibault Saunier + + * ges/ges.c: + ges: Make sure the GESTextOverlayClip is register on init + So it can be used when de serializing projects containing it. + https://bugzilla.gnome.org/show_bug.cgi?id=743406 + +2015-01-12 16:14:32 +0100 Stefan Sauer + + * common: + Automatic update of common submodule + From f2c6b95 to bc76a8b + +2014-12-18 10:56:54 +0100 Sebastian Dröge + + * common: + Automatic update of common submodule + From ef1ffdc to f2c6b95 + +2014-12-13 15:13:32 +0100 Thibault Saunier + + * tools/ges-launch.c: + tools: Avoid trying to remove a signal handler that has already been removed + +2014-12-12 12:02:41 +0100 Thibault Saunier + + * ges/ges-uri-asset.c: + uri-asset: Check that the newly computed URI exist + No the one we know failed! + +2014-12-10 10:21:16 +0100 Thibault Saunier + + * ges/ges-project.c: + * ges/ges-validate.c: + validate: Add an action type to load a project from its content + +2014-12-06 10:41:25 +0100 Thibault Saunier + + * ges/ges-video-track.c: + Revert "track: [API]: ges_track_update_restriction_caps." + This reverts commit e9544ce1d67da6990f0a1cae75774063ec37be9d. + This commit should never have landed we decided we do not want to do + that. + +2014-12-01 00:38:07 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + timeline: Add a method to easily check if a timeline is empty + API: + + ges_timeline_is_empty + +2014-12-01 00:34:38 +0100 Thibault Saunier + + * ges/ges-group.c: + * ges/ges-timeline.c: + * tests/check/ges/group.c: + ges: Recompute Group priority when one of its clip.layer change priority + And add a unit test for that case where it was previously failing + +2014-11-29 01:12:43 +0100 Thibault Saunier + + * tests/validate/geslaunch.py: + validate: Start using the new testsuite based API from GstValidate + +2014-11-26 00:28:35 +0100 Mathieu Duponchelle + + * ges/ges-video-track.c: + track: [API]: ges_track_update_restriction_caps. + + And specify default restriction caps for audio and video tracks. + + Add ges_track_set_restriction_caps to the sections, it was missing. + https://bugzilla.gnome.org/show_bug.cgi?id=740726 + +2014-11-25 23:35:55 +0100 Thibault Saunier + + * tests/check/Makefile.am: + * tests/check/ges/integration.c: + tests: Remove integration tests, GstValidate is the way forward! + +2014-11-25 19:15:52 +0100 Thibault Saunier + + * tests/validate/geslaunch.py: + * tools/ges-launch.c: + validate: Handle long tests in the TestManager + + Minor bug fixes + +2014-11-25 19:14:59 +0100 Thibault Saunier + + * ges/nle/nlecomposition.c: + nlecomposition: Minor debug enhancements + +2014-11-25 19:13:02 +0100 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Expose playsink::video-filter and playsink::audio-filter + That can be used to add filters at the very end of the pipeline, + and one can think of adding a watchdog element in there for + example. + +2014-11-25 18:46:03 +0100 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Dot not check for chain->tee twice + +2014-11-21 19:53:52 +0100 Thibault Saunier + + * ges/ges-validate.c: + * tools/ges-validate.c: + validate: Avoid depending on not stable APIs + And cleanup includes + +2014-11-21 19:53:36 +0100 Thibault Saunier + + * ges/ges-validate.c: + validate: Move to new action type registration API + +2014-11-16 20:07:24 +0100 Thibault Saunier + + * ges/ges-validate.c: + validate: Add missing action execution printing + +2014-11-16 20:05:25 +0100 Thibault Saunier + + * ges/ges-timeline-element.c: + timeline-element: Properly handle setting name to NULL + +2014-11-04 15:38:05 +0100 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-pipeline.c: + * ges/ges-track.c: + * ges/nle/nlecomposition.c: + nlecomposition: Add a 'query-position' signal + In order to get the precise position of the pipeline, the only + way is to ask the 'application' to query the pipeline position and + use it. + +2014-11-03 12:18:35 +0100 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-project.c: + * ges/ges-project.h: + * ges/ges-uri-asset.h: + * ges/ges.c: + * tools/ges-launch.c: + ges: Keep backward compatibility for relocated assets CLI + Meaning adding an API for user to add relacation URI paths + API: + ges_add_missing_uri_relocation_uri + +2014-11-03 12:17:42 +0100 Thibault Saunier + + * .gitignore: + Add some ignore files to .gitignore + +2014-11-03 11:59:32 +0100 Thibault Saunier + + * ges/ges.c: + * ges/ges.h: + ges: Add a method to pass argc/argv to GES at init time + Allowing user to set configuration actions without using + the GES GOptionGroup + https://bugzilla.gnome.org/show_bug.cgi?id=740717 + +2014-11-03 11:58:30 +0100 Thibault Saunier + + * ges/Makefile.am: + * ges/ges-validate.c: + * ges/ges.h: + * tools/ges-validate.c: + * tools/ges-validate.h: + validate: Expose GES Validate action + So other can also make use of those action outside + ges-launch itself + https://bugzilla.gnome.org/show_bug.cgi?id=740718 + +2014-11-03 11:55:29 +0100 Thibault Saunier + + * ges/ges-uri-asset.c: + uri-asset: Ensure that UriAssets loaded with error are remembered + +2014-11-03 11:54:10 +0100 Thibault Saunier + + * ges/ges-project.c: + * ges/ges-project.h: + project: Add a method to create assets synchronously + This allows to create a add an asset to a project in a + synchronous way. + API: + ges_project_create_asset_sync + https://bugzilla.gnome.org/show_bug.cgi?id=740716 + +2014-11-03 11:51:51 +0100 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-project.c: + * ges/ges-project.h: + * ges/ges.c: + * tools/ges-launch.c: + ges: Add an init option to set media paths for moved assets + Allowing user to easily set a set of paths to look for moved + assets instead of needing the to re implement that logic + over and over. + https://bugzilla.gnome.org/show_bug.cgi?id=740714 + +2014-11-03 11:14:45 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges.c: + * ges/ges.h: + ges: Add a method to get GES GOption group + This allow us to have global options to be + passed as arguments of the program to configure + GES behaviour + API: + ges_init_get_option_group + https://bugzilla.gnome.org/show_bug.cgi?id=740715 + +2014-11-16 16:51:54 +0100 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Disable encoding format renegotiation when rendering + +2014-12-05 22:19:32 +0100 Mathieu Duponchelle + + * tests/check/ges/track.c: + check/ges/track: add forgotten test file. + +2014-11-26 01:08:31 +0100 Mathieu Duponchelle + + * ges/ges-audio-source.c: + * ges/ges-title-source.c: + * ges/ges-video-source.c: + *source: new lines in xml break my parser. + + So I removed them cause I'm clever + https://bugzilla.gnome.org/show_bug.cgi?id=740727 + +2014-11-26 20:34:24 +0100 Mathieu Duponchelle + + * docs/libs/ges-sections.txt: + * ges/ges-audio-track.c: + * ges/ges-track.c: + * ges/ges-track.h: + * tests/check/Makefile.am: + * tests/check/ges/.gitignore: + track: [API]: ges_track_update_restriction_caps. + + And specify default restriction caps for audio tracks. + + Add ges_track_set_restriction_caps to the sections, it + was missing. + https://bugzilla.gnome.org/show_bug.cgi?id=740726 + +2014-11-27 17:13:27 +0100 Edward Hervey + + * common: + Automatic update of common submodule + From 7bb2bce to ef1ffdc + +2014-11-10 17:24:11 +0100 Thibault Saunier + + * tests/check/Makefile.am: + tests: Fix make distcheck + Some xges project are not used anymore and some new appeared, clean + that up in the Makefile.am + +2014-11-10 16:20:29 +0100 Thibault Saunier + + * ges/ges-timeline-element.c: + * ges/ges-xml-formatter.c: + element: Add a property allowing user to avoid serializing TimelineElements on demand + +2014-10-27 16:51:42 +0100 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-group.c: + * ges/ges-internal.h: + * ges/ges-timeline.c: + * ges/ges-xml-formatter.c: + * tests/check/ges/group.c: + * tests/check/ges/project.c: + * tests/check/ges/test-utils.c: + * tests/check/ges/test-utils.h: + xml-formatter: Serialize groups + They were not serialized until now. + That implies several changes: + * Override GESTimelineElement [start, inpoint, duration] properties in + GESGroup to ensure that those properties are not serialized as they + should not be. + * Rename GESBaseXmlContainer->clips field to + GESBaseXmlContainer->containers as the hashtable now contains Groups + https://bugzilla.gnome.org/show_bug.cgi?id=709148 + +2014-11-03 13:06:34 +0100 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Do not relink an already linked track + +2014-11-02 11:46:37 +0100 Thibault Saunier + + * ges/gstframepositionner.c: + * tests/check/ges/timelineedition.c: + frameposition: In case sources have the same size as track, follow track size + For example if the size has been serialized in a file, but the user has + not personalized the size, we want that whenever the restriction caps + change the size, the video should take the size of the track + restriction caps. + We know need to keep track of the current positionner.size even if + setting through caps size changes. + https://bugzilla.gnome.org/show_bug.cgi?id=739527 + +2014-11-01 11:32:16 +0100 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + xml-formatter: Do not forget to set properties on the track elements + We were just setting children properties, even if the propertie to be + set on themselves where properly passed in + https://bugzilla.gnome.org/show_bug.cgi?id=729487 + +2014-11-01 10:34:41 +0100 Thibault Saunier + + * ges/ges-title-source.c: + * ges/ges-title-source.h: + title-source: Expose the shaded-background property + Rework the way we override the background property to avoid trying to + lookup shaded-foreground-color! + https://bugzilla.gnome.org/show_bug.cgi?id=728635 + +2014-11-01 09:47:39 +0100 Thibault Saunier + + * ges/ges-title-source.c: + titlesource: Expose the outline-color property + https://bugzilla.gnome.org/show_bug.cgi?id=728634 + +2014-10-31 11:56:16 +0100 Thibault Saunier + + * tools/ges-launch.c: + tools: launch: Wait for the project to be loaded to activate gst-validate + Otherwize we could have a race where GstValidate actions are launched + even before the project has been loaded + +2014-10-31 11:32:37 +0100 Thibault Saunier + + * tools/ges-launch.c: + tools: Never try to propose same URI when we know it is missing URI + +2014-10-28 18:36:55 +0100 Mathieu Duponchelle + + * ges/ges-pipeline.c: + * ges/ges-timeline.c: + pipeline: connect tracks when added, not only on state change. + + ghost track src pad before calling track added so that + pipeline has a pad to link. + + Remove silly comment. + +2014-10-30 12:36:57 +0100 Thibault Saunier + + * ges/ges-audio-source.c: + * ges/ges-title-source.c: + * ges/ges-video-source.c: + Document known and usable child properties for GESTrackElements subclasses + +2014-10-30 12:38:16 +0100 Thibault Saunier + + * tools/ges-launch.c: + tools:launch: Properly terminate when we get a SIGINT signal + +2014-10-29 13:40:55 +0100 Thibault Saunier + + * ges/ges-title-source.c: + * ges/ges-title-source.h: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + trackelement: Add a lookup_child vmethod + This method can be used for subclass to override the default behaviour + for child lookup. This vmethod can be used for example in the case where + you want the name of a child property to be 'overridden'. + As an example in the GESTitleSource where we have a videotestsrc + which has a 'foreground-color' property that is used in the TitleSource + to set the background color of the title, this vmethod is now used to + tweak the name passed as parameter to rename "background" to + "foreground-backend" making our API understandable. + API: + GESTrackElement::lookup_child + https://bugzilla.gnome.org/show_bug.cgi?id=727880 + +2014-10-29 12:44:17 +0100 Thibault Saunier + + * ges/ges-title-clip.c: + * ges/ges-title-source.c: + title: Deprecate all method related to child properties + The standard way setting children properties is by using the + GESTrackElement::set_child_propery and friend methods + https://bugzilla.gnome.org/show_bug.cgi?id=727880 + +2014-10-29 13:38:13 +0100 Mathieu Duponchelle + + * tools/ges-validate.c: + ges-validate: inform of clip removal. + +2014-10-29 13:25:06 +0100 Mathieu Duponchelle + + * ges/ges-timeline.c: + timeline: connect_after to layer.object_added. + We want the user provided signal handlers to be called before + we add track elements. + +2014-10-28 17:33:09 +0100 Mathieu Duponchelle + + * ges/nle/nlecomposition.c: + * ges/nle/nleobject.c: + nle: Downgrade some INFO to DEBUG. + + makes it more pleasant to read logs in info. + +2014-10-22 13:49:27 +0200 Thibault Saunier + + * ges/nle/nlecomposition.c: + * ges/nle/nleobject.c: + * ges/nle/nleobject.h: + * ges/nle/nleoperation.c: + * ges/nle/nlesource.c: + * tests/check/Makefile.am: + nle: Handle sending SEEK event recursively to our children + Instead of relying on it being implemented in core (as it is currently + not!) + +2014-10-06 12:30:17 +0200 Thibault Saunier + + * tests/check/nle/nlecomposition.c: + tests: Use audiomixer as an audio mixing element + Adder is the past! + +2014-10-01 10:04:53 +0200 Thibault Saunier + + * tools/ges-validate.c: + validate: Implement validate Action type to handle KeyFrames + New action types: + * set-control-binding + * add-keyframe + * remove-keyframe + +2014-10-01 09:54:49 +0200 Thibault Saunier + + * ges/ges-track-element.c: + track-element: Add a signal about added control bindings + API: + GESTrackElement::control-binding-added + +2014-10-01 09:53:44 +0200 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-internal.h: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + * ges/ges-xml-formatter.c: + track-element: Add an API to list all set ControlBinding + API: + ges_track_element_get_all_control_bindings + +2014-09-27 09:59:12 +0200 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-title-clip.c: + * ges/ges-title-source.c: + * ges/ges-xml-formatter.c: + title-source: Properly expose children properties + + Make sure that the TitleClip properties are not serialized anymore as + they are serialized through children properties now. + + Enhance debugging for not serialized properties in GESXmlFormatter. + +2014-09-26 18:28:16 +0200 Mathieu Duponchelle + + * ges/nle/nlecomposition.c: + nlecomposition: update base time before seeking current stack. + There could be a race where the new segments were pushed after + a seek on some / all pads before the operation had had its basetime + updated, and thus incoming segments were tweaked wrongly. + Reproducible with 3 clips composited and multiple seeks, + FIXME hard to validate. + +2013-09-14 01:35:55 +0200 Joris Valette + + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-xml-formatter.c: + formatter: save and load source's children properties + +2013-09-23 18:40:34 +0200 Joris Valette + + * tests/check/ges/project.c: + * tests/check/ges/test-properties.xges: + tests: project: Add children properties check + Rename test_project_add_keyframes into test_project_add_properties + +2014-09-26 18:39:19 +0200 Thibault Saunier + + * ges/ges-project.c: + project: Do not concider adding am already tracked asset as failure + It is not really a failure, just a special case. + +2014-09-26 17:51:14 +0200 Thibault Saunier + + * tools/ges-launch.c: + * tools/ges-validate.c: + ges-validate: Add actions to add/remove object from container + + Add an action to set an asset on a GESTimelineElement + +2014-09-26 17:50:03 +0200 Thibault Saunier + + * tools/ges-validate.c: + ges-validate: Fix edit_container return code + It used to always return FALSE. + + Fix minor leaks + + Do not seek ourself, it is users responsability to seek and + commit these days. + +2014-09-26 17:44:12 +0200 Thibault Saunier + + * ges/ges-extractable.c: + * ges/ges-extractable.h: + * ges/ges-transition-clip.c: + extractable: Make extractable_set_asset return a boolean + WARNING: This is a minor API breakage, it should be harmless + and allows us to let users know whether changing setting the + asset worked or no. + +2014-09-25 17:31:49 +0200 Thibault Saunier + + * tools/ges-validate.c: + ges-validate: Add an action type to set restriction caps on track + +2014-09-25 17:31:05 +0200 Thibault Saunier + + * tools/ges-launch.c: + * tools/ges-validate.c: + * tools/ges-validate.h: + tools: Handle asset relocation for assets from scenario + Allowing us to share scenario and media file! + +2014-09-25 15:30:55 +0200 Thibault Saunier + + * tools/ges-validate.c: + ges-validate: Add support for Layer.auto-transition + + Fix a bug where the mandatory field name for the name of the clip to + remove in remove-clip did not correspond to what we used in the action + (clip-name vs name). + +2014-09-25 14:59:40 +0200 Thibault Saunier + + * tools/ges-validate.c: + ges-validate: Add commit and split-clip action types + And stop commit at the end of other action types, this now + has to be done in the scenario itself. + +2014-09-25 14:57:35 +0200 Thibault Saunier + + * tools/ges-validate.c: + ges-validate: Add start/inpoint/duration params to the add-clip action + +2014-09-25 14:55:15 +0200 Thibault Saunier + + * tools/ges-launch.c: + tools:ges-launch: Save the project at the end of execution + So that changes from scenarios are taken into account + +2014-09-25 14:53:36 +0200 Thibault Saunier + + * ges/nle/nlecomposition.c: + nlecomposition: Fix the get_current_position method + +2014-09-23 15:01:56 +0200 Thibault Saunier + + * ges/nle/nlecomposition.c: + nlecomposition: Wait for a buffer from the new segment to restart task + Avoiding races where we would launch a seek right after a FLUSH_STOP and + before we get a Buffer which would possibly lead to ERROR message when upstream + elements try to push a buffer and check_sticky fails because downstream + is flushing. + +2014-09-22 18:58:43 +0200 Thibault Saunier + + * ges/nle/nlecomposition.c: + nlecomposition: Do useless thing so that the compiler doesn't warn us! + Otherwize we get a gcc warning about useless statements. + +2014-09-19 17:14:51 +0200 Thibault Saunier + + * tools/ges-launch.c: + tools:ges-launch: Do not set pipeline state before the timeline is ready + When we are loading a project + +2014-09-19 17:13:52 +0200 Thibault Saunier + + * ges/nle/nlecomposition.c: + nlecomposition: Update start stop and duration on initialization + So that the composition is usable right after the initialization + +2014-09-19 17:12:18 +0200 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-project.c: + * ges/ges-timeline.c: + * ges/ges-track.c: + ges: Avoid to always commit when a project is loaded + In case we are not in a PLAYING state and the project is loaded, the + only thing that should be done is to fill the gaps and this way when the + composition get to PLAYING, their initialization will be enough to get + everything on track. + +2014-09-19 12:58:26 +0200 Thibault Saunier + + * tests/check/nle/nlecomposition.c: + * tests/check/nle/simple.c: + tests: Use the new gst_check_objects_destroyed_on_unref function + +2014-09-19 12:57:30 +0200 Thibault Saunier + + * configure.ac: + * ges/ges-track.c: + * tests/check/nle/common.c: + Start taking advantage of the fact that NLE is in the same three as GES! + +2014-09-19 12:55:51 +0200 Thibault Saunier + + * ges/nle/nlecomposition.c: + nlecomposition: Fix several leaks + * In the action closure invokation we were alway leaking the composition. + * gst_bin_add will actually take an extra ref since we already gst_object_ref_sink so we + own the object, other call to that method will increase the refcount which means we do + not need to pass an extra ref to the bin. + * We want to ref_sink right when the object is added to the composition, making things + cleaner and simpler to follow in the tests. + +2014-09-19 12:52:45 +0200 Thibault Saunier + + * ges/nle/nlecomposition.c: + nlecomposition: Activate the composition ghostpad to flush downstream + Since commit 060b16ac75ac227d4cfe1db89ccdc4f4b31545ff + "pad: don't accept flush-stop on inactive pads" in -core, the flush_stop event will not be + fowarded downstream in case the pad is not activated. In our case the element is in + READY state, so pads are deactivated. In that commit we simply make sure that the + event can be fowarded downstream + +2014-09-19 12:49:52 +0200 Thibault Saunier + + * ges/nle/nlecomposition.c: + nlecomposition: Restart the main task on FLUSH_STOP + It means stop using a dedicated probe to restart task so that the main probe does not + drop the FLUSH_STOP event before we have a chance to restart the task. (and this is + for sure cleaner/and simpler to read). + +2014-09-19 12:29:28 +0200 Thibault Saunier + + * ges/nle/nlesource.c: + nle:source: Protect the probeid field with the GST_OBJECT_LOCK + Avoiding races where we try to remove a probe on an already destroyed pad. + +2014-09-19 12:28:05 +0200 Thibault Saunier + + * ges/gstframepositionner.c: + framepositionner: Add a weak pointer to the track_source + Avoiding assertions when the object is destroyed. + +2014-08-25 18:11:52 +0200 Mathieu Duponchelle + + * ges/nle/nlesource.c: + nle: Seek gnlsource when prerolled only + Instead of implementing seek on ready all around GStreamer, just + seek in PAUSED, when the source gets 'prerolled'. + +2014-08-28 10:20:24 +0200 Thibault Saunier + + * ges/ges-track.c: + ges-track: Do not set removed object state + It is the composition to handle + +2014-08-20 13:15:30 +0200 Thibault Saunier + + * ges/nle/nlecomposition.c: + nle: Stop using a MainContext avoiding needing one iter per source dispach + Using GClosure to handle the source handling and handle our action + ordering ourselves + https://bugzilla.gnome.org/show_bug.cgi?id=733342 + +2014-10-21 11:01:17 +0200 Thibault Saunier + + * ges/ges-audio-transition.c: + * ges/ges-smart-adder.c: + ges: Use audiomixer instead of adder by default + +2014-10-21 10:59:43 +0200 Thibault Saunier + + * ges/ges-pipeline.c: + * ges/ges-timeline.c: + * ges/ges-track.c: + Port to the new NLE API + Port the timeline, track and pipeline to the new NLE API where + all objects have static src pads. + +2014-08-15 15:48:14 +0200 Thibault Saunier + + * docs/libs/ges-sections.txt: + * docs/random/design: + * docs/random/lifecycle: + * docs/random/scenarios: + * ges/Makefile.am: + * ges/ges-audio-source.c: + * ges/ges-audio-transition.c: + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-internal.h: + * ges/ges-layer.c: + * ges/ges-layer.h: + * ges/ges-operation.c: + * ges/ges-source.c: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + * ges/ges-track.c: + * ges/ges-utils.c: + * ges/ges-video-source.c: + * ges/ges.c: + * ges/nle/.gitignore: + * ges/nle/gnlmarshal.list: + * ges/nle/nle.h: + * ges/nle/nlecomposition.c: + * ges/nle/nlecomposition.h: + * ges/nle/nleghostpad.c: + * ges/nle/nleghostpad.h: + * ges/nle/nleobject.c: + * ges/nle/nleobject.h: + * ges/nle/nleoperation.c: + * ges/nle/nleoperation.h: + * ges/nle/nlesource.c: + * ges/nle/nlesource.h: + * ges/nle/nletypes.h: + * ges/nle/nleurisource.c: + * ges/nle/nleurisource.h: + * gnl/Makefile.am: + * gnl/gnl.c: + * gnl/gnlobject.h: + * tests/check/Makefile.am: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/project.c: + * tests/check/ges/test-utils.h: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + * tests/check/nle/common.c: + * tests/check/nle/common.h: + * tests/check/nle/complex.c: + * tests/check/nle/nlecomposition.c: + * tests/check/nle/nleoperation.c: + * tests/check/nle/nlesource.c: + * tests/check/nle/seek.c: + * tests/check/nle/simple.c: + * tests/examples/transition.c: + Cleanup import of GNL and rename gnl to nle for Non Linear Engine + Conflicts: + ges/ges-track-element.c + gnl/Makefile.am + gnl/common + Conflicts: + ges/ges-internal.h + ges/ges-track.c + ges/ges-utils.c + ges/nle/.gitignore + ges/nle/gnlmarshal.list + ges/nle/nle.h + ges/nle/nlecomposition.c + ges/nle/nlecomposition.h + ges/nle/nleghostpad.c + ges/nle/nleghostpad.h + ges/nle/nleobject.c + ges/nle/nleoperation.c + ges/nle/nleoperation.h + ges/nle/nlesource.c + ges/nle/nlesource.h + ges/nle/nletypes.h + ges/nle/nleurisource.c + ges/nle/nleurisource.h + gnl/Makefile.am + gnl/gnl.c + gnl/gnl.h + gnl/gnl/gnl.h + gnl/gnl/gnlcomposition.c + gnl/gnl/gnlcomposition.h + gnl/gnl/gnlghostpad.c + gnl/gnl/gnlghostpad.h + gnl/gnl/gnlmarshal.list + gnl/gnl/gnlobject.c + gnl/gnl/gnloperation.c + gnl/gnl/gnloperation.h + gnl/gnl/gnlsource.c + gnl/gnl/gnlsource.h + gnl/gnl/gnltypes.h + gnl/gnl/gnlurisource.c + gnl/gnl/gnlurisource.h + gnl/gnlcomposition.c + gnl/gnlcomposition.h + gnl/gnlghostpad.c + gnl/gnlghostpad.h + gnl/gnlmarshal.list + gnl/gnlobject.c + gnl/gnlobject.h + gnl/gnloperation.c + gnl/gnloperation.h + gnl/gnlsource.c + gnl/gnlsource.h + gnl/gnltypes.h + gnl/gnlurisource.c + gnl/gnlurisource.h + gnl/tests/check/gnl/common.c + gnl/tests/check/gnl/common.h + gnl/tests/check/gnl/complex.c + gnl/tests/check/gnl/gnlcomposition.c + gnl/tests/check/gnl/gnloperation.c + gnl/tests/check/gnl/gnlsource.c + gnl/tests/check/gnl/seek.c + gnl/tests/check/gnl/simple.c + tests/check/gnl/common.c + tests/check/gnl/common.h + tests/check/gnl/complex.c + tests/check/gnl/gnlcomposition.c + tests/check/gnl/gnloperation.c + tests/check/gnl/gnlsource.c + tests/check/gnl/seek.c + tests/check/gnl/simple.c + tests/check/nle/common.c + tests/check/nle/common.h + tests/check/nle/complex.c + tests/check/nle/nlecomposition.c + tests/check/nle/nleoperation.c + tests/check/nle/nlesource.c + tests/check/nle/seek.c + tests/check/nle/simple.c + +2014-08-12 14:35:09 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Commit all values before initializing the pipeline + +2014-08-05 15:43:11 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Prevent update sources from being added after seek. + +2014-07-31 16:02:06 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: only flush stop after seek was set to READY. + +2014-07-29 23:41:45 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Also ignore all messages from children tearing them to READY + At that stage elements should not be taken into account anymore. In some + spacial cases they can post ERROR messages (when trying to push sticky + events on flushing pads) on the bus. We actually do not care about those + issues at that exact point. + +2014-07-28 20:24:50 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Do not try to paused the task that could have been stopped + There was a race where we ended up trying to update the pipeline and + stop our children task at the exact moment where we were actually + setting its state to PAUSED. Take the composition lock and make sure + that can't happen + +2014-07-25 10:55:52 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + * tests/check/gnl/gnlcomposition.c: + composition: Post messages on the bus when it updates itself + And properly set the seqnums of those messages so that the application, + parents have the exact information about what is going on and why. + +2014-07-22 18:22:09 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: push flush events on the target. + +2014-07-19 11:41:56 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + * gnl/gnlcomposition.h: + * tests/check/gnl/common.c: + composition: No need for action signal to add and remove objects! + +2014-07-21 16:59:24 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: we're initialized even if update_pipeline returned FALSE + +2014-07-21 16:57:14 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: reset base time to 0 when needed. + +2014-07-21 16:54:46 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Forward flushes on initialization + +2014-07-18 04:04:16 +0200 Mathieu Duponchelle + + * gnl/gnlobject.c: + object: make the check for self commit work in a crappy way. + +2014-07-18 04:01:25 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: use g_main_context_set_dispatches_per_iteration () + Let's hope this gets merged ... + +2014-07-15 15:17:43 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: set next_eos_seqnum when we get seeked too. + Co-Authored by: Thibault Saunier + +2014-07-15 15:47:59 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + * gnl/gnlghostpad.c: + * gnl/gnlobject.c: + * gnl/gnlobject.h: + gnl: Rely on the GstElement to properly handle their seqnums + Actually it is not exactly thread safe to tweak them ourself at the GNL + level. + Co-Authored by: Mathieu Duponchelle + +2014-07-15 15:16:23 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Always return a value in GSourceFuncs + Co-Authored by: Thibault Saunier + +2014-07-15 15:01:59 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Rename _flush_downstream to _have_to_seek_downstrean + Co-Authored by: Mathieu Duponchelle + +2014-07-15 14:59:54 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + * gnl/gnlobject.h: + * gnl/gnlsource.c: + * tests/check/gnl/common.c: + source: Remove cruft code to seek sources + We now seek on ready and thus do not need to do magic trying to seek + the source as soon as possible as we now do it even sooner than soon. + Co-Authored by: Thibault Saunier + +2014-07-15 09:46:03 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Do not remove all sources when stopping task + We only want to remove updates and seek, commits should be kept + Co-Authored by: Mathieu Duponchelle + +2014-07-15 02:37:25 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Don't send flushes downstream on pipeline update. + The code is still a bit redundant in set_current_bin_to_ready, need + to discuss. + Co-Authored by: Thibault Saunier + +2014-07-14 17:52:36 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Remove locking making sure that we manipulate children in right places + Co-Authored by: Mathieu Duponchelle + +2014-07-14 17:47:07 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Avoid a race in PAUSED_TO_READY + as we were using our children list in there without locking them. + Co-Authored by: Thibault Saunier + +2014-07-14 17:18:23 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: No need to reset the composition when going to PAUSED or NULL + Co-Authored by: Mathieu Duponchelle + +2014-07-14 17:10:35 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Make sure we have a peer when we send flushes downstream + And avoid leaks + Co-Authored by: Thibault Saunier + +2014-07-14 17:06:05 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Minor cleanups + +2014-07-14 16:51:56 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Take the objects lock when reseting the composition + Co-Authored by: Mathieu Duponchelle + +2014-07-14 16:47:45 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Remove the reset children method + as it is all already done in _empty_bin () + Co-Authored by: Thibault Saunier + +2014-07-14 16:44:43 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Set children caps only when they are added to the composition + No need to do it again on READY_TO_PAUSED + Co-Authored by: Mathieu Duponchelle + +2014-07-14 16:41:25 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: No need to children state locked anymore + Co-Authored by: Thibault Saunier + +2014-07-14 16:30:35 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: cleanup dispose / finalize + Co-Authored by: Mathieu Duponchelle + +2014-07-14 16:24:46 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: remove children warning drop HACK. + Co-Authored by: Thibault Saunier + +2014-07-14 16:12:00 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: remove now useless notion of GnlCompositionEntry. + Co-Authored by: Mathieu Duponchelle + +2014-07-14 15:50:58 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: cleanup GnlCompositionEntry before its actual removal. + Co-Authored by: Thibault Saunier + +2014-07-14 15:43:04 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: remove now useless prop "deactivated_elements_state". + Co-Authored by: Mathieu Duponchelle + +2014-07-14 15:40:28 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Remove now useless flag "reset_time". + Co-Authored by: Thibault Saunier + +2014-07-14 15:37:51 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Remove now useless flag "stackvalid". + Co-Authored by: Mathieu Duponchelle + +2014-07-14 15:35:47 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: remove now useless "flushing" flag. + Co-Authored by: Thibault Saunier + +2014-07-14 13:36:31 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Do not use 'update' seek for now + This is not working in our new context and the seek do not work at + all when we set seek start to CLOCK_TIME_NONE and type to TYPE_NONE. + Co-Authored by: Mathieu Duponchelle + +2014-07-14 13:35:24 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Add the stack start/stop that has been set in dotfile name + Co-Authored by: Thibault Saunier + +2014-07-14 13:34:25 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Push flush events *downstream* not 'somewhere' :) + Co-Authored by: Mathieu Duponchelle + +2014-07-13 16:59:15 +0200 Mathieu Duponchelle + + * gnl/gnlsource.c: + source: Atomically change the probe ID + Avoiding races where the probe would be removed 2 times + Co-Authored by: Thibault Saunier + +2014-07-13 11:51:51 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Avoid deadlock when setting current bin to ready (on commit or seek) + We need to make sure that between the time we send flush_start/stop and + the time we actually set the bin to READY, no buffer got prerolled again + as it would lead to a deadlock trying to set the bin to READY (while + deactivating the pads, it needs the streaming lock, which would be + taken in that case) + Co-Authored by: Mathieu Duponchelle + +2014-07-12 20:54:55 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Only sync state of current bin when activating new stack. + Co-Authored by: Thibault Saunier + +2014-07-11 21:59:43 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + * tests/check/gnl/complex.c: + * tests/check/gnl/gnlcomposition.c: + * tests/check/gnl/gnloperation.c: + * tests/check/gnl/simple.c: + composition: Start kindergarten task when going to READY. + And stop it when going back to NULL. + Update tests. + Co-Authored by: Mathieu Duponchelle + +2014-07-11 21:58:41 +0200 Mathieu Duponchelle + + * tests/check/gnl/gnlcomposition.c: + composition: add a new failing test for finalize on commit. + Co-Authored by: Thibault Saunier + +2014-07-11 19:13:29 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Make sure to remove all updates when updating the stack + The EOS we received before that became meaningless and thus the + associated GSources should no be triggered + Co-Authored by: Mathieu Duponchelle + +2014-07-11 18:29:17 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Avoid emitting COMMITED like crazy + Co-Authored by: Thibault Saunier + +2014-07-11 18:27:25 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Add an enum to define why we update the stack + Making the code simpler to follow + Co-Authored by: Mathieu Duponchelle + +2014-07-11 17:48:05 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: We are now waiting for caps to restart our task + Co-Authored by: Thibault Saunier + +2014-07-11 17:31:34 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Remove useless functions to add GSources and add debugging + Co-Authored by: Mathieu Duponchelle + +2014-07-11 17:25:44 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Remove DONE fixme + Co-Authored by: Thibault Saunier + +2014-07-11 17:23:29 +0200 Thibault Saunier + + * gnl/gnlghostpad.c: + ghostpad: Do not try to be smarter than possible with seqnum + We can have several CAPS event comming at any time and thuse we will + need to rely on elements to handle their seqnum properly as we can not + do a safe guard at our level + Co-Authored by: Mathieu Duponchelle + +2014-07-11 17:22:24 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Keep our GSources in a list making their thread safe + Co-Authored by: Thibault Saunier + +2014-07-11 16:08:20 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + * gnl/gnlghostpad.c: + composition: Concider the last action as DONE when we get a CAPS or SEGMENT + Co-Authored by: Mathieu Duponchelle + +2014-07-11 15:41:50 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: only forward our flush start / stops + Co-Authored by: Thibault Saunier + +2014-07-11 14:18:58 +0200 Thibault Saunier + + * gnl/gnlghostpad.c: + ghostpad: remove useless debug + Co-Authored by: Mathieu Duponchelle + +2014-07-11 14:17:36 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: cleanup and enhance debug + Bye Bye STRAM START you were a brave little debug we will miss you. + Co-Authored by: Thibault Saunier + +2014-07-11 14:11:21 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: do not flush downstream when updating pipeline ourselves. + Co-Authored by: Mathieu Duponchelle + +2014-07-11 12:20:53 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Set update to TRUE when updating the stack because of EOS + Co-Authored by: Thibault Saunier + +2014-07-10 18:01:32 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Handle seeking current stack while PAUSED + Co-Authored by: Mathieu Duponchelle + +2014-07-10 16:26:48 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Add and enhance some debug + Co-Authored by: Thibault Saunier + +2014-07-10 16:21:31 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Dot the newly created stacks + Co-Authored by: Mathieu Duponchelle + +2014-07-10 16:17:20 +0200 Mathieu Duponchelle + + * gnl/gnlghostpad.c: + gnlghostpad: Add some more debugging and fix mistakes in seqnum handling + Co-Authored by: Thibault Saunier + +2014-07-10 15:48:50 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Factor out a function to seek the current stack + Co-Authored by: Mathieu Duponchelle + +2014-07-10 15:46:19 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Set the seqnum only when receiving the actual seek event + Setting it before calling seek_handling is racy! + Co-Authored by: Thibault Saunier + +2014-07-10 15:45:19 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Set the real_eos_seqnum in a dedicated method + Co-Authored by: Mathieu Duponchelle + +2014-07-10 15:43:26 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Reset the real_eos_seqnum when reseting the composition + Co-Authored by: Thibault Saunier + +2014-07-10 15:42:48 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Release OBJECTS_LOCK when emiting the "commited" + Co-Authored by: Mathieu Duponchelle + +2014-07-10 15:33:23 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Use the private struct directly + Co-Authored by: Thibault Saunier + +2014-07-09 12:51:36 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + * gnl/gnlobject.c: + * gnl/gnlobject.h: + * gnl/gnloperation.c: + * gnl/gnloperation.h: + gnloperation: Totally clear operations when removing emptying current_bin + Removing linked childre leads to weird behaviour, we want to make sure + that all elements are totally clean when they are out the current bin. + Co-Authored by: Mathieu Duponchelle + +2014-07-08 23:25:09 +0200 Mathieu Duponchelle + + * gnl/gnlghostpad.c: + gnlghostpad: Do not forget to set output segment seqnum + Co-Authored by: Thibault Saunier + +2014-07-08 23:00:29 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Fix the build + Co-Authored by: Mathieu Duponchelle + +2014-07-08 22:59:11 +0200 Mathieu Duponchelle + + * tests/check/gnl/seek.c: + tests:composition: Fix some refcounts + Co-Authored by: Thibault Saunier + +2014-07-08 22:58:02 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Relink nodes *after* setting the seqnum + Co-Authored by: Mathieu Duponchelle + +2014-07-08 22:57:22 +0200 Mathieu Duponchelle + + * gnl/gnlghostpad.c: + gnlghostpad: Do not tolerate getting seeked when no target is set + This should not happen anymore + + Do not set twice events seqnums + Co-Authored by: Thibault Saunier + +2014-07-08 22:55:15 +0200 Thibault Saunier + + * gnl/gnlghostpad.c: + gnlghostpad: Add missing seqnum tweaking + Co-Authored by: Mathieu Duponchelle + +2014-07-08 22:54:31 +0200 Mathieu Duponchelle + + * tests/check/gnl/gnloperation.c: + tests: Use compositor instead of videomixer + Co-Authored by: Thibault Saunier + +2014-07-08 22:53:57 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Handle the case were we get an EOS right after a segment + Co-Authored by: Mathieu Duponchelle + +2014-07-08 13:28:57 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Make basic seeking working + Co-Authored by: Thibault Saunier + +2014-07-07 23:28:43 +0200 Thibault Saunier + + * tests/check/gnl/gnloperation.c: + tests: Start fixing operation tests + Co-Authored by: Mathieu Duponchelle + +2014-07-07 23:24:46 +0200 Mathieu Duponchelle + + * tests/check/gnl/gnlcomposition.c: + * tests/check/gnl/simple.c: + tests: Fix composition tests + We can not expect a seek event anymore as we are seeking in READY the elements + themselves + +remove actual sinks + Co-Authored by: Thibault Saunier + +2014-07-07 23:07:15 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Implement the logic to PAUSE the task while executing actions + We need to wait for the pipeline update to be actually finished before we can start another + action. That means that we pause the task until one buffer from the new stack is + outputed. + Co-Authored by: Mathieu Duponchelle + +2014-07-07 23:08:56 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Do not forget to set expandables state to NULL when disposing + Co-Authored by: Thibault Saunier + +2014-07-07 23:01:24 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Factor out a function to commit only the values + Co-Authored by: Mathieu Duponchelle + +2014-07-07 22:58:27 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Get the current position before actually commiting values on commit + Co-Authored by: Thibault Saunier + +2014-07-07 22:54:25 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Add a method that set the current bin to READY + We need to get the stream lock in some conditions, and thuse send + flush event in those cases. + Co-Authored by: Mathieu Duponchelle + +2014-07-07 22:50:34 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Remove useless code + Co-Authored by: Thibault Saunier + +2014-07-07 22:33:09 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Clear the old stack when removing children that where used + Co-Authored by: Mathieu Duponchelle + +2014-07-07 22:27:21 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Remove useless toplevel_seek argiment from activate_new_stack + Co-Authored by: Thibault Saunier + +2014-07-07 22:25:51 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + * gnl/gnlghostpad.c: + * gnl/gnlobject.c: + * gnl/gnlobject.h: + composition: Rework the seqnum logic to avoid races when setting the new stack seqnum + When we were seeking the same stack without a logic that gurantees that we actually + saw the seek with the new seqnum set, we could have ended up with an EOS set with + the right seqnum even if it was actually not the case. + Co-Authored by: Mathieu Duponchelle + +2014-07-07 21:31:01 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Add/Remove children in the same order as they were called + Co-Authored by: Thibault Saunier + +2014-07-07 21:28:28 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Remove useless flush_start argument from the stop_task method + Co-Authored by: Mathieu Duponchelle + +2014-07-06 15:46:22 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Force setting children state to NULL rebfore unrefing them + Co-Authored by: Thibault Saunier + +2014-07-04 11:05:41 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + * gnl/gnlghostpad.c: + * gnl/gnlobject.c: + * gnl/gnlobject.h: + compositition: Check last stack in the children thread + Avoiding to take the OBJECT_LOCK when recieving EOS. The computation is + based on the GstEvent.seqnum to make sure that the EOS we receive + corresponds to the right sequence. + In that patch we tweak seqnums so that they are correctly computed + avoiding to depend on all elements to do it properly as it might pretty + much not be the case! + Co-Authored by: Mathieu Duponchelle + +2014-07-04 11:11:53 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Actiually set current_bin state even when not debugging + Co-Authored by: Thibault Saunier + +2014-07-03 17:42:06 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Set *all* children state when going back to READY and then NULL + Co-Authored by: Mathieu Duponchelle + +2014-07-03 17:36:01 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + * gnl/gnlghostpad.c: + * tests/check/gnl/gnlcomposition.c: + composition: Fix toplevel seek event refcounting + Co-Authored by: Thibault Saunier + +2014-07-03 16:46:21 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + * tests/check/gnl/complex.c: + composition: Teach the composition to seek same stack + Co-Authored by: Thibault Saunier + +2014-07-03 16:44:05 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + * tests/check/gnl/simple.c: + composition: The ref the user gave us is our, and we give another to the bin when needed + Co-Authored by: Mathieu Duponchelle + +2014-07-03 16:41:42 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Rename commit_pipeline_func to commit_func + We do not commit any pipeline, we commit the new state of the composition internals + Co-Authored by: Thibault Saunier + +2014-07-03 14:48:25 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Set the state of the internal bin before removing its children + Otherwize when we set the composition state to READY or NULL we can end + up with children in PAUSED state outside of everything + Co-Authored by: Mathieu Duponchelle + +2014-07-03 14:34:11 +0200 Mathieu Duponchelle + + * tests/check/gnl/common.c: + * tests/check/gnl/simple.c: + tests: Minor cleanup + Co-Authored by: Thibault Saunier + +2014-07-03 14:32:44 +0200 Thibault Saunier + + * gnl/gnlobject.c: + gnlobject: Allow commiting of object that are not inside a composition + Co-Authored by: Mathieu Duponchelle + +2014-07-03 14:32:18 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: Empty current_bin on gnl_composition_reset + Co-Authored by: Thibault Saunier + +2014-07-03 14:31:35 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Remove now useless external_gst_bin_add_remove field + Co-Authored by: Mathieu Duponchelle + +2014-07-03 11:36:20 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + * gnl/gnlsource.c: + composition: Remove now useless pad probes + Co-Authored by: Thibault Saunier + +2014-07-02 21:01:31 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + * gnl/gnlghostpad.c: + * gnl/gnlghostpad.h: + * gnl/gnlobject.c: + * gnl/gnlsource.c: + * tests/check/gnl/complex.c: + * tests/check/gnl/gnlcomposition.c: + * tests/check/gnl/gnloperation.c: + * tests/check/gnl/seek.c: + * tests/check/gnl/simple.c: + composition: Add an internal bin where that contain used children + Co-Authored by: Mathieu Duponchelle + +2014-07-02 17:33:35 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + * tests/check/gnl/gnlcomposition.c: + composition: Add objects to the pending IO list in a GSource + This way we make sure we do not manipulate our children from another + thread than the dedicated one. + +2014-07-01 18:08:32 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Start implementing seeking in a GSource + +2014-06-30 16:21:30 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + * gnl/gnlcomposition.h: + * tests/check/gnl/common.c: + * tests/check/gnl/common.h: + * tests/check/gnl/complex.c: + * tests/check/gnl/gnlcomposition.c: + * tests/check/gnl/gnloperation.c: + * tests/check/gnl/seek.c: + * tests/check/gnl/simple.c: + composition: switch to using an action signal to add and remove objects. + +2014-06-30 16:29:50 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: chain up finalize before clering mcontext_lock. + +2014-06-30 15:12:38 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Initialize the first stack async + +2014-06-29 22:35:34 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + * tests/check/gnl/gnlcomposition.c: + * tests/check/gnl/gnloperation.c: + Finish fixing tests + +2014-06-28 14:44:24 +0200 Mathieu Duponchelle + + * tests/check/gnl/common.c: + tests/common: disconnect commited handler + +2014-06-27 16:12:12 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + * tests/check/gnl/common.c: + * tests/check/gnl/common.h: + * tests/check/gnl/complex.c: + * tests/check/gnl/gnlcomposition.c: + * tests/check/gnl/seek.c: + * tests/check/gnl/simple.c: + composition: Actually commit in on our own thread + Avoiding races + +2014-06-27 17:03:44 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: start and stop task in init and finalize + +2014-06-27 17:01:34 +0200 Mathieu Duponchelle + + * tests/check/gnl/gnlcomposition.c: + tests: unref message correctly + +2014-06-27 16:40:19 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + * gnl/gnlcomposition.h: + composition: home grown task + +2014-06-27 16:12:28 +0200 Mathieu Duponchelle + + * tests/check/gnl/gnlcomposition.c: + Don't be sync silly test + +2014-06-27 15:00:48 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + composition: simplify update_pipeline_func + +2014-06-27 16:26:09 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Reset the srcpad target when removing the toplevelentry + +2014-06-27 12:15:10 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + * tests/check/gnl/simple.c: + composition: Use a GstPad task to run the update pipeline thread + +2014-06-26 23:48:09 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + gnlcomposition: Factor out code to deactivate old stack and activate new one + +2014-06-26 19:00:03 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + composition: Avoid looping using gotos + +2014-06-26 18:41:48 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + gnlcomposition: Use the new _object_block_and_drop_data where appropriate + Renaming block_object_src_pad to _object_block_and_drop_data + +2014-06-25 19:39:29 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + gnlcomposition: factor out some functions + +2014-06-25 19:18:29 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + gnlcomposition: Factor out the condition of _parent_or_priority_changed + +2014-06-25 19:17:55 +0200 Thibault Saunier + + * gnl/gnlcomposition.c: + gnlcomposition: Add a function to block object source pad + +2014-06-24 13:44:13 +0200 Mathieu Duponchelle + + * gnl/gnlcomposition.c: + * gnl/gnlghostpad.c: + * gnl/gnlghostpad.h: + * gnl/gnlobject.c: + * gnl/gnlobject.h: + * gnl/gnloperation.c: + * gnl/gnloperation.h: + * gnl/gnlsource.c: + * tests/check/gnl/complex.c: + * tests/check/gnl/gnlcomposition.c: + * tests/check/gnl/gnloperation.c: + * tests/check/gnl/gnlsource.c: + * tests/check/gnl/seek.c: + * tests/check/gnl/simple.c: + gnl: Add the srcpad directly to GnlObject + Starting from now we will not claim that we support GnlObject that have + several source pads as this is + 1- Not true at all; + 2- the design of priorities in the GnlComposition tree does not allow that; + 3- Not very useful in most of the cases and it complexifies quite a lot the code + in the composition. + Conflicts: + configure.ac + tests/check/Makefile.am + +2014-06-25 15:35:08 +0200 Thibault Saunier + + * gnl/gnlobject.c: + Revert "gnlobject: Commit object in READY_TO_PAUSED" + This causes races when seeking, reverting for now even if we will + probably want to bring something like that back. + This reverts commit 3549e745a8f0de3977b83c60e9b447afaf55d8a0. + +2014-06-24 12:52:24 +0200 Mathieu Duponchelle + + * gnl/gnlsource.c: + * gnl/gnlsource.h: + gnlsource: remove useless "controls_one" field. + +2014-10-21 10:35:48 +0200 Thibault Saunier + + * gnl/Makefile.am: + * gnl/gnl.c: + * gnl/gnl.h: + * gnl/gnlcomposition.c: + * gnl/gnlcomposition.h: + * gnl/gnlghostpad.c: + * gnl/gnlghostpad.h: + * gnl/gnlmarshal.list: + * gnl/gnlobject.c: + * gnl/gnlobject.h: + * gnl/gnloperation.c: + * gnl/gnloperation.h: + * gnl/gnlsource.c: + * gnl/gnlsource.h: + * gnl/gnltypes.h: + * gnl/gnlurisource.c: + * gnl/gnlurisource.h: + * tests/check/gnl/common.c: + * tests/check/gnl/common.h: + * tests/check/gnl/complex.c: + * tests/check/gnl/gnlcomposition.c: + * tests/check/gnl/gnloperation.c: + * tests/check/gnl/gnlsource.c: + * tests/check/gnl/seek.c: + * tests/check/gnl/simple.c: + Import GNL from 978332e7c4c3bba1949421d28b492540ab471450 'Release 1.4.0' + +2014-08-15 18:02:36 +0200 Thibault Saunier + + * tests/check/ges/timelineedition.c: + tests: timelineedition: Init GES once at the beginning. + Avoiding to forget to init in a particular test and failling stupidly + +2014-08-15 18:00:24 +0200 Thibault Saunier + + * tests/check/ges/backgroundsource.c: + test: backgroundsource: Disable tests that rely on nlecomposition internals + We can't rely on that, in particular now that it does not actually + add its children all the time but only when it is needed (and that + it has an internal bin where actual things happen). + +2014-08-13 13:15:02 +0200 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + timeline: Add a method to retrieve a pad from a track + It allows user to easily get the proxied pad from a track. + API: + + ges_timeline_get_pad_for_track + +2014-07-28 15:26:18 +0200 Thibault Saunier + + * tools/ges-launch.c: + tools:launcher: Avoid commiting when we are not done loading the timeline + +2014-07-26 00:31:32 +0200 Thibault Saunier + + * ges/ges-timeline.c: + ges: Use the new GNL element message feature to notify async operations + +2014-07-24 17:55:35 +0200 Mathieu Duponchelle + + * ges/ges-base-xml-formatter.c: + xml-formatter: message-forward is not something that should be parsed. + +2014-07-25 14:47:07 +0200 Mathieu Duponchelle + + * ges/ges-timeline.c: + timeline: handle async start + +2014-10-23 21:46:04 +0200 Mathieu Duponchelle + + * configure.ac: + * tests/Makefile.am: + * tests/validate/Makefile.am: + * tests/validate/geslaunch.py: + * tests/validate/scenarios/Makefile.am: + * tests/validate/scenarios/ges-edit-clip-while-paused.scenario: + tests: implement our validate TestManager. + And make sure it installs alongside the other validate apps. + https://bugzilla.gnome.org/show_bug.cgi?id=739093 + +2014-10-30 15:54:04 +0000 Tim-Philipp Müller + + * ges/ges-smart-video-mixer.c: + smart-video-mixer: unref pads_info hash table in dispose + Before gst_bin_dispose() runs and destroys elements. + +2014-10-30 15:20:18 +0000 Tim-Philipp Müller + + * ges/ges-smart-adder.c: + smart-adder: fix crash in unit test + Unref pads_info hash table in dispose instead of + finalize, i.e. before gst_bin_dispose runs and + destroys pads_info->bin (to which the pads_info + does not hold a ref). + +2014-10-27 18:01:56 +0100 Sebastian Dröge + + * common: + Automatic update of common submodule + From 84d06cd to 7bb2bce + +2014-10-26 20:36:22 +0000 Tim-Philipp Müller + + * ges/ges-track.c: + track: fix indentation + +2014-10-26 20:35:30 +0000 Tim-Philipp Müller + + * ges/ges-timeline-element.c: + timeline-element: don't leak name string + +2014-10-26 20:34:29 +0000 Tim-Philipp Müller + + * ges/ges-timeline.c: + timeline: fix mutex and all_elements hash table leaks + +2014-10-26 20:33:50 +0000 Tim-Philipp Müller + + * ges/ges-timeline.c: + timeline: free tracks obtained via select-tracks-for-object signal + +2014-10-26 20:32:41 +0000 Tim-Philipp Müller + + * ges/ges-timeline.c: + timeline: free track elements list + +2014-10-26 20:31:40 +0000 Tim-Philipp Müller + + * ges/ges-video-test-source.c: + video-test-source: fix caps leak + +2014-10-26 20:31:26 +0000 Tim-Philipp Müller + + * ges/gstframepositionner.c: + framepositionner: fix caps leak + +2014-10-26 20:31:08 +0000 Tim-Philipp Müller + + * ges/ges-audio-track.c: + audiotrack: fix caps leak + +2014-10-26 20:30:53 +0000 Tim-Philipp Müller + + * ges/ges-video-track.c: + videotrack: fix caps leak + +2014-10-26 20:30:29 +0000 Tim-Philipp Müller + + * ges/ges-track.c: + track: don't leak restriction caps + +2014-10-26 20:29:06 +0000 Tim-Philipp Müller + + * ges/ges-smart-adder.c: + smart-adder: don't leak pads_infos hash table + +2014-10-26 20:28:09 +0000 Tim-Philipp Müller + + * ges/ges-smart-video-mixer.c: + smart-video-mixer: don't leak pads_infos hash table + +2014-10-26 20:27:17 +0000 Tim-Philipp Müller + + * ges/ges-project.c: + project: fix string leak + +2014-10-26 20:25:46 +0000 Tim-Philipp Müller + + * ges/ges-meta-container.c: + metacontainer: don't leak GValue contents + +2014-10-26 20:24:09 +0000 Tim-Philipp Müller + + * tests/check/ges/basic.c: + tests: don't leak clips list in basic unit test + +2014-10-26 20:23:26 +0000 Tim-Philipp Müller + + * Makefile.am: + Parallelise 'make check-valgrind' + +2014-10-22 14:15:11 +0200 Mathieu Duponchelle + + * tools/ges-validate.c: + ges-validate: issues naming have changed. + Update the override. + +2014-10-21 13:04:26 +0100 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From a8c8939 to 84d06cd + +2014-10-21 13:01:04 +0200 Stefan Sauer + + * common: + Automatic update of common submodule + From 36388a1 to a8c8939 + +2014-10-20 13:37:25 +0200 Thibault Saunier + + * configure.ac: + Back to development + +=== release 1.4.0 === + +2014-10-20 11:56:36 +0200 Thibault Saunier + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.4.0 + +2014-10-16 14:18:16 +0200 Thibault Saunier + + * tools/ges-validate.c: + validate: Fix naming of add_action_type to register_action_type + That function was just renamed in Validate + +2014-10-12 19:46:59 +0200 Thibault Saunier + + * tools/ges-launch.c: + validate: Rename --list-action-types to --inspect-action-type + Making clearer the meaning of the parameter and closer to + the usual naming in the GStreamer land. + +=== release 1.3.90 === + +2014-09-24 11:07:40 +0200 Thibault Saunier + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 1.3.90 + +2014-09-13 16:16:15 +0100 Tim-Philipp Müller + + * configure.ac: + configure: bump (soft) gst-validate requirement + Won't build against all older git versions with + 0.0.1.0 as version number. + +2014-07-20 11:47:18 +0200 Lubosz Sarnecki + + * tools/ges-launch.c: + ges-launch: option to select encoding profile from xml + https://bugzilla.gnome.org/show_bug.cgi?id=735121 + +2014-07-20 11:47:56 +0200 Lubosz Sarnecki + + * tools/ges-launch.c: + ges-launch: clean up help page + https://bugzilla.gnome.org/show_bug.cgi?id=735121 + +2014-09-05 23:14:10 +0200 Thibault Saunier + + * tools/ges-validate.c: + validate: gst_validate_print_action_types now takes a const gchar ** + +2014-09-05 23:08:41 +0200 Thibault Saunier + + * tools/ges-validate.c: + validate: Add the "ges" as implementer namespace for our action types + +2014-09-05 22:09:44 +0300 Sebastian Dröge + + * tools/ges-launch.c: + ges-launch: Fix typo in --help output + +2014-09-05 22:08:49 +0300 Sebastian Dröge + + * tools/ges-launch.c: + * tools/ges-validate.h: + ges-launch: Fix compiler warnings + ges_validate_print_action_types() takes a const gchar **. + +2014-08-22 21:02:58 +0200 Thibault Saunier + + * tools/ges-validate.c: + ges-validate: Rename edit-clip to edit-container + So it represent better what the action does at the GES level + +2014-08-22 21:01:07 +0200 Thibault Saunier + + * tools/ges-launch.c: + * tools/ges-validate.c: + * tools/ges-validate.h: + ges-validate: Port to the new GstValidate action registration API + +2014-07-24 19:03:50 +0200 Thibault Saunier + + * tools/ges-validate.c: + tools:validate: Concider seek in PAUSED position being not exact as WARNING + In some corner cases in GNL it is totally correct that a position in a + seek in paused is not perfectly exact + +2014-08-08 10:41:48 +0200 Thibault Saunier + + * tools/ges-validate.c: + ges-validate: Let GstValidate handle assert logs + +2014-07-27 15:42:42 +0200 Thibault Saunier + + * tools/ges-launch.c: + * tools/ges-validate.c: + * tools/ges-validate.h: + ges:validate: Port to the new handles-states API + +2014-08-01 10:44:57 +0200 Edward Hervey + + * Makefile.am: + * common: + Makefile: Add usage of build-checks step + Allows building checks without running them + +2014-07-24 13:23:36 +0300 Lazar Claudiu + + * ges/ges-text-overlay.c: + text-overlay: added text properties as child-properties + +2014-06-16 11:38:29 +0200 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-smart-video-mixer.c: + * ges/ges-utils.c: + * ges/ges-video-transition.c: + ges: Use registry to select the compositor element + +2014-07-18 18:27:20 +0200 Mathieu Duponchelle + + * ges/ges-xml-formatter.c: + xml-formatter: Set errno to 0 before g_ascii_strtoll. + +2014-06-12 09:51:02 +0100 Vincent Penquerc'h + + * ges/ges-smart-video-mixer.c: + smart-video-mixer: fix memory leak on error path + Coverity 1212166 + +2014-03-20 17:04:31 +0100 Thibault Saunier + + * ges/ges-track-element.c: + trackelement: Sort paramspec by name in list_children_properties + https://bugzilla.gnome.org/show_bug.cgi?id=720023 + +2014-06-06 12:08:47 +0200 Thibault Saunier + + * tools/ges-launch.c: + tools: Fix transition handling in ges-launch + https://bugzilla.gnome.org/show_bug.cgi?id=730806 + +2014-06-05 04:20:15 +0200 Thibault Saunier + + * ges/ges-clip.c: + * tests/check/ges/clip.c: + * tests/check/ges/test-utils.h: + clip: Fix the ges_clip_set_position function + And enhance the new test + https://bugzilla.gnome.org/show_bug.cgi?id=731248 + +2014-06-05 04:05:06 +0200 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-clip.c: + * ges/ges-clip.h: + clip: Rename top_effect_index to top_effect_index + Keeping the old method to not break the API but removing it from the + documentation as users should use the new method (which is the exact + same with a better naming) + https://bugzilla.gnome.org/show_bug.cgi?id=731248 + +2014-06-05 03:48:12 +0200 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-timeline.c: + * tests/check/ges/clip.c: + tests: Check ges_clip_set_position behaviour + + Minor fix to handle properly the feature when clip is not in any layer + https://bugzilla.gnome.org/show_bug.cgi?id=731248 + +2014-06-04 23:16:42 +0200 Alexandru Băluț + + * tests/check/ges/clip.c: + clip: Add test for effects priorities + https://bugzilla.gnome.org/show_bug.cgi?id=731248 + +2014-06-05 02:16:01 +0200 Thibault Saunier + + * ges/ges-pipeline.c: + * tools/ges-launch.c: + pipeline: Add support to rendering without container + + Simplify the support in ges-launch as we should not require the + profile desc to start with : in that case + https://bugzilla.gnome.org/show_bug.cgi?id=731245 + +2014-05-24 19:16:12 +0200 Christoph Reiter + + * ges/ges-enums.c: + Fix invalid GEnumValue.value_name entries. + This makes the enum entries in the gir have valid c:identifiers and documentation. + https://bugzilla.gnome.org/show_bug.cgi?id=730691 + +2014-06-03 17:53:23 +0200 Mathieu Duponchelle + + * ges/ges-track.c: + ges-track: fill the gaps left empty by deactivated track elements. + +2014-05-21 10:54:19 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From 211fa5f to 1f5d3c3 + +2014-05-19 12:21:52 +0200 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-clip.c: + * ges/ges-clip.h: + * tests/check/ges/clip.c: + ges:clip: Add a method to look for a list of TrackElement-s + + Add unit tests to check it works properly. + API: + + ges_clip_find_track_elements + +2014-05-18 18:34:26 +0200 Thibault Saunier + + * tools/ges-launch.c: + * tools/ges-validate.c: + * tools/ges-validate.h: + tools: Handle request state changes from GstValidate + +2014-05-15 20:44:35 +0200 Thibault Saunier + + * ges/ges-timeline-element.c: + * tests/check/ges/basic.c: + ges: Do not forget to update the count when updating already used name + That could still lead to naming conflicts + +2014-05-15 19:37:05 +0200 Thibault Saunier + + * ges/ges-video-source.c: + ges: Add a queue after the decoder in video test src + +2014-05-14 22:06:55 +0200 Thibault Saunier + + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + * tests/check/ges/basic.c: + ges: Avoid GESTimelineElement naming conflicts + When users (can be formatters) set timeline element names in the + default 'namespace' we need to update our counter to avoid setting + twice the same name on TimelineElements so afterward there is no + problem adding them in the GESTimeline + + add a testcase to check that new code and fix leaks on the + existing testcases. + + Sensibly enhance debugs + +2014-05-13 14:30:39 +0200 Edward Hervey + + * ges/ges-xml-formatter.c: + xml-formatter: Don't leak children_props + By going through the cleanup code-path + CID #1212146 + +2014-05-10 22:52:18 +0200 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * tools/ges-launch.c: + ges: Do not use freed pointers + COVERTY CID 1212182 + COVERTY CID 1212184 + COVERTY CID 1212185 + +2014-05-10 22:48:12 +0200 Thibault Saunier + + * ges/ges-smart-adder.c: + ges: Plug a leak in ges-smart-adder + COVERTY CID 1212166 + +2014-05-10 22:45:34 +0200 Thibault Saunier + + * ges/ges-xml-formatter.c: + ges-xml-formatter: fix memory leak + COVERTY CID 1212148 + +2014-05-10 22:41:23 +0200 Thibault Saunier + + * ges/ges-xml-formatter.c: + ges-xml-formatter: fix memory leak in error path + COVERITY CID 1212147 + +2014-05-10 22:38:21 +0200 Thibault Saunier + + * ges/ges-asset.c: + ges: Assert if an asset is not in the global hashtable + COVERITY CID 1151679 + +2014-05-10 22:33:15 +0200 Thibault Saunier + + * ges/ges-timeline.c: + ges: Remove useless pointer assignement + COVERITY CID: 1139442 + +2014-05-10 22:30:00 +0200 Thibault Saunier + + * ges/ges-meta-container.c: + ges: Remove useless pointer assignement + COVERITY CID: 1139941 + +2014-05-10 22:28:01 +0200 Thibault Saunier + + * ges/ges-meta-container.c: + ges: Add license header in ges-meta-container + +2014-05-10 22:09:31 +0200 Thibault Saunier + + * ges/ges-timeline.c: + ges: remove comparison of unsigned inferior to 0 + COVERITY CID 1139769 + +2014-05-09 13:00:32 +0100 Tim-Philipp Müller + + * ges/ges-xml-formatter.c: + ges-xml-formatter: fix memory leak in error path + CID 1212146 + +2014-05-08 17:21:33 +0200 Thibault Saunier + + * tools/ges-launch.c: + tool: Add the option to set audiosink + And use gst_parse_bin_from_description to create the sinks letting more + control to users. + +2014-05-08 17:11:54 +0200 Mathieu Duponchelle + + * tools/ges-validate.c: + scenarios: update the prototype of ges_validate_activate + if validate is not present. + +2014-05-08 14:12:11 +0200 Mathieu Duponchelle + + * tools/ges-launch.c: + ges-launch: add an option to use a custom video sink + +2014-05-08 01:38:26 +0200 Mathieu Duponchelle + + * tools/ges-validate.c: + validate: make sure we release our ref when we get_timeline. + Conflicts: + tools/ges-validate.c + +2014-05-08 01:15:42 +0200 Mathieu Duponchelle + + * tools/ges-validate.c: + scenarios: Check priority before creating a layer. + +2014-05-06 15:32:18 +0200 Mathieu Duponchelle + + * tools/ges-launch.c: + * tools/ges-validate.c: + * tools/ges-validate.h: + ges-launch: Make it so actions are executed directly when needed. + +2014-05-02 17:11:24 +0200 Mathieu Duponchelle + + * tools/ges-validate.c: + validate: implement remove / add clip actions + And a helper to get a layer by priority + +2014-05-02 16:48:46 +0200 Mathieu Duponchelle + + * ges/ges-timeline-element.c: + timeline-element: return TRUE in _set_name when both names match. + +2014-05-02 14:17:07 +0200 Mathieu Duponchelle + + * tools/ges-launch.c: + ges-launch: Only create a layer if needed. + That way scenarios can start with an empty timeline + +2014-05-02 13:37:04 +0200 Mathieu Duponchelle + + * tools/ges-validate.c: + validate: Add add-layer and remove-layer + +2014-05-08 01:13:02 +0200 Mathieu Duponchelle + + * tools/ges-validate.c: + scenarios: add a remove-asset action + +2014-05-01 17:02:05 +0200 Mathieu Duponchelle + + * tools/ges-validate.c: + ges-validate: add an add-asset action + Conflicts: + tools/ges-validate.c + +2014-05-01 17:00:25 +0200 Mathieu Duponchelle + + * tools/ges-launch.c: + ges-launch: When a scenario is set, don't request triplets + +2014-05-01 16:59:15 +0200 Mathieu Duponchelle + + * ges/ges-timeline.h: + ges-timeline: Fix ges_timeline_get_project macro. + This macro was a little confused about its own meaning. + +2014-05-05 11:58:45 +0100 Tim-Philipp Müller + + * tools/ges-launch.c: + ges-launch: remove dead code + duration can't be smaller than 0 because it's unsigned, + and it can't be 0 because 0 is transformed to CLOCK_TIME_NONE + earlier. + Coverity CID 1211822. + +2014-05-03 10:18:12 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From bcb1518 to 211fa5f + +2014-05-01 10:13:39 +0200 Thibault Saunier + + * tools/ges-launch.c: + tools: Add a way to look for moved media sample recursively + In ges-launch let the user set a folder where the media sample that + move can be found recursing into that specified folder. + https://bugzilla.gnome.org/show_bug.cgi?id=729382 + +2014-04-30 20:58:42 +0200 Thibault Saunier + + * ges/ges-track.c: + * tools/ges-launch.c: + tools: Add an option to disable mixing + + Add a a GObject property so that the info is seralized + https://bugzilla.gnome.org/show_bug.cgi?id=729382 + +2014-04-30 16:26:03 +0200 Thibault Saunier + + * ges/ges-project.c: + project: Enhance debugging when updating URI with an invalid one + https://bugzilla.gnome.org/show_bug.cgi?id=729382 + +2014-05-02 16:49:10 +0200 Thibault Saunier + + * tools/ges-validate.c: + tools: Always activate gst-validate to have position printing + https://bugzilla.gnome.org/show_bug.cgi?id=729382 + +2014-04-29 21:29:54 +0200 Thibault Saunier + + * tools/ges-launch.c: + tools: Handle times as doubles + concider duration=0 as TIME_NONE + https://bugzilla.gnome.org/show_bug.cgi?id=729382 + +2014-04-26 09:51:37 +0200 Thibault Saunier + + * tools/ges-launch.c: + tools: Disable --set-scenario if not compiled against gst-validate + https://bugzilla.gnome.org/show_bug.cgi?id=729382 + +2014-04-26 08:55:31 +0200 Thibault Saunier + + * ges/ges-clip.c: + ges: Rename remaning tlobj to clip + https://bugzilla.gnome.org/show_bug.cgi?id=729382 + +2014-05-02 16:43:42 +0200 Thibault Saunier + + * tools/ges-validate.c: + ges-validate: Add an action to serialize the project + https://bugzilla.gnome.org/show_bug.cgi?id=729382 + Conflicts: + tools/ges-validate.c + +2014-04-25 18:23:06 +0200 Thibault Saunier + + * tools/ges-validate.c: + tools:validate: Always seek after editing a clip + Otherwize the displayed frame will not be updated when paused. + + Add a get_timeline internal helper method in ges-validate.c + https://bugzilla.gnome.org/show_bug.cgi?id=729382 + +2014-03-14 12:10:53 +0100 Thibault Saunier + + * tools/ges-validate.c: + ges-validate: Add a GstValidate action to set children properties + https://bugzilla.gnome.org/show_bug.cgi?id=729382 + +2014-02-18 18:52:38 +0100 Thibault Saunier + + * configure.ac: + * tests/Makefile.am: + * tests/scenarios/Makefile.am: + * tests/scenarios/ges-edit-clip-while-paused.scenario: + scenario: Add a scenario that edits a clip while the pipeline is paused + https://bugzilla.gnome.org/show_bug.cgi?id=729382 + +2014-02-18 17:25:05 +0100 Thibault Saunier + + * tools/ges-validate.c: + tools:validate: Add an action to allow editing clips + https://bugzilla.gnome.org/show_bug.cgi?id=729382 + +2014-02-18 15:14:40 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-internal.h: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-xml-formatter.c: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/group.c: + * tests/check/ges/layer.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/uriclip.c: + Add a notion of 'name' in GESTimelineElement + https://bugzilla.gnome.org/show_bug.cgi?id=729382 + +2014-04-23 11:28:20 +0200 Thibault Saunier + + * tools/ges-launch.c: + * tools/ges-validate.c: + tools: Position printing is now done at the gst-validate level + https://bugzilla.gnome.org/show_bug.cgi?id=729382 + +2014-04-17 13:04:26 +0200 Thibault Saunier + + * ges/ges-uri-asset.c: + uri-asset: Check return value of gst_tag_list_copy_value + CID 1139597 + +2014-04-10 18:03:55 +0200 Edward Hervey + + * ges/gstframepositionner.c: + framepositioner: Set the proper default value + Most likely a copy/paste error. + CID #1139646 + +2014-04-10 18:01:03 +0200 Edward Hervey + + * ges/ges-xml-formatter.c: + xml-formatter: Use proper value for string extraction + The pass would be filled with some bogus (pointer) numerical value + CID #1139652 + +2014-04-10 17:52:20 +0200 Edward Hervey + + * ges/ges-base-xml-formatter.c: + base-xml-formatter: Don't attempt to use NULL entry + Instead return straight away + CID #1139739 + +2014-04-07 21:02:48 +0200 Christoph Reiter + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/ges-asset.c: + * ges/ges-audio-source.c: + * ges/ges-audio-test-source.c: + * ges/ges-audio-track.c: + * ges/ges-audio-transition.c: + * ges/ges-audio-uri-source.c: + * ges/ges-base-effect-clip.c: + * ges/ges-base-effect.c: + * ges/ges-base-transition-clip.c: + * ges/ges-clip-asset.c: + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-effect-asset.c: + * ges/ges-effect-clip.c: + * ges/ges-effect.c: + * ges/ges-extractable.c: + * ges/ges-formatter.c: + * ges/ges-group.c: + * ges/ges-image-source.c: + * ges/ges-layer.c: + * ges/ges-meta-container.c: + * ges/ges-multi-file-source.c: + * ges/ges-operation-clip.c: + * ges/ges-operation.c: + * ges/ges-overlay-clip.c: + * ges/ges-pipeline.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-project.c: + * ges/ges-source-clip.c: + * ges/ges-source.c: + * ges/ges-test-clip.c: + * ges/ges-text-overlay-clip.c: + * ges/ges-text-overlay.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + * ges/ges-title-clip.c: + * ges/ges-title-source.c: + * ges/ges-track-element-asset.c: + * ges/ges-track-element.c: + * ges/ges-track.c: + * ges/ges-transition-clip.c: + * ges/ges-transition.c: + * ges/ges-uri-asset.c: + * ges/ges-uri-clip.c: + * ges/ges-video-source.c: + * ges/ges-video-test-source.c: + * ges/ges-video-track.c: + * ges/ges-video-transition.c: + * ges/ges-video-uri-source.c: + Include class related section documentation in the gir file. + g-ir-scanner includes section docs as class/interface docs if the section name is equal to the lowercase type name. + Since all the documentation is in section blocks, rename them to match the type names. + https://bugzilla.gnome.org/show_bug.cgi?id=727776 + +2014-04-06 16:39:33 +0200 Thibault Saunier + + * ges/Makefile.am: + build: Add reference to GstVideo in gir file + +2014-03-26 23:48:45 +0100 Lubosz Sarnecki + + * ges/ges-types.h: + multifilesrc: remove unused class declaration + +2014-03-26 23:47:03 +0100 Lubosz Sarnecki + + * ges/Makefile.am: + build: install ges-version.h + +2014-03-26 11:45:07 +0100 Thibault Saunier + + * tools/ges-launch.c: + ges-launch: Not being able to load an asset is an error + +2014-03-21 10:22:52 +0100 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Do not set EncodingProfile.presence when we have no track for the type + That leads to freeze as encodebin will be waiting for a pad and EOS + forever + +2014-01-09 16:31:01 +0100 Thibault Saunier + + * tools/ges-launch.c: + ges-launch: Format the position printing as in gst-validate and friends + +2013-09-13 18:15:21 -0300 Thibault Saunier + + * configure.ac: + * tools/Makefile.am: + * tools/ges-launch.c: + * tools/ges-validate.c: + * tools/ges-validate.h: + ges-launch: Play nicely with gst-validate if avalaible + +2013-09-20 01:31:10 +0200 Thibault Saunier + + * tools/ges-launch.c: + ges-launch: Add a way to mute test video and audio output + +2013-10-12 10:07:28 -0300 Thibault Saunier + + * tools/ges-launch.c: + ges-launch: Properly handle project loading + So we start the pipeline only when the project is done loading, and we save it when it is + loaded, taking into account possible media URI updates. + +2013-11-12 12:13:31 +0100 Lubosz Sarnecki + + ges: multifilesrc support + * GESMultiFileSource class + * multifilesrc example + * Support multifile:// urls in uri asset + * start/stop index modification + * Doc + https://bugzilla.gnome.org/show_bug.cgi?id=719373 + +2014-03-16 12:48:22 +0100 Thibault Saunier + + * configure.ac: + Back to development + +=== release 1.2.0 === + +2014-03-16 12:46:26 +0100 Thibault Saunier + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * ges/ges.c: + * gst-editing-services.doap: + Release 1.2.0 + +2014-03-15 10:34:17 +0100 Thibault Saunier + + * configure.ac: + * ges/ges-version.h.in: + * ges/ges.h: + Properly generate versioning #define-s during autogen + +2014-03-14 20:04:33 +0100 Thibault Saunier + + * ges/ges-container.c: + * ges/ges-timeline-element.c: + * tests/check/ges/group.c: + container: Properly handle the case where we could not set parent + In this case we had a FIXME about reverting everything that was done, + implement that FIXME! + +2014-03-14 19:59:27 +0100 Andreas Schwab + + * ges/ges-smart-adder.c: + ges: remove extra semicolon + https://bugzilla.gnome.org/show_bug.cgi?id=726365 + +2014-03-14 18:48:44 +0100 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Always set the encoding profile presence to 1 + We currenty do not support multiple tracks with same type in GESPipeline + and we actually need to set the presence field to avoid a scenario where + we have only video in a video track, and no audio in the audio track. So + audiotestsrc is used and we end up encoding the whole audio stream but + no decoded video frame as reached the decodebin src pad, so the pad + has not been created and thus it will not be linked to the encodebin. + On the audio part, the EOS will be emitted so fast that the resulting stream will + not have any video in it as the muxer will not even have a video pad created. + Setting the presence will ensure that the muxer does have a video pad + (because of how encodebin behaves) and thus will create a pad for it + and wait for its EOS. + +2014-03-10 11:18:21 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-title-clip.c: + * ges/ges-title-source.c: + * ges/ges-title-source.h: + title-source: Rename ges_title_clip_set_backrgound_colour as appropriate + The method was badly called _clip_ instead of _source_ we have not release the API + so we still can change it. + +2014-03-08 11:26:13 +0000 Dan Williams + + * ges/ges-project.c: + ges: fix finalize/dispose mixup + https://bugzilla.gnome.org/show_bug.cgi?id=725918 + +2014-03-07 14:48:06 -0600 Dan Williams + + * ges/ges-clip-asset.c: + * ges/ges-container.c: + * ges/ges-formatter.c: + * ges/ges-project.c: + * ges/gstframepositionner.c: + ges: Ensure GObject finalize and dispose methods chain up to parents + https://bugzilla.gnome.org/show_bug.cgi?id=725918 + +2014-03-07 09:28:16 -0600 Dan Williams + + * ges/ges-base-xml-formatter.c: + Fix use-after-free in _free_pending_clip() + https://bugzilla.gnome.org/show_bug.cgi?id=725855 + +2014-02-28 09:37:01 +0100 Sebastian Dröge + + * common: + Automatic update of common submodule + From fe1672e to bcb1518 + +2014-02-26 04:36:11 +0100 Alexandru Băluț + + * docs/design/encoding-research.txt: + * docs/design/metadata.txt: + * ges/ges-pitivi-formatter.c: + * ges/ges-pitivi-formatter.h: + Update the documentation to use Pitivi instead of PiTiVi + +2014-02-26 04:17:36 +0100 Alexandru Băluț + + * ges/ges-base-effect.c: + * ges/ges-effect-clip.c: + * ges/ges-effect.c: + * ges/ges-test-clip.c: + * ges/ges-text-overlay.c: + * ges/ges-transition-clip.c: + Remove "#" from short-description + It breaks the display in the index.html page. + +2014-02-18 22:14:00 +0100 Alexandru Băluț + + * ges/ges-asset.c: + * ges/ges-extractable.c: + Minor documentation fixes: GESExtractable, GESAsset + +2014-02-26 22:16:13 +0100 Stefan Sauer + + * common: + Automatic update of common submodule + From 1a07da9 to fe1672e + +2014-02-18 12:40:06 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + docs: Fix documentation about copying timeline elements + +2014-02-17 13:33:51 +0100 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Make sure not to add 2 times a TrackElement in the same track + Without that, if a UriClip contains several tracks of a same type (ie. + video or audio...), we would add all the TrackElements to each track + making everything failling as we end up with several GNL sources at + the same position with the same priority. + +2014-02-17 12:34:04 +0100 Thibault Saunier + + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges-pipeline.c: + * tests/check/ges/integration.c: + * tests/examples/concatenate.c: + * tests/examples/ges-ui.c: + * tests/examples/overlays.c: + * tests/examples/test4.c: + * tests/examples/text_properties.c: + * tests/examples/thumbnails.c: + * tests/examples/transition.c: + * tools/ges-launch.c: + Rename TIMELINE_MODE_XXX to GES_PIPELINE_MODE_XXX so it corresponds to reality + First, it was not in any namespace, second the name of the enum is + GESPipelineFlags. + +2014-02-14 13:20:31 +0100 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + ges: Give a reference to the formatter for the idle callback + This avoid segfault as we are not guaranteed that the formatter will + not be destroyed in the meantime. + + Minor cleanup of handling of private members + https://bugzilla.gnome.org/show_bug.cgi?id=724337 + +2014-02-09 23:50:25 +0100 Sebastian Dröge + + * ges/ges-meta-container.c: + * ges/ges-pitivi-formatter.c: + ges: Remove unused functions + +2014-02-08 20:19:53 +0100 Sebastian Dröge + + * ges/ges-group.c: + ges-group: Properly check for integer underflows + error: comparison of unsigned expression < 0 is always false + +2014-02-08 20:18:11 +0100 Sebastian Dröge + + * ges/ges-extractable.c: + ges-extractable: Return NULL instead of G_TYPE_INVALID + The return type of this function is gchar *, not GType + +2014-02-05 00:10:52 +0100 Thibault Saunier + + * ges/ges-enums.h: + ges: Some documentation cleanup + +2014-02-04 13:58:48 +0100 Thibault Saunier + + * tests/check/ges/integration.c: + tests:integration: Fix a race about get_position being called before AYNC_DONE happens + +2014-02-04 10:45:58 +0100 Alexandru Băluț + + * bindings/python/examples/simple.py: + * docs/libs/ges-sections.txt: + * ges/ges-pipeline.c: + * ges/ges-pipeline.h: + * tests/check/ges/integration.c: + * tests/check/ges/project.c: + * tests/check/ges/test-utils.c: + * tests/examples/concatenate.c: + * tests/examples/ges-ui.c: + * tests/examples/overlays.c: + * tests/examples/simple1.c: + * tests/examples/test1.c: + * tests/examples/test2.c: + * tests/examples/test3.c: + * tests/examples/test4.c: + * tests/examples/text_properties.c: + * tests/examples/thumbnails.c: + * tests/examples/transition.c: + * tools/ges-launch.c: + ges-pipeline: Rename add_timeline to set_timeline + API BREAKAGE: + - ges_pipeline_add_timeline + + ges_pipeline_set_timeline + +2014-01-16 15:25:06 +0100 Thibault Saunier + + * ges/ges-project.c: + * ges/ges-uri-asset.c: + uri-asset: Let a chance for user to change URI if the set one is not readable + It sounds like the most logical thing to do in that case. + https://bugzilla.gnome.org/show_bug.cgi?id=721111 + +2014-01-15 19:12:08 +0100 Thibault Saunier + + * ges/ges-video-source.c: + * ges/ges-video-uri-source.c: + videosource: Always add a deinterlace at the beining of videosrcbin + It might be needed in some cases (for example when decoding prores files) and + it is the way it is done with playbin now. Also deinterlace now properly supports + passtrough mode. + +2014-01-27 15:30:40 +0100 Thibault Saunier + + * ges/ges-asset.c: + * tests/check/ges/project.c: + * tests/check/ges/uriclip.c: + ges-asset: Do not forget to give a ref to the registry + + Add test in the testsuite + + Fix broken tests + https://bugzilla.gnome.org/show_bug.cgi?id=721111 + +2014-01-30 10:46:09 +0100 Edward Hervey + + * common: + Automatic update of common submodule + From d48bed3 to 1a07da9 + +2014-01-09 18:13:00 +0100 Mathieu Duponchelle + + * ges/ges-track-element.c: + track-element: clamp interpolated keyframe values. + +2014-01-10 00:05:01 +0000 Tim-Philipp Müller + + * .gitignore: + .gitignore: add test driver and more test binaries + +2013-12-27 10:08:47 +0100 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + base-xml-formatter: Emit 'loaded' right after a project with empy timeline is loaded + https://bugzilla.gnome.org/show_bug.cgi?id=720040 + +2013-12-24 15:34:51 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-audio-test-source.h: + * ges/ges-audio-uri-source.h: + * ges/ges-image-source.h: + * ges/ges-internal.h: + * ges/ges-title-source.h: + * ges/ges-video-test-source.h: + * ges/ges-video-uri-source.h: + track-element: Remove constructors for TrackElement from the API + Most of the time the user should not create GESTrackElements + himself, instead he should add a GESAsset to a layer, that will + result in a clip creation and the proper TrackElements to be + created and added to the tracks. + The case of effects and overlays is a bit different as the user should + create the TrackElement and add them to a clip. + +2013-12-24 15:08:24 +0100 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-effect-clip.c: + * ges/ges-effect.c: + * ges/ges-text-overlay-clip.c: + * ges/ges-text-overlay.c: + * ges/ges-timeline.c: + * ges/ges-title-clip.c: + * ges/ges-title-source.c: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + * ges/ges-track.c: + ges: Remove versionning infos now that we start on the 1.X API serie + They are now meaningless, all the current symbols are the basic + ones for the 1.X serie. + +2013-12-24 14:34:09 +0100 Thibault Saunier + + * ges/ges-base-xml-formatter.h: + * ges/ges-effect-asset.h: + * ges/ges-smart-adder.h: + * ges/ges-smart-video-mixer.h: + * ges/ges-xml-formatter.h: + * ges/gstframepositionner.h: + ges: Add padding for API extension where missing + +2013-12-22 22:36:16 +0000 Tim-Philipp Müller + + * autogen.sh: + * common: + Automatic update of common submodule + From dbedaa0 to d48bed3 + +2013-11-28 15:13:06 -0300 Thibault Saunier + + * ges/ges-project.c: + * ges/ges-timeline.c: + ges: Reimplement 'always create a project to back a timeline' + Keeping it simple, and making sure everything is synchronous + +2013-11-28 15:08:33 -0300 Thibault Saunier + + * ges/ges-project.c: + * ges/ges-timeline.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/group.c: + * tests/check/ges/layer.c: + * tests/check/ges/mixers.c: + * tests/check/ges/overlays.c: + * tests/check/ges/test-utils.c: + * tests/check/ges/test-utils.h: + * tests/check/ges/text_properties.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + Revert "ges: Always create a project to back a timeline" + This reverts commit 59d83f1a93055391097e7c1fe34f5a39eb8ec625. + Conflicts: + tests/check/ges/backgroundsource.c + tests/check/ges/effects.c + tests/check/ges/overlays.c + tests/check/ges/simplelayer.c + tests/check/ges/text_properties.c + tests/check/ges/titles.c + +2013-11-25 15:17:33 -0300 Thibault Saunier + + * ges/ges-project.c: + * ges/ges-timeline.c: + timeline: Add all assets of the clip added to the timeline to the project + +2013-11-22 17:49:49 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-base-effect-clip.c: + * ges/ges-effect-clip.c: + * ges/ges-simple-layer.c: + * ges/ges-simple-layer.h: + * ges/ges-transition-clip.c: + * ges/ges.h: + * tests/check/Makefile.am: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/effects.c: + * tests/check/ges/overlays.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/text_properties.c: + * tests/check/ges/titles.c: + * tests/examples/concatenate.c: + * tests/examples/ges-ui.c: + * tests/examples/simple1.c: + * tests/examples/test3.c: + * tests/examples/test4.c: + * tests/examples/thumbnails.c: + * tools/ges-launch.c: + Remove GESSimplerLayer, that API should land into GESLayer in the end + The priority handling of clip is now handled by GESLayer itself, and + handling clip as a ordered list should be implemented in GESLayer itself + too, this way the user can decide to switch mode at any time instead of + +2013-11-22 17:36:12 -0300 Thibault Saunier + + * ges/ges-layer.c: + layer: Set clip start to the duration of the layer if == TIME_NONE + In the provided start of a clip is GST_CLOCK_TIME_NONE in + ges_layer_add_asset, it means that we want the clip to be + added at the end of the layer + +2013-11-22 17:33:18 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-layer.c: + * ges/ges-layer.h: + layer: Add an API to get the total duration of the layer + API: + ges_layer_get_duration + +2013-11-22 17:23:31 -0300 Thibault Saunier + + Remove the android/ toplevel directory + To build gstreamer for android we are now using androgenizer which + generates the needed Android.mk files. Androgenizer can be found here: + * http://cgit.collabora.com/git/android/androgenizer.git/ + +2013-11-13 13:18:00 +0100 Lubosz Sarnecki + + * ges/Makefile.am: + * ges/ges-gerror.h: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + * ges/ges-uri-asset.c: + * ges/ges-utils.c: + * ges/ges-utils.h: + gir: fix warnings + +2013-11-18 13:41:07 -0300 Thibault Saunier + + * ges/ges-video-uri-source.c: + video-uri-source: Handle interlaced videos + https://bugzilla.gnome.org/show_bug.cgi?id=710168 + +2013-11-14 16:17:31 -0300 Thibault Saunier + + * tools/ges-launch.c: + ges-launch: Let user set the track types to use + +2013-11-09 09:55:39 -0300 Thibault Saunier + + * bindings/python/examples/material.py: + python: Remove old material.py example + +2013-11-09 09:51:55 -0300 Thibault Saunier + + * tools/ges-launch.c: + ges-launch: Dot the pipeline on state changes and warnings + +2013-11-09 09:49:03 -0300 Thibault Saunier + + * ges/gstframepositionner.c: + framepositionner: Fix the range of properties dealing with number of pixels + This way it is possible to interpolate those values. + +2013-11-09 09:47:21 -0300 Thibault Saunier + + * ges/ges-project.c: + * ges/ges-timeline.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/group.c: + * tests/check/ges/layer.c: + * tests/check/ges/mixers.c: + * tests/check/ges/overlays.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/test-utils.c: + * tests/check/ges/test-utils.h: + * tests/check/ges/text_properties.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + ges: Always create a project to back a timeline + And fix all the tests as we need to wait for the project to be loaded + to check the reference count of the timeline (as we keep a ref on the + timeline in project to later emit "loaded" on idle). + +2013-11-09 09:46:10 -0300 Thibault Saunier + + * bindings/python/examples/simple.py: + bindings: Cleanup and fix simple python example + +2013-11-05 11:23:08 +0000 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From 865aa20 to dbedaa0 + +2013-10-17 15:16:00 +0200 Kishore Arepalli + + * ges/ges-audio-source.c: + * ges/ges-image-source.c: + * ges/ges-pipeline.c: + * ges/ges-smart-adder.c: + * ges/ges-smart-video-mixer.c: + * ges/ges-source.c: + * ges/ges-title-source.c: + * ges/ges-track.c: + * ges/ges-video-track.c: + ges: Fix several memory leaks + https://bugzilla.gnome.org/show_bug.cgi?id=710390 + +2013-10-30 00:27:36 +0100 Mathieu Duponchelle + + * ges/ges-clip.c: + track-element: add start to the position to which we wish we split the bindings. + The bindings split is relative to the beginning of the clip. + +2013-10-29 07:59:22 -0300 Thibault Saunier + + * ges/ges-clip.c: + clip: Fix the find_track_element method + What we want is to be able to find a TrackElement by its type, and + possibly specify a Track where to look into. + +2013-10-15 10:57:31 +0200 Kishore Arepalli + + * ges/ges-extractable.h: + * ges/ges-formatter.h: + * ges/ges-uri-asset.h: + ges-formatter: don't use 'class' as function argument name in headers + It's a keyword in C++ and C++ compilers won't like it. + https://bugzilla.gnome.org/show_bug.cgi?id=710172 + +2013-10-11 17:00:22 -0300 Thibault Saunier + + * ges/ges-asset.c: + * ges/ges-uri-asset.c: + uri-asset: Check if file exists before trying it as a proxy + This avoids: + 1- discovering file that we know do not exist + 2- proposing the current proxy path (that failed) as a possible proxy + which lead to errors + +2013-10-11 17:05:03 -0300 Thibault Saunier + + * tools/ges-launch.c: + ges-launch: Remember URIS that we tried to discover + So we do not fail several time trying to discover the same URI + Conflicts: + tools/ges-launch.c + +2013-10-08 13:45:55 -0300 Thibault Saunier + + * ges/ges-formatter.c: + formatter: Try to use best formatter first + +2013-10-09 20:07:14 -0300 Thibault Saunier + + * ges/ges-audio-source.c: + audiosource: Add audioconvert and audioresample before the volume element + https://bugzilla.gnome.org/show_bug.cgi?id=709777 + +2013-10-01 20:07:10 +0200 Mathieu Duponchelle + + * ges/ges-timeline.c: + timeline: pass the correct argument to disconnect_by_func. + fixes #709205 + +2013-09-28 21:07:10 +0200 Thibault Saunier + + * configure.ac: + Back to development + +=== release 1.1.90 === + +2013-09-28 20:49:13 +0200 Thibault Saunier + + * ChangeLog: + * configure.ac: + Release 1.1.90 + +2013-09-28 18:09:49 +0200 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-clip.h: + clip: Return the newly created TrackElement when adding an asset + This is a minor API change + +2013-09-28 15:42:20 +0200 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Add a signal to know when it is commited + +2013-09-27 02:56:44 +0200 MathieuDuponchelle + + * ges/ges-clip.c: + clip: split_bindings at position * + inpoint* + +2013-09-25 23:52:46 +0200 MathieuDuponchelle + + * ges/ges-video-track.c: + video-track: update gaps framerate along with restriction caps. + +2013-09-25 19:48:45 +0200 MathieuDuponchelle + + * ges/ges-base-xml-formatter.c: + * ges/ges-xml-formatter.c: + xml-formatter: add support for restriction caps. + +2013-09-05 01:03:51 +0200 Simon Corsin + + * ges/ges-video-source.c: + * ges/gstframepositionner.c: + * ges/gstframepositionner.h: + video-source: Add a videorate in video-source. + And control it in framepositionner. + Conflicts: + ges/ges-video-source.c + +2013-09-24 18:35:56 +0100 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From 6b03ba7 to 865aa20 + +2013-09-22 21:56:14 +0200 Thibault Saunier + + * tests/check/ges/clip.c: + * tests/check/ges/layer.c: + tests: Make sure not to test freed objects type + +2013-09-16 13:30:33 -0300 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Let some queuing in encodebin + It is sometimes necessary + +2013-09-16 11:19:13 -0300 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + * tests/examples/ges-ui.c: + examples: Make project loading more generic + Pitivi formatter is deprecated, do not use it by default + +2013-09-16 11:16:18 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges.c: + * ges/ges.h: + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + Revert "ges: Disable the Pitivi formatter" + This reverts commit e54ceff7204e712daa9949ef41b73d96035a0446. + Let's just keep it... it does not cost anything. + +2013-09-20 16:19:06 +0200 Edward Hervey + + * common: + Automatic update of common submodule + From b613661 to 6b03ba7 + +2013-09-19 18:46:26 +0100 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From 74a6857 to b613661 + +2013-09-19 17:39:44 +0100 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From 12af105 to 74a6857 + +2013-09-14 04:19:57 +0200 Joris Valette + + * tests/check/ges/timelineedition.c: + tests: timelineedition: cast start and duration values as guint64 + +2013-09-13 20:38:43 -0300 Thibault Saunier + + * tools/ges-launch.c: + ges-launch: Handle path for project uri + +2013-09-13 20:37:58 -0300 Thibault Saunier + + * ges/ges-xml-formatter.c: + formatter: Keep timeline duration as a meta + +2013-09-12 18:34:55 -0300 Thibault Saunier + + * ges/ges-project.c: + * tools/ges-launch.c: + ges-launch: Make it possible to provid pathes to look for moved asset + For example if a project was sent from someone else thus the pates in + there are meaningless on the other computer, we need to be able + to specify a list of pathes where the files are. + + Fix documentation + +2013-09-12 09:05:51 +0200 Kishore Arepalli + + * ges/ges-pipeline.c: + ges-pipeline: Don't unref buffer obtained from a GstSample + https://bugzilla.gnome.org/show_bug.cgi?id=707914 + +2013-09-10 18:17:57 +0200 Mathieu Duponchelle + + * tests/check/ges/integration.c: + tests: integration: set restriction_caps on the video encoding profile + We need this cause now videomixer renegotiates downstream. + +2013-09-09 12:47:32 -0300 Thibault Saunier + + * ges/ges-pipeline.c: + * ges/ges.c: + pipeline: Create it through a factory + Making it possible to use it with GstValidate LD_PRELOAD feature + +2013-09-09 12:47:02 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-pipeline.c: + * ges/ges-pipeline.h: + * tests/examples/thumbnails.c: + pipeline: Finnish renaming from GESTimelinePipeline + +2013-09-08 19:27:04 -0300 Thibault Saunier + + * tests/check/ges/timelineedition.c: + tests: timelineedition: Minor cleanups + +2013-09-08 19:19:24 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Make sure we do not move object when only trimming + We were missing a few checks so that we do not move objects when their + duration is equal to the max duration, or 0 + +2013-09-07 12:59:17 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Avoid setting duration > max_duration when rippling + We should use the trimming method to set duration to make sure to avoid + going over the max duration. + Also avoid computing when setting duration to the same old value. + +2013-09-07 02:11:23 -0400 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-container.h: + * ges/ges-group.c: + * ges/ges-timeline.c: + * tests/check/ges/timelineedition.c: + ges: Handle trimming in groups + This was broken, clips where moving all around, make it behave properly. + +2013-09-07 02:10:12 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Make ripple start be trimming + This is a more natural behaviour as a user instead of doing nothing at + all. + +2013-09-03 20:50:54 -0400 Thibault Saunier + + * ges/ges-audio-source.c: + * ges/ges-video-source.c: + ges: Make GESAudioSource and GESVideoSource abstract + +2013-08-22 23:06:38 +0200 Mathieu Duponchelle + + * ges/gstframepositionner.c: + gstframepositionner: correctly tag metadata. + We do not use GST_VIDEO_META_TAG_STR as it would mean depending on + GstVideo which is not the case right now + +2013-09-01 12:19:32 -0400 Thibault Saunier + + * ges/ges-video-source.c: + videosource: Make sure to update z-order when layer priority changes + Conflicts: + ges/ges-video-source.c + +2013-09-01 12:18:53 -0400 Thibault Saunier + + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + timeline-element: Add a set_parent vmethod + API: + GESTimelineElment->set_parent vmethod + +2013-08-29 11:35:30 +0200 Simon Corsin + + * tests/check/ges/timelineedition.c: + tests: timelineedition: Add a test_scaling. + It will check that the clip updates its size correctly. + +2013-08-25 17:08:00 +0200 Simon Corsin + + * tests/check/ges/test-utils.c: + * tests/check/ges/test-utils.h: + test-utils: Adds a utility function to quickly check the timeline. + +2013-08-15 20:12:30 +0200 Simon Corsin + + * ges/ges-video-transition.c: + videotransition: No need to hard set width and height anymore. + +2013-08-21 11:32:45 +0200 Simon Corsin + + * ges/ges-video-source.c: + * ges/gstframepositionner.c: + * ges/gstframepositionner.h: + gstframepositionner: Install width and height properties. + + And manage them properly. + +2013-08-17 14:57:15 +0200 Simon Corsin + + * ges/ges-audio-track.c: + * ges/ges-internal.h: + * ges/ges-track.c: + * ges/ges-track.h: + ges-track: Add the notion of resriction caps to GESTrack + This way we can let the user determine what he want to come out of the + track. + API: + - ges_track_set_caps (The track caps are now construct only) + + ges_track_set_restriction_caps + + ges_track_get_restriction_caps + + GESTrack.props.restriction_caps + +2013-07-09 15:31:15 +0200 Simon Corsin + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-audio-source.c: + * ges/ges-audio-source.h: + * ges/ges-audio-test-source.c: + * ges/ges-audio-test-source.h: + * ges/ges-audio-uri-source.c: + * ges/ges-audio-uri-source.h: + * ges/ges-image-source.c: + * ges/ges-image-source.h: + * ges/ges-source.c: + * ges/ges-source.h: + * ges/ges-title-source.c: + * ges/ges-title-source.h: + * ges/ges-types.h: + * ges/ges-uri-asset.c: + * ges/ges-uri-clip.c: + * ges/ges-video-source.c: + * ges/ges-video-source.h: + * ges/ges-video-test-source.c: + * ges/ges-video-test-source.h: + * ges/ges-video-uri-source.c: + * ges/ges-video-uri-source.h: + * ges/ges.h: + * tests/check/ges/uriclip.c: + GES: Add GESVideoSource and GESAudioSource base classes + + Update documentation. + + Implements subclasses audio-uri-source and video-uri-source + +2013-07-03 18:27:00 +0200 Simon Corsin + + * ges/ges-audio-test-source.c: + * ges/ges-video-test-source.c: + testsource: Handle child properties as child properties + Makes $make check pass. + Standardizes property handling. + +2013-07-02 11:12:00 +0200 Simon Corsin + + * ges/ges-internal.h: + * ges/ges-source.c: + source: Make a ges_source_create_topbin internal helper method + +2013-06-27 14:20:00 +0200 Simon Corsin + + * ges/ges-audio-test-source.c: + * ges/ges-image-source.c: + * ges/ges-source.c: + * ges/ges-source.h: + * ges/ges-title-source.c: + * ges/ges-uri-source.c: + * ges/ges-uri-source.h: + * ges/ges-video-test-source.c: + ges-source: Move common elements handling to the base class + + And port all the subclasses + +2013-09-02 13:57:15 -0400 Thibault Saunier + + * ges/ges-container.c: + container: Do not forget to initialize the timeline before using it + +2013-09-02 00:19:30 +0100 Tim-Philipp Müller + + * ges/ges-xml-formatter.c: + ges-xml-formatter: use g_ascii_dtostr() instead of messing with setlocale() + Libraries shouldn't use setlocale(). + +2013-09-01 00:46:45 +0200 Mathieu Duponchelle + + * ges/ges-xml-formatter.c: + xml-formatter: set LC_NUMERIC locale before saving values. + Avoiding to save ',' instead of '.' for floats in certain locals + +2013-08-29 23:45:56 +0200 Mathieu Duponchelle + + * ges/ges-video-transition.c: + video-transition: Keep switch transition type simple + Also make sure there is a proper default value for transition type. + +2013-08-30 20:32:56 -0400 Thibault Saunier + + * tools/ges-launch.c: + launch: Simplify encoding profile description + Use a 'simple' synthax to describe encoding profiles + +2013-08-30 20:03:16 -0400 Thibault Saunier + + * tools/ges-launch.c: + launch: Make it easier to render project + Now providing an output uri is enough to tell that you want to render. + It will use project rendering infos when possible, missing a way to + specify which info if various are disponnible (we use the first one + right now). + + Make options more logical now, -l mean --load, and -r means repeat + +2013-08-30 18:45:31 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: No autotrans between elements in same toplevel container + This makes no sense, we ended up creating/removing tons of transition + while moving groups + +2013-08-29 11:10:33 -0400 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges.c: + * ges/ges.h: + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + ges: Disable the Pitivi formatter + It lacks to many feature and the code is too bad, desactivation until + someone comes and fix it... The code should be removed if it never + happens + +2013-08-28 19:56:29 +0200 Mathieu Duponchelle + + * ges/ges-clip.c: + ges-clip: when a child, is removed, disconnect from its notifies. + +2013-08-27 19:12:26 -0400 Thibault Saunier + + * tests/check/ges/integration.c: + tests: Make use of g_assert_no_error when it makes sense + +2013-08-27 18:40:55 -0400 Thibault Saunier + + * tests/check/Makefile.am: + tests: Let use 20 sec to execute tests + +2013-08-26 23:31:14 -0400 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-base-effect.c: + * ges/ges-effect.c: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + * ges/ges-uri-source.c: + trackelement: Simplify the way we handle children properties + So subclass do not have to implement a new logic all the time, but + instead can use a simple method to add properties as needed. + +2013-08-26 19:26:08 -0400 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-effect-clip.c: + * ges/ges-test-clip.c: + * ges/ges-text-overlay-clip.c: + * ges/ges-title-clip.c: + * ges/ges-track-element.c: + * ges/ges-transition-clip.c: + * ges/ges-uri-clip.c: + clip: Remove the ges_clip_fill_track method + Its was only use by the old custom source which is dead now. + API: + Remove ges_clip_fill_track + https://bugzilla.gnome.org/show_bug.cgi?id=706855 + +2013-08-26 19:15:08 -0400 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-custom-source-clip.c: + * ges/ges-custom-source-clip.h: + * ges/ges-types.h: + * ges/ges.h: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/layer.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/timelineedition.c: + ges: Remove custom clip + If you want a custom clip then you have to subclass GESClip, + This class was pre historicall and only used for testing purposes, we + have GESTestClip for that. + https://bugzilla.gnome.org/show_bug.cgi?id=706855 + +2013-08-26 19:25:20 -0400 Thibault Saunier + + * acinclude.m4: + Remove acinclude.m4 as we do not use it + and it is anyway removed by autogen.sh + +2013-08-26 18:56:49 -0400 Thibault Saunier + + * tests/check/ges/integration.c: + integration: Add titles test + +2013-08-26 17:41:14 -0400 Thibault Saunier + + * bindings/python/gi/overrides/GES.py: + python: Do not initialize GES at import time + +2013-08-24 18:21:26 +0100 Tim-Philipp Müller + + * tests/check/ges/test-utils.c: + tests: fix NULL pointer dereference, ternary operator silliness and message type use + +2013-08-24 11:39:11 -0400 Thibault Saunier + + * tests/check/ges/test-utils.c: + tests: Give more debugging info when samples could not be generated + +2013-08-24 02:41:07 -0400 Thibault Saunier + + * tests/check/Makefile.am: + * tests/check/ges/project.c: + tests: Fix make distcheck + We need to make sure that we can write to the directory where we save + project files, so doing it in the tmp folder. + + Properly dist test data files + +2013-08-13 18:05:55 +0200 Mathieu Duponchelle + + * ges/ges-clip.c: + * ges/ges-internal.h: + * ges/ges-track-element.c: + trackelement: split bindings correctly. + +2013-08-13 17:57:33 +0200 Mathieu Duponchelle + + * ges/ges-track-element.c: + trackelement: update control bindings correctly. + When duration or inpoint change, we need to remove edge control points, + and set new control points with interpolated values. + Also when duration == 0, we need to remove all control points, as otherwise + the controller will raise !is_end assertions. + It's the duty of the application to set keyframes back when duration gets + != 0 again. + +2013-08-12 21:25:31 +0200 Mathieu Duponchelle + + * ges/ges-container.c: + container: resort children after prepending an element. + +2013-08-12 16:13:40 +0200 Mathieu Duponchelle + + * ges/ges-timeline.c: + timeline: when there are no objects anymore, set duration to 0. + +2013-08-12 15:01:53 +0200 Mathieu Duponchelle + + * ges/ges-audio-track.c: + * ges/ges-audio-track.h: + ges-audio-track: Change contructor prototype. + We return an AudioTrack. + +2013-08-11 20:06:49 +0200 Mathieu Duponchelle + + * docs/libs/ges-sections.txt: + * ges/ges-pipeline.c: + * ges/ges-pipeline.h: + pipeline: add a get_mode method. + +2013-08-07 19:37:49 +0200 Mathieu Duponchelle + + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-xml-formatter.c: + basexmlformatter: Only set timeline auto transitions when done loading. + +2013-08-07 16:12:27 +0200 Mathieu Duponchelle + + * tests/check/ges/integration.c: + integration: make test_basic be two concatenated clips. + +2013-08-20 08:22:24 -0400 Thibault Saunier + + * ges/ges-formatter.c: + formatter: Plug leaks in the can_save_to_uri method + https://bugzilla.gnome.org/show_bug.cgi?id=679941 + +2013-08-19 15:13:48 -0400 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Remove the dynamic lock + We actually do not need it has everywhere where we would need it we are + already locked against the timeline.dyn_lock, we need to make sure it is + always the case in the future. + The hierarchy of the mutex was wrong and could possibly lead to + deadlocks + +2013-08-19 15:12:48 -0400 Thibault Saunier + + * ges/ges-formatter.c: + * ges/ges-formatter.h: + formatter: Remove the unsed can_save_uri vmethod + This virtual method does not make much sense right now, we might need it + again later, but most probably with a sensibly different API so removing + it for now. + +2012-07-20 14:19:01 +0200 Paul Lange + + * ges/ges-formatter.c: + ges-formatter: Check if directory of URI is writeable + https://bugzilla.gnome.org/show_bug.cgi?id=679941 + +2013-08-06 18:35:24 +0200 Thibault Saunier + + * tests/check/ges/integration.c: + tests: integration: Rework the way we handle seeking while fully paused + The idea is that we should first play until the time we reach the first + position, at that point we PAUSE the pipeline, then, afterward do the + seeks as asked. + If we get the position before the ASYNC DONE, just accept it. + +2013-08-05 01:07:36 +0200 Thibault Saunier + + * tests/check/ges/integration.c: + tests: integration: PNG file was renamed to png.png + +2013-08-04 17:46:33 +0200 Thibault Saunier + + * tests/check/ges/integration.c: + tests: integration: Add a list tests only option + Also add an empty filed in the GOptionEntry array avoiding segfaults + +2013-08-02 14:23:13 +0200 Lubosz Sarnecki + + * configure.ac: + build: add subdir-objects to AM_INIT_AUTOMAKE + Fixes warnings with automake 1.14 + https://bugzilla.gnome.org/show_bug.cgi?id=705350 + +2013-08-04 17:35:20 +0200 Thibault Saunier + + * tests/check/ges/integration.c: + tests: integration: Only use 2 layers for the mixing for now + + Call the TSuite "integration" instead of "render" + +2013-08-04 16:14:42 +0200 Thibault Saunier + + * tests/check/ges/integration.c: + tests: check: All assets moved to assets/ + +2013-08-03 17:01:22 +0200 Thibault Saunier + + * tests/check/ges/integration.c: + tests: integration: Make it possible to list all avalaible tests + You can not use make check-integration --list-tests, you have to use + ./integration --list-tests instead + +2013-08-02 14:16:26 +0200 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Add support for group-id in the stream-start event + +2013-08-01 18:14:36 +0200 Thibault Saunier + + * tests/check/ges/integration.c: + Fix compilation + +2013-08-01 17:56:16 +0200 Thibault Saunier + + * tests/check/ges/integration.c: + tests: integration: Avoid leak + +2013-08-01 17:47:50 +0200 Thibault Saunier + + * tests/check/ges/integration.c: + tests: integration: Do not forget to set encoding profiles + +2013-07-18 23:09:51 +0200 Vasilis Liaskovitis + + * tests/check/ges/integration.c: + integration: add mixing tests + Add a new test that creates a given number of layers. Each layer has the same + assets / clips shifted by a different amount in the timeline. Alpha and volume + properties are different for each layer. This test is similar to the mixer + example in: + http://gist.github.com/MathieuDuponchelle/5736992#file-mixit-py + We should be able to add more clips to each layer, but this example test only + tests mixing 1 clip across 4 layers. + Conflicts: + tests/check/ges/integration.c + +2013-08-01 11:31:16 +0200 Mathieu Duponchelle + + * tests/check/ges/integration.c: + tests/integration: display test name when running it. + +2013-08-01 11:32:44 +0200 Mathieu Duponchelle + + * tests/check/assets/png.png: + * tests/check/ges/integration.c: + tests/integration: add an asset directory. + +2013-07-23 01:50:28 +0200 Vasilis Liaskovitis + + * tests/check/ges/integration.c: + integration: add seek tests to paused pipeline (no playing) + This second set of seeking tests performs the seeks in a PAUSED + pipeline. After all seeks are successful, the pipeline is resumed so that the + test does not timeout. + Conflicts: + tests/check/ges/integration.c + +2013-07-19 00:40:00 +0200 Vasilis Liaskovitis + + * tests/check/ges/integration.c: + integration: add paused pipeline seek tests + +2013-07-16 19:42:53 +0200 Mathieu Duponchelle + + * tests/check/ges/integration.c: + tests/integration: adds image_filename in the test generation macro + +2013-07-27 10:18:30 +0200 Thibault Saunier + + * ges/ges-project.c: + project: Make sure error-loading-asset is emited when needed + In case ges_project_try_updating_id would be called from outside ges-project the signal + was not emitted, change that. + + Add some debugging + +2013-07-24 22:37:06 -0400 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + timeline: Add methods to get and set the snapping distance + +2013-07-24 14:26:18 -0400 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-internal.h: + * ges/ges-track-element.c: + clip: Fix the spliting method + We should make sure that the newly created trackelement are inside + a container when adding them to as this is needed for GESUriClip-s. + Also do not try to set a child property on the TrackElement itself. + https://bugzilla.gnome.org/show_bug.cgi?id=703152 + +2013-07-23 19:20:34 -0400 Thibault Saunier + + * tests/check/ges/integration.c: + tests: integration: Let the user mute the tests when needed + Add an environment variable so the user can make sur that + integration tests use fakesinks instead of real sinks + +2013-07-22 20:06:25 -0400 Thibault Saunier + + * tests/check/ges/mixers.c: + tests: Make sure we can have the results into an XML file + +2013-07-21 21:41:13 -0400 Thibault Saunier + + * ges/ges-pipeline.c: + pipeline: Check that the profile could actually be set on the encodebin + Setting the profile on an encodebin can fail, and if that happens, there + will be no profile set at all, we should return FALSE in GESPipeline + when that happens + +2013-07-01 16:27:54 +0200 Lubosz Sarnecki + + * android/ges.mk: + * docs/libs/architecture.xml: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-enums.h: + * ges/ges-pipeline.c: + * ges/ges-pipeline.h: + * ges/ges-types.h: + * ges/ges.h: + * tests/check/ges/basic.c: + * tests/check/ges/integration.c: + * tests/check/ges/mixers.c: + * tests/check/ges/project.c: + * tests/check/ges/test-utils.c: + * tests/check/ges/test-utils.h: + * tests/examples/concatenate.c: + * tests/examples/ges-ui.c: + * tests/examples/overlays.c: + * tests/examples/simple1.c: + * tests/examples/test1.c: + * tests/examples/test2.c: + * tests/examples/test3.c: + * tests/examples/test4.c: + * tests/examples/text_properties.c: + * tests/examples/thumbnails.c: + * tests/examples/transition.c: + * tools/ges-launch.c: + Rename GESTimelinePipeline to GESPipeline + rename ges_timeline_pipeline methods to ges_pipeline + +2013-07-17 22:48:17 -0400 Thibault Saunier + + * tests/check/ges/integration.c: + tests: integration: Add some pipeline dumps + When we go to PLAYING, or when we get an error on the bus + + Activate the hack so that we dump the pipeline on first buffer + pushed by the smart adder + +2013-07-17 22:47:31 -0400 Thibault Saunier + + * tests/check/ges/integration.c: + tests: integration: Add video/audio only basic tests + +2013-07-17 20:54:20 -0400 Thibault Saunier + + * ges/ges-uri-asset.c: + * tests/check/ges/integration.c: + tests: integration: Give some more information to user on errors + +2013-07-17 18:34:22 -0400 Thibault Saunier + + * tests/check/ges/integration.c: + tests: intergration: Add some more encoding profiles + +2013-07-17 16:09:29 -0400 Thibault Saunier + + * tests/check/ges/integration.c: + tests: integration: Give the user more details about failure when checking transcoded file + +2013-07-17 16:06:09 -0400 Thibault Saunier + + * tests/check/ges/integration.c: + tests: integration: Be more precise with namings + Fully define formats in the namings + + Add an mp3 + h264 in mov test + +2013-07-17 13:06:05 -0400 Thibault Saunier + + * tests/check/ges/integration.c: + tests: integration: Refactor and make easier to add encoding profiles + +2013-07-17 12:31:02 -0400 Thibault Saunier + + * tests/check/Makefile.am: + * tests/check/ges/integration.c: + tests: integration: Add audio/video only seeking tests + +2013-07-17 12:05:26 -0400 Thibault Saunier + + * tests/check/ges/integration.c: + tests: integration: Remove the effect in test_seeking + It complexifies the test but this is not what we actually want in + that test + +2013-07-16 21:58:16 -0400 Thibault Saunier + + * tests/check/ges/integration.c: + tests: Remove prints + And use GST_DEBUG and friends instead + +2013-07-14 14:03:46 +0200 Vasilis Liaskovitis + + * tests/check/ges/integration.c: + integration: some fixes for seek tests + - Use g_list_remove_link so that ordering of seeks is not mandatory + - use g_slice allocator for SeekInfo structs + - Fix leak in freeing seek list + - Check for NULL seeks at end of test, otherwise fail and free failed seeks + +2013-07-13 15:15:04 +0200 Vasilis Liaskovitis + + * tests/check/ges/integration.c: + integration: add SeekInfo and get_position callback for seek tests + A Seekinfo structure consists of 2 fields: + - position: the position to seek to + - seeking_position: the position to perform the seek from + Seeks can be appended to a global list e.g. from code: + seeks = g_list_append (seeks, new_seek_info (0.2 * GST_SECOND, 0.6 * GST_SECOND)); + seeks = g_list_append (seeks, new_seek_info (1.0 * GST_SECOND, 1.2 * GST_SECOND)); + seeks = g_list_append (seeks, new_seek_info (1.5 * GST_SECOND, 1.8 * GST_SECOND)); + The get_position callback checks the current position and attempts to perform + the corresponding seek with gst_element_seek_simple + +2013-07-02 20:50:05 +0200 Mathieu Duponchelle + + * ges/ges-uri-clip.c: + ges-uri-clip: Add the possibility to specify an assets directory + through the GES_TESTING_ASSETS_DIRECTORY environment variable. + +2013-07-12 19:44:46 -0400 Thibault Saunier + + * tests/check/ges/integration.c: + tests: Simplifie integration tests using macros all around + +2013-04-26 00:03:21 +0200 Mathieu Duponchelle + + * Makefile.am: + * tests/check/Makefile.am: + * tests/check/ges/integration.c: + * tests/check/ges/test-utils.c: + * tests/check/ges/test-utils.h: + tests: Add integration tests + Those are test with real media files, they are run separetely from other + unit tests using the make check-integration command (can be done from + the toplevel directory) + +2013-04-28 00:22:42 +0200 Mathieu Duponchelle + + * tests/check/ges/test-utils.c: + * tests/check/ges/test-utils.h: + test-utils: Add test file generation code. + +2013-06-28 15:49:03 +0200 Mathieu Duponchelle + + * ges/ges-track-element.c: + track-element: No need to log when prio == MIN_GNL_PRIO. + +2013-06-27 23:33:21 +0200 Mathieu Duponchelle + + * ges/gstframepositionner.c: + framepositionner: fix messup with propname enum. + +2013-06-26 23:23:59 +0200 Mathieu Duponchelle + + * ges/ges-track.c: + * ges/ges-track.h: + track: Make it possible to disable the mixing feature + API: + ges_track_set_mixing + ges_track_get_mixing + +2013-06-29 00:17:31 +0200 Mathieu Duponchelle + + * ges/ges-timeline.c: + timeline: create_transitions_on_layer *before* actually commiting + Everything need to be in place before commiting, otherwize it makes no + sense at all. + +2013-07-12 11:55:46 -0400 Thibault Saunier + + * ges/ges-group.c: + * ges/ges-group.h: + group: Add an empty group constructor + As it is more intuitive for users. + API: + ges_group_new + +2013-07-10 23:33:51 +0200 Mathieu Duponchelle + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-group.c: + container/group/clip: Allow creating an empty group. + This is a legitimate use case. + +2013-07-10 21:24:28 +0200 Mathieu Duponchelle + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-container.h: + * ges/ges-group.c: + * ges/ges-timeline.c: + container: Add a 'recursive' argument to the get_children method + API: + - ges_container_get_children (GESContainer *container); + + ges_container_get_children (GESContainer *container, gboolean recurse); + +2013-07-11 02:16:19 +0200 Mathieu Duponchelle + + * ges/ges-group.c: + group: set priv->setting_value to TRUE when moving ourselves in _child_removed + +2013-07-10 23:15:17 -0400 Thibault Saunier + + * ges/ges-timeline-element.c: + * tests/check/ges/group.c: + timelineelement: Make sure that we will never set a negative start + Currently we can end up overflowing the start of others child of our + parent, avoid that making sure we can set our start to what was + requested by the user before actually doing it + + Add a test + +2013-07-09 21:30:59 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Append missing layers when moving groups between layers + This was a missing feature of the newly added groups + +2013-06-26 17:08:57 -0400 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-container.h: + * ges/ges-group.c: + * ges/ges-group.h: + * ges/ges-internal.h: + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + * ges/ges-types.h: + * ges/ges.c: + * ges/ges.h: + * tests/check/Makefile.am: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/group.c: + * tests/check/ges/test-utils.h: + * tests/check/ges/timelineedition.c: + ges: Implement a GESGroup class, subclass of GESContainer + The GESGroup class is used to group various GESContainer + together, it can contain either GESClips or GESGroup or both. + +2013-07-07 22:40:55 -0400 Thibault Saunier + + * ges/ges-xml-formatter.c: + xml-formatter: Indent formatted files + +2013-07-03 18:33:05 -0400 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + timeline-element: Add a method to get the topelevel parent of an element + API: + ges_timeline_element_get_toplevel_parent + +2013-07-03 12:48:58 -0400 Thibault Saunier + + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/test-utils.c: + * tests/check/ges/test-utils.h: + * tests/check/ges/timelineedition.c: + * tests/check/ges/transition.c: + tests: More safely check if objects where destroyed + Check if an object rthat has already been freed has been destroyed is not safe. + Add a helper function that uses weak reference to check that objects that are expected + to be destroyed when unrefing an object are actually destroyed. + +2013-07-02 19:47:48 -0400 Thibault Saunier + + * ges/ges-clip.c: + clip: Emit the notify::layer signal only when actually needed + That means: + - only when we do change layer + - At the end of moving between two layers + +2013-07-02 10:56:40 -0400 Thibault Saunier + + * ges/ges-clip.c: + clip: Avoid list corruption when grouping objects + We are currently iterating over a list that is modified in the same + method, we have to get a copy of the list, and iterate over the copy. + +2013-07-01 20:35:39 -0400 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-internal.h: + clip: Add an internal method to easily get the priority of the layer the clip is in + +2013-07-01 17:57:03 -0400 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-container.h: + container: Remove the get_priority_range vmethod + We now let full control to subclasses so we do not need it anymore. + +2013-07-01 17:51:32 -0400 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-container.h: + container: Let full control of children priorities to subclasses + For that we make the children_control_mode a protected filed, directly usable by + subclasses, removing the method to set it. + And we let the subclass set and get the priority offsets to the container class. + +2013-07-01 16:19:31 -0400 Thibault Saunier + + * ges/ges-track.c: + track: Make sure that new gaps are filled before removing the old ones + Currently we can end up having gaps in track as the first step of the + gap filling method removes currently set gaps. + +2013-06-29 19:31:23 -0400 Thibault Saunier + + * ges/ges-clip.c: + * tests/check/ges/effects.c: + clip: Handle child priority offsets when setting priority + +2013-06-28 19:17:54 -0400 Thibault Saunier + + * ges/ges-container.c: + container: Do not allow adding an element to a container if it already has a parent + This should never happen, an element can have 1 and only 1 parent. + +2013-06-28 19:16:47 -0400 Thibault Saunier + + * ges/ges-container.c: + container: "Implement" the set_priority vmethod + This way we will just accept any value setted + +2013-06-28 19:15:59 -0400 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-container.c: + ges: Avoid leaking the timeline when grouping containers + +2013-06-28 14:39:16 -0400 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-container.h: + container: Let subclasses decide when height change should be computed + API: + - GESContainer.compute_height vmethod + + _ges_container_set_height + +2013-07-02 13:43:49 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + base-xml-formatter: s/ducation/duration/ + +2013-06-28 12:56:17 -0400 Thibault Saunier + + * ges/ges-container.c: + container: ges_container_ungroup return a transfer full list + +2013-06-28 11:23:27 -0400 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-container.c: + * ges/ges-container.h: + * tests/benchmarks/timeline.c: + * tests/check/ges/layer.c: + * tests/check/ges/timelineedition.c: + ges: Move ges_clip_edit to GESContainer + This exact same method will be needed in GESGroup, so we should have the method + in the common parent class. + API: + - ges_clip_edit + + ges_container_edit + + GESContainer->edit vmethod + +2013-06-26 19:55:37 -0400 Thibault Saunier + + * ges/ges-container.c: + container: Update offsets in GESTimelineElement vmethod implementations + So subclasses just have to link up to resync offsets + +2013-06-26 17:08:16 -0400 Thibault Saunier + + * ges/ges-effect-asset.c: + * ges/ges-gerror.h: + docs: Misc documentation fixes + +2013-06-25 18:37:48 -0400 Thibault Saunier + + * ges/ges-clip.c: + clip: Never try to set the start after the end of an element when trimming + +2013-06-25 18:37:17 -0400 Thibault Saunier + + * ges/ges-internal.h: + internal: Add a macro to make it easier to get the end of a TimelineElement + +2013-06-25 18:36:24 -0400 Thibault Saunier + + * ges/ges-track.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/layer.c: + track: Update gaps only when commiting + We were still updating them at useless moments, do it only when absolutely needed. + +2013-06-25 18:34:44 -0400 Thibault Saunier + + * ges/ges-container.c: + container: Fix the way we check priority of subclasses when grouping objects + The resulting list was from lower to higher, we need the contrary + +2013-06-26 16:54:02 -0400 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-container.c: + ges: Move GESTimelineElemt vmethod implementation from container to clip + This is where they belong to has they are specific to that + implementation of the baseclass + +2013-06-25 18:32:49 -0400 Thibault Saunier + + * ges/ges-container.c: + * ges/ges-container.h: + container: Make initiated_move a read only protected member + It is a interesting information for subclasses. + Conflicts: + ges/ges-container.c + +2013-07-09 10:57:51 -0400 Thibault Saunier + + * ges/ges-internal.h: + internal: Fix typo in the header + ges_base_xml_formatter_add_control_bindingi was meant to be + ges_base_xml_formatter_add_control_binding + +2013-07-01 23:33:01 +0200 Mathieu Duponchelle + + * ges/ges-audio-transition.c: + * ges/ges-track-element.h: + * ges/ges-video-transition.c: + track-element: Remove duration_changed virtual method. + We use notifies for the properties. + + Use notifies in audio-transition and video-transition + +2013-07-09 00:31:30 +0200 Mathieu Duponchelle + + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-xml-formatter.c: + formatters: Save and load bindings applied to sources. + +2013-06-17 07:55:54 +0200 Alban Browaeys + + * tools/ges-launch.c: + ges-launch: make it portable to all locales. + Call setlocale (LC_ALL, "") as per setlocale man page + to make ges-launch portable to all locales (instead of default + "C" one). + Fixes g_option_context_parse on: + $ ges-launch-1.0 --verbose -r -q /home/prahal/Vidéos/Test3.xges -o + file:///home/prahal/Test3.mpeg + Error initializing: Invalid byte sequence in conversion input + The accentuated character in "Vidéos" the french xdg user directory + for "Videos" is what is choked upon. + https://bugzilla.gnome.org/show_bug.cgi?id=702425 + +2013-06-28 00:24:33 +0100 Tim-Philipp Müller + + * ges/ges-timeline-pipeline.c: + timelinepipeline: fix caps leak + +2013-06-26 12:57:17 +0000 Руслан Ижбулатов + + * ges/ges-timeline-pipeline.c: + timelinepipeline: make the caps from encoding profile writable + https://bugzilla.gnome.org/show_bug.cgi?id=703121 + +2013-06-23 18:27:41 -0400 Thibault Saunier + + * ges/ges-uri-source.c: + urisource: Do not let user reset the URI property + This is not supported right now and would lead to unexpected behaviours + +2013-06-03 23:02:15 +0200 Mathieu Duponchelle + + * ges/Makefile.am: + * ges/ges-smart-video-mixer.c: + * ges/ges-uri-source.c: + * ges/ges.c: + * ges/gstframepositionner.c: + * ges/gstframepositionner.h: + ges: Add a framepositionner element used in ges-smart-mixer and ges-uri-source + It adds metadata on the buffers and the mixer parses them. + This is done because we want to keep positionning properties + and set them on the dynamic mixer pad. + Conflicts: + ges/Makefile.am + +2013-05-30 06:05:48 +0200 Mathieu Duponchelle + + * tests/check/ges/mixers.c: + tests: Add a audio/video mixing test. + +2013-05-30 06:04:47 +0200 Mathieu Duponchelle + + * ges/ges-video-track.c: + video-track: "implement" get_mixing_element. + +2013-05-29 18:48:42 +0200 Mathieu Duponchelle + + * ges/Makefile.am: + * ges/ges-smart-video-mixer.c: + * ges/ges-smart-video-mixer.h: + * ges/ges-video-track.h: + ges-smart-mixer: first code dump, mainly copy paste from ges-smart-adder. + +2013-04-30 19:19:39 +0200 Simon Corsin + + * ges/ges-layer.c: + ges-layer.c: notify priority changes. + +2013-05-16 09:40:22 +0200 Mathieu Duponchelle + + * ges/ges-uri-source.c: + ges-uri-source: Refactoring work. + + Categorize functions (Callbacks, vmethods) + + make more generic functions for the creation of the bin. + +2013-05-16 08:10:35 +0200 Mathieu Duponchelle + + * ges/ges-base-effect.c: + * ges/ges-uri-source.c: + * ges/ges-utils.c: + * ges/ges-utils.h: + uri-source: Expose the volume property. + + Make the pspec_hash function an internal util. + + Add a create_props_hashtable implementation + + If TRACK_TYPE_AUDIO, put the volume properties in the hashtable. + +2013-05-16 04:22:16 +0200 Mathieu Duponchelle + + * ges/ges-uri-source.c: + * ges/ges-uri-source.h: + uri-source: when creating the audio element, set the volume to the layr volume when necessary. + +2013-05-16 03:27:20 +0200 Mathieu Duponchelle + + * ges/ges-uri-source.c: + ges-uri-source: don't use gnlurisource but a custom bin. + + This bin is a uridecodebin when GES_TRACK_TYPE_VIDEO + + This bin contains a uridecodebin and a volume when GES_TRACK_TYPE_AUDIO + +2013-05-15 18:59:10 +0200 Mathieu Duponchelle + + * ges/ges-smart-adder.c: + smart-adder: remove volume from the bin, which quite simplifies the code. + + Don't be too smart, adder. + +2013-03-31 00:08:15 +0100 Thibault Saunier + + * ges/Makefile.am: + * ges/ges-audio-track.c: + * ges/ges-audio-track.h: + * ges/ges-smart-adder.c: + * ges/ges-smart-adder.h: + * ges/ges-track.c: + * tests/check/Makefile.am: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/effects.c: + * tests/check/ges/layer.c: + * tests/check/ges/mixers.c: + smart-adder: Implement a GESSmartAdder bin element to be used as mixing element + ..in audio tracks + +2013-04-22 00:21:58 -0300 Thibault Saunier + + * ges/ges-layer.c: + * ges/ges-meta-container.h: + * tests/check/ges/layer.c: + meta-container: Add a VOLUME default meta to layers + +2013-03-31 12:34:58 +0200 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-custom-source-clip.c: + ges: Misc documentation fixes + +2013-03-30 19:02:52 +0100 Thibault Saunier + + * ges/ges-track.c: + * ges/ges-track.h: + track: Implement infrastructure for mixing + +2013-03-30 19:01:26 +0100 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-track-element.c: + track-element: Set a reference to the GESTrackElement on the GnlObjects using qdata + +2013-03-29 19:23:00 +0100 Thibault Saunier + + * ges/ges-auto-transition.c: + * ges/ges-clip.c: + * ges/ges-internal.h: + * ges/ges-layer.c: + * ges/ges-simple-layer.c: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + * ges/ges-track.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/project.c: + * tests/check/ges/test-utils.h: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + ges: Make space in the GESTracks to be able to add mixing elements later + And update the tests + +2013-03-29 19:04:54 +0100 Thibault Saunier + + * tests/check/ges/effects.c: + tests:effects: Make use of normal layers, and enhance tests + +2013-03-29 18:56:31 +0100 Thibault Saunier + + * ges/ges-layer.c: + layer: Fix some mix up in variable names + +2013-03-29 18:55:27 +0100 Thibault Saunier + + * ges/ges-layer.c: + layer: Simplify a bit how we handle priorities + +2013-03-29 18:53:25 +0100 Thibault Saunier + + * ges/ges-timeline-element.c: + * ges/ges-timeline.c: + timeline-element: Make it possible to reset the timeline property to NULL + + Add some debug symbol + +2013-03-28 18:51:45 +0100 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-audio-track.c: + * ges/ges-audio-track.h: + * ges/ges-pitivi-formatter.c: + * ges/ges-track.c: + * ges/ges-track.h: + * ges/ges-types.h: + * ges/ges-utils.c: + * ges/ges-video-track.c: + * ges/ges-video-track.h: + * ges/ges.h: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/text_properties.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + * tests/examples/ges-ui.c: + * tests/examples/overlays.c: + * tests/examples/simple1.c: + * tests/examples/test2.c: + * tests/examples/test3.c: + * tests/examples/test4.c: + * tests/examples/text_properties.c: + * tests/examples/thumbnails.c: + * tests/examples/transition.c: + * tools/ges-launch.c: + ges: Implement GESAudioTrack and GESVideoTrack, subclasses of GESTrack + +2013-06-16 21:47:52 -0400 Thibault Saunier + + * tools/ges-launch.c: + ges-launch: Report position while playing back + Giving more feedbacks to the user + +2013-06-16 19:10:18 -0400 Thibault Saunier + + * tools/ges-launch.c: + ges-launch: Remove xptv formatter related code + It is not usefull anymore + +2013-06-15 22:13:20 -0400 Thibault Saunier + + * tools/ges-launch.c: + ges-launch: Properly add UriClipAssets to the project + +2013-06-09 12:29:05 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-project.c: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-track-element.c: + * ges/ges-track.c: + * ges/ges-track.h: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/test-utils.h: + * tests/check/ges/timelineedition.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + * tools/ges-launch.c: + ges: Port to the new commit based API in GNL + The GNL API changed to go from a model where user could + enable/disable updates in the composition, which leaded to races + in many places, to a model where any positioning change in the + composition is not directly done but 'cached' and then the user + has to commit those changes so they become effective in the media + processing stack. + The new API in GES is pretty similare and is basically copy + pasting this new design. + We still need to see if in some context it would make sense to add + a mode where we would commit any changes ourself at the end of our + operation for basic use cases. + Removed APIs: + ges_timeline_enable_update + ges_timeline_is_updating + ges_track_enable_update + ges_track_is_updating + New APIs: + ges_track_commit + ges_timeline_commit + +2013-06-20 14:23:26 +0200 Lubosz Sarnecki + + * ges/ges-base-xml-formatter.c: + * ges/ges-project.c: + timeline: fix segfaults + don't call the timeline update, when the reference is invalid + https://bugzilla.gnome.org/show_bug.cgi?id=702605 + +2013-06-18 13:32:38 +0100 Tim-Philipp Müller + + * autogen.sh: + * common: + autogen.sh: generate from common module, fixing srcdir != builddir build + https://bugzilla.gnome.org/show_bug.cgi?id=702424 + +2013-06-18 13:14:48 +0100 Tim-Philipp Müller + + * gst-editing-services.doap: + Add .doap file + Needed for common/update-autogen, but generally not a bad idea. + +2013-04-30 19:16:10 +0200 Mathieu Duponchelle + + * ges/ges-uri-asset.c: + ges-uri-asset.c: Fix ges_uri_asset_request_sync annotations. + +2013-06-12 11:32:16 -0400 Thibault Saunier + + * ges/ges-formatter.c: + * ges/ges-project.c: + project: Disable update in the project rather than the formatter + We need to make sure the update are disabled until the project is fully + loaded, let the responsability to the project instead of the formatter + +2013-06-12 11:09:13 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Sync track enable_update property with parent + When we add a track to a timeline, we want it "enable update" property + to be set to the timeline's + +2013-06-12 10:48:03 -0400 Thibault Saunier + + * tools/ges-launch.c: + ges-launch: Make use of assets for uri clips + It avoids races in TrackElement creations. + We should make use of assets everywhere in ges-launch but start using + them for uriclips first for now. + +2013-06-05 15:18:36 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From 098c0d7 to 01a7a46 + +2013-05-30 11:40:36 -0400 Thibault Saunier + + * tests/check/ges/uriclip.c: + tests: Pass a ref of CAPS_ANY to ges_track_new + +2013-05-29 16:48:03 -0400 Thibault Saunier + + * tests/check/ges/basic.c: + * tests/check/ges/test-utils.c: + * tests/check/ges/test-utils.h: + tests: Add a basic test for pipeline state change + Add some test utils to create a pipeline + +2013-05-29 14:05:52 -0400 Thibault Saunier + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline.c: + ges: Protect from Gst dynamic callbacks + The pad-added and no-more-pad signal can be emited from any thread + so we have to protect our code from that + +2013-05-23 15:52:35 -0400 Thibault Saunier + + * ges/ges-track.c: + * tests/check/ges/backgroundsource.c: + track: Update all gaps when timeline duration changed + And add a unit test to check that a gap is created in empty tracks + +2013-05-23 13:16:22 -0400 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + * ges/ges-uri-source.c: + * ges/ges-uri-source.h: + * tests/check/ges/uriclip.c: + Finish renaming filesource to urisource + +2013-05-23 11:57:42 -0400 Thibault Saunier + + * ges/ges-track.c: + track: Update gaps when we set the timeline + +2013-05-28 08:51:08 +0200 Sebastian Dröge + + * ges/ges-pitivi-formatter.c: + ges-pitivi-formatter: Remove some unneeded includes and clean up includes + Fixes the build on Windows, where there's no unistd.h... which wasn't + needed at all. + https://bugzilla.gnome.org/show_bug.cgi?id=701115 + +2013-05-27 22:10:03 -0400 Thibault Saunier + + * tests/check/ges/asset.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/project.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/text_properties.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + tests: Use the gst_check_run_suite helper everywhere + Using GST_CHECK_MAIN where appropriate + This way it is possible to specify an XML file to store tests results in + +2013-05-15 10:55:22 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From 5edcd85 to 098c0d7 + +2013-03-05 17:09:18 -0500 Nicolas Dufresne + + * .gitignore: + Update gitignore + +2013-03-05 17:07:29 -0500 Nicolas Dufresne + + * ges/Makefile.am: + * ges/ges-asset.h: + * ges/ges-internal-enums.h: + GESAssetLoadingReturn cannot be internal + The enumeration is referenced in a public API. + +2013-05-05 11:13:24 +0100 Thibault Saunier + + * ges/ges-clip.c: + clip: Avoid corruption of our list of children while ungrouping + +2013-04-24 15:25:20 +0300 Anton Belka + + * tests/check/Makefile.am: + * tests/check/ges/project.c: + * tests/check/ges/test-auto-transition.xges: + tests: add project auto-transition test + +2013-04-24 15:23:44 +0300 Anton Belka + + * tests/check/ges/layer.c: + tests: add timeline auto-transition test + +2013-04-24 15:18:01 +0300 Anton Belka + + * docs/libs/ges-sections.txt: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + timeline: add auto-transition + API: + ges_timeline_get_auto_transition + ges_timeline_set_auto_transition + GESTimeline::auto-transition + +2013-04-30 18:26:57 +0100 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + basexmlformatter: Do not allow empty file loading + +2013-04-27 03:45:29 -0300 Thibault Saunier + + * ges/ges-video-test-source.c: + video-test-src: Force video/x-raw + Avoiding to hit errors with video/x-bayer + +2013-04-21 21:35:22 +0200 Mathieu Duponchelle + + * ges/ges-audio-transition.c: + audiotransition: Add a resampler in the audio transition bin + +2013-04-21 19:21:14 +0200 Mathieu Duponchelle + + * ges/ges-audio-transition.c: + audiotransition: Fix porting error of the interpollator + + update debug statements s/LOG/INFO + (acontrolsource != bcontrolsource) + +2013-04-27 03:44:40 -0300 Thibault Saunier + + * ges/ges-audio-test-source.c: + * ges/ges-test-clip.c: + * ges/ges-video-test-source.c: + * tests/check/ges/backgroundsource.c: + test-clip: Do not set black/silent by default + +2013-04-24 03:50:40 +0200 Mathieu Duponchelle + + * ges/ges-video-transition.c: + replace query_caps with get_current_caps. don't check for unused gnlobject. + +2013-04-23 20:04:04 -0300 Thibault Saunier + + * android/ges.mk: + * bindings/python/examples/material.py: + * bindings/python/examples/simple.py: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-auto-transition.c: + * ges/ges-auto-transition.h: + * ges/ges-base-effect-clip.c: + * ges/ges-base-effect.c: + * ges/ges-base-xml-formatter.c: + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-container.c: + * ges/ges-effect-clip.c: + * ges/ges-effect.c: + * ges/ges-internal.h: + * ges/ges-layer.c: + * ges/ges-layer.h: + * ges/ges-operation-clip.c: + * ges/ges-overlay-clip.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-simple-layer.c: + * ges/ges-simple-layer.h: + * ges/ges-simple-timeline-layer.h: + * ges/ges-source-clip.c: + * ges/ges-source-clip.h: + * ges/ges-test-clip.c: + * ges/ges-text-overlay-clip.c: + * ges/ges-text-overlay.c: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-title-clip.c: + * ges/ges-title-clip.h: + * ges/ges-track-element.c: + * ges/ges-transition-clip.c: + * ges/ges-types.h: + * ges/ges-utils.c: + * ges/ges-xml-formatter.c: + * ges/ges.h: + * tests/benchmarks/timeline.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/project.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/text_properties.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + * tests/examples/concatenate.c: + * tests/examples/ges-ui.c: + * tests/examples/overlays.c: + * tests/examples/simple1.c: + * tests/examples/test1.c: + * tests/examples/test2.c: + * tests/examples/test3.c: + * tests/examples/test4.c: + * tests/examples/text_properties.c: + * tests/examples/thumbnails.c: + * tests/examples/transition.c: + * tools/ges-launch.c: + Rename GESTimelineLayer to GESLayer + +2013-04-23 19:57:44 -0300 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + * ges/ges-timeline.c: + * ges/ges-xml-formatter.c: + * tests/benchmarks/timeline.c: + * tests/check/ges/clip.c: + * tests/check/ges/layer.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/uriclip.c: + * tests/examples/test1.c: + layer: Remove the "rate" property of ges_timeline_layer_add_asset + API: + - ges_timeline_layer_add_asset (layer, asset, start, inpoint, duration, rate, track_types); + + ges_timeline_layer_add_asset (layer, asset, start, inpoint, duration, track_types); + +2013-04-23 22:38:23 +0200 Mathieu Duponchelle + + * ges/ges-track-element.c: + trackelement: asynchronously add bindings if the track-element is not in a track yet. + Also fix annotations. + +2013-04-22 17:34:09 -0300 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-formatter.c: + * ges/ges-formatter.h: + * ges/ges-pitivi-formatter.c: + formatter: Pass a dummy instance of formatter to virtual method + Instead of passing the class itself + +2013-04-22 23:56:03 +0100 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From 3cb3d3c to 5edcd85 + +2013-04-22 09:41:26 -0300 Thibault Saunier + + * ges/ges-xml-formatter.c: + Fix compilation + +2013-04-21 21:29:29 -0300 Thibault Saunier + + * ges/ges-xml-formatter.c: + xml-formatter: Use G_GUINT64_FORMAT where needed + +2013-04-21 21:13:00 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + uri-clip-asset: Add "_class_" to a class method + API CHANGE: + - ges_uri_clip_asset_set_timeout + + ges_uri_clip_asset_class_set_timeout + +2013-04-21 21:11:52 -0300 Thibault Saunier + + * ges/ges-timeline.c: + * ges/ges-xml-formatter.c: + ges: Fix compilation with clang + +2013-04-19 19:58:21 -0300 Thibault Saunier + + * ges/ges-project.c: + project: Accept NULL as a valid value for @id in _create_asset + +2013-04-18 18:41:02 -0300 Thibault Saunier + + * tests/check/ges/clip.c: + tests: Check splitting a clip with several TrackElement + +2013-04-18 21:45:18 -0300 Thibault Saunier + + * ges/ges-clip.c: + clip: Rework the splitting method + + Avoid setting clip duration of our parent ourself + Now each and every TrackElement inside a clip have the same + start/inpoint/duration + +2013-04-18 18:59:52 -0300 Thibault Saunier + + * ges/ges-timeline-pipeline.c: + ges: Fix compilation + +2013-04-18 18:37:17 -0300 Thibault Saunier + + * ges/ges-timeline-pipeline.c: + pipeline: Add API guards where needed + +2013-04-17 16:51:30 -0300 Thibault Saunier + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + * tests/examples/thumbnails.c: + * tools/ges-launch.c: + pipeline: Add a GError argument + +2013-04-17 16:48:05 -0300 Thibault Saunier + + * ges/ges-timeline-pipeline.c: + pipeline: Return FALSE in save_thumbnail when the operation fails + +2013-04-15 01:30:10 +0200 Mathieu Duponchelle + + * tests/check/ges/timelineedition.c: + tests:timelineedition: Add a simple trimming test + +2013-04-14 23:19:02 -0300 Thibault Saunier + + * ges/ges-container.c: + container: The TimelineElement.inpoint property is call "in-point" not inpoint + +2013-03-22 19:44:28 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-clip.c: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + ges-clip: Remove the unlocked TrackElement APIs + Remove APIs: + ges_track_element_set_locked + ges_track_element_is_locked + Those APIs where really not nice to use and were causing more issues + than solving them. If 2 time related properties of TimelineElement must + be different, then those element can *not* have the same parent. + Plus, with the new ges_container_group () API, we will recreate 1 + GESClip containing the proper GESTimelineElements if it is the thing + to do. + +2013-03-22 19:34:14 -0300 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + pitivi-formatter: Remove broken code to handle unlocked track object + WARNING: The plan is to remove unlocked track object APIs so this is + the first part of that process... that code was already broken, and + *needs* to be fixed anyway, better do it using new APIs + +2013-03-22 18:43:30 -0300 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + pitivi-formatter: Remove saving code + +2013-04-14 17:58:38 +0100 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From aed87ae to 3cb3d3c + +2013-04-09 21:03:03 +0200 Stefan Sauer + + * common: + Automatic update of common submodule + From 04c7a1e to aed87ae + +2013-04-09 00:02:14 -0300 Thibault Saunier + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline.c: + timeline: call sync_state_with_parent when adding a child + +2013-03-31 16:07:14 +0200 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + * tests/check/ges/project.c: + track-element: Rename set_property_controlling_parameters set_control_source + + Generate the documentation + +2013-03-30 18:54:50 +0100 Mathieu Duponchelle + + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + * ges/ges-xml-formatter.c: + * tests/check/ges/project.c: + * tests/check/ges/test-keyframes.xges: + [Keyframes] Adds API to set a control binding on a track element, and the serialization code. + +2013-03-30 15:40:38 +0100 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + * ges/ges-timeline-layer.c: + Changing remaining clip::track-element-added to container::child-added + +2013-03-30 14:35:45 +0100 Thibault Saunier + + * ges/ges-timeline-pipeline.c: + pipeline: Implement the video overlay interface + +2013-03-30 13:37:43 +0100 Thibault Saunier + + * tools/ges-launch.c: + ges-launch: Do no create "normal" timeline when you load a project + +2013-03-30 13:34:56 +0100 Thibault Saunier + + * ges/ges-timeline-pipeline.c: + timeline-pipeline: Enhance API guards + +2013-03-30 13:34:36 +0100 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Do no unref the timeline before returning it + +2013-03-30 12:30:47 +0100 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-container.h: + container: s/get_priorty_range/get_priority_range/ + +2013-03-29 15:50:12 +0100 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Comment some variables goals + +2013-03-28 19:00:25 +0100 Thibault Saunier + + * tests/check/ges/effects.c: + tests:effect: Do not re-add effect to the track + +2013-03-24 18:42:55 +0100 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-container.h: + * tests/check/ges/simplelayer.c: + container: Let subclasses handle the height + + Fix tests (starting using GESTestClip instead of GESCustomClip) + Now the height is not only growing, but can also go down, as the value + is just simply computed + API: + GESContainer::compute_height virtual method + +2013-03-23 09:46:38 +0100 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-container.h: + container: Properly implement ges_container_group + +2013-03-23 08:48:43 +0100 Thibault Saunier + + * tests/check/ges/basic.c: + test:basic: Do not add useless references, and minor improvements + +2013-03-23 08:45:00 +0100 Thibault Saunier + + * ges/ges-clip.c: + clip: Emit notify signal when setting Clip.layer + +2013-03-23 08:14:55 +0100 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Remove TrackElement from its container on GESTimelineLayer::"clip-removed" + .... when the Track is NULL + +2013-03-23 03:27:46 -0300 Thibault Saunier + + * ges/ges-container.c: + * tests/check/ges/clip.c: + container: Make sure that the child exists when emiting the "child-removed" signal + + Add a test + +2013-03-23 03:26:33 -0300 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-container.h: + * ges/ges-internal.h: + container: Replace ignore_notify by a GESChildrenControlMode flag + +2013-03-23 01:35:02 -0300 Thibault Saunier + + * ges/ges-container.c: + container: Stop ignoring notifies if ->add_child fails + +2013-03-23 01:33:39 -0300 Thibault Saunier + + * ges/ges-container.c: + * ges/ges-internal.h: + * ges/ges-utils.c: + internal: Add a element_end_compare + +2013-03-23 01:31:23 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-element.h: + timeline-element: Add a macro to get element 'end' + +2013-03-22 17:39:04 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-asset.c: + * ges/ges-base-xml-formatter.c: + * ges/ges-gerror.h: + * ges/ges-uri-asset.c: + ges: Keep ges-gerror categories simple. + +2013-03-21 22:17:10 -0300 Thibault Saunier + + * ges/ges-timeline-element.c: + * ges/ges-uri-asset.h: + * tests/check/ges/test-utils.h: + * tests/check/ges/uriclip.c: + Misc cleaning + +2013-03-21 22:12:47 -0300 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-track-element.h: + track-element: Make ges_track_element_set_track internal + Removed API: + + ges_track_element_set_track + +2013-03-21 22:03:09 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-clip.h: + * ges/ges-internal.h: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/overlays.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + ges: Make ges_clip_create_track_element(s) internal methods + + Fix tests (we still need a round of modernisation, making use of + assets where it makes sense) + There is no reason to use those method outside of GES, so remove them, + cleaning the API and making it easier for users. + Removed APIs: + ----------- + * ges_clip_create_track_element + * ges_clip_create_track_elements + +2013-03-21 21:42:31 -0300 Thibault Saunier + + * tests/check/Makefile.am: + * tests/check/ges/image.png: + * tests/check/ges/test-utils.c: + * tests/check/ges/test-utils.h: + * tests/check/ges/uriclip.c: + tests::uriclip: Use a real file to test still images + + Make use of GESAssets + And do proper refactoring + +2013-03-19 21:07:58 -0300 Thibault Saunier + + * ges/ges-uri-asset.c: + uri-asset: Properly handle images and do not duplicate the TrackType + +2013-03-19 19:49:09 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-asset.c: + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + * tests/check/ges/uriclip.c: + uri-asset: Implement a ges_uri_clip_asset_request_sync method + This way we let the possibility to the user to actually do it, but we avoid him to do it + without knowing it is absolutely not recommanded to. + API: + + ges_uri_clip_asset_request_sync + +2013-03-18 12:41:06 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-asset.c: + * ges/ges-base-xml-formatter.c: + * ges/ges-gerror.h: + * ges/ges.h: + Start categorizing GError types in GES + +2013-03-18 10:03:19 -0300 Thibault Saunier + + * ges/ges-timeline-element.c: + timeline-element: Do not give a reference to the calles of g_object_get_property + ... for both the "parent" and the "timeline" properties + Making things simpler to handle for the copy method. + +2013-03-18 10:02:10 -0300 Thibault Saunier + + * ges/ges-clip.c: + * tests/check/ges/clip.c: + clip: Make it mandatory that a clip is in a layer to be splittable + Otherwize we will not be able to describe if the returned object has a floating reference or not, and this would screw the introspection. + +2013-03-18 09:49:18 -0300 Thibault Saunier + + * ges/ges-project.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline.c: + * ges/ges-track.c: + Use gst_object_ref_sink instead of g_object_ref_sink when appropriate + Making refcount issue debugging simpler + +2013-03-16 19:05:04 -0300 Thibault Saunier + + * ges/ges-asset.c: + * ges/ges-base-xml-formatter.c: + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-project.c: + * ges/ges-simple-timeline-layer.c: + * ges/ges-test-clip.c: + * ges/ges-text-overlay-clip.c: + * ges/ges-text-overlay.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline.c: + * ges/ges-title-clip.c: + * ges/ges-title-source.c: + * ges/ges-track-element.c: + * ges/ges-track.c: + * ges/ges-transition-clip.c: + * ges/ges-uri-asset.c: + * ges/ges-utils.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/effects.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/test-utils.c: + * tests/check/ges/text_properties.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + * tests/examples/assets.c: + * tests/examples/concatenate.c: + * tests/examples/ges-ui.c: + * tools/ges-launch.c: + Always prefer gst_object_(un)ref over g_object_(un)ref + Making the refcount issue debugging easier + +2013-03-15 12:01:58 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-clip.h: + * ges/ges-internal.h: + clip: Make set/is_moving_from_layer internal + +2013-03-15 11:58:59 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-clip.h: + * ges/ges-internal.h: + clip: Reindent header and make ges_clip_set_layer internal + +2013-03-15 11:32:48 -0300 Thibault Saunier + + * ges/ges-track.c: + * ges/ges-track.h: + track: Cleanup header and add a FIXME + +2013-03-15 00:01:47 -0300 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-timeline.c: + * ges/ges-track-element.c: + * ges/ges-track.c: + * tests/check/ges/basic.c: + Remove GESTrackElements from GESTracks when removing from a GESClip + ... Not the other way round. + + Add and enhance debugging info on the way + The user should not be responsible for removing the GESTrackElements from + GESTracks, instead, removing it from a GESClip should imply removing + it from any GESTrack it is in. + This patch changes sensibly the behaviour when we remove a + GESTrackElement from a GESTrack, not remoing it from the GESClip it is + in. *But*, users should never remove a GESTrackElement from a GESTrack + anyway. The testsuite has been updated to that new behaviour. + +2013-03-14 12:53:25 -0400 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-clip.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-timeline.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/effects.c: + * tests/examples/ges-ui.c: + ges: Make GESTimeline responsible for adding GESTrackElement to GESTrack + + Fix tests as necessary (Do not use agingtv as it can be "applied" on any TrackType + and is not representative of what happens IRL) + We already had the infrastructure so the user can have the control over where to add + the elements (through the "select-track-for-object" signal). We now make use of that + signal everytime a GESClip is added to a GESTimelineLayer. This make user's life easier, + and object responsability clearer. + +2013-03-14 11:14:31 -0400 Thibault Saunier + + * ges/Makefile.am: + * ges/ges-effect-asset.c: + * ges/ges-effect-asset.h: + * ges/ges-effect.c: + Add a GESEffectAsset class, and make sure to set the GESTrackType asap on effects + + Make use of the asset in ges_effect_new + +2013-03-03 11:50:10 -0300 Thibault Saunier + + * ges/ges-custom-source-clip.c: + * ges/ges-effect-clip.c: + * ges/ges-test-clip.c: + * ges/ges-text-overlay-clip.c: + * ges/ges-title-clip.c: + * ges/ges-transition-clip.c: + ges: Use GESAsset in clip contructors when possible + +2013-03-03 11:16:10 -0300 Thibault Saunier + + * ges/ges-timeline-element.c: + timeline-element: Set asset from the copied element to the new copy + +2013-03-02 18:35:34 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-container.h: + * tests/check/ges/clip.c: + container: Add a ges_container_group method + + Add some basic unit tests + API: + GESContainer:group vmethod + ges_container_group + +2013-03-01 22:26:01 -0300 Thibault Saunier + + * ges/ges-track.c: + track: Do not remove a TrackElement from a NULL clip + +2013-03-01 20:25:17 -0300 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-container.c: + * ges/ges-container.h: + * tests/check/ges/clip.c: + container: Add a way to ungroup a GESContainer into several GESContainers + + Add simple unit test + API: + GESContainerClass::ungroup vmethod + ges_container_ungroup + +2013-03-01 22:05:45 -0300 Thibault Saunier + + * ges/ges-timeline-element.c: + timeline-element: Make it possible to reset parent to NULL + +2013-03-01 19:18:10 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-base-xml-formatter.h: + * ges/ges-container.c: + * ges/ges-container.h: + * ges/ges-pitivi-formatter.h: + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + * ges/ges-timeline-element.h: + * ges/ges-timeline-layer.h: + * ges/ges-timeline.h: + Fix some documentations + +2013-02-28 22:27:50 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-auto-transition.c: + * ges/ges-base-xml-formatter.c: + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-container.c: + * ges/ges-container.h: + * ges/ges-internal.h: + * ges/ges-pitivi-formatter.c: + * ges/ges-simple-timeline-layer.c: + * ges/ges-source-clip.c: + * ges/ges-test-clip.c: + * ges/ges-text-overlay-clip.c: + * ges/ges-timeline.c: + * ges/ges-title-clip.c: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + * ges/ges-track.c: + * ges/ges-transition-clip.c: + * ges/ges-types.h: + * ges/ges-uri-clip.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + * tests/examples/ges-ui.c: + * tests/examples/transition.c: + Implement GESContainer + + Fix unit tests + + Minor enhancement in unit tests + API changes: + ----------- + * ges_track_element_get_clip -> ges_timeline_element_get_parent + * ges_clip_add_track_element -> ges_container_add + * ges_clip_release_track_element -> ges_container_remove + * ges_clip_get_track_elements -> ges_container_get_children + (or GES_CONTAINER_CHILDREN) + +2013-03-01 11:03:18 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + docs: Remove ges_clip_lock_track_elements + it does not exist anymore... + +2013-02-28 22:22:35 -0300 Thibault Saunier + + * ges/ges-timeline-element.c: + timeline-element: Enhance debug statement and documentation + + Accept NULL as a parent + +2013-02-28 15:12:15 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-timeline-layer.c: + * ges/ges-track.c: + timeline-element: Add a "timeline" property + +2013-02-28 18:14:22 -0300 Thibault Saunier + + * .gitignore: + gitignore: Ignore *.page + +2013-03-14 16:09:37 -0300 Thibault Saunier + + * docs/random/rework_class_hierarchie.html: + docs: Add an little explanation about the class hierarchie rework + +2013-03-07 00:04:38 +0000 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From 2de221c to 04c7a1e + +2013-03-06 10:27:15 +0400 Руслан Ижбулатов + + * ges/ges-timeline-element.c: + ges-timeline-element: Fix GST_DEBUG_OBJECT invocations + Fixes #695267 + +2013-02-14 23:34:48 -0300 Thibault Saunier + + * ges/ges-audio-transition.c: + * ges/ges-auto-transition.c: + * ges/ges-base-xml-formatter.c: + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-custom-source-clip.c: + * ges/ges-custom-source-clip.h: + * ges/ges-image-source.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-simple-timeline-layer.c: + * ges/ges-test-clip.c: + * ges/ges-text-overlay-clip.c: + * ges/ges-text-overlay.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline.c: + * ges/ges-title-clip.c: + * ges/ges-track-element.c: + * ges/ges-transition-clip.c: + * ges/ges-uri-clip.c: + * ges/ges-uri-source.c: + * tests/benchmarks/timeline.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/text_properties.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + * tests/examples/ges-ui.c: + * tools/ges-launch.c: + Rename object/tobj/trobj to clip or track_element as necessary + Not really complete but it is a good start! + +2013-02-09 21:49:16 -0300 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-effect-clip.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-timeline.c: + * ges/ges-transition-clip.c: + Finish renaming tck_obj and derivate to track_element + +2013-02-08 17:25:25 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-clip.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-simple-timeline-layer.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + * ges/ges-timeline.c: + * ges/ges-xml-formatter.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/effects.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/project.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/text_properties.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/titles.c: + * tests/examples/ges-ui.c: + * tests/examples/overlays.c: + * tests/examples/simple1.c: + * tests/examples/test2.c: + * tests/examples/test3.c: + * tests/examples/test4.c: + * tests/examples/text_properties.c: + * tests/examples/transition.c: + * tools/ges-launch.c: + Rename GESTimelineLayer.xxx_object to GESTimelineLayer.xxx_clip + +2013-02-08 17:23:18 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-base-xml-formatter.c: + * ges/ges-clip.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-timeline.c: + * ges/ges-track.c: + * ges/ges-track.h: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/effects.c: + * tests/check/ges/project.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/uriclip.c: + * tests/examples/ges-ui.c: + track: Rename all GESTrack.xxx_object to GESTrack.xxx_element + +2013-02-08 17:19:39 -0300 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline.c: + * tests/examples/ges-ui.c: + Properly rename object-added to clip-added + +2013-02-08 17:11:22 -0300 Thibault Saunier + + * ges/ges-clip.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-simple-timeline-layer.c: + * ges/ges-test-clip.c: + * ges/ges-text-overlay-clip.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline.c: + * ges/ges-title-clip.c: + * ges/ges-uri-clip.c: + * ges/ges.c: + * tests/check/ges/basic.c: + * tests/check/ges/uriclip.c: + * tests/examples/ges-ui.c: + * tests/examples/test1.c: + * tests/examples/test2.c: + * tests/examples/test3.c: + * tests/examples/test4.c: + * tools/ges-launch.c: + Finish renaming timeline object to clip + +2013-02-08 16:39:18 -0300 Thibault Saunier + + * ges/ges-audio-test-source.c: + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-custom-source-clip.h: + * ges/ges-image-source.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-test-clip.c: + * ges/ges-text-overlay.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline.c: + * ges/ges-title-source.c: + * ges/ges-track-element.c: + * ges/ges-track.c: + * ges/ges-uri-clip.c: + * ges/ges.c: + * tests/check/ges/basic.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + * tests/examples/ges-ui.c: + Finish renaming track object to track element + +2013-02-01 17:51:02 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + Add GESTimelineElement.{start, inpoint, duration, maxduration, priority} getters + +2013-01-28 14:36:06 -0300 Thibault Saunier + + * ges/ges-uri-clip.c: + uriclip: Fix wrong acces to object instead of its duration field + +2013-01-27 16:21:01 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-title-clip.c: + * ges/ges-title-clip.h: + * ges/ges-title-source.c: + * ges/ges-title-source.h: + * tests/check/ges/titles.c: + Reword ges_title_clip_set_color to ges_title_clip_set_text_color + +2013-01-27 16:16:27 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-effect-clip.c: + * ges/ges-title-clip.c: + * ges/ges-title-clip.h: + * ges/ges-title-source.c: + * ges/ges-title-source.h: + Rename ges_title_.*_set_background to set_background_color + +2013-01-27 16:07:12 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-title-clip.c: + * ges/ges-title-clip.h: + titleclip: Remove useless mute property + +2013-01-27 16:02:31 -0300 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + pitiviformatter: Fix renaming issues + +2013-01-27 16:02:19 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/ges-text-overlay-clip.c: + * ges/ges-text-overlay-clip.h: + * tests/check/ges/overlays.c: + * tests/examples/overlays.c: + Rename overlay_text to text_overlay + +2013-01-27 12:51:52 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-clip-asset.c: + * ges/ges-clip-asset.h: + * ges/ges-types.h: + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + * ges/ges-uri-clip.c: + * ges/ges.h: + Rename GESAssetClip to GESClipAsset + +2013-01-27 12:44:13 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-audio-transition.c: + * ges/ges-audio-transition.h: + * ges/ges-transition-clip.c: + * ges/ges-types.h: + * ges/ges.h: + Rename GESTrackAudioTransition to GESAudioTransition + +2013-01-27 12:41:51 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-track-video-transition.h: + * ges/ges-transition-clip.c: + * ges/ges-types.h: + * ges/ges-video-transition.c: + * ges/ges-video-transition.h: + * ges/ges.h: + * tests/check/ges/transition.c: + Rename GESTrackVideoTransition to GESVideoTransition + +2013-01-27 12:31:10 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-timeline.c: + * ges/ges-track-audio-transition.c: + * ges/ges-track-audio-transition.h: + * ges/ges-track-video-transition.c: + * ges/ges-track-video-transition.h: + * ges/ges-transition-clip.c: + * ges/ges-transition.c: + * ges/ges-transition.h: + * ges/ges-types.h: + * ges/ges.h: + Rename GESTrackTransition to GESTransition + +2013-01-27 12:27:19 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-base-effect.c: + * ges/ges-base-effect.h: + * ges/ges-operation.c: + * ges/ges-operation.h: + * ges/ges-text-overlay.c: + * ges/ges-text-overlay.h: + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + * ges/ges-types.h: + * ges/ges.h: + Rename GESTrackOperation to GESOperation + +2013-01-27 12:24:44 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-text-overlay-clip.c: + * ges/ges-text-overlay.c: + * ges/ges-text-overlay.h: + * ges/ges-track-text-overlay.h: + * ges/ges-types.h: + * ges/ges.h: + * tests/check/ges/overlays.c: + * tests/check/ges/text_properties.c: + Rename GESTrackTextOverlay to GESTextOverlay + +2013-01-26 14:25:14 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-title-clip.c: + * ges/ges-title-source.c: + * ges/ges-title-source.h: + * ges/ges-track-text-overlay.c: + * ges/ges-track-text-overlay.h: + * ges/ges-track-title-source.h: + * ges/ges-types.h: + * ges/ges.h: + * tests/check/ges/titles.c: + Rename GESTrackTitleSource to GESTitleSource + +2013-01-26 14:21:56 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-image-source.c: + * ges/ges-image-source.h: + * ges/ges-types.h: + * ges/ges-uri-clip.c: + * ges/ges.h: + * tests/check/ges/uriclip.c: + Rename GESTrackImageSource to GESImageSource + +2013-01-26 14:14:57 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-asset-track-object.h: + * ges/ges-track-element-asset.c: + * ges/ges-track-element-asset.h: + * ges/ges-types.h: + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + * ges/ges-uri-clip.c: + * ges/ges-uri-source.c: + * ges/ges.h: + Rename GESAssetTrackElement to GESTrackElementAsset + +2013-01-26 14:07:01 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-types.h: + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + * ges/ges-uri-clip.c: + * ges/ges-uri-source.c: + * ges/ges-uri-source.h: + * ges/ges.h: + Rename TrackFileSource to UriSource + +2013-01-26 13:08:20 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * docs/working-diagrams.svg: + * ges/Makefile.am: + * ges/ges-audio-test-source.c: + * ges/ges-audio-test-source.h: + * ges/ges-clip.c: + * ges/ges-custom-source-clip.c: + * ges/ges-source-clip.c: + * ges/ges-source.c: + * ges/ges-source.h: + * ges/ges-timeline.c: + * ges/ges-track-filesource.c: + * ges/ges-track-filesource.h: + * ges/ges-track-image-source.c: + * ges/ges-track-image-source.h: + * ges/ges-track-title-source.c: + * ges/ges-track-title-source.h: + * ges/ges-types.h: + * ges/ges-video-test-source.c: + * ges/ges-video-test-source.h: + * ges/ges.h: + Rename GESTrackSource to GESSource + +2013-01-26 13:03:39 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-test-clip.c: + * ges/ges-track-title-source.c: + * ges/ges-track-video-test-source.h: + * ges/ges-types.h: + * ges/ges-video-test-source.c: + * ges/ges-video-test-source.h: + * ges/ges.h: + * tests/check/ges/backgroundsource.c: + Rename GESTrackVideoTestSource to GESVideoTestSource + +2013-01-26 13:02:02 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-audio-test-source.c: + * ges/ges-audio-test-source.h: + * ges/ges-test-clip.c: + * ges/ges-track-audio-test-source.h: + * ges/ges-types.h: + * ges/ges-uri-clip.c: + * ges/ges.h: + * tests/check/ges/backgroundsource.c: + Rename GESTrackAudioTestSource to GESAudioTestSource + +2013-01-26 12:40:51 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-asset.c: + * ges/ges-effect-clip.c: + * ges/ges-effect.c: + * ges/ges-effect.h: + * ges/ges-pitivi-formatter.c: + * ges/ges-track-parse-launch-effect.h: + * ges/ges-types.h: + * ges/ges.c: + * ges/ges.h: + * tests/check/ges/asset.c: + * tests/check/ges/effects.c: + * tests/check/ges/project.c: + * tests/check/ges/test-project.xges: + * tests/examples/ges-ui.c: + Rename GESTrackParseLaunchEffect to GESEffect + +2013-01-26 12:35:19 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * docs/working-diagrams.svg: + * ges/Makefile.am: + * ges/ges-asset.c: + * ges/ges-base-effect.c: + * ges/ges-base-effect.h: + * ges/ges-base-xml-formatter.c: + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-pitivi-formatter.c: + * ges/ges-track-parse-launch-effect.c: + * ges/ges-track-parse-launch-effect.h: + * ges/ges-types.h: + * ges/ges-xml-formatter.c: + * ges/ges.h: + * tests/check/ges/effects.c: + * tests/check/ges/project.c: + Rename TrackEffect to BaseEffect + +2013-01-26 12:31:33 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-asset-clip.c: + * ges/ges-asset-track-object.c: + * ges/ges-asset-track-object.h: + * ges/ges-asset.c: + * ges/ges-auto-transition.c: + * ges/ges-auto-transition.h: + * ges/ges-base-xml-formatter.c: + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-custom-source-clip.c: + * ges/ges-custom-source-clip.h: + * ges/ges-effect-clip.c: + * ges/ges-internal.h: + * ges/ges-pitivi-formatter.c: + * ges/ges-test-clip.c: + * ges/ges-text-overlay-clip.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline.c: + * ges/ges-title-clip.c: + * ges/ges-track-audio-test-source.c: + * ges/ges-track-audio-transition.c: + * ges/ges-track-effect.c: + * ges/ges-track-element.c: + * ges/ges-track-element.h: + * ges/ges-track-filesource.c: + * ges/ges-track-image-source.c: + * ges/ges-track-operation.c: + * ges/ges-track-operation.h: + * ges/ges-track-parse-launch-effect.c: + * ges/ges-track-source.c: + * ges/ges-track-source.h: + * ges/ges-track-text-overlay.c: + * ges/ges-track-title-source.c: + * ges/ges-track-video-test-source.c: + * ges/ges-track-video-transition.c: + * ges/ges-track.c: + * ges/ges-track.h: + * ges/ges-transition-clip.c: + * ges/ges-types.h: + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + * ges/ges-uri-clip.c: + * ges/ges-xml-formatter.c: + * ges/ges.h: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/project.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/text_properties.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/check/ges/uriclip.c: + * tests/examples/ges-ui.c: + * tests/examples/transition.c: + Rename GESTrackObject to GESTrackElement + +2013-01-25 15:51:02 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-effect-clip.c: + * ges/ges-effect-clip.h: + * ges/ges-types.h: + * ges/ges.h: + * tests/check/ges/effects.c: + Rename GESStandardEffectClip to GESEffectClip + +2013-01-25 15:45:07 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-base-effect-clip.c: + * ges/ges-base-effect-clip.h: + * ges/ges-standard-effect-clip.c: + * ges/ges-standard-effect-clip.h: + * ges/ges-types.h: + * ges/ges.h: + Rename GESEffectClip to GESBaseEffectClip + +2013-01-25 15:16:21 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-asset.c: + * ges/ges-enums.c: + * ges/ges-timeline.c: + * ges/ges-transition-clip.c: + * ges/ges-transition-clip.h: + * ges/ges-types.h: + * ges/ges.c: + * ges/ges.h: + * tests/check/ges/asset.c: + * tests/check/ges/layer.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/transition.c: + * tests/examples/ges-ui.c: + * tests/examples/transition.c: + * tools/ges-launch.c: + Rename GESStandardTransitionClip to GESTransitionClip + +2013-01-25 11:26:14 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-auto-transition.c: + * ges/ges-base-transition-clip.c: + * ges/ges-base-transition-clip.h: + * ges/ges-simple-timeline-layer.c: + * ges/ges-standard-transition-clip.c: + * ges/ges-standard-transition-clip.h: + * ges/ges-types.h: + * ges/ges.h: + * tests/examples/ges-ui.c: + Rename GESTransitionClip to GESBaseTransitionClip + +2013-01-20 12:58:05 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + timelineelement: Implement the notion of parenting + +2013-01-17 00:58:28 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-test-clip.c: + * ges/ges-test-clip.h: + * ges/ges-timeline-test-source.h: + * ges/ges-types.h: + * ges/ges.c: + * ges/ges.h: + * tests/benchmarks/timeline.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/effects.c: + * tests/check/ges/layer.c: + * tests/check/ges/project.c: + * tests/check/ges/text_properties.c: + * tests/examples/ges-ui.c: + * tests/examples/test1.c: + * tests/examples/thumbnails.c: + * tools/ges-launch.c: + Rename GESTimelineTestSource to GESTestSourceClip + +2013-01-17 00:55:03 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-timeline-title-source.h: + * ges/ges-title-clip.c: + * ges/ges-title-clip.h: + * ges/ges-types.h: + * ges/ges.c: + * ges/ges.h: + * tests/check/ges/titles.c: + * tests/examples/ges-ui.c: + * tools/ges-launch.c: + Rename GESTimelineTileSource to GESTitleClip + +2013-01-17 00:53:26 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-text-overlay-clip.c: + * ges/ges-text-overlay-clip.h: + * ges/ges-timeline-text-overlay.h: + * ges/ges-types.h: + * ges/ges.h: + * tests/check/ges/overlays.c: + * tests/examples/overlays.c: + Rename GESTimelineTextOverlay to GESTextOverlayClip + +2013-01-17 00:49:43 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-overlay-clip.c: + * ges/ges-overlay-clip.h: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-text-overlay.h: + * ges/ges-types.h: + * ges/ges.c: + * ges/ges.h: + * tests/check/ges/asset.c: + Rename GESTimelineOverlay to GESOverlayClip + +2013-01-17 00:35:39 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-custom-source-clip.c: + * ges/ges-custom-source-clip.h: + * ges/ges-simple-timeline-layer.c: + * ges/ges-source-clip.c: + * ges/ges-source-clip.h: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-test-source.h: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-title-source.h: + * ges/ges-track-effect.c: + * ges/ges-track-parse-launch-effect.c: + * ges/ges-types.h: + * ges/ges-uri-clip.c: + * ges/ges-uri-clip.h: + * ges/ges.h: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/layer.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/timelineedition.c: + Rename GESTimelineSource to GESSourceClip + And GESCustomTimelineSource to GESCustomSourceClip + +2013-01-17 00:26:35 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-effect-clip.c: + * ges/ges-effect-clip.h: + * ges/ges-operation-clip.c: + * ges/ges-operation-clip.h: + * ges/ges-timeline-overlay.c: + * ges/ges-timeline-overlay.h: + * ges/ges-transition-clip.c: + * ges/ges-transition-clip.h: + * ges/ges-types.h: + * ges/ges.h: + Rename GESTimelineOperation to GESOperationClip + +2013-01-17 00:04:41 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-standard-effect-clip.c: + * ges/ges-standard-effect-clip.h: + * ges/ges-timeline-parse-launch-effect.h: + * ges/ges-types.h: + * ges/ges.h: + * tests/check/ges/effects.c: + Rename GESTimelineParseLaunchEffect to GESStandardEffectClip + +2013-01-16 23:21:01 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-asset.c: + * ges/ges-enums.c: + * ges/ges-meta-container.c: + * ges/ges-standard-transition-clip.c: + * ges/ges-standard-transition-clip.h: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-standard-transition.h: + * ges/ges-timeline.c: + * ges/ges-types.h: + * ges/ges.c: + * ges/ges.h: + * tests/check/ges/asset.c: + * tests/check/ges/effects.c: + * tests/check/ges/layer.c: + * tests/check/ges/project.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/transition.c: + * tests/examples/ges-ui.c: + * tests/examples/transition.c: + * tools/ges-launch.c: + Rename GESTimelineStandardTransition to GESStandardTransitionClip + +2013-01-16 23:16:02 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-auto-transition.c: + * ges/ges-auto-transition.h: + * ges/ges-meta-container.c: + * ges/ges-simple-timeline-layer.c: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-standard-transition.c: + * ges/ges-timeline-standard-transition.h: + * ges/ges-timeline.c: + * ges/ges-transition-clip.c: + * ges/ges-transition-clip.h: + * ges/ges-types.h: + * ges/ges.h: + * tests/check/ges/effects.c: + * tests/check/ges/project.c: + * tests/examples/ges-ui.c: + Rename GESTimelineTransition to GESTransitionClip + +2013-01-16 23:11:14 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-effect-clip.c: + * ges/ges-effect-clip.h: + * ges/ges-meta-container.c: + * ges/ges-timeline-parse-launch-effect.c: + * ges/ges-timeline-parse-launch-effect.h: + * ges/ges-timeline-pipeline.c: + * ges/ges-types.h: + * ges/ges.h: + * tests/check/ges/effects.c: + * tests/check/ges/project.c: + Rename GESTimelineEffect to GESEffectClip + +2013-01-20 12:44:57 -0300 Thibault Saunier + + * android/ges.mk: + * bindings/python/examples/material.py: + * bindings/python/examples/simple.py: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-asset-clip.c: + * ges/ges-asset.c: + * ges/ges-extractable.c: + * ges/ges-meta-container.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-timeline-file-source.h: + * ges/ges-timeline-pipeline.c: + * ges/ges-track-filesource.c: + * ges/ges-types.h: + * ges/ges-uri-asset.c: + * ges/ges-uri-asset.h: + * ges/ges-uri-clip.c: + * ges/ges-uri-clip.h: + * ges/ges.c: + * ges/ges.h: + * tests/check/Makefile.am: + * tests/check/ges/asset.c: + * tests/check/ges/effects.c: + * tests/check/ges/project.c: + * tests/check/ges/test-project.xges: + * tests/check/ges/uriclip.c: + * tests/examples/assets.c: + * tests/examples/concatenate.c: + * tests/examples/ges-ui.c: + * tests/examples/overlays.c: + * tests/examples/simple1.c: + * tests/examples/test2.c: + * tests/examples/test3.c: + * tests/examples/test4.c: + * tests/examples/text_properties.c: + * tests/examples/transition.c: + * tools/ges-launch.c: + Rename GESTimelineFileSource to GESUriClip + Conflicts: + ges/ges-pitivi-formatter.c + ges/ges-uri-clip.c + tests/check/ges/project.c + tests/check/ges/uriclip.c + +2013-01-20 12:42:29 -0300 Thibault Saunier + + * android/ges.mk: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-asset-clip.c: + * ges/ges-asset-clip.h: + * ges/ges-asset-file-source.c: + * ges/ges-asset-file-source.h: + * ges/ges-asset-track-object.c: + * ges/ges-asset.c: + * ges/ges-auto-transition.c: + * ges/ges-auto-transition.h: + * ges/ges-base-xml-formatter.c: + * ges/ges-clip.c: + * ges/ges-clip.h: + * ges/ges-custom-timeline-source.c: + * ges/ges-custom-timeline-source.h: + * ges/ges-internal.h: + * ges/ges-meta-container.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + * ges/ges-timeline-operation.c: + * ges/ges-timeline-operation.h: + * ges/ges-timeline-parse-launch-effect.c: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-source.c: + * ges/ges-timeline-source.h: + * ges/ges-timeline-standard-transition.c: + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-track-filesource.c: + * ges/ges-track-image-source.c: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track.c: + * ges/ges-types.h: + * ges/ges-xml-formatter.c: + * ges/ges.h: + * tests/benchmarks/timeline.c: + * tests/check/Makefile.am: + * tests/check/ges/.gitignore: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/clip.c: + * tests/check/ges/effects.c: + * tests/check/ges/filesource.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/project.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/test-project.xges: + * tests/check/ges/text_properties.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/examples/ges-ui.c: + * tests/examples/overlays.c: + * tests/examples/simple1.c: + * tests/examples/test1.c: + * tests/examples/test2.c: + * tests/examples/test3.c: + * tests/examples/test4.c: + * tests/examples/text_properties.c: + * tests/examples/thumbnails.c: + * tests/examples/transition.c: + * tools/ges-launch.c: + Rename GESTimelineObject to GESClip + +2013-01-15 10:52:17 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-auto-transition.c: + * ges/ges-internal.h: + * ges/ges-simple-timeline-layer.c: + * ges/ges-timeline-element.c: + * ges/ges-timeline-element.h: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-file-source.h: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline.c: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track.c: + * ges/ges-types.h: + * ges/ges-utils.c: + * ges/ges-xml-formatter.c: + * ges/ges.h: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/effects.c: + * tests/check/ges/filesource.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/test-utils.h: + * tests/check/ges/timelineedition.c: + * tests/check/ges/timelineobject.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/examples/ges-ui.c: + Add a GESTimelineElement base class + + Port GESTrackObject and GESTimelineObject to the new baseclass + +2013-02-10 12:07:48 -0500 Jean-François Fortin Tam + + * docs/libs/architecture.xml: + docs: Clarify the distinction between Tracks and Layers + +2013-01-30 20:12:26 -0300 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-project.c: + project: Update loading asset when a URI is missing + +2013-01-30 01:27:17 -0800 Kerrick Staley + + * configure.ac: + build: replace AM_CONFIG_HEADER with AC_CONFIG_HEADERS to fix build with automake 1.13 + AM_CONFIG_HEADER is deprecated; see + https://lists.gnu.org/archive/html/automake/2012-12/msg00038.html + https://bugzilla.gnome.org/show_bug.cgi?id=692864 + +2013-01-28 20:46:06 +0100 Stefan Sauer + + * common: + Automatic update of common submodule + From a942293 to 2de221c + +2013-01-22 18:44:00 -0300 Thibault Saunier + + * configure.ac: + configure: Properly check if PyGObject is present + And make use of the PyGObject overrides if present + +2013-01-22 18:08:44 -0300 Thibault Saunier + + * configure.ac: + Bump Glib dependency to 2.34 + We use new APIs (g_list_copy_deep) that appeared in GLib 2.34 + +2013-01-22 19:51:25 +0000 Tim-Philipp Müller + + * ges/ges-base-xml-formatter.c: + * ges/ges-project.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-track-object.c: + * tests/check/ges/simplelayer.c: + Fix various printf format issues in debug messages + +2013-01-15 15:09:39 +0000 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From a72faea to a942293 + +2013-01-14 09:01:24 -0300 Thibault Saunier + + * configure.ac: + Bump GStreamer dependency version to current master (1.1.0) + +2013-01-12 20:49:31 -0300 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + pitiviformatter: Handle project metadatas + +2013-01-12 10:50:24 -0300 Thibault Saunier + + * ges/ges-timeline-object.h: + * ges/ges-xml-formatter.c: + xmlformatter: Do no allow saving CONSTRUCTONLY properties + +2013-01-11 19:10:31 -0300 Thibault Saunier + + * ges/ges-timeline-layer.c: + layer: State assets we create async as loading to the project + +2013-01-11 19:07:22 -0300 Thibault Saunier + + * ges/ges-project.c: + * tests/check/ges/project.c: + project: Track Asset that were loaded with error + +2013-01-11 11:49:02 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-asset.c: + * ges/ges-base-xml-formatter.c: + * ges/ges-internal.h: + * ges/ges-pitivi-formatter.c: + * ges/ges-project.c: + * ges/ges-project.h: + * ges/ges-timeline-layer.c: + * tests/check/ges/project.c: + project: Handle assets that are being loaded + API: + ges_project_get_loading_assets + +2013-01-11 15:26:26 -0300 Thibault Saunier + + * ges/ges-timeline-object.h: + * ges/ges-timeline.h: + ges: Documentations fixes + +2013-01-10 18:50:54 -0300 Thibault Saunier + + * ges/Makefile.am: + * ges/ges-auto-transition.c: + * ges/ges-auto-transition.h: + * ges/ges-timeline-layer.c: + * ges/ges-timeline.c: + * tests/check/ges/layer.c: + Reimplement the auto-transition feature + + Actually implement unit tests + +2013-01-10 18:09:23 -0300 Thibault Saunier + + * ges/ges-track-object.c: + trackobject: Make the GESTrack (parent track) a GObject property + +2013-01-10 18:01:33 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Minor refactoring + +2013-01-10 13:32:15 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Avoid recreating the moving_tlobjs when unecessary + +2013-01-10 12:41:13 -0300 Thibault Saunier + + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline.c: + * ges/ges-track-object.c: + Misc debug message enhancements + +2013-01-10 12:24:20 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Keep track of whether updates are enabled or not + Check if we want to track Track-s enable status and update our status according + to that + +2013-01-10 11:58:59 -0300 Thibault Saunier + + * ges/ges-timeline-layer.c: + layer: Properly emit the notify signal when auto_transition changes + +2013-01-10 11:39:46 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Track TrackObject-s by layer + +2013-01-10 11:18:46 -0300 Thibault Saunier + + * tests/check/ges/test-utils.h: + tests: Add a macro for type checking + +2013-01-10 11:15:32 -0300 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-timeline-layer.c: + * ges/ges-timeline.c: + * ges/ges-track.c: + * ges/ges-utils.c: + utilities: Make internal utilities instead of copy/pasting functions + +2013-01-10 11:01:05 -0300 Thibault Saunier + + * .gitignore: + gitignore: Ignore anjuta files + +2013-01-05 12:02:03 -0300 Thibault Saunier + + * configure.ac: + * tests/Makefile.am: + * tests/benchmarks/Makefile.am: + * tests/benchmarks/timeline.c: + Benchmark rippling + +2013-01-04 13:11:51 -0300 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-timeline-layer.c: + Move LAYER_HEIGHT definition from -timeline-layer.c c to -internal.h + +2013-01-04 13:04:26 -0300 Thibault Saunier + + * ges/ges-asset-track-object.c: + asset-track-object: Minor doc fixing + +2013-01-03 11:43:05 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Remove dead macros + +2013-01-03 11:41:48 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Use g_sequence_sort_changed when appropriate + +2013-01-03 10:34:17 -0300 Thibault Saunier + + * ges/ges-track.c: + track: Keep in cache the GSequenceIter so we get a faster acces to them + +2012-12-30 22:37:22 -0300 Thibault Saunier + + * ges/ges-formatter.c: + formatter: Plug some leaks + +2012-12-29 19:36:07 -0300 Thibault Saunier + + * tests/check/ges/project.c: + test: project: Fix various leaks + +2012-12-29 19:34:29 -0300 Thibault Saunier + + * ges/ges-base-xml-formatter.c: + * ges/ges-xml-formatter.c: + xmlformatter: Plug various leaks + +2012-12-29 18:24:05 -0300 Thibault Saunier + + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline.c: + * ges/ges-track.c: + Misc nitpick fixing + +2012-12-29 18:04:25 -0300 Thibault Saunier + + * ges/ges-project.c: + project: Plug various leaks. + +2012-12-29 18:02:35 -0300 Thibault Saunier + + * ges/ges-meta-container.c: + meta-container: Plug various leaks + +2012-12-29 17:58:02 -0300 Thibault Saunier + + * ges/ges-asset.c: + asset: Do not allow proxying over the same currently proxied asset + +2012-12-29 17:54:51 -0300 Thibault Saunier + + * ges/ges-asset.c: + asset: Fix some leaks + +2012-12-29 17:52:42 -0300 Thibault Saunier + + * ges/ges-asset.c: + * ges/ges-custom-timeline-source.c: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-standard-transition.c: + * ges/ges-track-parse-launch-effect.c: + asset: Avoid leaking the GParameter array and content + +2012-12-29 14:10:11 -0300 Thibault Saunier + + * ges/ges-asset-file-source.c: + assetfilesource: Fix some leaks + +2012-12-29 14:09:26 -0300 Thibault Saunier + + * tests/examples/concatenate.c: + tests: Remove useless mutex + +2012-12-29 14:08:58 -0300 Thibault Saunier + + * .gitignore: + Add some more gitignore + +2012-12-28 19:10:17 -0300 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + * ges/ges-xml-formatter.c: + Refrase formatters descriptions + +2012-12-28 19:06:30 -0300 Thibault Saunier + + * ges/ges-formatter.c: + formatter: First check the extension when checking if can load URI + +2012-12-28 11:40:33 -0300 Thibault Saunier + + * ges/ges-project.c: + project: Run the vmethod in first stage for the "loaded" signal + This is most probably what sublcasses will need + +2012-12-24 09:29:48 -0300 Thibault Saunier + + * ges/ges-screenshot.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/timelineobject.c: + * tests/check/ges/titles.c: + tests: Fix misc issues + Now GST_CAPS_ANY is a singleton, it is not returning a newly created caps + anymore + +2012-12-24 09:29:04 -0300 Thibault Saunier + + * docs/libs/Makefile.am: + * ges/Makefile.am: + * tests/check/Makefile.am: + Allow checking code coverage + +2012-12-21 20:17:41 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-asset-file-source.c: + * ges/ges-asset-timeline-object.c: + * ges/ges-asset-track-object.c: + * ges/ges-asset.c: + * ges/ges-custom-timeline-source.c: + * ges/ges-extractable.c: + * ges/ges-project.c: + * ges/ges-simple-timeline-layer.c: + * ges/ges-timeline-effect.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline-operation.c: + * ges/ges-timeline-overlay.c: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-source.c: + * ges/ges-timeline-text-overlay.c: + * ges/ges-track-effect.c: + * ges/ges-track-object.c: + Misc documentation fixes + Using "#" a in short_description screws the display + +2012-12-21 20:48:03 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-asset-file-source.c: + * ges/ges-asset-file-source.h: + * ges/ges-asset-track-object.c: + * ges/ges-asset-track-object.h: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline.c: + * ges/ges-track-filesource.c: + * ges/ges-types.h: + * ges/ges.h: + Implement a GESAssetTrackObject class + + Addapt the rest of the code to make use of it + +2012-12-21 18:51:26 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-asset-file-source.c: + * ges/ges-asset-file-source.h: + * ges/ges-asset-timeline-object.c: + * ges/ges-asset-timeline-object.h: + * ges/ges-meta-container.h: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-object.c: + * ges/ges-types.h: + * ges/ges.h: + Implement a GESAssetTimelineObject class + + Make GESAssetFileSource a subclass of it + + Remove ges_asset_filesource_get_supported_type as it is now in GESAssetTimelineObject + + Remove the GES_META_TIMELINE_OBJECT_SUPPORTED_FORMATS as it is useless now + +2012-12-21 14:28:16 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/ges-asset-file-source.c: + * ges/ges-asset-file-source.h: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-track-filesource.c: + * ges/ges-types.h: + Add a GESAssetTrackFileSource class and make use of it all around + +2012-12-20 20:23:54 -0300 Sebastian Dröge + + * ges/ges-custom-timeline-source.c: + * ges/ges-pitivi-formatter.c: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline-parse-launch-effect.c: + * ges/ges-timeline-standard-transition.c: + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline.c: + * ges/ges-track-audio-test-source.c: + * ges/ges-track-audio-transition.c: + * ges/ges-track-image-source.c: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track-text-overlay.c: + * ges/ges-track-title-source.c: + * ges/ges-track-video-test-source.c: + * ges/ges-track-video-transition.c: + * ges/ges-track.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/effects.c: + * tests/check/ges/filesource.c: + * tests/check/ges/overlays.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/timelineobject.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + Allow applications to select to which track a track object should be added + Modifies some API: + ges_timeline_object_create_track_objects now take a GESTrackType instead of a + GESTrack as second argument, and return a GList instead of a boolean + ges_timeline_object_create_track_object now take a GESTrackType instead of a + GESTrack as second argument + +2012-12-20 20:21:51 -0300 Thibault Saunier + + * ges/ges-enums.c: + * ges/ges-enums.h: + Add a utility method to get the name of a GESTrackType + API: + ges_track_type_name + +2012-12-20 14:58:35 -0300 Thibault Saunier + + * ges/ges-timeline-pipeline.c: + timelinepipeline: Properly reset #GESTrack caps when switching back to playback + +2012-12-20 11:28:39 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Properly popullate the tracks field with GESTrack-s + + add priv_tracks private field that contained TrackPrivate structures + We now have 2 list containing our tracks, one with TrackPrivate structures, and one the + GESTrack-s themselves. + +2012-12-21 10:43:41 -0300 Thibault Saunier + + * .gitignore: + Add some ignored files + +2012-12-20 10:17:24 -0300 Thibault Saunier + + * ges/ges-timeline-pipeline.c: + Revert "Revert "ges: timeline-pipeline: Remove playsink send_event hack"" + This reverts commit 094669391ddf8a29b3a1d1168a78cc50c20341b4. + Conflicts: + ges/ges-timeline-pipeline.c + +2012-12-17 22:35:28 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-base-xml-formatter.h: + * ges/ges-enums.h: + * ges/ges-extractable.h: + * ges/ges-formatter.h: + * ges/ges-meta-container.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-types.h: + Misc documentation fixing + +2012-12-05 08:51:48 -0300 Thibault Saunier + + * ges/ges-formatter.c: + formatter: Rework the _save_to_uri method to give more debug information + +2012-12-17 17:06:33 -0300 Thibault Saunier + + * ges/ges-timeline-layer.c: + timeline-layer: Handle project when adding a GESTimelineObject directly + +2012-11-29 17:07:24 -0300 Thibault Saunier + + * ges/ges-timeline-layer.c: + timeline-layer: Implement the Extractable type + We can imagine user implemts more Layer type, it could be usefull for formatters + to instanciate through a GESMaterial + +2012-11-27 13:54:54 -0300 Thibault Saunier + + * ges/ges-timeline-object.h: + timeline-object: Reindent header file + +2012-11-27 13:53:22 -0300 Thibault Saunier + + * ges/ges-timeline-file-source.c: + * ges/ges.c: + docs: Minor documentation fixes + +2012-11-27 13:52:59 -0300 Thibault Saunier + + * ges/ges-internal.h: + internale: Add the G_GNUC_INTERNAL attribute to all internal methods + +2012-09-09 21:25:54 -0300 Volodymyr Rudyi + + * tests/check/ges/asset.c: + tests: Add testcase for GESAsset + +2012-11-27 13:52:20 -0300 Thibault Saunier + + * ges/ges-asset.c: + * ges/ges-enums.c: + * ges/ges-timeline-transition.h: + ges: Create assets for all GESTimelineStandardTransition on ges_init() + + Add some testsuite + +2012-11-27 12:53:14 -0300 Thibault Saunier + + * ges/ges.c: + ges: Make sure not to initialize twice + +2012-11-27 12:18:27 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + timelineobject: Add a method to add a GESAsset + + Avoid to assume function arguments are correct before actually testing + them in ges_timeline_object_add_track_object + API: ges_timeline_object_add_asset + +2012-11-26 17:27:24 -0300 Thibault Saunier + + * ges/ges-timeline-standard-transition.c: + timeline-standard-transition: Override the GESExtractable implementation + Standard transition material have the vtype property as ID, it has the particularity + that the ID can be changed at runtime + + Implement tests to make sure it behaves properly + +2012-11-26 17:24:43 -0300 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-file-source.h: + timelinefilesource: Remove deprectated methods + Removed API: + ges_timeline_filesource_get_supported_formats + +2012-11-24 00:09:28 -0300 Thibault Saunier + + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * tests/examples/ges-ui.c: + * tools/ges-launch.c: + timeline: Make use of the Project API for timeline saving + API: + * Add a formatter_type paramatter to ges_timeline_save_to_uri + +2012-09-23 02:13:38 +0200 Thibault Saunier + + * tests/check/Makefile.am: + * tests/check/ges/project.c: + * tests/check/ges/test-project.xges: + * tests/check/ges/test.xptv: + tests: Add GESProject tests + +2012-11-19 13:24:03 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-base-xml-formatter.c: + * ges/ges-base-xml-formatter.h: + * ges/ges-internal.h: + * ges/ges-xml-formatter.c: + * ges/ges-xml-formatter.h: + * ges/ges.c: + * ges/ges.h: + xml-formatter: Implement a GESXmlFormatter + +2012-11-18 20:20:47 -0300 Thibault Saunier + + * ges/ges-track.c: + track: Set the gap element creator function when tracks are using raw audio/video + +2012-11-18 20:19:01 -0300 Thibault Saunier + + * ges/ges-timeline-pipeline.c: + pipeline: Minor documentation fixes + +2012-11-18 12:46:05 -0300 Thibault Saunier + + * ges/ges-custom-timeline-source.c: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-object.c: + * tests/check/ges/layer.c: + Check in TimelineObject what tracks are supported before creating TrackObject-s + We used to do it in TimelineFileSource which does not make sense. + At the same time we set AUDIO | VIDEO as default supported types as it is more + likely to be what subclasses support. If it is not the case, they need to + specify it as shown in ges-timeline-custom-timeline-source.c + + Fix the tests accordingly + +2012-11-20 18:23:59 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Implement the GESMetaContainerInterface + +2012-09-23 02:11:46 +0200 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Implement the GESExtractable interface + +2012-11-11 13:51:45 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + timeline: Make timeline->track and timeline->layers public fields + +2012-12-17 19:26:23 -0300 Thibault Saunier + + formatter: Make it work with GESProject + + Compile new GESProject code + The formatter and projects should work together, and the user will in the end not need + the GESFormatter API in most cases. Start making that happening + Update the GESPitiviFormatter to the new behaviour and remove APIs that became + obselete + API: + + Adds: + * Pass the GESFormatterClass to can_load/save_uri vmethods + * Add an @overwrite argumenent to ges_formatter_save_to_uri and the + corresponding vmethod + * Add name, description, extension, mimetype, version, rank metadatas + to GESFormatterClass + + Removes: + * ges_pitivi_formatter_set_sources: + * ges_pitivi_formatter_get_sources: + +2012-09-24 22:24:42 +0200 Thibault Saunier + + * ges/ges-formatter.c: + * ges/ges-formatter.h: + formatter: Implement the GESExtractable interface + Make it a GInitially unowned, GESProject will become the owner + +2012-09-21 15:48:56 +0200 Thibault Saunier + + * tests/check/ges/audio_only.ogg: + * tests/check/ges/audio_video.ogg: + * tests/check/ges/filesource.c: + tests: First filesource test port to assets + +2012-09-20 12:16:38 +0200 Thibault Saunier + + * tests/examples/test1.c: + Examples: Use GESTimelineTestSource instead of GESCustomTimelineSource in test1 + +2012-09-02 15:14:27 +0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/ges-internal.h: + * ges/ges-project.c: + * ges/ges-project.h: + * ges/ges-types.h: + * ges/ges.h: + project: Implement GESProject + Do not build yet, waiting for everythnig to be in place before doing so + Co-Authored-By: Volodymyr Rudyi + +2012-11-21 10:22:41 -0300 Thibault Saunier + + * ges/ges-track.c: + track: Implement the GESMetaContainer interface + +2012-11-20 18:25:31 -0300 Thibault Saunier + + * ges/ges-track-object.c: + trackobject: Implement the GESMetaContainerInterface + +2012-11-20 00:29:23 -0300 Thibault Saunier + + * ges/ges-timeline-object.c: + timeline-object: Implement the GESMetadataContainer interface + +2012-11-19 23:42:47 -0300 Thibault Saunier + + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track-parse-launch-effect.c: + trackobject: Implement the GESExtractable interface + +2012-09-19 22:36:38 +0200 Thibault Saunier + + * ges/ges-timeline-test-source.c: + docs: Update GESTimelineTestSource documentation + +2012-09-18 14:42:58 +0200 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-custom-timeline-source.c: + * ges/ges-custom-timeline-source.h: + customtimelinesource: Override Extractable + API: ges_material_custom_timeline_source_new (helper method) + +2012-09-18 14:40:51 +0200 Thibault Saunier + + * bindings/python/examples/material.py: + * bindings/python/examples/simple.py: + bindings: Add simple python examples + +2012-09-14 01:05:45 +0200 Thibault Saunier + + * Makefile.am: + * bindings/Makefile.am: + * bindings/python/Makefile.am: + * bindings/python/examples/Makefile.am: + * bindings/python/gi/Makefile.am: + * bindings/python/gi/__init__.py: + * bindings/python/gi/overrides/GES.py: + * bindings/python/gi/overrides/Makefile.am: + * bindings/python/gi/overrides/__init__.py: + * configure.ac: + bindings: Start implementing overrides for python + +2012-09-09 21:26:49 -0300 Thibault Saunier + + * tests/examples/concatenate.c: + example: Port the concatenate example to assets + +2012-09-09 21:26:15 -0300 Volodymyr Rudyi + + * tests/examples/Makefile.am: + * tests/examples/assets.c: + examples: Add basic examples of asset + +2012-12-17 17:05:56 -0300 Thibault Saunier + + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + * ges/ges-timeline.c: + ges: Implement the ges_timeline_layer_add_asset method + + Remove GstDiscoverer related code in GESTimeline as we do not need it anymore + + Refactor the ges_timeline_layer_add_object method to make sure it is still working as intended + API: + ges_timeline_layer_add_asset + +2012-09-09 21:21:21 -0300 Volodymyr Rudyi + + * ges/ges-timeline-file-source.c: + timelinefilesource: Override default GESExtractable interface implementation + +2012-09-09 21:20:46 -0300 Thibault Saunier + + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + timelineobject: Implement the GESExtractable interface + +2012-09-09 21:15:17 -0300 Volodymyr Rudyi + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-asset-file-source.c: + * ges/ges-asset-file-source.h: + * ges/ges-asset.c: + * ges/ges-internal.h: + * ges/ges-types.h: + * ges/ges.c: + * ges/ges.h: + ges: Implement GESAssetFileSource + + Generate the documentation + + Make the new Asset infrastructure compile + Co-Authored-By: Thibault Saunier + +2012-09-09 21:12:06 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/ges-extractable.c: + * ges/ges-extractable.h: + * ges/ges-internal.h: + * ges/ges-types.h: + * ges/ges.h: + ges: Implement the GESExtractable interface + + Generate the documentation + Note: Do not compile (add to Makefile.am) for now as we are missing pieces at that point + Co-Authored-By: Volodymyr Rudyi + +2012-08-31 19:36:37 -0700 Volodymyr Rudyi + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-asset.c: + * ges/ges-asset.h: + * ges/ges-internal-enums.h: + * ges/ges-internal.h: + * ges/ges-types.h: + * ges/ges.h: + ges: Implement GESAsset + + Generate the documentation + Note: Do not compile (add to Makefile.am) for now as we are missing pieces at that point + Co-Authored-By: Thibault Saunier + +2012-12-17 15:27:52 -0300 Thibault Saunier + + * docs/design/asset.txt: + design: Add asset design document + Co-Authored-By: Volodymyr Rudyi + +2012-08-10 12:58:13 -0400 Thibault Saunier + + * ges/ges-timeline-file-source.c: + filesource: Make the uri property CONSTRUCT_ONLY + This is the way it should always have been. + +2012-07-21 17:12:08 +0200 Thibault Saunier + + * .gitignore: + gitignore: Ignore some more files + +2012-12-17 15:17:50 -0300 Thibault Saunier + + * tests/check/ges/layer.c: + tests: implement GESMetaContainer tests + Co-Authored-By: Paul Lange + +2012-12-17 15:24:52 -0300 Paul Lange + + * ges/ges-timeline-layer.c: + timeline-layer: implement the GESMetaContainer interface + +2012-12-17 15:23:39 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges-meta-container.c: + * ges/ges-meta-container.h: + * ges/ges.c: + metacontainer: Finnish GESMetaContainer implementation + +2012-11-26 13:31:17 -0300 Paul Lange + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-meta-container.c: + * ges/ges-meta-container.h: + * ges/ges.c: + * ges/ges.h: + implement the GESMetaContainer interface + +2012-05-14 22:14:37 +0300 Thibault Saunier + + * docs/design/metadata.txt: + docs: Added metadata design doc + +2012-11-23 11:44:08 -0300 Thibault Saunier + + * ges/ges-timeline-layer.c: + timeline-layer: Fix track-added Callback parametters + +2012-12-01 13:56:37 -0300 Thibault Saunier + + * tests/check/ges/test-utils.h: + test-utils: Add some more utilities + +2012-11-18 20:23:13 -0300 Thibault Saunier + + * tests/check/ges/test-utils.c: + Minor fixes to the test utils + +2012-12-01 13:51:33 -0300 Thibault Saunier + + * ges/ges-timeline-object.c: + timeline-object: Edit can only work work with GESTrackSource + +2012-11-23 23:52:32 -0300 Thibault Saunier + + * ges/ges-formatter.c: + * ges/ges-formatter.h: + * ges/ges-pitivi-formatter.c: + formatter: Remove obselete APIs + Removed APIs: + ges_formatter_update_source_uri + GESFormatter::source-moved + ges_formatter_update_source_uri + ges_formatter_load + ges_formatter_save + ges_formatter_set_data + ges_formatter_clear_data + ges_formatter_get_data + GESFormatterLoadMethod + GESFormatterSaveMethod + This is now GESProject's role + +2012-11-23 23:51:17 -0300 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-formatter.c: + * ges/ges-formatter.h: + * ges/ges-keyfile-formatter.c: + * ges/ges-keyfile-formatter.h: + * ges/ges-timeline.c: + * ges/ges.c: + * ges/ges.h: + * tests/check/Makefile.am: + * tests/check/ges/save_and_load.c: + * tests/examples/ges-ui.c: + Remove the GESKeyFileFormatter + It was using deprecated URI, and can not be used in real life anymore. + Also remove the ges_formatter_default_new method ges_formatter_new_for_uri + that are useless now + +2012-11-19 14:19:17 -0300 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + pitiviformatter: Some minor restrtucturation + +2012-12-01 13:53:06 -0300 Thibault Saunier + + * ges/ges-track.c: + track: Force video/x-raw in raw gaps + +2012-12-17 12:27:54 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Keep track of GSequenceIter for each GESTrackObject + This way we do not have to look for them in the sequence itself, and + make things simpler + +2012-12-17 13:51:49 -0300 Thibault Saunier + + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + timelineobject: Give a direct access to the list of TrackObject + Avoid to have to copy the list each time we want to access it + +2012-12-19 10:37:02 -0300 Thibault Saunier + + * docs/design/effects.txt: + * docs/libs/ges-sections.txt: + * ges/ges-pitivi-formatter.c: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * tests/check/ges/effects.c: + trackobject: Properly rename get/set_child_property + We used to have a ges_track_object_get/set_child_property that was in + fact letting user set/get various properties at once, rename it to + get/set_properties, and implement: + API: + ges_track_object_get_child_property (GESTrackObject *object, const gchar + *property_name, GValue * value); + ges_track_object_set_child_property (GESTrackObject *object, const gchar + *property_name, GValue * value); + +2012-12-18 19:47:50 -0300 Thibault Saunier + + * ges/ges-track-object.c: + trackobject: Some GI annotation fix + +2012-11-25 16:11:17 -0300 Thibault Saunier + + * docs/Makefile.am: + docs: Add make upload + +2012-11-19 11:31:33 +0000 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From 6bb6951 to a72faea + +2012-11-17 00:10:20 +0000 Tim-Philipp Müller + + * ges/ges-track-effect.c: + ges-track-effect: don't use deprecated API + +2012-11-04 00:25:20 +0000 Tim-Philipp Müller + + * COPYING: + * COPYING.LIB: + * docs/design/gstencodebin.h: + * docs/design/gstprofile.h: + * ges/ges-custom-timeline-source.c: + * ges/ges-custom-timeline-source.h: + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges-formatter.c: + * ges/ges-formatter.h: + * ges/ges-internal.h: + * ges/ges-keyfile-formatter.c: + * ges/ges-keyfile-formatter.h: + * ges/ges-pitivi-formatter.c: + * ges/ges-pitivi-formatter.h: + * ges/ges-screenshot.c: + * ges/ges-screenshot.h: + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + * ges/ges-timeline-effect.c: + * ges/ges-timeline-effect.h: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-file-source.h: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline-operation.c: + * ges/ges-timeline-operation.h: + * ges/ges-timeline-overlay.c: + * ges/ges-timeline-overlay.h: + * ges/ges-timeline-parse-launch-effect.c: + * ges/ges-timeline-parse-launch-effect.h: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + * ges/ges-timeline-source.c: + * ges/ges-timeline-source.h: + * ges/ges-timeline-standard-transition.c: + * ges/ges-timeline-standard-transition.h: + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-test-source.h: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-text-overlay.h: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-title-source.h: + * ges/ges-timeline-transition.c: + * ges/ges-timeline-transition.h: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-track-audio-test-source.c: + * ges/ges-track-audio-test-source.h: + * ges/ges-track-audio-transition.c: + * ges/ges-track-audio-transition.h: + * ges/ges-track-effect.c: + * ges/ges-track-effect.h: + * ges/ges-track-filesource.c: + * ges/ges-track-filesource.h: + * ges/ges-track-image-source.c: + * ges/ges-track-image-source.h: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track-operation.c: + * ges/ges-track-operation.h: + * ges/ges-track-parse-launch-effect.c: + * ges/ges-track-parse-launch-effect.h: + * ges/ges-track-source.c: + * ges/ges-track-source.h: + * ges/ges-track-text-overlay.c: + * ges/ges-track-text-overlay.h: + * ges/ges-track-title-source.c: + * ges/ges-track-title-source.h: + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + * ges/ges-track-video-test-source.c: + * ges/ges-track-video-test-source.h: + * ges/ges-track-video-transition.c: + * ges/ges-track-video-transition.h: + * ges/ges-track.c: + * ges/ges-track.h: + * ges/ges-types.h: + * ges/ges-utils.c: + * ges/ges-utils.h: + * ges/ges.c: + * ges/ges.h: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/effects.c: + * tests/check/ges/filesource.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/save_and_load.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/test-utils.c: + * tests/check/ges/test-utils.h: + * tests/check/ges/text_properties.c: + * tests/check/ges/timelineedition.c: + * tests/check/ges/timelineobject.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/examples/concatenate.c: + * tests/examples/ges-ui.c: + * tests/examples/overlays.c: + * tests/examples/simple1.c: + * tests/examples/test1.c: + * tests/examples/test2.c: + * tests/examples/test3.c: + * tests/examples/test4.c: + * tests/examples/text_properties.c: + * tests/examples/thumbnails.c: + * tests/examples/transition.c: + * tools/ges-launch.c: + Fix FSF address + +2012-10-31 14:49:44 -0300 Thibault Saunier + + * ges/Makefile.am: + ges: fix g-i search path for GstAudio GstVideo GstTag and GstBase + +2011-12-23 14:07:21 +0100 Xabier Rodriguez Calvar + + * ges/ges-timeline-pipeline.c: + timeline: Added timeline and mode as properties + +2011-11-11 17:29:20 +0100 Xabier Rodriguez Calvar + + * ges/ges-timeline-pipeline.c: + timeline: Adding GObject property API to get/set preview audio and video sinks + +2011-11-11 17:09:34 +0100 Xabier Rodriguez Calvar + + * ges/ges-timeline-pipeline.c: + timeline: Initialize as NULL the preview sinks when getting them. + This way, if there is a problem getting the properties from the + playsink, we do not return garbage. + +2012-10-06 15:02:54 +0100 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From 6c0b52c to 6bb6951 + +2012-09-25 15:07:17 +0200 Thibault Saunier + + * ges/ges.h: + ges: Update reported version to 1.0 + +2012-07-20 14:11:56 +0300 Volodymyr Rudyi + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + timeline: Added 'const' modifier + +2012-09-09 21:27:08 -0300 Thibault Saunier + + * ges/ges-timeline-title-source.c: + titlesource: Use GST_DEBUG_OBJECT when appropriate + +2012-09-22 18:51:46 +0200 Thibault Saunier + + * ges/ges-formatter.h: + * ges/ges-timeline-object.h: + * ges/ges-track-object.h: + * ges/ges-types.h: + Reset ABI for 1.0 and ensure that extensible baseclasses are extensible enough + +2012-09-22 13:10:55 +0200 Thibault Saunier + + * tests/check/ges/backgroundsource.c: + * tests/check/ges/filesource.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/test-utils.h: + * tests/check/ges/text_properties.c: + * tests/check/ges/timelineobject.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + tests: Move common code to test-utils + +2012-09-23 02:24:14 +0200 Thibault Saunier + + * tests/check/ges/save_and_load.c: + * tests/check/ges/test.xptv: + * tests/check/ges/wrong_test.xptv: + tests: Implement tests for ges_formatter_can_load_uri + +2012-09-23 02:23:20 +0200 Thibault Saunier + + * tests/check/Makefile.am: + * tests/check/ges/test-utils.c: + * tests/check/ges/test-utils.h: + tests: Add some utils for test writing + +2012-09-23 02:07:04 +0200 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + pitiviformatter: Implement can_load_uri vmethod + +2012-09-23 02:06:44 +0200 Thibault Saunier + + * ges/ges-formatter.c: + formatter: Implement a usefull version of ges_formatter_can_load_uri + +2012-09-23 02:05:42 +0200 Thibault Saunier + + * ges/ges-formatter.c: + * ges/ges-formatter.h: + * ges/ges-pitivi-formatter.c: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges.c: + * tests/check/ges/save_and_load.c: + * tests/examples/ges-ui.c: + * tools/ges-launch.c: + formatter: Add GError everywhere needed in the API + We should give as much information as possible to the user when serialization/deserialization doesn't work. + +2012-09-22 13:27:20 +0200 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Plug a leak in the movecontext code + +2012-09-22 16:12:05 +0100 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From 4f962f7 to 6c0b52c + +2012-09-07 12:58:19 -0400 Nicolas Dufresne + + * ges/ges-timeline-pipeline.c: + timeline-pipeline: use downstream block probe to avoid deadlock on duration query + +2012-09-06 16:58:21 -0400 Nicolas Dufresne + + * ges/ges-timeline-pipeline.c: + timeline-pipeline: Clean the blocked pad + We do need to cleanup the pad now, otherwise the probe will get remove a + second time in pad_removed_cb causing an assertion. + +2012-08-26 15:35:01 -0400 Thibault Saunier + + * tests/check/ges/save_and_load.c: + * tests/check/ges/titles.c: + tests: Fix tests + In TimelineTitleSource We do not add a TrackAudioTestSource in the audio track + anymore as it was a hack to work around the fact that we used not to have gap + support, now we do, remove related tests + +2012-08-16 11:20:44 +0100 Matas Brazdeikis + + * docs/libs/ges-sections.txt: + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-title-source.h: + * ges/ges-track-title-source.c: + * ges/ges-track-title-source.h: + title-source: add background color option + +2012-08-13 16:00:28 +0100 Matas Brazdeikis + + * ges/ges-timeline-title-source.c: + timeline-title-source: remove audio-test-source + +2012-08-22 13:35:27 +0200 Stefan Sauer + + * common: + Automatic update of common submodule + From 668acee to 4f962f7 + +2012-08-10 12:39:10 -0400 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-formatter.h: + * ges/ges-simple-timeline-layer.c: + * ges/ges-timeline-effect.c: + * ges/ges-timeline-file-source.h: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-parse-launch-effect.c: + * ges/ges-timeline-standard-transition.c: + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline.c: + * ges/ges-track-effect.c: + * ges/ges-track-filesource.c: + * ges/ges-track-image-source.c: + * ges/ges-track-object.c: + * ges/ges-track-parse-launch-effect.c: + * ges/ges-track-video-transition.c: + * ges/ges-track.c: + Misc documentation fixing + +2012-08-14 20:33:57 -0400 Thibault Saunier + + * ges/ges-track-object.c: + GI: Fix some annotations in TrackObject + +2012-08-09 10:14:57 +0200 Sebastian Dröge + + * tests/examples/ges-ui.c: + * tests/examples/thumbnails.c: + examples: Use GRegex instead of POSIX regex + They are not available on Windows. + +2012-08-05 16:44:22 +0100 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From 94ccf4c to 668acee + +2012-07-28 21:45:03 -0400 Thibault Saunier + + * ges/ges-timeline-object.c: + * ges/ges-timeline-parse-launch-effect.c: + * ges/ges-track-object.c: + * ges/ges-utils.c: + ges: Remove useless and error prone 'transfer full' annotations + transfer full is default and there is currently a bug in GES when constructor + are declared as transfer full + +2012-07-23 08:48:43 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From 98e386f to 94ccf4c + +2012-07-01 20:54:42 -0400 Thibault Saunier + + Merge remote-tracking branch 'origin/0.10' + Conflicts: + common + +2012-07-01 20:03:37 -0400 Thibault Saunier + + * docs/libs/ges-sections.txt: + docs: Add the new ges_formatter_emit_loaded API to the docs + + Fix sections + +2012-07-01 19:57:30 -0400 Thibault Saunier + + * ges/ges-formatter.c: + * ges/ges-formatter.h: + * ges/ges-pitivi-formatter.c: + formatter: Make the emit_loaded a real method and not a virtual method + + Modify formatter subclasses accordingly + API:ges_formatter_emit_loaded + This API wasn't released so it could still be changed + +2012-07-01 19:39:57 -0400 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + pitivi-formatter: Make use of the Formatter:timeline protected field + +2012-07-01 19:34:53 -0400 Thibault Saunier + + * ges/ges-formatter.c: + * ges/ges-formatter.h: + formatter: Add a timeline protected field + +2012-04-08 00:30:03 +0300 Volodymyr Rudyi + + * ges/ges-pitivi-formatter.c: + ges-pitivi-formatter: Fixed loading of projects with empty timeline + Because 'project-loaded' signal was triggered from track object loading + callback in case with projects that have empty timeline this signal was + never emitted. + +2012-04-08 00:08:43 +0300 Volodymyr Rudyi + + * ges/ges-formatter.c: + ges-formatter: Removed assert to allow saving projects with empty timeline + Removed assert in ges-formatter.c to allow saving projects with empty timeline. + +2012-06-25 10:32:36 +0200 Sebastian Dröge + + * ges/ges-timeline-pipeline.c: + gestimelinepipeline: Fix for gst_element_make_from_uri() API changes + +2012-06-08 15:07:15 +0200 Edward Hervey + + * common: + Automatic update of common submodule + From 03a0e57 to 98e386f + +2012-06-08 14:27:34 +0200 Edward Hervey + + * common: + Automatic update of common submodule + From b811047 to 3baf58a + +2012-06-06 18:20:59 +0200 Edward Hervey + + * common: + Automatic update of common submodule + From 1fab359 to 03a0e57 + +2012-06-06 18:20:11 +0200 Edward Hervey + + * common: + Automatic update of common submodule + From b098abb to b811047 + +2012-06-01 10:31:08 +0200 Edward Hervey + + * common: + Automatic update of common submodule + From f1b5a96 to 1fab359 + +2012-06-01 10:23:17 +0200 Edward Hervey + + * common: + Automatic update of common submodule + From 96f075b to b098abb + +2012-05-31 13:12:01 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From 92b7266 to f1b5a96 + +2012-05-30 13:41:17 +0200 Sebastian Dröge + + * tests/examples/Makefile.am: + examples: Fix linking by passing -export-dynamic in the right variable + +2012-05-30 12:49:02 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From ec1c4a8 to 92b7266 + +2012-05-30 12:42:18 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From 1e6c5ea to 96f075b + +2012-05-30 12:33:40 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From ff4cad1 to 1e6c5ea + +2012-05-30 11:27:44 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From 3429ba6 to ec1c4a8 + +2012-05-30 11:27:43 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From 11f0cd5 to ff4cad1 + +2012-05-30 11:24:29 +0200 Sebastian Dröge + + * configure.ac: + configure: Don't check for OBJC compiler + +2012-05-30 11:24:29 +0200 Sebastian Dröge + + * configure.ac: + configure: Don't check for OBJC compiler + +2012-05-27 22:55:12 -0400 Thibault Saunier + + * ges/ges-track.c: + track: Make sure to remove the proper TrackObject from the GSequence + +2012-05-27 22:55:12 -0400 Thibault Saunier + + * ges/ges-track.c: + track: Make sure to remove the proper TrackObject from the GSequence + +2012-05-26 17:41:43 -0400 Thibault Saunier + + * tests/check/ges/backgroundsource.c: + tests: Fix backgroundsource test + +2012-05-26 17:00:50 -0400 Thibault Saunier + + Merge remote-tracking branch 'origin/0.10' + Conflicts: + bindings/python/ges.defs + ges/ges-screenshot.c + ges/ges-track-video-transition.c + +2012-05-21 19:38:10 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Do not use meaningless offset values when snapping + +2012-05-21 18:10:29 -0400 Thibault Saunier + + * ges/ges-formatter.c: + formatter: Disable updates when loading a project + +2012-05-21 12:45:00 -0400 Thibault Saunier + + * ges/ges-track-video-transition.c: + videotransition: Some explanations about the invert property + +2012-05-21 13:05:53 -0400 Thibault Saunier + + * ges/ges-track-video-transition.c: + videotransition: Misc cleanup in the smpte/crossfade transition type switches + +2012-05-21 13:05:14 -0400 Thibault Saunier + + * ges/ges-track-video-transition.c: + videotransition: Do not wait pad to be blocked before switching transitions + ... from smpte to crossfad and the other way around + This avoid useless async operations + +2012-05-18 13:17:17 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Move all callbacks to the callback section of the file + +2012-05-18 13:16:50 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Make the update property a GObject property + API: timeline::update property + +2012-05-18 11:13:11 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Try to resnap at same snapping point before calculating new value + +2012-05-18 10:33:44 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Avoid to recalculate the moving context unecessarly + +2012-05-18 10:28:26 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Create a debug logging category for the timeline + +2012-05-16 15:53:07 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Properly set TimelineFileSource-s duration and max duration + When we get the information of duration of files after discoverying them, + use that information to set the values on the TimelineFileSource-s + +2012-05-15 14:38:38 -0400 Thibault Saunier + + * ges/ges-timeline-layer.c: + timeline-layer: Rework the way we calculate in which layer a TrackObject is + +2012-05-09 12:12:38 -0400 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-pitivi-formatter.c: + * ges/ges-pitivi-formatter.h: + * ges/ges-screenshot.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline.c: + * ges/ges-track-object.c: + * ges/ges-track-video-transition.c: + docs: Misc documentation fixing + +2012-05-09 11:51:33 -0400 Thibault Saunier + + * tests/check/ges/backgroundsource.c: + tests: Add basic gaps tests + +2012-05-09 11:45:02 -0400 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-track.c: + * ges/ges-track.h: + track: Properly fill gaps + API: GESCreateElementForGapFunc Virtual method type + API: ges_track_set_create_element_for_gap_func + +2012-05-09 11:20:24 -0400 Thibault Saunier + + * ges/ges-track.c: + track: Restructurate file so we have private method and API properly separeted + +2012-05-16 12:23:52 -0400 Thibault Saunier + + * ges/ges-track.c: + track: Use a GSequence to keep the sorted list of TrackObject-s + Use a GSequence instead of a GList to optimise the process. + Conflicts: + ges/ges-track.c + +2012-05-16 12:59:33 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Make use of our own knowledge of the timeline duration + Do not use each Track durations as it end going in loop as we have the Tracks + that need to know about timeline's duration to create or not gaps in the end and + then the timeline references on Tracks duration for its duration. We have this + information locally so just make proper use of it. + +2012-05-17 20:49:01 -0400 Thibault Saunier + + * bindings/python/ges.defs: + * docs/libs/ges-sections.txt: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + timeline: Add a method to get the timeline duration + + Bind it in python + API: ges_timeline_get_duration + +2012-05-13 15:59:21 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From dc70203 to 3429ba6 + +2012-05-10 14:56:34 -0400 Thibault Saunier + + Merge remote-tracking branch 'origin/0.10' + +2012-05-10 12:40:23 -0400 Thibault Saunier + + * ges/ges-track-object.c: + * ges/ges-track.c: + track-object: Keep a reference to our gnlobject + Avoid refering to an object that doesn't exists and segfault in some cases. + We do not need to increase the reference to the gnlobj when the trackobject + is removed from a track because the TrackObject as its own reference and will + handle the disposal gracefully. + Add some guard around related APIs + +2012-05-08 19:34:48 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Avoid segfault when debug logging + +2012-05-07 16:11:26 +0100 Tim-Philipp Müller + + * ges/ges-track-video-transition.c: + ges: fix printf arguments in debug message + https://bugzilla.gnome.org/show_bug.cgi?id=675547 + +2012-05-06 18:52:25 +0200 Mathieu Duponchelle + + * ges/ges-track-video-transition.c: + track-video-transition: Make the invert property management coherent + +2012-05-06 04:52:40 +0200 Mathieu Duponchelle + + * ges/ges-track-video-transition.c: + track-video-transition: Fix set_inverted + +2012-05-05 13:00:49 -0400 Thibault Saunier + + Merge remote-tracking branch 'origin/0.10' + Conflicts: + bindings/python/ges.defs + ges/ges-track-video-transition.c + +2012-05-05 12:31:28 -0400 Thibault Saunier + + * ges/ges-timeline-object.c: + * ges/ges-track-object.c: + track-object: Set minimum value of max-duration to 0 + GST_CLOCK_TIME_NONE was nonsense + Minor documentation fixing on the way + +2012-05-03 15:41:08 +0200 Edward Hervey + + * ges/ges-timeline-object.c: + * ges/ges-track-object.c: + ges: Remove invalid ' < 0' checks + It's an unsigned value, it will never be < 0. + +2012-05-02 23:56:35 -0400 Thibault Saunier + + * ges/ges-timeline-object.c: + * ges/ges-timeline.c: + * ges/ges-track-object.c: + * ges/ges-track.c: + Do no check if GLib >2.26 as we depend on GLib 2.28 + +2012-05-02 23:44:31 -0400 Thibault Saunier + + * ges/ges-track-video-transition.c: + track-video-transition: Properly emit notify for the invert and border properties + +2012-05-02 23:43:50 -0400 Thibault Saunier + + * ges/ges-track-video-transition.c: + track-video-transition: Expose the transition type as a GObject property + API: GESTrackVideoTransition::transition-type property + +2012-05-03 03:35:16 +0200 Mathieu Duponchelle + + * ges/ges-track-video-transition.c: + track-video-transition: expose border and inverted as GObject properties + +2012-05-03 02:44:00 +0200 Mathieu Duponchelle + + * bindings/python/ges.defs: + python : binds the getter and setter for the "inverted" property + +2012-05-03 02:28:41 +0200 Mathieu Duponchelle + + * docs/libs/ges-sections.txt: + * ges/ges-track-video-transition.c: + * ges/ges-track-video-transition.h: + track-video-transition: Expose the invert property from smpte + Also, add/fixup some doc + API: ges_track_video_transition_get_inverted + API: ges_track_video_transition_set_inverted + +2012-05-02 22:03:51 -0400 Thibault Saunier + + Merge remote-tracking branch 'origin/0.10' + Conflicts: + bindings/python/ges.defs + ges/ges-track-video-transition.c + +2012-05-02 18:38:42 -0400 Thibault Saunier + + * ges/ges-track-video-transition.c: + track-video-transition: Reuse interpollation setting functions when possible + +2012-05-02 18:07:01 -0400 Thibault Saunier + + * ges/ges-track-video-transition.h: + track-video-transition: Reindent header + +2012-05-02 18:04:54 -0400 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-track-video-transition.c: + * ges/ges-track-video-transition.h: + track-video-transition: Add a way to get current border value + Add some documentation for the border property + Change the border value in set_border to a guint as the value can be negative + API: ges_track_video_transition_get_border + +2012-05-02 00:27:31 +0200 Mathieu Duponchelle + + * docs/libs/Makefile.am: + build: Fixes distclean + +2012-05-01 23:42:47 +0200 Mathieu Duponchelle + + * ges/ges-track-video-transition.c: + track-video-transition: Return pending type in transition_get_type when needed + +2012-05-01 16:01:39 +0200 Mathieu Duponchelle + + * bindings/python/ges.defs: + python: Binds the "set_border" function + +2012-05-02 01:09:07 +0200 Mathieu Duponchelle + + * ges/ges-track-video-transition.c: + * ges/ges-track-video-transition.h: + track-video-transition: expose the border property of smptealpha + API: ges_track_video_transition_set_border + +2012-05-02 01:08:08 +0200 Mathieu Duponchelle + + * ges/ges-track-video-transition.c: + * tests/check/ges/transition.c: + ges-track-video-transition: Enables switching from crossfade to smpte and vice versa + Fix the tests properly + +2012-05-02 08:44:25 +0100 Tim-Philipp Müller + + * tools/ges-launch.c: + ges-launch: replace home-grown version of gst_filename_to_uri() + and remove superfluous check if file is readable with + fopen. Code appears to also want to accept URIs, so this + doesn't work so well, and should probably be done differently + anyway if required. + https://bugzilla.gnome.org/show_bug.cgi?id=674296 + +2012-04-17 19:18:44 +0400 Руслан Ижбулатов + + * tools/ges-launch.c: + ges-launch: use GRegex instead of POSIX regex + http://bugzilla-attachments.gnome.org/attachment.cgi?id=212249 + +2012-04-17 19:18:21 +0400 Руслан Ижбулатов + + * ges/ges-simple-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-track-title-source.c: + ges: fix some format strings in debug messages + https://bugzilla.gnome.org/show_bug.cgi?id=674265 + +2012-05-01 19:16:42 +0100 Tim-Philipp Müller + + * ges/ges-timeline-pipeline.c: + * ges/ges-track-text-overlay.c: + * ges/ges-track-title-source.c: + ges: fix some not entirely correct casts for vararg function arguments + +2012-05-01 19:06:20 +0100 Tim-Philipp Müller + + * ges/ges-track-video-transition.c: + track-video-transition: update for videomixer pad template name change + +2012-05-01 19:05:51 +0100 Tim-Philipp Müller + + * tests/examples/overlays.c: + * tests/examples/simple1.c: + * tests/examples/test2.c: + * tests/examples/test3.c: + * tests/examples/test4.c: + * tests/examples/text_properties.c: + * tests/examples/transition.c: + examples: create URIs properly from filenames + +2012-05-01 18:50:34 +0100 Tim-Philipp Müller + + * .gitignore: + * tests/check/ges/.gitignore: + * tools/.gitignore: + .gitignore: ignore more + +2012-05-01 18:48:57 +0100 Tim-Philipp Müller + + * ges/ges.c: + ges: fix gnonlin version check + +2012-05-01 18:43:02 +0100 Tim-Philipp Müller + + * ges/ges-track-audio-transition.c: + track-audio-transition: fix adder sink pad template name + +2012-04-18 18:34:01 +0400 Руслан Ижбулатов + + * ges/ges-timeline-pipeline.c: + timeline-pipeline: fix src pad request template for tee + https://bugzilla.gnome.org/show_bug.cgi?id=674339 + +2012-04-25 17:53:38 -0400 Thibault Saunier + + Merge remote-tracking branch 'origin/0.10' + Conflicts: + bindings/python/ges-types.defs + bindings/python/ges.defs + bindings/python/ges.override + configure.ac + ges/ges-timeline.c + +2012-04-25 17:09:19 -0400 Thibault Saunier + + * ges/ges-enums.c: + * ges/ges-enums.h: + docs: Add some more docs about editing mode + Also add the documentation "Section" into ges-enum.c so the file documentation ar + actualy taken into account in the final generated documentation. + +2012-04-25 14:55:46 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Rework snapping signaling so it is easier to use + API: GESTimeline::snap-started signal + API: GESTimeline::snap-ended signal + (This code has not been released so we can still change the API) + +2012-04-23 20:17:42 -0400 Thibault Saunier + + * ges/ges-timeline-file-source.c: + * ges/ges-track-object.c: + trackobject: Take into account the max duration when trying to set a new duration + Change its default value to GST_CLOCK_TIME_NONE instead of 0. + (unreleased code so it still can be changed) + +2012-04-23 19:20:18 -0400 Thibault Saunier + + * ges/ges-track-object.c: + trackobject: Add API guards all around + +2012-04-23 19:17:51 -0400 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-internal.h: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + trackobject: Add the copy method to the API + Add documentation and plug a leak at the same time. + API: ges_track_object_copy + +2012-04-23 19:10:16 -0400 Thibault Saunier + + * ges/ges-timeline-object.c: + timelineobject: Ignore notifies when needed + +2012-04-22 15:24:25 -0400 Thibault Saunier + + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + timelineobject: Fix wrong naming when connection to in-point notify + inpoint -> in-point + +2012-02-10 16:58:14 -0300 Thibault Saunier + + * bindings/python/ges-types.defs: + * bindings/python/ges.defs: + * bindings/python/ges.override: + python: Bind the new Timeline editing mode API + +2012-04-22 13:09:11 -0400 Thibault Saunier + + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline-source.c: + timelineobject: Make changing start/duration sensible to snapping + Adapt the documentation so users are aware of the behaviour + Conflicts: + ges/ges-timeline-object.c + +2012-04-23 20:55:27 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Minor documentations fixes + +2012-04-23 20:54:15 -0400 Thibault Saunier + + * tests/check/Makefile.am: + * tests/check/ges/timelineedition.c: + tests: Add a testsuite for the new timeline edition API + +2012-04-23 20:52:45 -0400 Thibault Saunier + + * configure.ac: + * docs/libs/ges-sections.txt: + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges-internal.h: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline.c: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * tests/check/ges/basic.c: + ges: Add a timeline edition mode API + + timeline: Add a snapping-distance property + + Bump the GLib dependency to 2.28 in the mean time as we need some functions from GSequence that only landed + + Update the testsuite accordingly + API: GESTimeline:snapping-distance property + API: ges_timeline_object_edit + API: ges_timeline_object_ripple + API: ges_timeline_object_ripple_end + API: ges_timeline_object_roll_start + API: ges_timeline_object_roll_end + API: ges_timeline_object_trim_start + API: ges_track_object_edit + API: GESEdge enum + API: GESEditMode enum + +2012-04-20 20:05:46 -0400 Thibault Saunier + + * tests/check/ges/timelineobject.c: + tests: Add a basic test for the timeline_object_split method + +2012-04-20 19:22:56 -0400 Thibault Saunier + + * bindings/python/ges.defs: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + timelineobject: Make the 'position' argument of the split method a guint64 not gin64 + This makes more sense to be a guint64 as it actually is a GstClockTime, + and this way we keep the API concistent. + (This code has not been release so we can still change the API.) + +2012-04-20 19:19:49 -0400 Thibault Saunier + + * ges/ges-timeline-object.c: + timelineobject: Reimplement properly the splitting method + +2012-04-20 19:02:19 -0400 Thibault Saunier + + * ges/ges-timeline-object.c: + timelineobject: Misc API guard fixes + +2012-04-19 00:34:59 -0400 Thibault Saunier + + * ges/ges-internal.h: + * ges/ges-track-object.c: + track-object: Add method to copy a TrackObject + API: ges_track_object_copy + +2012-04-17 18:42:41 -0400 Thibault Saunier + + * ges/ges-track-object.c: + * ges/ges-track.c: + ges-track-object: Make possible to add a track already containing a gnlobject to a track + +2012-04-23 14:40:26 -0300 Thiago Santos + + * ges/ges-timeline-pipeline.c: + ges-timeline-pipeline: add todo to remember to remove hack + Remove playsink hack once we depend on gst-plugins-base 0.10.37 + (next gst-plugins-base release) + +2012-04-23 14:38:31 -0300 Thiago Santos + + * ges/ges-timeline-pipeline.c: + Revert "ges: timeline-pipeline: Remove playsink send_event hack" + This reverts commit 54aac450dab9ac052f2c0a913bfba5f77c1670ba. + We need this hack until we depend on gst-p-base 0.10.36 + +2012-04-20 14:18:18 -0400 Thibault Saunier + + * ges/ges-timeline.c: + Port to the new GMutex API + +2012-04-16 09:12:06 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From 6db25be to dc70203 + +2012-04-13 13:59:17 +0200 Sebastian Dröge + + * autogen.sh: + * configure.ac: + * ges/Makefile.am: + configure: Modernize autotools setup a bit + Also we now only create tar.bz2 and tar.xz tarballs. + +2012-04-13 13:39:50 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From 464fe15 to 6db25be + +2012-04-07 22:31:23 -0400 Thibault Saunier + + Merge branch '0.10' + Conflicts: + bindings/python/ges.defs + +2012-03-29 18:57:47 -0400 Thibault Saunier + + * ges/ges-timeline-object.c: + timeline-object: Add TrackObject to the Track after the TimelineObject + This way, the Track::track-object-added is emited after the TrackObject is ready to be used, and it make the API easier to use. + +2012-04-07 21:40:07 -0400 Thibault Saunier + + * ges/ges-timeline-layer.c: + timeline-layer: Factor out a method to start observing timeline for auto-transitions + +2012-04-07 21:24:18 -0400 Thibault Saunier + + * ges/ges-timeline-layer.c: + timeline-layer: Reorganize file + +2012-04-07 21:04:21 -0400 Thibault Saunier + + * ges/ges-timeline-layer.c: + timeline-layer: Rework auto transition callbacks management + We now have a GESTrack::track-object-added signal so we now depend on it rather than on each GESTimelineObject::track-object-added signal. + +2012-03-30 03:40:50 -0400 Thibault Saunier + + * ges/ges-timeline-object.c: + timeline-object: Properly reflect contained TrackObject duration and inpoint properties changes + +2012-01-30 22:55:59 +0100 Thibault Saunier + + * ges/ges-track-object.c: + trackobject: Some documentation fixing + +2012-01-27 16:04:00 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + docs: Add ges_timeline_object_release_track_object + We need it especially in the case of effects + +2012-04-04 20:47:04 -0400 Thibault Saunier + + * ges/ges-track-object.c: + trackobject: Add API documentation + +2012-02-02 15:29:30 -0300 Thibault Saunier + + * ges/ges-timeline-standard-transition.c: + * ges/ges-timeline.c: + ges: Calm logging when not created TrackObject on purpose + +2012-03-31 13:57:04 -0400 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Restructurate the file separting methods/callbacks/API + +2012-01-25 15:12:05 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + timelineobject: Emit effect-added when adding any kind of TrackEffect + We were only emitting it when working with TrackParseLaunch effects + +2012-02-01 20:25:35 -0300 Thibault Saunier + + * ges/ges-timeline-layer.c: + docs: Tell users that adding an object to a timeline layer creates media related objects + +2012-01-20 17:03:58 -0300 Thibault Saunier + + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + ges: Misc debug loggin cleanup + +2012-01-20 16:37:28 -0300 Thibault Saunier + + * ges/ges-timeline.c: + timeline: Plug a leak when calling enable_update + +2012-01-22 23:03:22 -0300 Thibault Saunier + + * ges/ges-timeline.c: + * ges/ges-timeline.h: + timeline: Rework the append_layer method + ges_timeline_append_layer now creates a new layer, adds it to the timeline + and returns it + This code has not been released yet so we can break this API. + +2012-01-20 14:36:36 -0300 Thibault Saunier + + * bindings/python/ges.defs: + * docs/libs/ges-sections.txt: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + layer: Add a method to check if a layer is empty or not + API: ges_timeline_layer_is_empty + +2012-01-16 09:37:22 -0300 Thibault Saunier + + * bindings/python/ges.defs: + * docs/libs/ges-sections.txt: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-track-filesource.c: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * tests/check/ges/save_and_load.c: + ges: Move the max-duration property from TrackFileSource to TrackObject + This property was firstly added to TrackFileSource, but in the end, it makes + more sense for it to be directly in TrackOject as it can be usefull in other cases. + +2012-04-05 18:45:53 +0200 Sebastian Dröge + + * common: + Automatic update of common submodule + From 7fda524 to 464fe15 + +2012-03-30 03:36:27 -0400 Thibault Saunier + + * ges/ges-timeline-layer.c: + timeline-layer: Do not call track_get_by_layer when track == NULL + +2012-04-04 14:02:43 -0300 Thiago Santos + + Merge branch '0.10' + +2012-03-30 19:10:33 -0300 Thiago Santos + + * ges/ges-timeline-pipeline.c: + ges: timeline-pipeline: Remove playsink send_event hack + This is fixed now in upstream playsink, remove the hack + https://bugzilla.gnome.org/show_bug.cgi?id=673211 + +2012-04-04 14:50:23 +0200 Sebastian Dröge + + * configure.ac: + * docs/libs/Makefile.am: + * docs/libs/ges-docs.sgml: + * docs/version.entities.in: + * ges/Makefile.am: + * gst-editing-services.spec.in: + * pkgconfig/Makefile.am: + * pkgconfig/gst-editing-services-uninstalled.pc.in: + * pkgconfig/gst-editing-services.pc.in: + * tests/check/Makefile.am: + * tests/examples/Makefile.am: + * tools/Makefile.am: + ges: Update versioning + +2012-04-04 12:08:06 +0200 Sebastian Dröge + + Merge remote-tracking branch 'origin/0.10' + Conflicts: + bindings/python/Makefile.am + ges/Makefile.am + +2012-04-03 19:25:18 -0400 Thibault Saunier + + * bindings/python/Makefile.am: + * configure.ac: + * docs/libs/Makefile.am: + * ges/Makefile.am: + * ges/ges-formatter.c: + * tools/Makefile.am: + formatter: Try to figure out new paths when media files have moved + Introduces a dependency to GIO + +2012-03-29 12:55:44 -0400 Thibault Saunier + + Merge branch '0.10' + Conflicts: + bindings/python/ges.defs + +2012-03-29 15:10:09 +0200 Sebastian Dröge + + Merge remote-tracking branch 'origin/0.10' + +2012-01-30 17:47:42 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + timelineobject: Set TrackObject's TimelineObject only when calling the add function + ges_timeline_object_add_track_object actually calls + ges_track_object_set_timeline_object so do not do it once more ourself. + Especially since it results in having a TrackObject.timeline_object refering + to a TimelineObject it is not actually in yet. + +2012-03-28 02:53:50 +0300 Volodymyr Rudyi + + * ges/ges-track.c: + ges-track: Set gnlobject state to NULL before disposing it + Work around a deadlock if setting state to NULL right before removing the + gnlobject from the composition. + https://bugzilla.gnome.org/show_bug.cgi?id=672751 + +2012-01-27 17:09:08 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + timelineobject: Update the nb_effect when releasing a TrackEffect + +2012-01-22 22:50:24 -0300 Thibault Saunier + + * bindings/python/ges.defs: + * docs/libs/ges-sections.txt: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-track.c: + * ges/ges-track.h: + ges: Add a way to know whether a timeline is updating on each changes + + Bind the new API in python + API: ges_timeline_is_updating + API: ges_track_is_updating + +2012-01-26 11:53:54 +0100 Thibault Saunier + + * ges/ges-track-object.c: + trackobject: Enable adding a TrackObject in a Track before a TimelineObject + We were requiring it only for GESCustomTimelineSource, but it is not actually + necessary so, we can just check if the TrackObject is in a TimelineObject or + not, and react accordingly. + +2012-01-25 12:47:24 +0100 Thibault Saunier + + * bindings/python/ges.defs: + * docs/libs/ges-sections.txt: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + timeline-layer: Add a method to get the timeline it is currently in + API: ges_timeline_layer_get_timeline + Bind it in python + +2012-03-28 02:53:50 +0300 Volodymyr Rudyi + + * ges/ges-track.c: + ges-track: Set gnlobject state to NULL before disposing it + Work around a deadlock if setting state to NULL right before removing the + gnlobject from the composition. + https://bugzilla.gnome.org/show_bug.cgi?id=672751 + +2012-03-26 12:43:30 +0200 Wim Taymans + + Replace master with 0.11 + +2012-03-19 10:56:53 +0000 Tim-Philipp Müller + + * ges/ges-pitivi-formatter.c: + * ges/ges-pitivi-formatter.h: + ges-pitivi-formatter: add copyright/license headers + https://bugzilla.gnome.org/show_bug.cgi?id=644943 + +2012-03-13 13:52:32 +0000 Tim-Philipp Müller + + Merge remote-tracking branch 'origin/master' into 0.11 + +2012-03-13 11:36:15 +0000 Tim-Philipp Müller + + * tests/check/ges/save_and_load.c: + tests: fix weird windowsy code in save_and_load unit test + Fixes compiler error about FILENAME_MAX in 0.11 + +2012-03-12 16:22:22 +0000 Tim-Philipp Müller + + * ges/ges-timeline-pipeline.c: + ges-timeline-pipeline: port to 0.11 + +2012-03-12 15:46:42 +0000 Tim-Philipp Müller + + * ges/Makefile.am: + * ges/ges-formatter.c: + * ges/ges-simple-timeline-layer.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline.c: + * ges/ges-track-object.c: + * ges/ges-track.c: + * ges/ges-utils.c: + Remove ges-marshal.[ch] and use the generic marshaller + +2012-03-12 15:37:33 +0000 Tim-Philipp Müller + + * configure.ac: + configure: bump GLib requirement in line with core and other libs + +2012-03-12 15:25:49 +0000 Tim-Philipp Müller + + Merge remote-tracking branch 'origin/master' into 0.11 + Conflicts: + bindings/python/Makefile.am + bindings/python/ges-types.defs + bindings/python/ges.defs + bindings/python/ges.override + bindings/python/gesmodule.c + bindings/python/testsuite/test_textoverlay.py + +2012-03-12 15:15:22 +0000 Tim-Philipp Müller + + * configure.ac: + * ges/Makefile.am: + configure: check for libxml2 explicitly + GStreamer may be built without the libxml2 dependency. + +2012-03-12 15:09:39 +0000 Tim-Philipp Müller + + * ges/ges-pitivi-formatter.c: + * ges/ges-pitivi-formatter.h: + ges-pitivi-formatter: move libxml includes into .c file + There's no need to have them in the header file. + +2012-03-06 15:37:18 +0100 Sebastian Dröge + + * ges/ges-timeline-title-source.c: + ges: Fix 'implicit conversion from enumeration type 'GESTextHAlign' to different enumeration type 'GESTextVAlign'' and similar compiler warnings + +2012-03-06 15:35:51 +0100 Sebastian Dröge + + * ges/ges-timeline-object.c: + ges: Fix 'comparison of unsigned expression < 0 is always false' + +2012-02-17 13:46:36 +0000 Tim-Philipp Müller + + * bindings/python/Makefile.am: + bindings: don't link to libges four times + And even less different versions of it. + +2012-02-10 19:44:49 +0000 Tim-Philipp Müller + + * tests/check/ges/simplelayer.c: + tests: ges_track_new() takes ownership of caps, so can't use GST_CAPS_ANY + +2012-02-10 19:42:16 +0000 Tim-Philipp Müller + + * tests/check/ges/save_and_load.c: + tests: port tests to new raw caps + +2012-02-10 19:36:49 +0000 Tim-Philipp Müller + + * ges/ges-track.c: + track: add g-i annotation that ges_track_new() takes ownership of caps passed + +2012-02-10 19:35:28 +0000 Tim-Philipp Müller + + * ges/ges-timeline-pipeline.c: + * ges/ges-track-video-transition.c: + * ges/ges-track.c: + ges: port to new raw audio/video caps + Completely untested, but more likely to work than the + existing code. + +2012-02-10 19:17:38 +0000 Tim-Philipp Müller + + * tests/examples/ges-ui.c: + * tests/examples/overlays.c: + * tests/examples/simple1.c: + * tests/examples/text_properties.c: + * tests/examples/thumbnails.c: + * tests/examples/transition.c: + * tools/ges-launch.c: + examples, ges-launch: remove deprecated g_thread_init() + Not needed any more with recent glib versions + +2012-02-10 19:13:44 +0000 Tim-Philipp Müller + + * ges/Makefile.am: + g-i: need to call gst_init() before ges_init() so GST_TYPE_CAPS is set + Fixes "g_param_spec_boxed: assertion `G_TYPE_IS_BOXED (boxed_type)' failed" + warnings when running g-ir-scanner. + +2012-02-10 19:01:03 +0000 Tim-Philipp Müller + + * ges/ges-track-audio-transition.c: + * ges/ges-track-video-transition.c: + track-{audio,video}-transition: update for controller API changes + +2012-02-10 18:43:51 +0000 Tim-Philipp Müller + + * ges/ges-timeline-pipeline.c: + timeline-pipeline: use standard GLib API to save thumbnail data to file + +2012-02-10 18:35:07 +0000 Tim-Philipp Müller + + * ges/ges-timeline-pipeline.c: + timeline-pipeline: update for new gst_buffer_map() API + +2012-01-30 11:34:09 +0100 Mark Nauwelaerts + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline.c: + ges: support and handle no-more-pads in GESTimeline(Pipeline) + ... to arrange for a clean READY to PAUSED state change transition. + Not doing so might have playsink reaching PAUSED prematurely + as one track prerolls, only to lose this state again (temporarily) + when the other track needs to preroll. + This is generally not nice or convenient, and particularly nasty + when trying to perform seek in PAUSED. + +2012-01-25 14:13:02 +0100 Thomas Vander Stichele + + * common: + Automatic update of common submodule + From c463bc0 to 7fda524 + +2012-01-25 11:41:15 +0100 Sebastian Dröge + + * common: + Automatic update of common submodule + From 2a59016 to c463bc0 + +2012-01-18 16:48:52 +0100 Sebastian Dröge + + * common: + Automatic update of common submodule + From 0807187 to 2a59016 + +2012-01-12 16:34:13 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-pitivi-formatter.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + * ges/ges-timeline.c: + * ges/ges-track.c: + * tests/check/ges/layer.c: + * tests/check/ges/save_and_load.c: + ges: Various doc fixups and cleanups + +2012-01-12 15:12:14 +0100 Mark Nauwelaerts + + * ges/ges-pitivi-formatter.c: + * ges/ges-timeline-layer.c: + ges: only use glib constructs as required in configure.ac + +2012-01-12 15:11:10 +0100 Mark Nauwelaerts + + * pkgconfig/gst-editing-services-uninstalled.pc.in: + pkgconfig: fix uninstalled pkgconfig to handle out-of-source build case + +2012-01-07 13:36:33 -0300 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + pitivi-formatter: Use the new Formatter->project_loaded vmethod + +2012-01-07 13:28:15 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-formatter.c: + * ges/ges-formatter.h: + formatter: Add a "loaded" signal + API: GESFormatter::loaded signal + API: GESFormatter->project_loaded VMethod + +2011-12-22 17:11:34 +0100 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/ges-pitivi-formatter.c: + * ges/ges-pitivi-formatter.h: + docs: Add a pitivi-formatter documentation + Move the API to the API section of the pitivi-formatter file + +2012-01-05 13:21:40 -0300 Thibault Saunier + + * ges/ges-formatter.c: + formatter: Enhance some debug logging + +2012-01-04 19:04:53 -0300 Thibault Saunier + + * bindings/python/ges.defs: + * bindings/python/ges.override: + * ges/ges-pitivi-formatter.c: + * ges/ges-pitivi-formatter.h: + ges: Rework the ges_pitivi_get_sources method + + Remove the URI parameter of ges_pitivi_get_sources + + Rework how we handle the PitiviFormatterPrivate.source_table HashTable + rename it to sources_table to make a difference between it and the + source_table(s) it containes + +2012-01-04 18:06:37 -0300 Thibault Saunier + + * bindings/python/ges.defs: + * bindings/python/ges.override: + * ges/ges-pitivi-formatter.c: + * ges/ges-pitivi-formatter.h: + ges: Let user set a source list on the PitiviFormatter + API: ges_pitivi_formatter_set_sources + Bind it in python + +2012-01-04 15:06:11 -0300 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + ges: Handle the new SourceMoved API in PitiviFormatter + +2012-01-04 15:05:15 -0300 Thibault Saunier + + * bindings/python/ges.defs: + bindings: Bind the new formatter API + +2012-01-04 14:59:21 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-formatter.c: + * ges/ges-formatter.h: + ges: Add an API to Formatter to be able to handle moved sources + API: GESFormatter::source-moved signal + API: GESFormatter::update_source_uri virtual method + Conflicts: + ges/ges-formatter.h + +2012-01-04 14:46:54 -0300 Thibault Saunier + + * ges/ges-timeline.c: + ges: Add a "discovery-error" signal to GESTimeline + API: GESTimeline::discovery-error signal + +2012-01-04 14:24:05 -0300 Thibault Saunier + + * ges/ges-timeline-file-source.c: + ges: Let user set TimelineObject URI while not containing any TrackObject + In the case of not properly set uri, we can keep using the same + TimelineFileSource changing its URI until its TrackObject could be created. + This is particularly usefull in the case of formatter trying to load filesource + when the file has been moved + +2012-01-03 11:59:29 +0100 mathieu duponchelle + + * bindings/python/ges.defs: + * bindings/python/ges.override: + * ges/ges-pitivi-formatter.c: + * ges/ges-pitivi-formatter.h: + Add API to get all sources from xptv project + +2011-12-23 17:16:20 +0100 mathieu duponchelle + + * ges/ges-timeline-layer.c: + Disconnect handlers when object is removed from layer + +2011-12-22 21:21:37 +0100 mathieu duponchelle + + * ges/ges-timeline-layer.c: + Edit : typos + +2011-08-24 12:04:32 +0200 Mathieu Duponchelle + + * ges/ges-timeline-layer.c: + * ges/ges-timeline.c: + GES : remove transitions when needed + Conflicts: + ges/ges-timeline.c + +2011-12-21 19:48:22 +0100 mathieu duponchelle + + * ges/ges-timeline-layer.c: + Fixes auto transitions on layers + n. + +2011-12-29 13:56:08 +0100 Thibault Saunier + + * ges/ges-timeline-file-source.c: + * ges/ges-timeline.c: + * ges/ges-track-filesource.c: + ges: Add a maxduration property to TrackFileSource + API: GESTrackFileSource::maxduration property + +2011-12-23 19:23:31 +0100 Thibault Saunier + + * ges/ges-track.c: + ges: Disconnect the TrackObject when removed from a Track + +2011-12-26 02:54:29 +0100 Thibault Saunier + + * bindings/python/ges.defs: + * bindings/python/ges.override: + * docs/libs/ges-sections.txt: + * ges/ges.c: + * ges/ges.h: + ges: Add a runtime version checking function + Bind it in python + API: ges_version + +2011-12-22 15:59:51 +0100 Thibault Saunier + + * ges/ges-track-object.h: + ges: Reindent ges-track-object.h + +2011-12-22 14:41:39 +0100 Thibault Saunier + + * bindings/python/gesmodule.c: + bindings: Register the various enums/flags in python + +2011-12-19 11:21:18 +0100 Thibault Saunier + + * ges/ges-track-object.c: + ges: Make TrackObject:locked a GObject property + +2011-12-18 01:49:24 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + ges: Fix the TimelineObject::effect-added signal emission timing + We were emitting it before it gets added to the track_object list, + so the list we were getting with get_top_effects was containing + a TrackObject that wasen't a TrackEffect + + A bit of refactoring + +2011-12-16 09:56:08 +0100 Thibault Saunier + + * ges/ges-track.h: + ges: Reindent ges-track.h + +2011-12-16 09:54:58 +0100 Thibault Saunier + + * ges/ges-timeline.c: + * ges/ges-track-object.c: + ges: Some debug logging enhancements + +2011-12-16 09:52:35 +0100 Thibault Saunier + + * ges/ges-track.c: + ges: Expand track background duration equal to timeline duration + +2011-12-16 09:35:31 +0100 Thibault Saunier + + * ges/ges-timeline.c: + ges: Add a duration property to GESTimeline + API: GESTimeline:duration property + +2011-12-16 04:23:58 -0300 Thibault Saunier + + * ges/ges-timeline-object.c: + ges: Fix TimelineObject movement that contains unlocked and relocked objects + Record the TrackObject that initiated a TimelineObject movement so we don't + get inifite loops. + Also fix the new TrackObject calculation: + child.start = time - offset (not time + offset) + +2011-06-17 14:29:52 -0400 Thibault Saunier + + * ges/ges-timeline-layer.c: + * tests/check/ges/layer.c: + ges: Bump layer height from 10 to 1000 + +2011-12-07 20:17:55 -0300 Thibault Saunier + + * bindings/python/ges.defs: + * ges/ges-timeline-object.c: + ges: Little fixes to timeline_object_g(s)et_supprted_formats + +2011-12-07 20:50:13 -0300 Thibault Saunier + + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + ges: Add guards to all API calls in GESTimelineObject + And reindent the .h file + +2011-12-07 20:36:24 -0300 Thibault Saunier + + * bindings/python/ges.defs: + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + ges: Add a method to TimelineObject to set contained TrackObject-s locked state + API: ges_timeline_object_objects_set_locked + +2011-12-06 23:11:25 -0300 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-formatter.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline.c: + * ges/ges-track.c: + docs: Update documentation + +2011-12-06 14:11:21 -0300 Thibault Saunier + + * ges/ges-timeline-layer.c: + ges: Remove transitions properly + +2011-12-06 14:10:14 -0300 Thibault Saunier + + * ges/ges-timeline-object.c: + ges: Add a TimelineObject::track-object-removed signal + API: TimelineObject::track-object + +2011-11-30 21:47:54 -0300 Thibault Saunier + + * ges/ges-timeline-object.c: + * ges/ges-track.c: + ges: Enhance some debug logging + Also make sure not to warn when it shouldn't + +2011-11-30 21:46:21 -0300 Thibault Saunier + + * ges/ges-timeline-layer.c: + ges: Misc fixes in ges-timeline-layer + Put the comment where they are meant to be + Make static functions static + Use LAYER_HEIGHT where needed + +2011-11-02 13:51:36 -0300 Thibault Saunier + + * ges/ges-timeline-layer.c: + ges: Auto transition cleanup + +2011-11-30 20:13:09 -0300 Thibault Saunier + + * ges/ges-timeline-file-source.c: + * tests/check/ges/filesource.c: + ges: Do not add any audio source when still image + We now have a backgroud so no problem with that. + Fix the testsuite accordingly + Fix #657514 + +2011-11-16 15:22:33 -0300 Thibault Saunier + + * ges/ges-track-object.c: + ges: Fix debugging symbol to avoid segfaults + +2011-11-02 13:52:16 -0300 Thibault Saunier + + * ges/ges-pitivi-formatter.c: + ges: Big PiTiVi formatter cleanup + Also set the version to 0.2 + +2011-10-09 12:28:39 -0400 Stéphane Maniaci + + * ges/ges-pitivi-formatter.c: + ges: Don't release unexisting sources when destroying the formatter + This happens in case of an empty project. + +2011-10-20 16:16:30 +0200 Thibault Saunier + + * ges/ges-track.c: + ges: Add a gnl background object to tracks + This is in order to support gaps in the timeline. + This is not the proper solution, we should make sure to fill gaps properly, + but for the time being, it makes the trick + +2011-12-06 18:04:11 -0300 Thibault Saunier + + * ges/ges-simple-timeline-layer.c: + ges: Make sure not to set transition start to negative + gnlobject.start is a guint64, we can not set it to a negative value + +2011-09-14 14:58:01 +0200 Mathieu Duponchelle + + * ges/ges-timeline-layer.c: + ges: makes "pass over" accurate and reset priority when transition is removed + +2011-08-28 01:13:20 +0200 Mathieu Duponchelle + + * ges/ges-timeline.c: + ges: don't reset the supported formats in the timeline when they're already set + My mom never told me goto was evil + +2011-08-26 18:39:39 +0200 Mathieu Duponchelle + + * ges/ges-timeline-object.c: + ges: make the offset positive as it should have been + Took me two days to figure that out :/ I'm pretty sure it's the way things are supposed to be + +2011-08-24 12:06:22 +0200 Mathieu Duponchelle + + * ges/ges-track.c: + ges: modifies emission of the track/object-removed signal + Make it be emitted right before the track objects is removed so we don't end up + with a TrackObject that has already been freed + +2011-08-24 11:48:14 +0200 Mathieu Duponchelle + + * bindings/python/ges.defs: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + ges: adds a ges_timeline_object_split method + Slightly improves the copy function to do so. + API: ges_timeline_object_split + +2011-06-08 20:36:58 -0400 Thibault Saunier + + * ges/ges-timeline-object.c: + ges: add a timeline_object copy function + +2011-08-09 15:56:56 +0200 Mathieu Duponchelle + + * bindings/python/ges.defs: + python : bind the new timeline_object functions + + move_to_layer + + is_moving_from_layer + + set_moving_from_layer + +2011-08-03 02:33:10 +0200 Mathieu Duponchelle + + * bindings/python/ges.defs: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-track.c: + * ges/ges-track.h: + ges: adds an enable_update function to the GESTimeline + Binds it in python + API: ges_timeline_enable_update + +2011-07-28 18:49:04 +0200 Mathieu Duponchelle + + * ges/ges-track.c: + ges : add a track-object(removed signal to the track + API: GESTrack::track-object-removed signal + +2011-12-01 00:33:38 -0300 Thibault Saunier + + * ges/ges-timeline-standard-transition.c: + ges: Handle supported formats in TimelineStandardTransition + +2011-07-24 02:49:36 +0200 Mathieu Duponchelle + + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + * tests/check/ges/layer.c: + ges: add an auto-transition to the layer + API: GESTimelineLayer.auto_transition property + +2011-07-27 02:04:48 +0200 Mathieu Duponchelle + + * ges/ges-track.c: + ges: Keep the track object list sorted in track + +2011-07-27 02:02:20 +0200 Mathieu Duponchelle + + * bindings/python/testsuite/test_textoverlay.py: + * ges/ges-timeline-object.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/filesource.c: + * tests/check/ges/overlays.c: + * tests/check/ges/timelineobject.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + ges: add track objects to the track before the track-object-added is emitted + NOTE: The caller of ges_timeline_object_create_track_object now needs to add it to + the timeline_object after calling the function. + Fix the testsuite to support that accordingly + +2011-07-13 18:30:06 +0200 Mathieu Duponchelle + + * bindings/python/ges.defs: + * bindings/python/ges.override: + python: bind and override the ges_track_get_objects method + +2011-07-08 03:37:28 +0200 Mathieu Duponchelle + + * ges/ges-track.c: + * ges/ges-track.h: + ges: Add API to get the TrackObject-s contained in a Track + Sort the track_objects list + API: ges_track_get_objects + +2011-08-28 03:59:19 +0200 Mathieu Duponchelle + + * bindings/python/ges-types.defs: + * bindings/python/ges.defs: + python: binds the PiTiVi formatter + +2011-08-28 03:58:21 +0200 Mathieu Duponchelle + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + examples: Add a load project option to ges-ui + +2011-08-28 03:57:13 +0200 Mathieu Duponchelle + + * tools/ges-launch.c: + tools: Add a -y option to ges-launch to launch pitivi projects + +2011-08-28 03:56:26 +0200 Mathieu Duponchelle + + * tests/check/ges/save_and_load.c: + test: Add a pitivi formatter test + For now we requiere a project files and media files to be on the host system, + this is not optimal and we should rework that in the future. + +2011-08-28 03:55:46 +0200 Mathieu Duponchelle + + * ges/Makefile.am: + * ges/ges-pitivi-formatter.c: + * ges/ges-pitivi-formatter.h: + * ges/ges-types.h: + * ges/ges.h: + ges: Implement a Pitivi Formatter + API: ges_pitivi_formatter_new + +2011-08-28 03:48:36 +0200 Mathieu Duponchelle + + * ges/ges-track.c: + ges: add a track-object-added signal to GESTrack + API: GESTrack::track-object-added signal + +2011-08-28 06:25:37 +0200 Mathieu Duponchelle + + * ges/ges-timeline-object.c: + ges: Add a track-object-added signal to GESTimelineObject + API: GESTimelineObject::track-object-added signal + +2011-12-01 00:18:30 -0300 Thibault Saunier + + * bindings/python/ges.defs: + * docs/libs/ges-sections.txt: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * tests/check/ges/save_and_load.c: + ges: Move supported formats from filesource to timelineobject + This is usefull by any subclass of GESTimelineObject + + Bind it in python + + Fix the keyfile formatter tests + API: ges_timeline_object_set_supported_formats + API: ges_timeline_object_get_supported_formats + +2011-06-07 12:54:06 -0400 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + ges: Add a ges_timeline_append_layer convenience method + API: ges_timeline_append_layer + +2011-06-06 15:56:23 -0400 Thibault Saunier + + * tests/check/ges/layer.c: + test: Better layer priority handling testing + We use the ges_timeline_object_move_to_layer new function to make sure it works, + and that everything goes well on priority handling with this new method + +2011-06-06 15:55:47 -0400 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline.c: + ges: add a function to move TimelineObject from a layer to another + API: ges_timeline_object_move_to_layer + API: ges_timeline_object_is_moving_from_layer + API: ges_timeline_object_set_moving_from_layer + +2011-06-02 22:03:19 -0400 Thibault Saunier + + * ges/ges-timeline.c: + ges: Keep layers sorted by priorities in the timeline + +2011-06-02 22:01:43 -0400 Thibault Saunier + + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + ges: Add some debugging symbols + +2011-04-15 19:34:28 -0400 Thibault Saunier + + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * tests/check/ges/layer.c: + * tests/check/ges/save_and_load.c: + ges: Handle TimelineLayer and its contained TimelineObject priorities properly + GESTimelineObject.priority is now actually relative to its containing layer + priority. + Test it in the layer test-suite. + +2011-06-02 21:35:59 -0400 Thibault Saunier + + * ges/ges-timeline-layer.c: + ges: Define a LAYER_HEIGHT constant in the normal layer + +2012-01-11 15:31:41 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + docs: Add pipeline {get|set}_{audio|video}_sink() docs + +2012-01-06 09:16:09 -0300 Robert Swain + + * ges/ges-timeline.c: + timeline: simplify code to remove an object from the pendingobjects list + g_list_remove_all () can be used as a simplification as the private data to ges + timeline object are 1:1. + +2012-01-04 17:24:16 +0100 Robert Swain + + * ges/ges-timeline.c: + GESTimeline: Lock object discovery list + TimelineFileSource objects are asynchronously discovered with discoverer + with such objects being added to a pendingobjects list. If one were to + remove a layer before an object in said layer had been discovered, a + segfault could occur. + As such, management of the list has been made more robust with the + addition of a mutex and removal of the object from the pendingobjects + list upon layer removal. + +2011-11-08 17:29:38 -0500 Mateu Batle + + * ges/ges-timeline-object.c: + GESTimelineObject: fix trigger notify changing props + Notify signal was not triggered when changing properties through + ges_timeline_object_set_* functions, only when done through g_object_set + +2012-01-04 19:56:19 +0000 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From 710d453 to 0807187 + +2012-01-02 15:58:17 +0100 Edward Hervey + + * ges/ges.c: + ges: Update for registry API change + +2011-12-30 17:24:37 +0100 Edward Hervey + + Merge remote-tracking branch 'origin/master' into 0.11 + Conflicts: + bindings/python/Makefile.am + bindings/python/ges.override + bindings/python/gesmodule.c + configure.ac + +2011-12-30 17:18:18 +0100 Edward Hervey + + * tests/examples/ges-ui.c: + * tools/ges-launch.c: + tools: Add proper include for g_printf + +2011-12-30 17:18:40 +0100 Edward Hervey + + * tests/examples/thumbnails.c: + tests/thumbnails: Updates + +2011-12-30 17:18:18 +0100 Edward Hervey + + * tests/examples/ges-ui.c: + * tools/ges-launch.c: + tools: Add proper include for g_printf + +2011-12-30 17:17:11 +0100 Edward Hervey + + * tests/check/ges/save_and_load.c: + tests/save_and_load: Cleanups and leak fixing + +2011-12-30 17:16:29 +0100 Edward Hervey + + * tests/check/ges/effects.c: + tests/effects: Update for new 'parent' property in objects + +2011-12-30 17:15:07 +0100 Edward Hervey + + * ges/ges-enums.c: + * ges/ges-screenshot.c: + * ges/ges-screenshot.h: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + * ges/ges-track-audio-transition.c: + * ges/ges-track-transition.h: + * ges/ges-track-video-transition.c: + * ges/ges.c: + ges: Update for 0.11 changes + * Changes with controller API + * Use new GstSample for screenshot API + +2011-11-30 16:15:35 +0100 Mark Nauwelaerts + + * tools/ges-launch.c: + ges-launch: allow for optional audio or video track + +2011-11-30 15:44:45 +0100 Mark Nauwelaerts + + * tools/ges-launch.c: + ges-launch: port over gst-launch verbose setting + ... as it is useful for a quick peek as to what is going on. + +2011-11-07 15:08:34 +0100 Robert Swain + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + GESTimelinePipeline: Add API to get/set audio sink + +2011-10-12 12:49:32 +0100 Tim-Philipp Müller + + * docs/libs/Makefile.am: + docs: link against libgstreamer for gst_init() and fix order in GTKDOC_CFLAGS + Add missing backslash so we link against libgstreamer. + +2011-10-12 12:37:54 +0100 Tim-Philipp Müller + + * bindings/python/ges.override: + * bindings/python/gesmodule.c: + bindings: fix up pygst includes for new install directory + Changes from pygst/pygst.h to gst/pygst.h to match the source + code layout, which makes things easier in an uninstalled setup. + https://bugzilla.gnome.org/show_bug.cgi?id=657435 + https://bugzilla.gnome.org/show_bug.cgi?id=657436 + +2011-10-12 12:32:16 +0100 Tim-Philipp Müller + + * configure.ac: + configure: require pygst from git for the headers + +2011-08-26 15:21:25 +0200 Sebastian Dröge + + * bindings/python/Makefile.am: + python: Add $(PYGST_CFLAGS) to CFLAGS to fix the build + https://bugzilla.gnome.org/show_bug.cgi?id=657436 + +2011-10-11 10:12:05 +0200 Edward Hervey + + * docs/libs/ges-sections.txt: + docs: Add new API + +2011-10-11 10:08:47 +0200 Edward Hervey + + * Makefile.am: + * bindings/Makefile.am: + * bindings/python/Makefile.am: + * bindings/python/arg-types.py: + * bindings/python/codegen/Makefile.am: + * bindings/python/codegen/__init__.py: + * bindings/python/codegen/argtypes.py: + * bindings/python/codegen/code-coverage.py: + * bindings/python/codegen/codegen.py: + * bindings/python/codegen/definitions.py: + * bindings/python/codegen/defsparser.py: + * bindings/python/codegen/docextract.py: + * bindings/python/codegen/docgen.py: + * bindings/python/codegen/h2def.py: + * bindings/python/codegen/mergedefs.py: + * bindings/python/codegen/mkskel.py: + * bindings/python/codegen/override.py: + * bindings/python/codegen/reversewrapper.py: + * bindings/python/codegen/scmexpr.py: + * bindings/python/examples/Makefile.am: + * bindings/python/examples/effect.py: + * bindings/python/examples/simple.py: + * bindings/python/ges-types.defs: + * bindings/python/ges.defs: + * bindings/python/ges.override: + * bindings/python/gesmodule.c: + * bindings/python/testsuite/Makefile.am: + * bindings/python/testsuite/common.py: + * bindings/python/testsuite/runtests.py: + * bindings/python/testsuite/test_global_functions.py: + * bindings/python/testsuite/test_layer.py: + * bindings/python/testsuite/test_simple_layer.py: + * bindings/python/testsuite/test_textoverlay.py: + * bindings/python/testsuite/test_timeline.py: + * bindings/python/testsuite/test_timeline_file_source.py: + * bindings/python/testsuite/test_timeline_parse_launch_effect.py: + * bindings/python/testsuite/test_timeline_pipeline.py: + * bindings/python/testsuite/test_timeline_test_source.py: + * bindings/python/testsuite/test_timeline_title_source.py: + * bindings/python/testsuite/test_track.py: + * bindings/python/testsuite/test_transition.py: + * configure.ac: + bindings: We no longer use static bindings in 0.11 + +2011-10-11 10:02:11 +0200 Edward Hervey + + * ges/ges-track-video-transition.c: + TrackVideoTransition: Fix after merge + +2011-10-11 09:58:46 +0200 Edward Hervey + + Merge remote-tracking branch 'origin/master' into 0.11 + +2011-10-11 09:54:56 +0200 Edward Hervey + + * common: + common: Update to tip of 0.11 branch + +2011-10-11 09:51:43 +0200 Edward Hervey + + * ges/ges-track-image-source.c: + * ges/ges-track-parse-launch-effect.c: + * ges/ges-track-text-overlay.c: + * ges/ges-track-video-transition.c: + ges: ffmpegcolorspace is dead, long live videoconvert + +2011-10-11 09:51:35 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline.c: + * ges/ges-track-effect.c: + ges: Port to 0.11 API + +2011-10-11 09:50:30 +0200 Edward Hervey + + * ges/ges-screenshot.c: + screenshot: Use new 0.11 API + FIXME : Need to figure out how to get the buffer caps. + +2011-10-05 12:24:36 +0200 Robert Swain + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: API documentation improvements + Added notes for refcounts and transference to API documentation for the + video sink getter/setter. + +2011-10-04 16:25:22 +0200 Robert Swain + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + GESTimelinePipeline: Add video sink get/set API + This new API allows getting/setting of the preview mode's video sink + element through playsink's video-sink property. + +2011-09-07 15:51:36 +0200 Stefan Sauer + + * docs/libs/Makefile.am: + docs: cleanup makefiles + Remove commented out parts that we don't need. Remove "the wingo addition" - no + so useful after all. Narrow down file-globs for plugin docs. + +2011-09-06 21:53:57 +0200 Stefan Sauer + + * common: + Automatic update of common submodule + From a39eb83 to 11f0cd5 + +2011-09-06 16:07:30 +0200 Stefan Sauer + + * common: + Automatic update of common submodule + From 605cd9a to a39eb83 + +2011-09-02 19:26:43 +0200 Edward Hervey + + * docs/random/design: + docs: clarify sentence a bit + +2011-09-02 18:20:00 +0200 Edward Hervey + + * docs/random/design: + design: More specifications of compositing and material handling + Doing it this way will enable us to handle: + * Output conforming (proper scaling/conversion at the right place) + * Compositing in an easy way at the layer level + * Avoid having too many transformation elements + +2011-09-02 17:45:52 +0200 Edward Hervey + + * docs/random/design: + design: Re-order items by importance + Plugins, templates and so-forth are not top priorities. + Also update the index + +2011-09-02 16:57:37 +0200 Edward Hervey + + * docs/libs/ges-sections.txt: + docs: Remove duplicate symbol + +2011-09-02 16:43:13 +0200 Edward Hervey + + * docs/random/design: + docs: Add note about merging GNonLin and GES + +2011-08-30 16:03:22 +0200 Andoni Morales Alastruey + + * tests/Makefile.am: + Don't build the examples if it's disabled in configure + Fixes: #657707. + +2011-08-30 16:40:03 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Return before the error labels + Avoids a warning for no reason + +2011-08-29 12:00:06 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Handle some trivial error cases + Avoids ending up calling potentially NULL variables + +2011-08-29 11:47:01 +0200 Edward Hervey + + * docs/random/design: + docs/design: Add section on compositing and mixing + +2011-08-29 09:51:10 +0200 Edward Hervey + + * docs/random/design: + docs/design: updates on effects and conforming materials + +2011-08-17 12:24:48 +0200 Luis de Bethencourt + + * bindings/python/examples/simple.py: + pyges: updating copyright of simple example + Signed-off-by: Thibault Saunier + +2011-08-14 20:27:08 +0200 Luis de Bethencourt + + * ges/ges-track-video-transition.c: + ges/ges-track-video-transition.c: fix transition of different video sizes + +2011-08-14 00:52:23 +0200 Luis de Bethencourt + + * ges/ges-track-object.c: + GESTrackObject: missing Since tag and typo fixes + +2011-08-13 19:34:55 +0200 Luis de Bethencourt + + * docs/libs/ges-sections.txt: + docs: adding GESPipelineFlags to docs + +2011-08-13 18:38:31 +0200 Luis de Bethencourt + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-text-overlay.c: + * ges/ges-track-text-overlay.c: + docs: add Since tag to new TextOverlay functions + And add them to the GES API doc + +2011-08-13 17:51:48 +0200 Luis de Bethencourt + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-title-source.c: + * ges/ges-track-title-source.c: + docs: add Since tag to new TitleSource functions + And add them to the GES API doc + +2011-08-12 23:32:52 +0200 Luis de Bethencourt + + * bindings/python/examples/Makefile.am: + * bindings/python/examples/effect.py: + pyges: Add an effect example + +2011-08-11 18:26:08 +0200 Luis de Bethencourt + + * bindings/python/examples/simple.py: + pyges: fix and clean examples/simple.py + +2011-08-11 16:35:11 +0200 Edward Hervey + + * .gitignore: + bindings: Ignore more files + +2011-08-11 16:32:51 +0200 Edward Hervey + + * bindings/Makefile.am: + * configure.ac: + bindings: makefile => Makefile + More in sync with all other Makefiles + +2011-08-11 16:28:14 +0200 Edward Hervey + + * bindings/python/testsuite/common.py: + * bindings/python/testsuite/runtests.py: + testsuite: Remove print statements + +2011-08-11 14:31:47 +0200 Thibault Saunier + + * bindings/python/testsuite/test_timeline_file_source.py: + pyges: Fix the timeline_file_source test suite + Can't create a GESTimelineFileSource if you don't have the protocol in the uri + +2011-08-11 14:27:31 +0200 Thibault Saunier + + * bindings/python/Makefile.am: + * bindings/python/testsuite/test_global_functions.py: + * bindings/python/testsuite/test_layer.py: + * bindings/python/testsuite/test_simple_layer.py: + * bindings/python/testsuite/test_textoverlay.py: + * bindings/python/testsuite/test_timeline.py: + * bindings/python/testsuite/test_timeline_file_source.py: + * bindings/python/testsuite/test_timeline_parse_launch_effect.py: + * bindings/python/testsuite/test_timeline_pipeline.py: + * bindings/python/testsuite/test_timeline_test_source.py: + * bindings/python/testsuite/test_timeline_title_source.py: + * bindings/python/testsuite/test_track.py: + * bindings/python/testsuite/test_transition.py: + pyges: Install it so we now use import ges + Using from gst import ges did not make much sense + +2011-08-11 14:22:50 +0200 Thibault Saunier + + * bindings/makefile.am: + pyges: Do not try to build the bindings if no python found + +2011-08-11 14:21:18 +0200 Thibault Saunier + + * bindings/python/Makefile.am: + * bindings/python/examples/Makefile.am: + * bindings/python/testsuite/Makefile.am: + * configure.ac: + pyges: Install files from the examples and testsuite + +2011-05-06 19:39:56 -0300 Thibault Saunier + + * bindings/python/examples/simple.py: + pyges: Add a PyGes example + +2011-08-09 22:11:03 +0200 Thibault Saunier + + * bindings/python/Makefile.am: + * bindings/python/arg-types.py: + pyges: Add the GstArgtypes, get all the functions binded + +2011-08-09 17:16:44 +0200 Thibault Saunier + + * bindings/python/ges.override: + * bindings/python/gesmodule.c: + pyges: link against pygst and use GstMiniObject + +2011-06-10 16:58:55 +0200 Mathieu Duponchelle + + * bindings/python/testsuite/test_global_functions.py: + * bindings/python/testsuite/test_layer.py: + * bindings/python/testsuite/test_simple_layer.py: + * bindings/python/testsuite/test_textoverlay.py: + * bindings/python/testsuite/test_timeline.py: + * bindings/python/testsuite/test_timeline_file_source.py: + * bindings/python/testsuite/test_timeline_parse_launch_effect.py: + * bindings/python/testsuite/test_timeline_pipeline.py: + * bindings/python/testsuite/test_timeline_test_source.py: + * bindings/python/testsuite/test_timeline_title_source.py: + * bindings/python/testsuite/test_track.py: + pyges : Improve the test suite + +2011-06-08 03:23:17 +0200 Mathieu Duponchelle + + * bindings/python/ges.defs: + pyges : correct the defs for ges_track_audio_raw_new and ges_track_video_raw_new + +2011-06-07 01:43:42 +0200 Mathieu Duponchelle + + * bindings/python/codegen/argtypes.py: + * bindings/python/ges.override: + * bindings/python/testsuite/test_timeline.py: + pyges : Add overrides + +2011-06-06 01:02:17 +0200 Mathieu Duponchelle + + * bindings/python/ges.override: + pyges : Add *_valist and *_by_pspec to the ignore-glob + +2011-06-06 00:59:41 +0200 Mathieu Duponchelle + + * ges/ges.h: + Add ges-screenshot.h to ges.h + This is to make ges_play_sink_convert_frame available to the bindings. + +2011-06-08 03:50:51 +0200 Mathieu Duponchelle + + * bindings/python/ges.override: + pyges : override unhandled methods + +2011-06-08 03:50:25 +0200 Mathieu Duponchelle + + * bindings/python/codegen/argtypes.py: + pyges : add argtypes + +2011-06-07 19:59:16 +0200 Mathieu Duponchelle + + * bindings/python/ges.defs: + * bindings/python/ges.override: + pyges : Remove ges_formatter_set_data and get_data from the .defs + +2011-05-14 04:32:45 +0200 Mathieu Duponchelle + + * bindings/python/ges.override: + pyges : Override ges_timeline_parse_launch_effect_new to make it accept None + +2011-06-07 18:38:37 -0400 Thibault Saunier + + * bindings/python/ges-types.defs: + * bindings/python/ges.defs: + pyges: Update ges.defs and ges-types.defs with the new effect API + +2011-06-07 19:44:40 +0200 Mathieu Duponchelle + + * bindings/python/testsuite/test_textoverlay.py: + pyges : Add a text overlay test + +2011-06-07 18:09:35 -0400 Thibault Saunier + + * bindings/python/testsuite/test_timeline.py: + * bindings/python/testsuite/test_transition.py: + pyges : Add actual testing to the testcases + +2011-06-07 18:05:43 -0400 Thibault Saunier + + * bindings/python/ges.override: + pyges: add get_type method to the ignore_blob list + +2011-06-07 19:35:00 +0200 Mathieu Duponchelle + + * bindings/python/ges.override: + pyges: Override methods using GList + +2011-05-12 02:27:12 +0200 Mathieu Duponchelle + + * bindings/python/Makefile.am: + * bindings/python/testsuite/common.py: + * bindings/python/testsuite/runtests.py: + * bindings/python/testsuite/test_timeline.py: + * bindings/python/testsuite/test_transition.py: + pyges : Add a test suite with three test cases for the bindings + +2011-05-06 23:56:16 -0300 Thibault Saunier + + * bindings/python/ges-types.defs: + * bindings/python/ges.defs: + pyges: Regenerate ges.def and ges-types.def so the contructors are detected properly + +2011-05-06 18:11:11 -0300 Thibault Saunier + + * bindings/python/codegen/h2def.py: + pyges: Make use of the namespace for the constructor classnames in codegen + +2011-06-07 15:20:46 -0400 Thibault Saunier + + * bindings/python/codegen/__init__.py: + * bindings/python/codegen/argtypes.py: + * bindings/python/codegen/code-coverage.py: + * bindings/python/codegen/codegen.py: + * bindings/python/codegen/definitions.py: + * bindings/python/codegen/defsparser.py: + * bindings/python/codegen/docextract.py: + * bindings/python/codegen/docgen.py: + * bindings/python/codegen/h2def.py: + * bindings/python/codegen/override.py: + * bindings/python/codegen/reversewrapper.py: + * bindings/python/codegen/scmexpr.py: + pyges: Sync codegen with upstream + +2011-04-27 08:56:29 -0300 Thibault Saunier + + * bindings/python/ges.override: + pyges: override ges_track_get_timeline + This make the bindings compiling without warning + +2011-06-07 19:18:27 -0400 Thibault Saunier + + * Makefile.am: + * acinclude.m4: + * bindings/makefile.am: + * bindings/python/Makefile.am: + * bindings/python/codegen/Makefile.am: + * configure.ac: + building: add python bindings + +2011-06-07 19:17:10 -0400 Thibault Saunier + + * bindings/python/ges-types.defs: + * bindings/python/ges.defs: + * bindings/python/gesmodule.c: + pyges: Add the necessary file to compile the bindings + defs files have been generated with the h2defs.py script + +2011-06-07 16:55:41 -0400 Thibault Saunier + + * bindings/python/ges.override: + pyges: add registering functions prototypes to ges.override + +2011-04-25 19:13:38 -0400 Thibault Saunier + + * bindings/python/ges.override: + pyges: Define missing types + +2011-04-25 19:12:38 -0400 Thibault Saunier + + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges-timeline-pipeline.h: + GESTimeleinePipeline: Create a flag type instead of a simple enum + Make it binding friendly + +2011-06-07 03:37:50 +0200 Mathieu Duponchelle + + * bindings/python/ges.override: + pyges: Add ges.override + +2011-06-07 02:26:20 +0200 Mathieu Duponchelle + + * bindings/python/codegen/__init__.py: + * bindings/python/codegen/argtypes.py: + * bindings/python/codegen/code-coverage.py: + * bindings/python/codegen/codegen.py: + * bindings/python/codegen/definitions.py: + * bindings/python/codegen/defsparser.py: + * bindings/python/codegen/docextract.py: + * bindings/python/codegen/docgen.py: + * bindings/python/codegen/h2def.py: + * bindings/python/codegen/mergedefs.py: + * bindings/python/codegen/mkskel.py: + * bindings/python/codegen/override.py: + * bindings/python/codegen/reversewrapper.py: + * bindings/python/codegen/scmexpr.py: + Add codegen to the tracked files + +2011-08-09 19:15:18 +0200 Luis de Bethencourt + + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-title-source.h: + * ges/ges-track-title-source.c: + * ges/ges-track-title-source.h: + * tests/check/ges/save_and_load.c: + * tests/check/ges/titles.c: + GESTimelineTitleSource/TrackTitleSource: add xpos/ypos setting + Vertical and horizontal position properties of the title source + can be set and get. + +2011-08-09 19:13:37 +0200 Luis de Bethencourt + + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-title-source.h: + * ges/ges-track-title-source.c: + * ges/ges-track-title-source.h: + * tests/check/ges/save_and_load.c: + * tests/check/ges/titles.c: + GESTimelineTitleSource/TrackTitleSource: add color setting + Color property of the text overlay can be set and get. + +2011-08-09 17:39:02 +0200 Luis de Bethencourt + + * tests/examples/transition.c: + examples: add file inpoints and summary to overlay example + +2011-08-08 18:57:37 +0200 Luis de Bethencourt + + * tests/examples/overlays.c: + examples: add xpos and ypos options to overlay example + +2011-08-08 18:44:57 +0200 Luis de Bethencourt + + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-text-overlay.h: + * ges/ges-track-text-overlay.c: + * ges/ges-track-text-overlay.h: + * tests/check/ges/overlays.c: + GESTimelineTextOverlay/TrackTextOverlay: add xpos/ypos setting + Vertical and horizontal position properties of the text overlay + can be set and get. + +2011-08-08 18:30:42 +0200 Luis de Bethencourt + + * ges/ges-enums.c: + * ges/ges-enums.h: + ges-enums: completed support for all options in TextAlign + Added the center and position options to the vertical, and horizontal + properties of text alignment. + +2011-08-05 13:24:17 +0200 Edward Hervey + + * common: + * configure.ac: + * ges/Makefile.am: + * gst-editing-services.spec.in: + * tools/.gitignore: + Opening the 0.11 branch + +2011-08-03 12:37:14 +0200 Luis de Bethencourt + + * tests/examples/overlays.c: + examples: add color option to overlay example + +2011-08-03 12:27:04 +0200 Luis de Bethencourt + + * ges/ges-timeline-text-overlay.h: + * ges/ges-track-text-overlay.h: + ges: include indentation fixes + run gst-indent through ges-timeline-text-overlay.h and + ges-track-text-overlay.h + +2011-08-03 12:20:27 +0200 Luis de Bethencourt + + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-text-overlay.h: + * ges/ges-track-text-overlay.c: + * ges/ges-track-text-overlay.h: + * tests/check/ges/overlays.c: + GESTimelineTextOverlay/TrackTextOverlay: add color setting + Color property of the text overlay can be set and get. + +2011-08-01 13:42:17 +0200 Luis de Bethencourt + + * tools/ges-launch.c: + ges: easier to understand ges-launch summary + +2011-08-01 13:40:48 +0200 Luis de Bethencourt + + * tools/ges-launch.c: + ges: audio and video preset options in ges-launch + +2011-06-21 20:35:47 +0200 Mathieu Duponchelle + + * ges/ges-track-parse-launch-effect.c: + effects: implement the TrackParseLaunch get_property method properly + +2011-06-07 00:49:58 +0200 Mathieu Duponchelle + + * ges/ges-track-object.c: + GES : make sure to set n_properties to 0 when needed + +2011-07-01 19:30:01 +0200 Luis de Bethencourt + + * tests/examples/test4.c: + tests: selectable audio format/container in test4 + now users can select the desired rendering audio format and + container through --aformat and --format, like in ges-launch. + +2011-06-30 18:13:15 +0200 Luis de Bethencourt + + * tests/check/ges/filesource.c: + tests: fix TEST_URI for filesource tests + +2011-06-27 21:22:48 +0200 Luis de Bethencourt + + * tests/examples/test4.c: + examples: fix output_uri in test4 + clean the make_ogg_vorbis_profile () code. + remove extra second of execution time. + clean some comment typos. + +2011-06-27 20:39:42 +0200 Luis de Bethencourt + + * tests/examples/test2.c: + * tests/examples/test3.c: + examples: remove extra second in test2 and test3 + remove unnecessary sources GList. + and fix comment typos as well. + +2011-06-25 20:12:46 +0200 Luis de Bethencourt + + * ges/ges-timeline-file-source.c: + GESTimelineFileSource: Check uri at _new() + Check if uri is valid before creating a new object in + ges_timeline_filesource_new() + +2011-06-25 19:42:29 +0200 Luis de Bethencourt + + * ges/ges-timeline-file-source.c: + GESTimelineFileSource: Fix documentation + +2011-06-23 11:30:24 -0700 David Schleef + + * common: + Automatic update of common submodule + From 69b981f to 605cd9a + +2011-05-26 09:15:29 -0700 Edward Hervey + + * ges/ges-formatter.c: + GESFormatter: Plug a leak + And make the two save methods have the same code/look + +2011-05-20 16:45:25 +0200 Edward Hervey + + * ges/ges-formatter.h: + GESFormatter: Move comments out of the way + Avoids having them appear in gtk-doc + +2011-05-20 16:03:30 +0200 Edward Hervey + + * ges/ges-timeline-object.h: + * ges/ges-track-object.h: + GESTimelineObject/TrackObject: Don't break ABI for the Class + New addition go at the end, and the _reserved pointer gets reduced + accordingly + +2011-05-20 16:02:58 +0200 Edward Hervey + + * ges/ges-timeline-effect.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline-parse-launch-effect.c: + * ges/ges-track-effect.c: + * ges/ges-track-effect.h: + * ges/ges-track-object.c: + * ges/ges-track-parse-launch-effect.c: + ges: More "Since: 0.10.2" doc markers + +2011-05-20 15:51:33 +0200 Edward Hervey + + * configure.ac: + configure.ac: Require core/base 0.10.34 + +2011-05-18 10:46:34 -0400 Thibault Saunier + + * ges/ges.c: + doc: Update the ges_init documentation + +2011-05-09 15:15:27 -0400 Thibault Saunier + + * docs/random/design: + design: Update effect statuts + +2011-05-09 13:33:53 -0400 Thibault Saunier + + * ges/ges-formatter.c: + ges: Fix introspection annotations + +2011-05-05 15:02:28 -0300 Thibault Saunier + + * tools/ges-launch.c: + ges-launch: Add a proper error message on errors + +2011-04-27 10:11:44 -0300 Thibault Saunier + + * ges/ges-timeline-standard-transition.c: + GESTimelineStandardTransition: keep track of TrackVideoTransition + Use the new track_object_added/release vfunc to get cleaner code + +2011-04-27 08:47:02 -0300 Thibault Saunier + + * ges/ges-timeline-title-source.c: + GESTimelineTitleSource: Keep track of contained TrackTitleSource objects + We use the new track_object_added and track_object_released vfunc to keep track + of the TrackObject we might be interested in. Makes cleaner code + +2011-04-26 19:39:56 -0400 Thibault Saunier + + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + GESTimelineObject: add track_object_added and track_object_removed virtual methods + Those methods are meant to be used in sublassed when needed. They are not doing + anything at this time, but will be used to clean some code in GESTimelineObject + sublcasses. + +2011-04-25 17:01:48 -0400 Thibault Saunier + + * ges/ges-track.c: + GESTrack: add a duration property + User can connect to the notify::duration signal if needed + +2011-04-25 17:00:10 -0400 Thibault Saunier + + * ges/ges-track.c: + GESTrack: keep track of the properties GParamSpecs + +2011-05-19 23:01:16 +0300 Stefan Kost + + * common: + Automatic update of common submodule + From 9e5bbd5 to 69b981f + +2011-05-18 16:14:35 +0300 Stefan Kost + + * common: + Automatic update of common submodule + From fd35073 to 9e5bbd5 + +2011-05-18 12:27:56 +0300 Stefan Kost + + * common: + Automatic update of common submodule + From 46dfcea to fd35073 + +2011-05-09 14:26:53 +0200 Edward Hervey + + * ges/Makefile.am: + ges: Initialize GES when building the gir/typelib + +2011-05-09 14:25:50 +0200 Edward Hervey + + * docs/libs/ges-sections.txt: + docs: Move TimelineTestSource symbols to the proper section + +2011-05-09 14:25:32 +0200 Edward Hervey + + * docs/libs/ges.types: + docs: Comment enum types in ges.types + Not supported yet + +2011-05-09 14:24:26 +0200 Edward Hervey + + * ges/ges-timeline-object.c: + * ges/ges-timeline.c: + ges: Use %NULL instead of #NULL + Reported by Stefan Kost: + "% is for constants, # is for objects/structs/types, @ is for parameters." + +2011-05-07 16:59:06 +0200 Edward Hervey + + * ges/ges-custom-timeline-source.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-parse-launch-effect.c: + * ges/ges-track-object.c: + * ges/ges-track-parse-launch-effect.h: + ges: Fix docs for alignment and introspection annotations + gtk-doc comments need to have a space before the '*' of each line else + they won't be picked up by gtk-doc. + +2011-05-07 13:42:24 +0200 Edward Hervey + + * ges/ges-track.c: + GESTrack: Make debug statement more useful + +2011-05-07 13:41:11 +0200 Edward Hervey + + * ges/ges-track-parse-launch-effect.c: + GESTrackParseLaunchEffect: Name the convert elements better + And bump a DEBUG to an ERROR + +2011-05-07 13:40:11 +0200 Edward Hervey + + * ges/ges-track-effect.c: + GESTrackEffect: Cleanup of the property lookup code + +2011-05-07 13:26:01 +0200 Edward Hervey + + * ges/ges-track-effect.c: + GESTrackObject: Fix leak when iterating elements + We *always* need to unref elements and not just when they're effects + +2011-05-07 13:25:06 +0200 Edward Hervey + + * ges/ges-track-effect.c: + GESTrackObject: use gst_object_ref + +2011-05-07 13:22:50 +0200 Edward Hervey + + * ges/ges-track-effect.c: + GESTrackEffect: Use gst_object_unref for the hash value destroyfunc + The values are GstObjects and it makes it easier to track in debug logs + when they are being unreffed. + +2011-05-06 17:21:22 -0300 Thibault Saunier + + * ges/ges-track-effect.c: + * tests/check/ges/effects.c: + GESTrackkEffect: Fix Leaks + +2011-05-06 17:18:58 -0300 Thibault Saunier + + * ges/ges-timeline-parse-launch-effect.c: + GESTimelineParseLaunchEffect: finalize to avoid leaks + +2011-05-06 14:55:31 -0300 Thibault Saunier + + * ges/ges-timeline-effect.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-parse-launch-effect.c: + * ges/ges-track-effect.c: + * ges/ges-track-parse-launch-effect.c: + ges: Add 'Since 0.10.2' to the new effects related API + +2011-05-06 19:41:38 +0200 Edward Hervey + + * ges/ges-track-object.c: + GESTrackObject: Check for valid argument + +2011-05-06 19:41:16 +0200 Edward Hervey + + * ges/ges-track-object.c: + GESTrackObject: Make a local function static + +2011-05-06 19:40:22 +0200 Edward Hervey + + * ges/ges-timeline-object.c: + GESTimelineObject: Check for valid arguments + and minor indentation fix + +2011-05-06 19:39:26 +0200 Edward Hervey + + * ges/ges-track-parse-launch-effect.c: + ges: Debug statement fixups + +2011-05-06 19:38:26 +0200 Edward Hervey + + * ges/ges-timeline-object.c: + * ges/ges-timeline-parse-launch-effect.c: + * ges/ges-track-object.c: + * ges/ges-track-parse-launch-effect.c: + ges: Doc fixes + +2011-05-06 19:36:35 +0200 Edward Hervey + + * ges/ges-timeline-object.h: + * ges/ges-timeline-parse-launch-effect.h: + * ges/ges-track-object.h: + ges: Include indentation fixes + +2011-05-06 19:35:13 +0200 Edward Hervey + + * ges/ges-timeline-effect.h: + * ges/ges-timeline-parse-launch-effect.h: + * ges/ges-timeline-standard-transition.h: + * ges/ges-timeline-transition.h: + * ges/ges.h: + ges: Fix include orders + +2011-05-06 11:58:02 +0200 Edward Hervey + + * tests/check/ges/.gitignore: + tests: Add effects to ignored files + +2011-05-06 11:56:30 +0200 Edward Hervey + + * ges/ges-timeline-parse-launch-effect.c: + * ges/ges-track-object.c: + ges: Don't break debug lines + +2011-05-06 11:54:41 +0200 Edward Hervey + + * docs/libs/ges-sections.txt: + docs: Add missing symbol to ges-sections + +2011-05-06 11:54:19 +0200 Edward Hervey + + * ges/ges-track-parse-launch-effect.h: + TrackLaunchEffect: Fix macros + +2011-03-17 11:38:38 -0400 Thibault Saunier + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + ges-ui: add effects + +2011-03-16 17:06:08 -0400 Thibault Saunier + + * ges/ges-track-parse-launch-effect.c: + GESTrackParseLaunchEffect: better create_element implementation + +2011-03-16 16:23:53 -0400 Thibault Saunier + + * ges/ges-track-object.c: + TrackObject: fixe a crash in connect_signal + +2011-02-25 17:10:00 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * tests/check/ges/effects.c: + GESTrackObject: add a ges_track_object_list_children_properties method + test: Test the new method, and also set/get_child_property_by_spec + +2011-02-25 12:13:03 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * tests/check/ges/effects.c: + GESTrackObject: Implement a get/set_child_property_by_spec and get/set_child_property_valist methods + Reimplement the get/set_property accordingly + +2011-02-25 11:32:44 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + GESTrackObject: add a ges_track_object_lookup_child method + +2011-02-25 10:54:55 +0100 Thibault Saunier + + * ges/ges-track-object.c: + GESTrackObject: fixe the connect_properties_signals + +2011-02-23 20:30:04 +0100 Thibault Saunier + + * ges/ges-track-effect.c: + * ges/ges-track-object.c: + GESTrackObject: Change properties_hashtable format to GParamSpec->GstElement + It used to be 'ClassName-property-name' -> GstElement + +2011-02-16 18:35:02 +0100 Thibault Saunier + + * ges/ges-track-effect.c: + * ges/ges-track-parse-launch-effect.c: + GESTrackEffect: move get_props_hastable implementation from GESTackParseLaunchEffect + +2011-02-16 17:51:21 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + GESTimelineObject: set_top_effect_priority refactoring + +2011-02-16 17:45:05 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * tests/check/ges/effects.c: + GESTimelineObject: Change the get_effects method to get_top_effects. + +2011-02-16 15:51:20 +0100 Thibault Saunier + + * ges/ges-track-object.c: + * ges/ges-track-object.h: + GESTrackObject: fixe a few issues with the get_props_hastable vmethod + +2011-02-16 14:30:22 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + GESTimelineEffect: keep the list of TrackObjects always sorted + Make sort_track_effects function static + +2011-02-16 14:05:14 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * tests/check/ges/effects.c: + GESTimelineObject: use GESTrackEffect as base classe for effects and not GESTrackOperation. + +2011-02-11 09:17:58 +0100 Thibault Saunier + + * ges/ges-timeline-parse-launch-effect.c: + GESTimelineParseLaunchEffect: documentation fixing + +2011-02-11 09:14:33 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-parse-launch-effect.c: + * ges/ges-track-parse-launch-effect.c: + * ges/ges-track-parse-launch-effect.h: + * tests/check/ges/effects.c: + TrackParseLaunchEffect: rename ges_track_parse_launch_effect_new_from_bin_desc method to ges_track_parse_launch_effect_new + +2011-02-10 16:33:16 +0100 Thibault Saunier + + * docs/design/effects.txt: + design: Update effect implementation doc + +2011-02-10 16:15:50 +0100 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-timeline-effect.c: + * ges/ges-timeline-effect.h: + * ges/ges-timeline-parse-launch-effect.c: + * ges/ges-timeline-parse-launch-effect.h: + * ges/ges-types.h: + * ges/ges.h: + * tests/check/ges/effects.c: + Make GESTimelineEffect abstract and move its implementation to GESTimelineParseLaunchEffect + test: Adapte the test suite to suite the new API + +2011-02-10 12:17:50 +0100 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-timeline-effect.c: + * ges/ges-track-effect.c: + * ges/ges-track-effect.h: + * ges/ges-track-parse-launch-effect.c: + * ges/ges-track-parse-launch-effect.h: + * ges/ges-types.h: + * ges/ges.h: + * tests/check/ges/effects.c: + Make GESTrackEffect abstract and move its implementation to GESTrackParseLaunchEffect + test: update the effect test suite + +2011-02-08 16:08:28 +0100 Thibault Saunier + + * docs/libs/ges.types: + docs: Add missing symbols in docs/libs/ges.types so the class hierarchy is well generated + +2011-02-08 11:21:41 +0100 Thibault Saunier + + * docs/design/effects.txt: + design: update effect implementation progress + +2011-02-08 15:29:21 +0100 Thibault Saunier + + * ges/ges-track-object.c: + * tests/check/ges/effects.c: + GESTrackObject: add the deep-notify signal + tests: test the new signal + +2011-02-08 14:04:39 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + * tests/check/ges/effects.c: + GESTimelineObject: Emit signal when adding and removing effects + tests: test that those signals are actually well sent + +2011-02-08 11:10:31 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + docs: fixe the GESTimelineObject documentation + +2011-02-08 11:06:57 +0100 Thibault Saunier + + * docs/design/effects.txt: + * docs/libs/ges-sections.txt: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * tests/check/ges/effects.c: + GesTrackObject: add the ges_track_object_get_child_property method + test: Test this new method + design: change the design file to fit the implementation + +2011-02-08 10:25:41 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * tests/check/ges/effects.c: + GESTrackObject: add a ges_track_object_set_child_property_method + test: Test the new method + +2011-02-08 09:02:56 +0100 Thibault Saunier + + * tests/check/ges/effects.c: + tests: use the AgingTv as testing effects instead of identity + Lets us try the new property handling implementation in TrackObject + +2011-02-08 08:57:11 +0100 Thibault Saunier + + * ges/ges-track-effect.c: + GESTrackEffect: add the get_props_hastable virtual method + Implements this virtual method for bin described effects. + +2011-02-07 17:06:01 +0100 Thibault Saunier + + * ges/ges-track-object.c: + * ges/ges-track-object.h: + GESTrackObject: add an hashtable linking childs properityName -> GstElement + We also add a Virtual method that should be implementented in subclasses to generate the new GHasTable + +2011-02-04 11:44:19 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * tests/check/ges/effects.c: + GESTimelineObject: add a ges_timeline_object_set_top_effect_priority method + Lets the user have a minimum of control over effects priorities + +2011-02-04 11:26:11 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + GESTimelineObject: Do not rely on the fact that the trackobject list is sorted + +2011-02-03 16:03:10 +0100 Thibault Saunier + + * tests/check/ges/effects.c: + test: Better priorities height setting testing + +2011-02-03 15:40:05 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + GESTimelineObject: Update TrackObject priorities handling + make use of the new TrackObject getters + +2011-02-03 15:30:30 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + GESTimelineObject: make debugging symbols more usefull + +2011-02-03 15:11:54 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + GESTrackEffect: add getters for the: start, inpoint, duration, priority, active properties + docs: add new symbols + +2011-02-01 21:22:04 +0100 Thibault Saunier + + * ges/ges-timeline-effect.c: + * tests/check/ges/effects.c: + TimelineEffect: implement the create_track_object vmethod + tests: test the new vmethod + +2011-02-01 21:14:29 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + TimelineObject: take a private update_height method out of priority_update_cb + +2011-02-01 18:47:09 +0100 Thibault Saunier + + * ges/ges-timeline-effect.c: + * ges/ges-timeline-effect.h: + TimelineEffect: create 2 properties for bin_descrption, one for the audio track, another for the video one + This is more for testing purposes since in the long run we should use Materials + +2011-02-01 18:02:23 +0100 Thibault Saunier + + * tests/check/ges/effects.c: + test: check the height of a TimelineObject when adding effects to it + +2011-01-31 13:28:44 +0100 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-timeline-effect.c: + * ges/ges-timeline-effect.h: + * ges/ges-types.h: + * ges/ges.h: + TimelineEffect: Add the basis for GESTimelineEffect implementation + +2011-01-31 13:26:50 +0100 Thibault Saunier + + * docs/libs/ges-docs.sgml: + * ges/ges-track-effect.c: + TrackEffect: Fixe the documentation + +2011-01-31 11:53:38 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + TimelineObject: fixe new API documentation + +2011-01-31 11:41:37 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * tests/check/ges/effects.c: + TimelineObject: add the ges_timeline_object_get_top_effect_position method + tests: adapt the effect testsuite to use this function + docs: add the method to the documentation + +2011-01-31 11:33:56 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.c: + TimelineObject: remove trailling spaces + +2011-01-31 11:32:14 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * tests/check/ges/effects.c: + TimelineObject: add ges_timeline_object_get_effects API + tests: Test the new TimelineObject API + docs: add the corresponding fonction + +2011-01-31 11:22:31 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + TimelineObject: adapt the add_track_object so we can add effects to it. + We keep the list of contained TrackObject-s order by priority + +2011-01-31 11:15:33 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + TimelineObject: add a function to sort list of applied effects + +2011-01-31 11:10:35 +0100 Thibault Saunier + + * ges/ges-timeline-object.c: + TimelineObject: add a property to count the number of effects applied on it + +2011-02-01 21:23:22 +0100 Thibault Saunier + + * ges/ges-track-effect.h: + TrackEffect: add padding to give a margin for API expension without breaking ABI + +2011-01-31 11:43:04 +0100 Thibault Saunier + + * ges/ges-track-effect.c: + TrackEffect: change the create_element function arguments to fit what is expected + +2011-01-31 11:09:47 +0100 Thibault Saunier + + * docs/design/effects.txt: + design: spelling correction in the effect design document + +2011-01-31 11:05:10 +0100 Thibault Saunier + + * docs/design/effects.txt: + design: add a description of the ges_timeline_object_get_top_effect_postion method + +2011-01-25 19:53:36 +0100 Thibault Saunier + + * docs/design/effects.txt: + Specs: Add a description of the GESEffect class + +2011-01-21 11:11:12 +0100 Thibault Saunier + + * ges/ges-track-effect.h: + TrackEffect: Change copyright + +2011-01-21 10:43:09 +0100 Thibault Saunier + + * docs/design/effects.txt: + design: fixe effects API after Edward review + +2011-01-18 20:03:42 +0100 Edward Hervey + + * docs/design/effects.txt: + pending fixups/comments + +2011-01-18 20:05:54 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-track-effect.c: + * ges/ges-track-effect.h: + * ges/ges.h: + * tests/check/ges/effects.c: + effects: Make TrackEffect implementation corresponding to the new effect API description + Make the effects testsuite correspond to the new API + Fixe a few compilation issues due to TrackEffect + +2011-01-18 20:03:51 +0100 Thibault Saunier + + * docs/design/effects.txt: + design: move GESTimlineSource new API to GESTimelineObject + +2011-01-12 11:47:30 +0100 Thibault Saunier + + * docs/design/effects.txt: + design: Effect API draft V2 + +2010-12-09 16:01:02 +0100 Thibault Saunier + + * docs/design/effects.txt: + Specs: review effect API Draft + +2010-12-07 13:47:47 +0100 Thibault Saunier + + * docs/design/effects.txt: + Specs: Add effect API Draft + +2010-12-04 12:22:54 +0100 Thibault Saunier + + * docs/design/effects.txt: + Specs: Review design of effect implementation proposal + +2010-11-15 23:32:23 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-track-effect.c: + * ges/ges-track-effect.h: + * tests/check/ges/effects.c: + GESTrackEffect: add private struct + +2010-11-08 21:53:26 +0100 Thibault Saunier + + * docs/design/effects.txt: + Specs: proposal for effects implementation + +2010-11-05 12:12:24 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-track-effect.c: + * ges/ges-track-effect.h: + * ges/ges-types.h: + * tests/check/Makefile.am: + * tests/check/ges/effects.c: + GESTrackEffect: implementation of this new class + +2011-01-17 16:46:15 +0100 Edward Hervey + + * tools/ges-launch.c: + ges-launch-0.10: Cleanup for error cases and print statements + * Use g_error for fatal errors + * Don't exit the application from the middle of nowhere + * Properly cleanup even in error cases + * Don't print out things which aren't needed + +2011-04-24 14:07:33 +0100 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From c3cafe1 to 46dfcea + +2011-01-27 17:47:25 +0100 Alessandro Decina + + * Android.mk: + * android/ges-launch.mk: + * android/ges.mk: + * ges/Makefile.am: + * tools/Makefile.am: + android: make it ready for androgenizer + Remove the android/ top dir + Fixe the Makefile.am to be androgenized + To build gstreamer for android we are now using androgenizer which generates the needed Android.mk files. + Androgenizer can be found here: http://git.collabora.co.uk/?p=user/derek/androgenizer.git + +2011-04-04 16:00:37 +0300 Stefan Kost + + * common: + Automatic update of common submodule + From 1ccbe09 to c3cafe1 + +2011-03-25 22:39:04 +0100 Sebastian Dröge + + * common: + Automatic update of common submodule + From 193b717 to 1ccbe09 + +2011-03-25 14:58:45 +0200 Stefan Kost + + * common: + Automatic update of common submodule + From b77e2bf to 193b717 + +2011-03-25 10:01:45 +0100 Sebastian Dröge + + * Makefile.am: + build: Include lcov.mak to allow tests coverage report generation + +2011-03-25 09:35:38 +0100 Sebastian Dröge + + * common: + Automatic update of common submodule + From d8814b6 to b77e2bf + +2011-03-25 09:12:04 +0100 Sebastian Dröge + + * common: + Automatic update of common submodule + From 6aaa286 to d8814b6 + +2011-03-24 18:51:48 +0200 Stefan Kost + + * common: + Automatic update of common submodule + From 6aec6b9 to 6aaa286 + +2011-03-16 19:58:54 -0400 Thibault Saunier + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: the _init_ method shouldn't return before the end + Fixes #644939 + +2011-03-18 19:34:57 +0100 Luis de Bethencourt + + * autogen.sh: + autogen: wingo signed comment + +2011-03-15 14:05:07 +0100 Edward Hervey + + * tests/examples/transition.c: + examples: Make sure we don't end up using NULL values + +2011-03-15 14:04:49 +0100 Edward Hervey + + * tests/examples/overlays.c: + * tests/examples/text_properties.c: + examples: Remove unused code + +2011-03-15 14:02:14 +0100 Edward Hervey + + * tools/ges-launch.c: + ges-launch: Handle return value of regcomp() + +2011-03-15 14:01:41 +0100 Edward Hervey + + * ges/ges-keyfile-formatter.c: + KeyFileFormatter: Handle return value of gst_value_deserialize() + +2011-03-01 17:38:52 +0100 Alessandro Decina + + * ges/ges-timeline-object.c: + * ges/ges-track-object.c: + ges: fix compiler warnings + +2011-02-16 15:21:48 +0000 Vincent Penquerc'h + + * ges/ges-formatter.c: + * ges/ges-formatter.h: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + ges: make uri strings const + +2011-02-28 18:35:14 +0100 Mark Nauwelaerts + + * common: + Automatic update of common submodule + From 1de7f6a to 6aec6b9 + +2011-02-14 12:57:00 +0200 Stefan Kost + + * common: + Automatic update of common submodule + From f94d739 to 1de7f6a + +2011-02-09 11:21:02 +0100 Edward Hervey + + * ges/ges-timeline-object.c: + TimelineObject: Store GParamSpec for height and layer + +2011-02-07 12:19:18 +0000 Vincent Penquerc'h + + * ges/ges.c: + * ges/ges.h: + * tools/ges-launch.c: + ges: Check that the gnonlin elements are present at initialization time + This avoids hanging with no obvious cause later when they're not. + https://bugzilla.gnome.org/show_bug.cgi?id=641246 + +2011-01-31 19:01:46 +0000 Tim-Philipp Müller + + * tools/.gitignore: + tools: ignore unversioned ges-launch as well + +2011-01-31 19:01:24 +0000 Tim-Philipp Müller + + * tools/ges-launch.c: + ges-launch: fix printf format issue + +2011-01-31 19:00:49 +0000 Tim-Philipp Müller + + * ges/ges-keyfile-formatter.c: + * ges/ges-simple-timeline-layer.c: + * ges/ges-timeline-layer.c: + ges: fix a couple of printf format warnings + +2011-01-27 17:46:19 +0100 Sebastian Dröge + + * tests/examples/concatenate.c: + examples: Fix uninitialized variable compiler warning with gcc 4.6 + +2011-01-27 17:43:47 +0100 Alessandro Decina + + * ges/ges-keyfile-formatter.c: + ges: fix compiler warnings + +2011-01-26 23:50:00 +0200 Stefan Kost + + * tools/ges-launch.c: + launch: fix typo in help output + +2011-01-25 11:21:06 +0100 Edward Hervey + + * configure.ac: + configure.ac: And back to development we go + +=== release 0.10.1 === + +2011-01-20 22:04:06 +0100 Edward Hervey + + * ChangeLog: + * Makefile.am: + * NEWS: + * RELEASE: + * configure.ac: + * gst-editing-services.doap: + Release 0.10.1 + +2011-01-18 19:06:45 +0100 Edward Hervey + + * docs/random/design: + random: Update goals/features document + So that everybody can know what features we want. + +2011-01-17 14:01:28 +0100 Edward Hervey + + * configure.ac: + configure.ac: 0.10.0.4 pre-release + +2011-01-17 13:59:44 +0100 Edward Hervey + + * tools/ges-launch.c: + ges-launch: Set restriction on video profile if present + +2011-01-12 17:52:10 +0100 Edward Hervey + + * tools/ges-launch.c: + ges-launch: Remove dead code and make functions/variables static + +2011-01-12 17:45:23 +0100 Edward Hervey + + * tools/ges-launch.c: + ges-launch: Fix and cleanup enum listing + It wasn't displaying anything lately. + +2011-01-11 20:28:25 +0100 Edward Hervey + + * configure.ac: + 0.10.0.3 pre-release + +2011-01-11 16:57:45 +0100 Sebastian Dröge + + * tools/.gitignore: + * tools/Makefile.am: + ges-launch: Add GStreamer major/minor version to the executable filename + https://bugzilla.gnome.org/show_bug.cgi?id=639222 + +2011-01-11 18:14:41 +0100 Edward Hervey + + * ges/ges-formatter.c: + * ges/ges-keyfile-formatter.c: + ges: Fix more ges_timeline_get_layers() usage memory leaks + +2011-01-11 17:19:54 +0100 Sebastian Dröge + + * ges/Makefile.am: + * ges/ges-internal.h: + * ges/ges.c: + ges: Don't install ges-internal.h and hide the GES debug category symbols + Fixes bug #639219. + +2011-01-11 17:55:25 +0100 Edward Hervey + + * ges/ges-utils.c: + ges-utils: minor doc update + +2011-01-11 16:32:56 +0100 Sebastian Dröge + + * ges/Makefile.am: + ges: Only export symbols starting with ges_ or GES_ + https://bugzilla.gnome.org/show_bug.cgi?id=639218 + +2011-01-11 16:35:05 +0100 Sebastian Dröge + + * ges/ges-timeline.c: + * ges/ges-track-object.c: + ges: Mark some private symbols static + https://bugzilla.gnome.org/show_bug.cgi?id=639218 + +2011-01-11 15:32:51 +0100 Sebastian Dröge + + * configure.ac: + configure: Require G-I 0.9.6 for the --identifier-prefix parameter + +2011-01-11 15:29:01 +0100 Sebastian Dröge + + * pkgconfig/gst-editing-services-uninstalled.pc.in: + * pkgconfig/gst-editing-services.pc.in: + pkg-config: Require gstreamer-controller and gstreamer-pbutils + Their headers are included by public GES headers + +2011-01-11 15:26:08 +0100 Sebastian Dröge + + * configure.ac: + configure: Add parameter to select GTK+ version to use and default to 2.0 + +2011-01-11 15:52:57 +0200 Stefan Kost + + * common: + Automatic update of common submodule + From e572c87 to f94d739 + +2011-01-10 16:40:02 +0000 Tim-Philipp Müller + + * common: + Automatic update of common submodule + From 8b72fde to e572c87 + +2011-01-10 16:51:34 +0100 Edward Hervey + + * common: + common: Update to current master + +2011-01-10 16:50:51 +0100 Edward Hervey + + * ges/ges-timeline-pipeline.c: + TimelinePipeline: minor doc fix + +2011-01-10 16:50:41 +0100 Edward Hervey + + * docs/libs/Makefile.am: + docs: Fix image inclusion + +2011-01-10 15:49:42 +0100 Edward Hervey + + * configure.ac: + configure.ac: 0.10.0.2 pre-release + +2011-01-10 15:24:13 +0100 Edward Hervey + + * docs/libs/Makefile.am: + docs: Fix for uploading docs + +2011-01-10 14:28:35 +0100 Thibault Saunier + + * ges/ges-formatter.c: + * ges/ges-keyfile-formatter.c: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-standard-transition.c: + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline.c: + * ges/ges-track-audio-test-source.c: + * ges/ges-track-audio-transition.c: + * ges/ges-track-filesource.c: + * ges/ges-track-image-source.c: + * ges/ges-track-object.c: + * ges/ges-track-text-overlay.c: + * ges/ges-track-text-overlay.h: + * ges/ges-track-title-source.c: + * ges/ges-track-video-test-source.c: + * ges/ges-track-video-transition.c: + * ges/ges-track.c: + docs: Document all the undocumented public functions + +2011-01-10 15:10:01 +0100 Edward Hervey + + * ges/ges-track-object.c: + TrackObject: Small cleanup + +2011-01-10 15:09:40 +0100 Edward Hervey + + * ges/ges-track-object.c: + TrackObject: Remove deprecated FIXME + You just need to connect to the notify signal to get updates + +2011-01-10 11:18:27 +0100 Edward Hervey + + * tests/check/ges/basic.c: + tests: Unref the GList returned by ges_timeline_get_layers... + ... in addition to the content themselves + +2011-01-10 11:13:13 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + docs: Fixup the sections for missing/renamed/removed symbols + +2011-01-10 11:12:55 +0100 Edward Hervey + + * ges/ges-track.h: + Track: Mark as private the instance private structure + +2011-01-10 11:12:38 +0100 Edward Hervey + + * ges/ges-timeline.c: + Timeline: Fix documentation of return value + +2011-01-08 16:01:31 +0100 Thibault Saunier + + * ges/ges-formatter.c: + * ges/ges-formatter.h: + GESFormatter: Add private instance and move private variables to it + +2011-01-08 15:25:22 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-file-source.h: + * ges/ges-timeline.c: + * tests/examples/ges-ui.c: + TimelineFileSource: Create instance private and move private variables to it + Fixe/Add getter and setters methods for those variables + Fixup documentation + +2011-01-08 11:22:36 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-formatter.c: + * ges/ges-keyfile-formatter.c: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * tests/check/ges/basic.c: + * tests/check/ges/save_and_load.c: + Timeline: Add instance private and Move private variables to it + Fixe/Add getter methods to get those variables + Fixup documentation + +2011-01-07 19:36:31 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-test-source.h: + * tests/check/ges/backgroundsource.c: + * tests/examples/ges-ui.c: + TimelineTestSource: Move private variables to instance private + Fixe/Add getter and setter methods for those variables + Fixup documentation + +2011-01-07 14:37:56 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-text-overlay.h: + * ges/ges-track-text-overlay.c: + TimelineTextOverlay: Move private variables to instance private + Fixe/Add getter and setter methods for those variables + Fixup documentation + +2011-01-07 13:48:53 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-title-source.h: + * tests/examples/ges-ui.c: + TimelineTitleSource: Move private variables to instance private + Fixe/Add getter and setters methods for those variables + Fixup documentation + +2011-01-06 16:59:52 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-track-audio-test-source.c: + * ges/ges-track-audio-test-source.h: + * tests/check/ges/backgroundsource.c: + TrackAudioTestSource: Move private variables to instance private + Add getter methods to get those variables + Fixup documentation + +2011-01-06 16:35:20 +0100 Thibault Saunier + + * ges/ges-track-audio-transition.c: + * ges/ges-track-audio-transition.h: + TrackAudioTransition: : Move private variables to instance private + +2011-01-06 15:35:42 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-track-text-overlay.c: + * ges/ges-track-text-overlay.h: + * tests/check/ges/overlays.c: + * tests/check/ges/text_properties.c: + TrackTextOverlay: Move private variables to instance private + Add getter methods to get those variables + Add/Fixup documentation + +2011-01-08 01:40:18 +0000 Tim-Philipp Müller + + * tests/check/Makefile.am: + test: make unit tests compile and work in uninstalled setup + +2011-01-08 01:36:13 +0000 Tim-Philipp Müller + + * configure.ac: + * ges/Makefile.am: + gobject-introspection: fix g-i build for uninstalled setup + Requires gst-plugins-base git (> 0.10.31.2) to actually work. + +2011-01-06 12:06:24 +0100 Edward Hervey + + * ges/ges-enums.c: + * ges/ges-timeline-standard-transition.c: + enums: Fix transition enum + Leftovers from when we were using the old name + +2011-01-06 12:04:53 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-track-title-source.c: + * ges/ges-track-title-source.h: + * tests/check/ges/titles.c: + TrackTitleSource: Move private variables to instance private + Add getter methods to get those variables + Add/Fixup documentation + +2011-01-06 11:30:26 +0100 Edward Hervey + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + TimelinePipeline: Fix thumbnail method docs and arguments + The provided gchar* aren't modified + +2011-01-06 11:29:44 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-track-video-test-source.c: + * ges/ges-track-video-test-source.h: + * tests/check/ges/backgroundsource.c: + TrackVideoTestSource: Move private data to instance private + Add a getter for the pattern + Document methods + +2011-01-06 10:55:37 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-standard-transition.c: + * ges/ges-track-video-transition.c: + * ges/ges-track-video-transition.h: + * tests/check/ges/transition.c: + TrackVideoTransition: Move private variable to instance private + Also add/fixup methods to get/set the transition type and document them. + +2011-01-06 10:55:06 +0100 Edward Hervey + + * ges/ges-timeline-pipeline.c: + TimelinePipeline: Updates for pbutils API change + +2011-01-05 11:32:29 +0100 Edward Hervey + + * configure.ac: + configure.ac: Require core git + +2010-12-21 15:24:26 +0100 Edward Hervey + + * ges/ges-track-audio-transition.c: + GESTrackAudioTransition: Fix empty if() body + +2010-12-20 19:09:48 +0100 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Fix usage of encodebin + The property name is now avoid-reencoding + +2010-12-20 12:02:40 +0100 Edward Hervey + + * ges/ges-timeline.c: + GESTimeline: minor cleanup + +2010-12-20 12:01:04 +0100 Edward Hervey + + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * tests/check/ges/basic.c: + * tests/check/ges/layer.c: + GESTimelineObject: Subclass from GInitiallyUnowned + The floating reference will be owned by the Layer + +2010-12-20 12:00:06 +0100 Edward Hervey + + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + * ges/ges-timeline.c: + * tests/check/ges/layer.c: + GESTimelineLayer: Subclass from GInitiallyUnowned + The floating reference will be owned by the Timeline + +2010-12-20 11:58:21 +0100 Edward Hervey + + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track.c: + GESTrackObject: Subclass from GInitiallyUnowned + The floating reference will be owned by the Track + +2010-12-20 11:56:37 +0100 Edward Hervey + + * ges/ges-timeline-object.c: + * tests/check/ges/basic.c: + * tests/check/ges/filesource.c: + TimelineObject: Hold a reference to the controlled TrackObject + +2010-12-20 11:38:31 +0100 Edward Hervey + + * tests/check/ges/backgroundsource.c: + * tests/check/ges/basic.c: + * tests/check/ges/filesource.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/save_and_load.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/text_properties.c: + * tests/check/ges/timelineobject.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + tests: Put clearer names on tests + Makes it easier to figure out which test failed :) + +2010-12-18 11:40:19 +0100 Edward Hervey + + * common: + Update common submodule + +2010-12-17 11:27:37 +0100 Edward Hervey + + * ges/ges-timeline-object.c: + * ges/ges-track-object.c: + GES*Object: only use g_object_notify_by_pspec if available + +2010-12-17 11:27:23 +0100 Edward Hervey + + * ges/ges-track-object.c: + GESTrackObject: Remove unused quarks + +2010-12-17 11:26:49 +0100 Edward Hervey + + * configure.ac: + configure.ac: Require GLib 2.22 + Same requirement as for GStreamer + +2010-12-16 19:36:15 +0100 Edward Hervey + + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + GESSimpleTimelineLayer: reverting const-ification + +2010-12-16 16:47:54 +0000 Brandon Lewis + + * tests/check/ges/simplelayer.c: + GESSimpleTimelineLayer: add test for _index() method + +2010-12-16 16:50:35 +0000 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + GESSimpleTimelineLayer: add _index() method + +2010-12-16 19:29:14 +0100 Edward Hervey + + * ges/ges-timeline-object.c: + * ges/ges-timeline-pipeline.c: + * tests/check/ges/layer.c: + * tests/check/ges/timelineobject.c: + GESTimelineObject: Add mapping/offset support [start/priority properties] + Allows moving independently (or not) timelineobjects and trackobjects and + have them synchronized with the offsets taken into account. + Right now only the start and priority properties are synchronized. The duration + and in-point properties will require more thoughts. + +2010-12-16 19:24:52 +0100 Edward Hervey + + * docs/random/mapping.txt: + random: Add explanation about TimelineObject<=>TrackObject mapping + +2010-12-16 19:24:25 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + GESTrackObject: priority offset is handled by the TimelineObject + +2010-12-16 18:20:47 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.c: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + GESTrackObject: re-factor property setting code + And make sure notifications are emitted at the right time + +2010-12-16 16:27:26 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.c: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * tests/check/ges/timelineobject.c: + GESTrackObject: Add a 'locked' property for position synchronization + And update all code using it + +2010-12-16 15:05:29 +0100 Edward Hervey + + * docs/libs/#ges-sections.txt#: + * docs/libs/.#ges-sections.txt: + * docs/libs/ges-decl-list.txt.bak: + * docs/libs/ges-decl.txt.bak: + docs: Remove more bogus files + +2010-12-16 15:00:46 +0100 Edward Hervey + + * docs/libs/#ges-sections.txt#: + * docs/libs/.#ges-sections.txt: + * docs/libs/ges-decl-list.txt.bak: + * docs/libs/ges-decl.txt.bak: + * docs/libs/ges-sections.txt: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline.c: + * ges/ges-track-audio-test-source.c: + * ges/ges-track-audio-transition.c: + * ges/ges-track-filesource.c: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track-video-test-source.c: + * ges/ges-track-video-transition.c: + * ges/ges-track.c: + * tests/check/ges/backgroundsource.c: + * tests/check/ges/filesource.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/timelineobject.c: + * tests/check/ges/titles.c: + * tests/check/ges/transition.c: + * tests/examples/overlays.c: + * tests/examples/test1.c: + * tests/examples/text_properties.c: + * tests/examples/transition.c: + * tools/ges-launch.c: + GESTrackObject: Hide more variables and provide accessors for them + +2010-12-16 12:46:48 +0100 Edward Hervey + + * ges/ges-timeline.c: + GESTimeline: Remove useless variable + +2010-12-16 12:41:26 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-keyfile-formatter.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + * ges/ges-timeline-pipeline.c: + * tests/check/ges/layer.c: + GESTimelineLayer: Hide the object list and priority + Add needed setters/getters + +2010-12-15 19:40:11 +0100 Edward Hervey + + * docs/libs/ges-docs.sgml: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline.c: + ges: Add more documentation and annotations + This should make GES gobject-introspection compliant now. + +2010-12-15 19:18:42 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.h: + TimelineObject: Put more function name in sync with others + +2010-12-15 19:18:16 +0100 Edward Hervey + + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + * tests/check/ges/simplelayer.c: + GESSimpleTimelineLayer: _nth() returns a const + The refcount isn't incremented. + +2010-12-15 19:05:48 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-keyfile-formatter.c: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track.c: + * ges/ges-track.h: + * tests/check/ges/basic.c: + * tests/check/ges/save_and_load.c: + GESTrack: Make more properties private + And ensure exported symbols are properly documented and have + argument checking. + +2010-12-15 15:50:44 +0000 Brandon Lewis + + * tests/check/ges/simplelayer.c: + GESSimpleTimelineLayer: add test for _nth() method + +2010-12-15 16:40:59 +0000 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + GESSimpleTimelineLayer: ensure the object can be located before "object-added" fires + +2010-12-15 15:51:23 +0000 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + GESSimpleTimelineLayer: add _nth() method + +2010-12-15 15:56:38 +0100 Edward Hervey + + * tests/check/ges/text_properties.c: + test: Disable the text_properties_in_layer test until it's properly implemented + +2010-12-15 15:52:03 +0100 Edward Hervey + + * .gitignore: + * ges/.gitignore: + * pkgconfig/.gitignore: + all: add/extend more .gitignore + +2010-12-15 15:51:41 +0100 Edward Hervey + + * Makefile.am: + * configure.ac: + * pkgconfig/Makefile.am: + * pkgconfig/gst-editing-services-uninstalled.pc.in: + * pkgconfig/gst-editing-services.pc.in: + Add .pc files + +2010-12-15 13:29:53 +0100 Edward Hervey + + * docs/libs/ges-decl-list.txt.bak: + * docs/libs/ges-decl.txt.bak: + docs: Remove .bak files + Added by error when I added the doc system + +2010-12-15 13:27:39 +0100 Edward Hervey + + * docs/libs/ges-decl-list.txt.bak: + * docs/libs/ges-decl.txt.bak: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/ges-custom-timeline-source.c: + * ges/ges-custom-timeline-source.h: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-file-source.h: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-text-overlay.h: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-title-source.h: + * ges/ges-track-title-source.c: + * ges/ges-track-title-source.h: + * ges/ges-track-video-transition.h: + ges: Don't shorten symbol names + It wasn't making us gain anything, and confuses the hell out of g-ir-scanner. + +2010-12-15 12:58:26 +0100 Edward Hervey + + * configure.ac: + * ges/Makefile.am: + ges: Add gobject-introspection support + +2010-12-15 12:36:25 +0100 Edward Hervey + + * configure.ac: + * ges/Makefile.am: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + * tests/examples/Makefile.am: + * tests/examples/concatenate.c: + * tests/examples/test4.c: + * tests/examples/thumbnails.c: + * tools/Makefile.am: + * tools/ges-launch.c: + ges: Switch to encoding-profile API from base + Remove dependency on gst-convenience. + +2010-12-15 11:17:21 +0100 Edward Hervey + + * docs/random/mapping.txt: + random: Add notes about Track mapping + +2010-12-14 17:38:55 +0100 Edward Hervey + + * ges/ges-timeline-object.c: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + GESTrackObject: Make base_priority/priority-offset a private field + +2010-12-14 17:37:13 +0100 Edward Hervey + + * docs/random/mapping.txt: + random: Add brainstorming about Timeline<=>Track object mapping + +2010-12-10 12:15:54 +0100 Edward Hervey + + * ges/ges-track-audio-test-source.c: + * ges/ges-track-audio-transition.c: + * ges/ges-track-image-source.c: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track-operation.c: + * ges/ges-track-operation.h: + * ges/ges-track-source.c: + * ges/ges-track-source.h: + * ges/ges-track-text-overlay.c: + * ges/ges-track-title-source.c: + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + * ges/ges-track-video-test-source.c: + * ges/ges-track-video-transition.c: + GESTrackObject: Add create_element vmethod + API: GESTrackObjectClass::gnlobject_factorytype + API: GESTrackObjectClass::create_element + Most track objects are only specific by the contents of the gnlobject, + therefore move the 'create_element' vmethod which was already present + in some subclasses to the top-level class. + Also make the code more robust + +2010-12-10 12:14:32 +0100 Edward Hervey + + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + GESTrackTransition: Make it a subclass of GESTrackOperation + +2010-12-09 19:36:44 +0100 Edward Hervey + + * ges/ges-timeline-source.c: + * ges/ges-timeline-source.h: + * tests/check/ges/layer.c: + * tests/check/ges/save_and_load.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/text_properties.c: + GESTimelineSource: Remove textoverlay properties + This will be made more generic by allowing any overlay/effect to + be put on any source object. + +2010-12-09 18:53:29 +0100 Edward Hervey + + * docs/random/lifecycle: + random: Add lifecycle document + +2010-12-09 17:43:08 +0100 Edward Hervey + + * tests/check/ges/basic.c: + * tests/check/ges/layer.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/timelineobject.c: + tests: Make sure gst_bin_add succeeds + And detect when we're trying to add contents to a gnlsource which + already has something + +2010-12-09 17:09:11 +0100 Edward Hervey + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges-simple-timeline-layer.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-standard-transition.c: + * ges/ges-timeline-standard-transition.h: + * ges/ges-timeline-transition.c: + * ges/ges-timeline-transition.h: + * ges/ges-track-operation.c: + * ges/ges-track-transition.c: + * ges/ges-track-video-transition.c: + * ges/ges-track-video-transition.h: + * ges/ges-types.h: + * ges/ges.c: + * ges/ges.h: + * tests/check/ges/save_and_load.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/transition.c: + * tests/examples/ges-ui.c: + * tests/examples/transition.c: + * tools/ges-launch.c: + GESTransition: Make it a base class and add GESTimelineStandardTransition + This is to ensure people can create their own Layer Transition subclass. + API : GESTimelineTransition is now GESTimelineStandardTransition + +2010-12-09 15:21:10 +0100 Edward Hervey + + * ges/ges-formatter.c: + * ges/ges-timeline-object.c: + * ges/ges-track-object.c: + ges: Make some classes abstract with G_DEFINE_ABSTRACT_TYPE + +2010-12-09 15:13:27 +0100 Edward Hervey + + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-transition.c: + ges: Avoid leaking a GList of GESTrackObject + +2010-12-09 15:12:34 +0100 Edward Hervey + + * ges/ges-custom-timeline-source.c: + * ges/ges-keyfile-formatter.c: + * ges/ges-simple-timeline-layer.c: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-overlay.c: + * ges/ges-timeline-source.c: + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-transition.c: + * ges/ges-track-audio-test-source.c: + * ges/ges-track-filesource.c: + * ges/ges-track-image-source.c: + * ges/ges-track-operation.c: + * ges/ges-track-source.c: + * ges/ges-track-title-source.c: + * ges/ges-track-transition.c: + * ges/ges-track-video-test-source.c: + ges: Remove unused GObject vmethods + +2010-12-09 14:25:22 +0100 Edward Hervey + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-timeline-operation.c: + * ges/ges-timeline-operation.h: + * ges/ges-timeline-overlay.c: + * ges/ges-timeline-overlay.h: + * ges/ges-timeline-transition.c: + * ges/ges-timeline-transition.h: + * ges/ges-types.h: + * ges/ges.h: + GESTimelineOperation: New abstract class for operations + This is a new class for all timeline objects that both produce and + consume data. + The existing subclasses of it are now: + * GESTimelineOverlay + * GESTimelineTransition + +2010-12-09 12:53:07 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + GESTimelineObject: Clarify usage of create_track_object(s) by subclasses + +2010-12-09 12:52:15 +0100 Edward Hervey + + * ges/ges-timeline-source.c: + GESTimelineSource: Remove empty create_track_object vmethod + The parent class will check if it is present or not, and call + track_objects if needed. + +2010-12-09 11:56:00 +0100 Edward Hervey + + * ges/ges-formatter.h: + GESFormatter: Hide the save/load vmethod from the docs + We need to deprecated them before API/ABI freeze + +2010-12-08 16:09:35 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-custom-timeline-source.c: + * ges/ges-timeline-source.c: + * ges/ges-track-operation.h: + * ges/ges-track-source.c: + * ges/ges-track-source.h: + ges: Remove creators for base classes + +2010-12-08 15:48:55 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-source.c: + * ges/ges-timeline-source.h: + GESTimelineSource: Remove _new() since it's a base class + +2010-12-08 15:36:55 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-custom-timeline-source.h: + * ges/ges-formatter.c: + * ges/ges-keyfile-formatter.h: + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + * ges/ges-timeline-file-source.h: + * ges/ges-timeline-layer.h: + * ges/ges-timeline-object.h: + * ges/ges-timeline-overlay.h: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + * ges/ges-timeline-source.h: + * ges/ges-timeline-test-source.h: + * ges/ges-timeline-text-overlay.h: + * ges/ges-timeline-title-source.h: + * ges/ges-timeline-transition.h: + * ges/ges-timeline.c: + * ges/ges-track-audio-test-source.h: + * ges/ges-track-audio-transition.h: + * ges/ges-track-filesource.h: + * ges/ges-track-image-source.h: + * ges/ges-track-object.h: + * ges/ges-track-operation.h: + * ges/ges-track-source.h: + * ges/ges-track-text-overlay.h: + * ges/ges-track-transition.h: + * ges/ges-track-video-test-source.h: + * ges/ges-track.h: + docs: A round of updates + +2010-12-08 15:36:00 +0100 Edward Hervey + + * docs/libs/ges-docs.sgml: + docs: expose the TrackImageSource docs + +2010-12-08 15:32:05 +0100 Edward Hervey + + * docs/libs/ges.types: + docs: Update ges.types with all types + +2010-11-28 13:24:07 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-simple-timeline-layer.c: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline-source.c: + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-transition.c: + * ges/ges-timeline.c: + * tests/check/ges/basic.c: + * tests/check/ges/save_and_load.c: + * tests/check/ges/simplelayer.c: + * tests/examples/overlays.c: + * tests/examples/text_properties.c: + * tests/examples/transition.c: + GESTimelineObject: add private structure + +2010-11-17 19:53:32 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + * tests/examples/ges-ui.c: + GESSimpleTimelineLayer: add private structure + +2010-12-04 19:54:13 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + * ges/ges-timeline-overlay.c: + * ges/ges-timeline-overlay.h: + * ges/ges-timeline-source.c: + * ges/ges-timeline-source.h: + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-test-source.h: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-text-overlay.h: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-title-source.h: + * ges/ges-timeline-transition.c: + * ges/ges-timeline-transition.h: + * ges/ges-track-audio-test-source.c: + * ges/ges-track-audio-test-source.h: + * ges/ges-track-audio-transition.c: + * ges/ges-track-audio-transition.h: + * ges/ges-track-filesource.c: + * ges/ges-track-filesource.h: + * ges/ges-track-image-source.c: + * ges/ges-track-image-source.h: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track-operation.c: + * ges/ges-track-operation.h: + * ges/ges-track-source.c: + * ges/ges-track-source.h: + * ges/ges-track-text-overlay.c: + * ges/ges-track-text-overlay.h: + * ges/ges-track-title-source.c: + * ges/ges-track-title-source.h: + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + * ges/ges-track-video-test-source.c: + * ges/ges-track-video-test-source.h: + * ges/ges-track-video-transition.c: + * ges/ges-track-video-transition.h: + * ges/ges-track.c: + * ges/ges-track.h: + ges: Add instance private structures + +2010-11-28 16:40:15 +0100 Thibault Saunier + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + GESTimelinePipeline: add a private structure + +2010-11-26 18:43:36 +0100 Thibault Saunier + + * docs/libs/ges-sections.txt: + * ges/ges-custom-timeline-source.c: + * ges/ges-custom-timeline-source.h: + GESCustomTimelineSource: add private structure + +2010-12-02 19:47:23 +0000 Brandon Lewis + + * tests/check/ges/save_and_load.c: + Tests: fix CMP_FAIL on 32-bit machines + +2010-11-10 19:52:16 +0100 Edward Hervey + + * docs/random/design: + docs: TODO idea dumping + Only the beginning + +2010-12-02 12:28:15 +0100 Edward Hervey + + * tests/check/ges/save_and_load.c: + tests: Make sure we specify guint64 with g_object_set arguments + Yup, had missed those because of weird macros :( + +2010-12-01 12:16:37 +0100 Thibault Saunier + + * ges/ges-simple-timeline-layer.c: + * tests/check/ges/simplelayer.c: + SimpleTimelineLayer: Remove bogus check and extend unit test to validate it + +2010-12-02 11:54:03 +0100 Edward Hervey + + * tests/check/ges/save_and_load.c: + tests: Make sure we specify guint64 with g_object_set arguments + Avoids crashers on 32bit machines + +2010-11-29 13:24:13 +0100 Edward Hervey + + * ges/ges-custom-timeline-source.h: + * ges/ges-formatter.h: + * ges/ges-keyfile-formatter.h: + * ges/ges-simple-timeline-layer.h: + * ges/ges-timeline-file-source.h: + * ges/ges-timeline-layer.h: + * ges/ges-timeline-object.h: + * ges/ges-timeline-overlay.h: + * ges/ges-timeline-pipeline.h: + * ges/ges-timeline-source.h: + * ges/ges-timeline-test-source.h: + * ges/ges-timeline-text-overlay.h: + * ges/ges-timeline-title-source.h: + * ges/ges-timeline-transition.h: + * ges/ges-timeline.h: + * ges/ges-track-audio-test-source.h: + * ges/ges-track-audio-transition.h: + * ges/ges-track-filesource.h: + * ges/ges-track-image-source.h: + * ges/ges-track-object.h: + * ges/ges-track-operation.h: + * ges/ges-track-source.h: + * ges/ges-track-text-overlay.h: + * ges/ges-track-title-source.h: + * ges/ges-track-transition.h: + * ges/ges-track-video-test-source.h: + * ges/ges-track-video-transition.h: + * ges/ges-track.h: + * ges/ges-types.h: + ges: Add padding to all public structures + This will give us margin for API expansion without breaking ABI. + The ABI restriction will only come in place once we do the first + official release (i.e. 0.x.0). + +2010-11-27 18:38:06 +0100 Edward Hervey + + * common: + Update common + +2010-10-22 15:57:45 +0100 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + ges-ui: allow enabling/disabling audio/video tracks + +2010-10-11 11:53:35 +0100 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + ges-ui: implement save as command in gtk demo + +2010-10-11 11:38:11 +0100 Brandon Lewis + + * tests/examples/ges-ui.c: + ges-ui: implement load command + +2010-10-11 11:37:51 +0100 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + ges-ui: don't assume we always have 1 layer and two tracks + +2010-10-08 12:32:15 +0100 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + ges-ui: add new command, remove quit command (closing last window quits app) + +2010-11-27 16:56:10 +0100 Edward Hervey + + * tests/check/ges/simplelayer.c: + test: Fix typo in simplelayer test + +2010-11-27 16:55:49 +0100 Edward Hervey + + * tests/check/ges/save_and_load.c: + check: Small cleanup of the save/load test + +2010-11-26 18:39:26 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-formatter.c: + * ges/ges-formatter.h: + * ges/ges-keyfile-formatter.c: + Formatter: Extend and fixup documentation + +2010-11-26 18:38:49 +0100 Edward Hervey + + * ges/ges-timeline.c: + Timeline: Add doc and more comments/fixmes + +2010-11-27 18:11:56 +0100 Edward Hervey + + * ges/ges-formatter.c: + GESTimeline: Prevent saving timelines without any layers + +2010-11-26 13:02:48 +0100 Edward Hervey + + * ges/ges-timeline-layer.c: + TimelineLayer: Add debug statement regarding priorities + +2010-10-20 18:01:37 +0100 Brandon Lewis + + * ges/ges-formatter.c: + * ges/ges-formatter.h: + * ges/ges-keyfile-formatter.h: + GESFormatter: fix a few typos in documention + +2010-10-19 13:35:58 +0100 Brandon Lewis + + * ges/ges-timeline.c: + GESTimeline: add more fixmes + +2010-10-07 16:52:51 +0100 Brandon Lewis + + * tests/check/ges/save_and_load.c: + tests: add save->load test case + +2010-10-07 16:51:38 +0100 Brandon Lewis + + * ges/ges-keyfile-formatter.c: + GESKeyFileFormatter: use ges_formatter_get/set data + +2010-10-07 14:55:14 +0100 Brandon Lewis + + * tests/check/ges/save_and_load.c: + tests: use ges_formatter_{get,set}_data accessors in unit tests + +2010-10-07 14:25:22 +0100 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-formatter.c: + * ges/ges-formatter.h: + GESFormatter: Add data-related methods + +2010-10-07 14:07:18 +0100 Brandon Lewis + + * tests/check/ges/save_and_load.c: + tests: update unit tests + +2010-10-07 13:49:15 +0100 Brandon Lewis + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-formatter.c: + * ges/ges-formatter.h: + * ges/ges-keyfile-formatter.c: + * ges/ges-keyfile-formatter.h: + * ges/ges-types.h: + * ges/ges.h: + GESKeyFileFormatter: New GKeyFile GESFormatter + +2010-09-24 19:31:53 +0100 Brandon Lewis + + * tools/ges-launch.c: + tools: add project file support to ges-launch + +2010-09-29 12:43:47 +0100 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + GESTimeline: implementation of save_to/load_from uri + +2010-09-21 15:39:07 +0100 Brandon Lewis + + * tests/check/Makefile.am: + * tests/check/ges/.gitignore: + * tests/check/ges/save_and_load.c: + tests: Add save/load tests + +2010-09-13 16:21:15 -0700 Brandon Lewis + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/Makefile.am: + * ges/ges-formatter.c: + * ges/ges-formatter.h: + * ges/ges-types.h: + * ges/ges.h: + GESFormatter: Project file format support + +2010-10-22 15:58:22 +0100 Brandon Lewis + + * ges/ges-timeline.c: + GESTimeline: set track state to NULL before removing from timeline + +2010-10-07 12:29:05 +0100 Brandon Lewis + + * ges/ges-timeline.c: + GESTimeline: release timeline objects when a layer is removed + +2010-10-22 14:02:29 +0100 Brandon Lewis + + * ges/ges-track.c: + * ges/ges-track.h: + GESTrack: keep track of trackobjects and remove/release them in dispose + +2010-10-22 14:01:34 +0100 Brandon Lewis + + * tests/check/ges/basic.c: + tests: test for track removal while timeline contains timeline objects + +2010-10-20 16:23:22 +0100 Brandon Lewis + + * tests/check/ges/basic.c: + tests: test that adding tracks after adding layers works + +2010-10-19 17:56:37 +0100 Brandon Lewis + + * tests/check/ges/basic.c: + tests: unit test to check that objects in layers are properly added to the timeline + +2010-09-22 12:32:47 +0100 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + GESSimpleTimelineLayer: override get_objects () virtual method + +2010-09-22 12:29:26 +0100 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + GESTimelineLayer: add get_objects virtual method + +2010-10-20 18:00:24 +0100 Brandon Lewis + + * ges/ges-timeline.c: + GESTimeline: allow adding tracks after layers + +2010-10-19 16:39:43 +0100 Brandon Lewis + + * ges/ges-timeline.c: + GESTimeline: add existing timeline objects when adding layers + +2010-09-23 14:49:04 +0100 Brandon Lewis + + * ges/ges.c: + ges: ensure built-in timeline object classes are registered + +2010-09-23 14:47:48 +0100 Brandon Lewis + + * ges/ges-timeline.c: + GESTimeline: remove layers before removing tracks in dispose () + +2010-11-25 14:03:07 +0100 Edward Hervey + + * tools/ges-launch.c: + ges-launch: Gracefully handle missing properties + +2010-11-25 14:02:26 +0100 Edward Hervey + + * ges/ges-timeline.c: + Timeline: Remove unneeded variable + +2010-11-25 14:01:15 +0100 Edward Hervey + + * ges/ges-timeline-object.c: + TimelineObject: Forgot a break in a switch/case + +2010-11-23 18:24:38 +0100 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Make sure playsink events are sent to all incoming streams + PlaySink will not send seek events to all incoming streams since it assumes that + they all come from the same source (like a file). + When used with multiple gnonlin compositions we need to make sure those seek events + are sent to all of them. + +2010-11-23 17:34:07 +0100 Edward Hervey + + * .gitignore: + ges: Ignore more + +2010-11-23 17:33:32 +0100 Edward Hervey + + * tests/check/ges/.gitignore: + * tests/examples/.gitignore: + tests: Ignore more files + +2010-10-23 17:38:31 +0200 Edward Hervey + + * ges/ges-track-video-transition.c: + GesTrackVideoTransition: Prefer videomixer2 to videomixer + If present + +2010-11-10 16:13:07 +0100 Edward Hervey + + * AUTHORS: + * README: + AUTHORS/README: cleanup + +2010-11-11 17:39:32 +0100 Edward Hervey + + * tools/ges-launch.c: + ges-launch: g_print => g_printerr for relevant messages + +2010-11-09 16:27:06 +0100 Edward Hervey + + * tests/examples/concatenate.c: + examples: Fix for latest GstDiscoverer API changes + +2010-11-04 12:29:20 +0100 Edward Hervey + + * configure.ac: + configure.ac: Require GStreamer core/base 0.10.30.4 + +2010-11-04 12:28:46 +0100 Edward Hervey + + * ges/ges-timeline.c: + GESTimeline: Change for updated GstDiscoverer API + +2010-09-28 16:30:30 +0200 Edward Hervey + + * configure.ac: + * ges/Makefile.am: + Add gstvideo in build dependencies + +2010-09-23 18:39:01 +0200 Edward Hervey + + * common: + common: Update to master + +2010-09-23 18:33:27 +0200 Edward Hervey + + * configure.ac: + * docs/libs/Makefile.am: + * ges/Makefile.am: + * ges/ges-screenshot.c: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * tests/check/Makefile.am: + * tests/examples/Makefile.am: + * tests/examples/concatenate.c: + * tools/Makefile.am: + all: Changes for discoverer being merged upstream + Along with a whole bunch of Makefile fixups + +2010-09-16 09:07:05 +0200 Edward Hervey + + * tools/ges-launch.c: + ges-launch: Return 0 if no errors, else 1 + +2010-09-16 08:42:50 +0200 Edward Hervey + + * common: + Update common + +2010-09-14 16:04:02 +0200 Edward Hervey + + * configure.ac: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-screenshot.c: + * ges/ges-screenshot.h: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + * ges/ges-timeline.c: + * tests/examples/concatenate.c: + * tests/examples/test4.c: + * tests/examples/thumbnails.c: + * tools/ges-launch.c: + Update for factorylist/convertframe being merged to gst core/base + +2010-08-20 12:40:05 +0200 Edward Hervey + + * configure.ac: + * ges/Makefile.am: + * ges/ges-screenshot.c: + * ges/ges-timeline-pipeline.h: + * ges/ges-timeline.c: + * tests/examples/concatenate.c: + * tests/examples/test4.c: + * tests/examples/thumbnails.c: + * tools/ges-launch.c: + Update to moved gst-convenience + +2010-09-02 18:19:51 +0200 Edward Hervey + + * common: + common: Update to latest version + +2010-08-05 18:32:17 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + relax restrictions on adding transitions + We don't need these any more: the valid property tells us whether it is safe + to go to GST_STATE_PLAYING or not. + +2010-08-05 18:11:49 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + move a callback into the proper section of the file + +2010-08-05 17:50:48 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + fix bug in time format regex + +2010-08-05 17:48:07 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + show duration text entry for all object types + +2010-08-05 16:19:35 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + connect to delete event instead of destroy + +2010-08-05 16:14:09 +0200 Brandon Lewis + + * tests/examples/ges-ui.glade: + hide empty menus + +2010-08-05 16:12:45 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + ignore some UI signals when selection changes + +2010-08-05 15:46:34 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + disable playback when layer is in invalid state + +2010-08-05 15:46:02 +0200 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + * tests/check/ges/simplelayer.c: + layer is also invalid when there are transitoins at the beginning/end + +2010-08-05 15:21:57 +0200 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + * tests/check/ges/simplelayer.c: + add vaid property and unit tests + +2010-08-05 15:21:04 +0200 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + add doc comment for object-moved signal + +2010-08-05 12:52:13 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + update list store layer emits object-moved + +2010-08-05 12:51:31 +0200 Brandon Lewis + + * tests/check/ges/simplelayer.c: + add unit tests for 'object-moved' signal + +2010-08-05 12:50:19 +0200 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + * ges/gesmarshal.list: + add object-moved signal to simple timeline layer + +2010-08-04 18:49:19 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + implement move up/down commands + +2010-08-04 18:31:34 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + connect to move_{up,down actions + +2010-08-04 18:25:24 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + also disallow moving past the start or end of timeline + +2010-08-04 18:02:14 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + can move only when exactly one clip is selected (and not in playback/paused) + +2010-08-04 17:48:12 +0200 Brandon Lewis + + * tests/examples/ges-ui.glade: + add move up/down actions + +2010-08-04 17:43:35 +0200 Brandon Lewis + + * tests/examples/ges-ui.glade: + re-arrange tool-bar buttons + +2010-08-04 17:34:51 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + don't allow changes to timeline if we're paused + +2010-08-04 17:27:01 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + implement the stop button + +2010-08-04 17:17:59 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + add stop button + +2010-08-04 16:57:18 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + code clean-up and comments + +2010-08-04 16:36:15 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + eliminate unecessary function prototypes + +2010-08-04 16:26:39 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + move UI callbacks to end of file + +2010-08-04 16:25:00 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + clean up application struct + +2010-08-03 19:53:34 +0200 Brandon Lewis + + * tests/examples/ges-ui.glade: + more xml tweaks + +2010-08-03 19:42:35 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + don't connect to selected objects unless selection is homogenous + +2010-08-03 19:38:13 +0200 Brandon Lewis + + * tests/examples/ges-ui.glade: + UI layout tweaks + +2010-08-03 19:31:23 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + only allow transitions to be added when the last object isn't a transition + +2010-08-03 16:19:01 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + use a private struct with get_selection_foreach + +2010-08-03 15:59:38 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + disable add_transition + +2010-08-03 15:54:25 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + get add_transition action from xml + +2010-08-03 15:51:29 +0200 Brandon Lewis + + * tests/examples/ges-ui.glade: + menu/toolbar for adding transitions + +2010-08-03 15:40:44 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + allow multiple selections + +2010-08-03 15:17:25 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + set pipeline to ready on EOS + +2010-08-03 15:03:24 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + make sure all actions that mutate timeline are disabled during playback + +2010-08-03 14:58:15 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + set frequency property when spin button changed + +2010-08-03 14:53:22 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + update frequency spin button when selection changed + +2010-08-03 14:46:21 +0200 Brandon Lewis + + * tests/examples/ges-ui.glade: + add frequency and volume widgets, with signal handlers + +2010-08-03 14:43:41 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + update source volume when slider is moved + +2010-08-03 14:39:56 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + update volume slider when volume changes + +2010-08-03 14:38:42 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + get freq/volume widgets + +2010-08-02 19:06:06 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + implement background widget + +2010-08-02 17:26:24 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + fill backround type table from enum values + +2010-08-02 17:25:26 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + adjust visibility calculations + +2010-08-02 17:24:24 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + allow adding test sources + +2010-08-02 17:22:39 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + get background widgets from xml + +2010-08-02 17:21:36 +0200 Brandon Lewis + + * tests/examples/ges-ui.glade: + test sources + +2010-07-27 15:25:20 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + add text box to set durations from formatted strings + +2010-07-26 20:40:06 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + set sate of pipeline to NULL prior to exit + +2010-07-23 19:14:21 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + update text property from text widget + +2010-07-23 18:59:40 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + call ges_simple_timeline_layer_add_object intead of base method + +2010-07-23 18:59:11 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + completely hide properties widgets when nothign is selected + +2010-07-23 18:56:48 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + refactor connecto_to_* family of functions + +2010-07-23 18:43:37 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + connect to text property widgets + +2010-07-23 18:42:53 +0200 Brandon Lewis + + * tests/examples/ges-ui.glade: + add widgets to UI for editing text properties + +2010-07-23 18:42:13 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + add signal handlers for text properties + +2010-07-23 18:38:46 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + keep track of the type of selected objects + +2010-07-23 18:36:54 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + add title source when action activates + +2010-07-23 10:58:11 +0200 Brandon Lewis + + * tests/examples/ges-ui.glade: + add text properties to UI + +2010-07-22 18:07:26 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + allow multiple files to be added + +2010-07-22 17:58:00 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + use file-chooser dialog when adding files + +2010-07-22 13:13:20 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + disable playback when there's nothing in the timeline + +2010-07-22 12:54:01 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + disable add_file during playback + +2010-07-22 12:51:07 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + disable delete during playback + +2010-07-22 12:48:34 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + disable properties during playback + +2010-07-22 12:43:13 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + update state from bus; set button icon from playback state + +2010-07-22 12:20:59 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + set the action sensitivity, not the menu item + +2010-07-22 12:17:24 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + toggle playback when button clicked + +2010-07-22 12:08:28 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + fix signal handler signatures + +2010-07-22 12:05:09 +0200 Brandon Lewis + + * tests/examples/ges-ui.glade: + add play action/controls + +2010-07-22 11:00:15 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + add toolbar; connect to action signals instead of menu items directly + +2010-07-22 10:18:41 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + also set state of delete menu item + +2010-07-21 20:21:01 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + update in-point from in-point slider + +2010-07-21 19:02:09 +0200 Brandon Lewis + + * tests/examples/ges-ui.glade: + add in-point slider + +2010-07-21 19:01:33 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + format duration nicely + +2010-07-21 18:15:56 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + set slider range before setting duration + +2010-07-21 18:15:06 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + remove some unneeded function protos + +2010-07-21 18:03:06 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + slider updates duration of selected object now + +2010-07-21 17:00:35 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + connect to duration and max-duration changed of filesources + +2010-07-21 16:29:12 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + do housekeeping each time selection is updated + +2010-07-21 15:43:28 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + clean up the create_ui function a bit + +2010-07-21 15:23:18 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + disable properties editor when nothing is selected + +2010-07-21 15:22:29 +0200 Brandon Lewis + + * tests/examples/ges-ui.glade: + rename a few attributes + +2010-07-21 13:54:24 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + update list model when objects are removed from layer + +2010-07-21 13:53:54 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + delete selected objects from layer when user issues delete command + +2010-07-21 13:53:01 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + store a pointer to the list selection + +2010-07-21 11:40:18 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + display filename and duration of added clip + +2010-07-21 11:36:41 +0200 Brandon Lewis + + * tests/examples/ges-ui.glade: + don't create tree model in XML + +2010-07-20 19:41:58 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + make sure we get everythign we need from the XML file + +2010-07-20 19:13:06 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + connect to layer object-{added,removed} and display message + +2010-07-20 19:05:15 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + add some simple code to add a source to the timeline + +2010-07-20 18:53:15 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + add liscence and some organizational comments + +2010-07-20 18:38:54 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + we don't have to free all that much, in fact + +2010-07-20 18:24:14 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + create a simple timeline layer + +2010-07-20 17:59:59 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + pass app instance to signal handlers + +2010-07-20 17:55:06 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + move app data to private struct + +2010-07-20 17:52:24 +0200 Brandon Lewis + + * tests/examples/Makefile.am: + export dynamic symbols in examples so signal autoconnect works + +2010-07-20 17:05:26 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + add protos so it builds under c90 mode + +2010-07-20 16:44:01 +0200 Brandon Lewis + + * tests/examples/Makefile.am: + add ges-ui.c to build system + +2010-07-20 16:43:03 +0200 Brandon Lewis + + * configure.ac: + add GTK+ to configure.ac as optional dependency for examples UI + +2010-07-20 16:23:40 +0200 Brandon Lewis + + * tests/examples/ges-ui.c: + * tests/examples/ges-ui.glade: + move ui to tests/examples + +2010-07-20 15:56:12 +0200 Brandon Lewis + + * tools/ges-ui.c: + * tools/ges-ui.glade: + add 'add_file' menu item + +2010-07-20 13:57:28 +0200 Brandon Lewis + + * tools/ges-ui.c: + * tools/ges-ui.glade: + flesh out ui design a bit more + +2010-07-19 19:39:26 +0200 Brandon Lewis + + * tools/ges-ui.c: + * tools/ges-ui.glade: + quit when main window closes + +2010-07-19 19:02:41 +0200 Brandon Lewis + + * tools/ges-ui.c: + check in UI implementation + +2010-07-19 18:09:32 +0200 Brandon Lewis + + * tools/ges-ui.glade: + check in ui file + +2010-09-02 17:55:20 +0200 Edward Hervey + + * ges/ges-track-object.c: + GESTrackObject: Fix debug statement + +2010-09-02 17:54:48 +0200 Edward Hervey + + * tests/check/ges/filesource.c: + tests: Fix a leak in test_filesource_images + +2010-09-01 17:04:26 +0200 Edward Hervey + + * ges/ges-timeline-source.c: + GESTimelineFileSource: Don't leak strings + +2010-07-16 16:43:38 +0200 Brandon Lewis + + * tests/check/ges/simplelayer.c: + update unit tests to catch an earlier bug + +2010-07-14 16:50:16 +0200 Brandon Lewis + + * tests/examples/Makefile.am: + * tests/examples/text_properties.c: + check in text properties example + +2010-07-14 16:14:19 +0200 Brandon Lewis + + * ges/ges-timeline-source.c: + deactivate overlay when no text is present + +2010-07-14 16:12:56 +0200 Brandon Lewis + + * tests/check/Makefile.am: + * tests/check/ges/text_properties.c: + add unit tests for text properties + +2010-07-14 15:23:35 +0200 Brandon Lewis + + * ges/ges-timeline-source.c: + * ges/ges-timeline-source.h: + expose text, font-desc, and alignment properties in GESTimelineSource + +2010-07-14 15:19:30 +0200 Brandon Lewis + + * ges/ges-timeline-text-overlay.c: + fix typo in docstring + +2010-07-14 13:18:57 +0200 Brandon Lewis + + * ges/ges-track-text-overlay.c: + add conversion elements to TrackTextOverlay + +2010-07-14 13:14:54 +0200 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + tweak gstl recalculate: cur track object sets priority for next transition + +2010-07-13 18:44:41 +0200 Brandon Lewis + + * tests/check/ges/backgroundsource.c: + * tests/check/ges/layer.c: + * tests/check/ges/overlays.c: + * tests/check/ges/titles.c: + supply type param to _find_track_objects in unit tests + +2010-07-13 18:42:46 +0200 Brandon Lewis + + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + add type argument to ges_timeline_object_find_track_objects() + +2010-07-13 18:14:33 +0200 Brandon Lewis + + * tests/check/ges/simplelayer.c: + unref timeline in unit test + +2010-07-13 18:12:34 +0200 Brandon Lewis + + * ges/ges-timeline-source.c: + don't add the same track object twice + +2010-07-13 17:13:02 +0200 Brandon Lewis + + * ges/ges-timeline-title-source.c: + check for proper instance type in _set_* functions + +2010-07-13 12:11:06 +0200 Brandon Lewis + + * ges/ges-timeline-object.h: + fix documentation comment + +2010-07-09 18:59:41 +0200 Brandon Lewis + + * ges/ges-timeline-source.c: + create a text overlay with default text for every TimelineSource + +2010-07-09 18:29:27 +0200 Brandon Lewis + + * ges/ges-timeline-source.c: + * tests/check/ges/layer.c: + * tests/check/ges/simplelayer.c: + increase default priority offset for sources; update unit tests + +2010-07-09 18:27:19 +0200 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + move this commit down where it belongs + +2010-07-09 18:26:56 +0200 Brandon Lewis + + * ges/ges-timeline-source.c: + override create_track_objects (plural) in TimelineSource + +2010-07-09 15:59:44 +0200 Brandon Lewis + + * tests/check/ges/layer.c: + test height notification + +2010-07-09 13:49:23 +0200 Brandon Lewis + + * ges/ges-timeline-object.c: + update height when track object priority offset changes + +2010-07-09 13:48:19 +0200 Brandon Lewis + + * ges/ges-track-object.c: + * ges/ges-track-object.h: + reword priority documentation comments + +2010-07-09 12:10:06 +0200 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + use object height in gstl recalcuate + +2010-07-09 12:09:29 +0200 Brandon Lewis + + * ges/ges-timeline-object.c: + initialize height to 1 + +2010-07-09 12:09:08 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.h: + add accessor macro + +2010-07-09 11:51:21 +0200 Brandon Lewis + + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + add height property + +2010-07-09 11:50:31 +0200 Brandon Lewis + + * ges/ges-timeline-object.c: + document timelineobject::priority + +2010-07-08 19:01:46 +0200 Brandon Lewis + + * ges/ges-track-object.c: + * tests/check/ges/layer.c: + expose priority-offset as a property + +2010-07-08 18:52:15 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * tests/check/ges/layer.c: + update documentation and unit tests + +2010-07-08 18:51:38 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + distinguish between base_priority, priority_offset, and gnl_priority + +2010-07-07 17:07:33 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + split timeline_object_add_track_object out of create_track_object + +2010-07-07 16:51:39 +0200 Brandon Lewis + + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline.c: + add create_track_objects + +2010-07-07 15:47:51 +0200 Brandon Lewis + + * ges/ges-timeline-object.h: + add create_track_objects declarations + +2010-07-07 15:47:12 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-timeline.c: + call create_track_objects in timeline.c + +2010-08-31 13:49:21 +0200 Edward Hervey + + * ges/ges-timeline-transition.c: + GESTimelineTransition: Remove unneeded variable + +2010-08-31 13:29:37 +0200 Edward Hervey + + * tools/ges-launch.c: + ges-launch: Fix memory leak + +2010-08-12 15:45:15 +0200 Brandon Lewis + + * ges/ges-track-image-source.c: + allow borders on still image videoscale + +2010-08-12 15:44:47 +0200 Brandon Lewis + + * ges/ges-timeline.c: + always support audio on still images + +2010-08-11 18:23:17 +0200 Brandon Lewis + + * ges/ges-track-image-source.c: + remove ffmpegcolorspace after freeze (see 626518) + +2010-08-10 16:17:07 +0200 Brandon Lewis + + * ges/ges-timeline.c: + don't set max duration on still images + +2010-08-10 10:54:04 +0200 Brandon Lewis + + * ges/ges-track-image-source.c: + implement still image sources + +2010-08-09 18:36:00 +0200 Brandon Lewis + + * ges/ges-track-image-source.c: + naive implementation of still images (seems broken) + +2010-08-09 18:35:26 +0200 Brandon Lewis + + * ges/ges-timeline-file-source.c: + * tests/check/ges/filesource.c: + create GESTrackAudioTestSource for audio tracks when is-image is true + +2010-08-09 18:34:35 +0200 Brandon Lewis + + * ges/ges-timeline.c: + set 'is-image' property true when source has an image stream type + +2010-08-09 13:27:25 +0200 Brandon Lewis + + * tests/check/ges/filesource.c: + set supported formats in new unit test + +2010-08-09 13:26:20 +0200 Brandon Lewis + + * ges/ges-timeline-file-source.c: + create image sources when is_image is set to true + +2010-08-09 12:01:34 +0200 Brandon Lewis + + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-file-source.h: + Add "is-image" property to GESTimelineFileSource + +2010-08-09 11:59:04 +0200 Brandon Lewis + + * tests/check/ges/filesource.c: + unit test for image sources + +2010-08-06 12:58:08 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-track-image-source.c: + * ges/ges-track-image-source.h: + * ges/ges-types.h: + * ges/ges.h: + check in GESTrackImageSource + +2010-08-05 12:19:32 +0200 Edward Hervey + + * ges/Makefile.am: + ges: Also dist the generated files + +2010-08-05 11:40:49 +0200 Edward Hervey + + * ges/Makefile.am: + marshal: Fix typo in the Makefile that prevented marshal .c being built + +2010-07-23 18:22:31 +0200 Brandon Lewis + + * ges/ges-track-title-source.c: + link to the right sink pad on textoverlay object + +2010-07-16 18:41:02 +0200 Brandon Lewis + + * ges/ges-timeline-pipeline.c: + fwrite doesn't return the number of bytes written. check that fwrite is non-zero and that ferror() isn't set instead. + +2010-07-16 18:39:07 +0200 Brandon Lewis + + * ges/ges-screenshot.c: + don't plug encoders when raw caps are given + +2010-07-16 18:37:54 +0200 Brandon Lewis + + * tests/examples/Makefile.am: + * tests/examples/thumbnails.c: + add thumbnailing example which tests rest of thumbnailing api + +2010-07-16 17:38:44 +0200 Brandon Lewis + + * ges/ges-timeline-pipeline.c: + ges_caps_set_simple was being called incorrectly + +2010-07-16 18:17:27 +0200 Edward Hervey + + * docs/libs/Makefile.am: + docs: Use the proper location for header files + +2010-07-16 18:00:05 +0200 Edward Hervey + + * ges/Makefile.am: + ges: Don't forget to dist ges-timeline-overlay.h + +2010-07-16 17:29:05 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + TimelinePipeline: Make sure fwrite completes successfully + +2010-07-15 19:50:22 +0200 Brandon Lewis + + * tools/ges-launch.c: + add option to ges-launch to save thumbnails periodicaly + +2010-07-15 19:49:53 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + update documentation + +2010-07-15 19:49:28 +0200 Brandon Lewis + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + add routine to save a thumbnail in the specified encoding + +2010-07-15 19:19:57 +0200 Brandon Lewis + + * ges/ges-screenshot.c: + add todo item + +2010-07-15 19:12:53 +0200 Brandon Lewis + + * ges/ges-screenshot.c: + factor encoder-finding code into separate function + +2010-07-15 18:59:50 +0200 Brandon Lewis + + * ges/ges-screenshot.c: + use gstprofile to plug an encoder and encode the current frame + +2010-07-15 16:58:22 +0200 Brandon Lewis + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + add methods to retreive the current frame as a thumbnail + +2010-07-15 16:56:00 +0200 Brandon Lewis + + * ges/Makefile.am: + * ges/ges-screenshot.c: + * ges/ges-screenshot.h: + duplicate code from gstscreenshot.{c,h} and gstplaysink.{c,h} + +2010-07-15 12:09:26 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Use smartencoder feature from encodebin + +2010-07-14 13:29:49 +0200 Edward Hervey + + * configure.ac: + configure: Require new core/base for fast pad linking + +2010-07-14 13:29:23 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + * ges/ges-track-audio-transition.c: + * ges/ges-track-title-source.c: + * ges/ges-track-video-transition.c: + GES: Switch to new fast pad linking + +2010-07-08 17:10:19 +0200 Brandon Lewis + + * tests/check/ges/transition.c: + fix mem leak in unit test + +2010-07-08 16:35:43 +0200 Brandon Lewis + + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges-timeline-test-source.c: + Don't expose test source enum value table + +2010-07-08 15:54:46 +0200 Brandon Lewis + + * ges/ges-enums.c: + * ges/ges-enums.h: + don't expose transition enum value table + +2010-07-08 15:54:27 +0200 Brandon Lewis + + * ges/ges-timeline-transition.c: + use gtype to get the enum value for the nick + +2010-07-08 13:20:56 +0200 Brandon Lewis + + * ges/ges-timeline-transition.c: + * ges/ges-track-video-transition.c: + * ges/ges-track-video-transition.h: + check whether setting vtype property actually succeeds + +2010-07-07 18:00:21 +0200 Brandon Lewis + + * ges/ges-timeline-transition.c: + * ges/ges-track-video-transition.c: + initialize transitions to type _TRANSITION_TYPE_NONE + +2010-07-08 13:41:12 +0200 Brandon Lewis + + * ges/ges-enums.c: + * ges/ges-enums.h: + add new invalid enum type + +2010-07-08 13:20:56 +0200 Brandon Lewis + + * ges/ges-track-audio-transition.c: + * ges/ges-track-video-transition.c: + refactor duration_changed method as this is now a TrackObjectClass method + +2010-07-08 13:20:08 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + remove notify::duration signal handler + +2010-07-08 12:35:41 +0200 Brandon Lewis + + * ges/ges-track-object.c: + * ges/ges-track-object.h: + add track object virtual methods for property change notifications + +2010-07-07 17:58:59 +0200 Brandon Lewis + + * ges/ges-track-audio-transition.c: + * ges/ges-track-video-transition.c: + remove unneeded assertions + +2010-07-07 17:34:58 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-overlay.c: + * ges/ges-timeline-overlay.h: + * ges/ges-timeline-transition.c: + * ges/ges-track-operation.c: + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + remove unneeded _new methods on certain base classes + +2010-07-06 19:08:56 +0200 Edward Hervey + + * docs/libs/ges-sections.txt: + docs: Add ges_track_video_test_source_set_pattern + +2010-07-06 19:07:50 +0200 Edward Hervey + + * ges/ges-track-transition.c: + TrackTransition: Don't return anything for unhandled tracks + +2010-07-06 19:06:24 +0200 Edward Hervey + + * ges/ges-track-audio-transition.c: + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + * ges/ges-track-video-transition.c: + TrackTransition: Remove second argument from duration_changed vmethod + And get properties directly from parent classes instead + +2010-07-06 19:05:38 +0200 Edward Hervey + + * ges/ges-timeline-text-overlay.c: + * ges/ges-track-audio-test-source.c: + * ges/ges-track-video-test-source.c: + GES: whitespace fixes + +2010-07-06 19:04:42 +0200 Edward Hervey + + * ges/ges-timeline-transition.c: + GES: Simplify loops + +2010-07-06 19:03:52 +0200 Edward Hervey + + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-title-source.c: + GES: Remove un-needed branches + res it initialized to NULL + +2010-07-06 19:03:05 +0200 Edward Hervey + + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-title-source.c: + GES: Fix initialization values + +2010-07-06 19:02:02 +0200 Edward Hervey + + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-transition.c: + GES: Remove useless variables + +2010-07-06 19:00:50 +0200 Edward Hervey + + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-transition.c: + * ges/ges-track-video-test-source.h: + GES: Fix function prototypes + +2010-07-06 18:58:16 +0200 Edward Hervey + + * ges/ges-timeline-test-source.c: + TimelineTestSource: Set freq/volume whether mute or not + +2010-07-06 18:57:22 +0200 Edward Hervey + + * ges/ges-timeline-overlay.c: + TimelineOverlay: Fix doc + +2010-07-06 18:54:33 +0200 Edward Hervey + + * ges/ges-track-video-transition.c: + * tests/check/ges/transition.c: + TrackVideoTransition: Avoid switching from crossfade to other types + This now exposes a bug in the TimelineTransition, since it will have + a transition type different from its track objects. + +2010-07-06 16:27:21 +0200 Edward Hervey + + * ges/ges-simple-timeline-layer.c: + SimpleTimelineLayer: Fix top-level doc + +2010-07-06 16:27:08 +0200 Edward Hervey + + * ges/ges-timeline-layer.c: + TimelineLayer: Document _set_priority + +2010-07-06 16:26:48 +0200 Edward Hervey + + * ges/ges-track-video-transition.c: + * ges/ges-track-video-transition.h: + TrackVideoTransition: Use proper transition type + +2010-07-06 16:26:26 +0200 Edward Hervey + + * ges/ges-utils.c: + utils: Document timeline_new_audio_video + +2010-07-06 16:25:50 +0200 Edward Hervey + + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + docs: hide _get_type/TYPE in private sections + +2010-07-02 16:39:33 +0200 Brandon Lewis + + * ges/ges-track-video-transition.h: + make type field of video transition private + +2010-07-02 16:23:41 +0200 Brandon Lewis + + * docs/libs/ges.types: + * ges/ges-timeline-overlay.h: + * ges/ges-timeline-test-source.h: + * ges/ges-timeline-text-overlay.h: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-title-source.h: + * ges/ges-track-audio-test-source.h: + * ges/ges-track-audio-transition.h: + * ges/ges-track-text-overlay.h: + * ges/ges-track-title-source.h: + * ges/ges-track-transition.h: + * ges/ges-track-video-test-source.h: + * ges/ges-track-video-transition.h: + * ges/ges.h: + another massive documentation update + +2010-07-02 15:42:48 +0200 Brandon Lewis + + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-test-source.h: + * ges/ges-track-audio-test-source.c: + * tests/check/ges/backgroundsource.c: + expose freq and volume props in GESTimelineTestSource + +2010-07-02 14:46:09 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-track-audio-test-source.c: + * ges/ges-track-audio-test-source.h: + add routines to AudioTestSource to set freq and volume + +2010-07-02 13:14:19 +0200 Brandon Lewis + + * ges/ges-timeline-text-overlay.c: + * tests/check/ges/overlays.c: + remove 'mute' property from GESTimelineTextOverlay + +2010-07-02 12:57:38 +0200 Brandon Lewis + + * ges/ges-enums.c: + fix incorrect type name strings + +2010-07-02 12:48:11 +0200 Brandon Lewis + + * docs/libs/ges.types: + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-test-source.h: + * ges/ges-track-title-source.c: + * ges/ges-track-video-test-source.c: + * ges/ges-track-video-test-source.h: + * tests/check/ges/backgroundsource.c: + convert rest of code to use GESVideoTestPattern + +2010-07-02 12:47:31 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/ges-enums.c: + * ges/ges-enums.h: + add GESVideoTestPattern enum + +2010-07-02 12:26:55 +0200 Brandon Lewis + + * tests/check/ges/overlays.c: + * tests/check/ges/titles.c: + update unit tests + +2010-07-02 12:26:42 +0200 Brandon Lewis + + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-title-source.c: + make sure to set properties on new track objects + +2010-07-02 12:25:58 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-track-text-overlay.c: + * ges/ges-track-text-overlay.h: + * ges/ges-track-title-source.c: + * ges/ges-track-title-source.h: + replace existing text position enums + +2010-07-02 12:25:12 +0200 Brandon Lewis + + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-text-overlay.h: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-title-source.h: + replace existing text position enums + +2010-07-02 12:12:30 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-enums.c: + * ges/ges-enums.h: + add text positioning enums + +2010-07-01 18:53:08 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + move missing symbol to enums section + +2010-07-01 18:50:55 +0200 Brandon Lewis + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + update documentation files + +2010-07-01 18:50:30 +0200 Brandon Lewis + + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges-timeline-transition.c: + * ges/ges-timeline-transition.h: + * ges/ges-track-video-transition.c: + * tests/check/ges/simplelayer.c: + * tests/check/ges/transition.c: + * tools/ges-launch.c: + move and rename TRANSITION_VTYPE into enums.h and rename + +2010-07-01 17:24:49 +0200 Brandon Lewis + + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges-track.c: + * ges/ges-track.h: + move track type enum to ges-enums.{h,c} + +2010-07-01 17:03:55 +0200 Brandon Lewis + + * ges/Makefile.am: + * ges/ges-enums.c: + * ges/ges-enums.h: + * ges/ges.h: + check in skeletal ges-enums.{c,h} + +2010-07-01 16:48:45 +0200 Brandon Lewis + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/ges-timeline-overlay.c: + * ges/ges-timeline-overlay.h: + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-transition.c: + * ges/ges-track-audio-test-source.c: + * ges/ges-track-audio-transition.c: + * ges/ges-track-filesource.c: + * ges/ges-track-object.c: + * ges/ges-track-operation.c: + * ges/ges-track-operation.h: + * ges/ges-track-source.h: + * ges/ges-track-text-overlay.c: + * ges/ges-track-title-source.c: + * ges/ges-track-transition.c: + * ges/ges-track-video-test-source.c: + * ges/ges-track-video-transition.c: + massive documentation updates + +2010-07-01 12:35:31 +0200 Brandon Lewis + + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-text-overlay.h: + GESTimelineTextOverlay inherits from GESTimelineOverlay + +2010-07-01 12:34:46 +0200 Brandon Lewis + + * ges/Makefile.am: + * ges/ges-timeline-overlay.c: + * ges/ges-timeline-overlay.h: + * ges/ges-types.h: + * ges/ges.h: + check in emtpy GESTimelineOverlay class + +2010-07-01 11:17:46 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-timeline-text-overlay.c: + * ges/ges-timeline-text-overlay.h: + * ges/ges-types.h: + * ges/ges.h: + * tests/check/ges/overlays.c: + * tests/examples/overlays.c: + GESTimelineOverlay -> GESTimelineTextOverlay + +2010-06-30 20:25:18 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-timeline-test-source.c: + * ges/ges-timeline-test-source.h: + * ges/ges-types.h: + * ges/ges.h: + * tests/check/ges/backgroundsource.c: + * tools/ges-launch.c: + GESTimelineBackgroundSource -> GESTimelineTestSource + +2010-06-30 20:01:18 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-timeline-background-source.c: + * ges/ges-timeline-title-source.c: + * ges/ges-track-audio-test-source.c: + * ges/ges-track-audio-test-source.h: + * ges/ges-types.h: + * ges/ges.h: + GESTrackAudioBackgroundSource -> GESTrackAudioTestSource + +2010-06-30 19:34:29 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-timeline-background-source.c: + * ges/ges-track-audio-test-source.c: + * ges/ges-track-audio-test-source.h: + * ges/ges-track-title-source.c: + * ges/ges-track-video-test-source.c: + * ges/ges-track-video-test-source.h: + * ges/ges-types.h: + * ges/ges.h: + * tests/check/ges/backgroundsource.c: + GESTrackVideoBackgroundSource -> GESTrackVideoTestSource + +2010-06-30 18:13:35 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-timeline-overlay.c: + * ges/ges-track-text-overlay.c: + * ges/ges-track-text-overlay.h: + * ges/ges-types.h: + * ges/ges.h: + * tests/check/ges/overlays.c: + GESTrackVideoOverlay -> GESTrackTextOverlay + +2010-06-30 18:02:49 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-track-overlay.c: + * ges/ges-track-overlay.h: + * ges/ges-types.h: + * ges/ges.h: + remove GESTrackOverlay + +2010-06-30 17:59:17 +0200 Brandon Lewis + + * ges/ges-timeline-overlay.c: + * ges/ges-track-video-overlay.c: + * ges/ges-track-video-overlay.h: + * tests/check/ges/overlays.c: + GESTrackVideoOverlay inherits directly from GESTrackOperation + +2010-06-30 17:50:49 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-track-operation.c: + * ges/ges-track-operation.h: + * ges/ges-types.h: + check in GESTrackOperation + +2010-06-30 17:34:54 +0200 Brandon Lewis + + * docs/libs/ges-docs.sgml: + * ges/ges-track-video-background-source.h: + documentation fixes + +2010-06-30 17:29:32 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + update documentation + +2010-06-30 17:29:21 +0200 Brandon Lewis + + * ges/ges-timeline-overlay.c: + * ges/ges-timeline-title-source.c: + * ges/ges-track-overlay.c: + * ges/ges-track-title-source.c: + * ges/ges-track-title-source.h: + * ges/ges-track-video-overlay.c: + * ges/ges-track-video-overlay.h: + * ges/ges-types.h: + * tests/check/ges/overlays.c: + * tests/check/ges/titles.c: + GESTrackVideoTitleSource -> GESTrackTitleSource + +2010-06-30 17:02:10 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-timeline-overlay.c: + * ges/ges-timeline-title-source.c: + * ges/ges-track-overlay.c: + * ges/ges-track-title-source.c: + * ges/ges-track-title-source.h: + * ges/ges-track-video-overlay.c: + * ges/ges-track-video-overlay.h: + * ges/ges-track-video-title-source.c: + * ges/ges-track-video-title-source.h: + * ges/ges-types.h: + * ges/ges.h: + remove GESTrackTitleSource + +2010-06-30 16:47:29 +0200 Brandon Lewis + + * ges/ges-track-source.h: + fix doc comments + +2010-06-30 16:47:12 +0200 Brandon Lewis + + * ges/ges-timeline-title-source.c: + * ges/ges-track-video-title-source.c: + * ges/ges-track-video-title-source.h: + GESTrackVideoTitleSource inherits directly from GESTrackObject + +2010-06-30 16:34:47 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-timeline-background-source.c: + * ges/ges-track-audio-background-source.h: + * ges/ges-track-background-source.c: + * ges/ges-track-background-source.h: + * ges/ges-types.h: + * ges/ges.h: + remove GESTrackBackgroundSource class + +2010-06-30 16:29:04 +0200 Brandon Lewis + + * ges/ges-track-audio-background-source.c: + * ges/ges-track-audio-background-source.h: + GESTrackAudioBackgroundSource inherits from GESTrackSource + +2010-06-30 16:25:01 +0200 Brandon Lewis + + * ges/ges-track-video-background-source.c: + * ges/ges-track-video-background-source.h: + GESTrackVideoBackgroundSource inherits directly from track object + +2010-06-30 15:40:31 +0200 Brandon Lewis + + * ges/ges-track-source.c: + * ges/ges-track-source.h: + move create_element virtual method up to TimelineSource class + +2010-06-30 15:39:24 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + fix documentation mistake + +2010-06-30 13:22:04 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-track-audio-title-source.c: + * ges/ges-track-audio-title-source.h: + * ges/ges-types.h: + * ges/ges.h: + remove GESTrackAudioTitleSource + +2010-06-28 18:24:12 +0200 Brandon Lewis + + * ges/ges-timeline-title-source.c: + create test track object instead of audio-title-source + +2010-06-28 18:23:37 +0200 Brandon Lewis + + * ges/ges-track-video-transition.c: + keep track of and release request pads for smpte also + +2010-06-28 18:20:15 +0200 Brandon Lewis + + * ges/ges-track-video-transition.c: + free mixer in dispose separately from sink pads + +2010-06-28 17:33:53 +0200 Edward Hervey + + * tests/examples/.gitignore: + examples: Ignore files + +2010-06-28 17:33:34 +0200 Edward Hervey + + * tests/check/ges/.gitignore: + check: Ignore files + +2010-06-28 17:24:25 +0200 Edward Hervey + + * docs/libs/ges-docs.sgml: + docs: Add links to all new documentation files + +2010-06-28 17:23:49 +0200 Edward Hervey + + * tests/examples/overlays.c: + tests: Add for exit usage + +2010-06-25 12:04:47 +0200 Brandon Lewis + + * ges/ges-track-video-overlay.c: + don't forget to unref pad targets + +2010-06-23 18:23:31 +0200 Brandon Lewis + + * ges/ges-track-video-overlay.c: + rough overlay implementation + +2010-06-23 18:22:56 +0200 Brandon Lewis + + * tests/examples/Makefile.am: + * tests/examples/overlays.c: + check in overlay test app + +2010-06-23 16:42:14 +0200 Brandon Lewis + + * ges/ges-timeline-overlay.c: + activate property setting functions + +2010-06-23 16:38:45 +0200 Brandon Lewis + + * tests/check/ges/overlays.c: + activate remaining overlay tests + +2010-06-23 16:38:19 +0200 Brandon Lewis + + * ges/ges.h: + add video overlays to main header + +2010-06-23 16:32:25 +0200 Brandon Lewis + + * ges/ges-timeline-overlay.c: + create timeline-overly creates appropriate track object + +2010-06-23 16:30:18 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-track-video-overlay.c: + * ges/ges-track-video-overlay.h: + * ges/ges-types.h: + check in GESTrackVideoOverlay + +2010-06-21 16:22:06 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-timeline-overlay.c: + * ges/ges-track-overlay.c: + * ges/ges-track-overlay.h: + * ges/ges-types.h: + * ges/ges.h: + check in ges-track-overlay.{c,h} + +2010-06-21 16:04:22 +0200 Brandon Lewis + + * tests/check/Makefile.am: + * tests/check/ges/overlays.c: + check in overlay unit tests + +2010-06-21 15:47:04 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-timeline-overlay.c: + * ges/ges-timeline-overlay.h: + * ges/ges-types.h: + * ges/ges.h: + check in timelineoverlay, structural copy of GESTimelineTitleSource + +2010-06-21 16:04:50 +0200 Brandon Lewis + + * ges/ges-track-transition.h: + update doc comment + +2010-06-18 16:36:54 +0200 Brandon Lewis + + * ges/ges-track-audio-transition.c: + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + * ges/ges-track-video-transition.c: + remove unneeded paramenter to create_element + +2010-06-18 16:26:24 +0200 Brandon Lewis + + * tests/check/ges/transition.c: + update unit tests + +2010-06-18 16:22:38 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + update documentation + +2010-06-18 16:22:21 +0200 Brandon Lewis + + * ges/ges-timeline-transition.c: + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + * ges/ges-track-video-transition.c: + * ges/ges-track-video-transition.h: + push struct fields down to VideoTransition + +2010-06-18 15:54:37 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + * ges/ges-track-video-transition.c: + push make_video_bin() down into subclass + +2010-06-18 15:21:02 +0200 Brandon Lewis + + * ges/ges-track-audio-transition.c: + * ges/ges-track-audio-transition.h: + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + push relevant struct fields into AudioTransition + +2010-06-18 15:20:06 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + update documentation + +2010-06-18 15:04:50 +0200 Brandon Lewis + + * ges/ges-track-audio-transition.c: + * ges/ges-track-transition.c: + push make_audio_bin down into subclass + +2010-06-18 13:42:47 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + add duration_changed virtual method to GESTrackTransition + +2010-06-18 12:55:30 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + add create_element vmethod to GESTrackTransition + +2010-06-18 11:50:08 +0200 Brandon Lewis + + * ges/ges-timeline-transition.c: + create transition subtype according to track type + +2010-06-18 11:24:07 +0200 Brandon Lewis + + * ges/Makefile.am: + * ges/ges-track-video-transition.c: + * ges/ges-track-video-transition.h: + * ges/ges-types.h: + check in GESTrackVideoTransition, empty subclass of TrackTransition + +2010-06-18 11:09:28 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-track-audio-transition.c: + * ges/ges-track-audio-transition.h: + * ges/ges-types.h: + check in GESTrackAudioTransition, empyt subclass of TrackTransition + +2010-06-17 18:31:07 +0200 Brandon Lewis + + * ges/ges-track-video-title-source.c: + * ges/ges-track-video-title-source.h: + fix header file param names + +2010-06-17 12:25:27 +0200 Brandon Lewis + + * ges/ges-track-video-title-source.c: + fix stupid copy/paste typo + +2010-06-17 11:22:30 +0200 Brandon Lewis + + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-transition.c: + * ges/ges-track-video-title-source.c: + free existing strings before assigning new ones + +2010-06-17 11:21:43 +0200 Brandon Lewis + + * tests/check/ges/titles.c: + clean up some memory leaks in the titles unit test + +2010-06-16 19:04:53 +0200 Brandon Lewis + + * tests/check/ges/titles.c: + unit tests for {h,v}alignment properties + +2010-06-16 19:03:51 +0200 Brandon Lewis + + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-title-source.h: + implement {h,v}alignment property for timeline titles + +2010-06-16 19:02:40 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + documentation + +2010-06-16 19:01:48 +0200 Brandon Lewis + + * ges/ges-track-video-title-source.c: + * ges/ges-track-video-title-source.h: + add ..._set_{h,v}alignment() methods to video titles + +2010-06-16 16:58:42 +0200 Brandon Lewis + + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-title-source.h: + add font-desc property to TimelineTitleSource + +2010-06-16 16:58:13 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-track-video-title-source.c: + * ges/ges-track-video-title-source.h: + add ...set_font_desc() method to VideoTitleSource + +2010-06-16 13:27:35 +0200 Brandon Lewis + + * ges/ges-track-video-title-source.c: + set black background on titles by default + +2010-06-16 13:22:15 +0200 Brandon Lewis + + * tools/ges-launch.c: + add title sources to ges-launch + +2010-06-16 13:21:19 +0200 Brandon Lewis + + * ges/ges-timeline-title-source.c: + have timeline title source create audio title sources + +2010-06-16 13:20:54 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-track-audio-title-source.c: + * ges/ges-track-audio-title-source.h: + * ges/ges-types.h: + * ges/ges.h: + check in ges-track-audio-title-source.{c,h} + +2010-06-15 19:22:04 +0200 Brandon Lewis + + * ges/ges-track-video-title-source.c: + don't forget to check for null + +2010-06-15 19:21:37 +0200 Brandon Lewis + + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-title-source.h: + set text on video track objects when text property changes + +2010-06-15 19:20:17 +0200 Brandon Lewis + + * ges/ges-timeline-title-source.c: + implement ges_timeline_title_source_create_track_object + +2010-06-15 17:10:17 +0200 Brandon Lewis + + * ges/ges-track-video-background-source.h: + remove trailing '$' accidentally pasted + +2010-06-15 17:09:50 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + update documentation + +2010-06-15 17:09:31 +0200 Brandon Lewis + + * ges/ges-track-video-title-source.c: + * ges/ges-track-video-title-source.h: + these should have been checked in before + +2010-06-15 13:16:28 +0200 Brandon Lewis + + * ges/ges-timeline-title-source.c: + add text property to GESTimelineTitleSource + +2010-06-15 13:14:14 +0200 Brandon Lewis + + * tests/check/Makefile.am: + * tests/check/ges/titles.c: + check in unit tests for titles + +2010-06-14 19:19:23 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-background-source.c: + * ges/ges-timeline-background-source.h: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-title-source.h: + * ges/ges-timeline-transition.c: + * ges/ges-track-audio-background-source.c: + * ges/ges-track-audio-background-source.h: + * ges/ges-track-background-source.c: + * ges/ges-track-background-source.h: + * ges/ges-track-title-source.c: + * ges/ges-track-title-source.h: + * ges/ges-track-video-background-source.c: + * ges/ges-track-video-background-source.h: + massive update to doc comments + +2010-06-14 19:18:46 +0200 Brandon Lewis + + * ges/ges-timeline-background-source.c: + * ges/ges-track-video-background-source.h: + remove the zone plate and gamut enum values + +2010-06-14 17:52:29 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + massive update to documentation + +2010-06-14 17:52:09 +0200 Brandon Lewis + + * ges/Makefile.am: + * ges/ges-track-video-background-source.h: + * ges/ges-types.h: + * ges/ges.h: + check in skeletal GESTrackVideoTitleSource + +2010-06-14 15:34:08 +0200 Brandon Lewis + + * ges/Makefile.am: + * ges/ges-track-title-source.c: + * ges/ges-track-title-source.h: + * ges/ges-types.h: + check in skeletal GESTrackTitleSource + +2010-06-14 13:31:15 +0200 Brandon Lewis + + * ges/Makefile.am: + * ges/ges-timeline-title-source.c: + * ges/ges-timeline-title-source.h: + * ges/ges-types.h: + * ges/ges.h: + check in sekeletal GESTimelineTitleSource + +2010-06-11 17:57:20 +0200 Brandon Lewis + + * tests/check/ges/backgroundsource.c: + don't forget to unref objects in unit tests + +2010-06-11 17:21:45 +0200 Brandon Lewis + + * ges/ges.h: + * tests/check/ges/backgroundsource.c: + test vpatern property in unit tests + +2010-06-11 17:02:55 +0200 Brandon Lewis + + * ges/ges-track-audio-background-source.c: + make audio background-sources output silence + +2010-06-11 16:55:31 +0200 Brandon Lewis + + * tools/ges-launch.c: + use ges_timeline_background_source_new_for_nick when creating pattern sources + +2010-06-11 16:53:03 +0200 Brandon Lewis + + * ges/ges-timeline-background-source.c: + don't initialize vpattern field + +2010-06-11 16:51:44 +0200 Brandon Lewis + + * ges/ges-timeline-background-source.c: + * ges/ges-timeline-background-source.h: + implement ges_timeline_background_source_new_for_nick() + +2010-06-11 16:50:07 +0200 Brandon Lewis + + * ges/ges-timeline-background-source.c: + move enum table into file scope + +2010-06-11 15:28:43 +0200 Brandon Lewis + + * tools/ges-launch.c: + rewrite print_pattern_list to use GEnumValues + +2010-06-11 15:28:17 +0200 Brandon Lewis + + * tools/ges-launch.c: + remove unnecessary g_print + +2010-06-11 15:19:28 +0200 Brandon Lewis + + * tools/ges-launch.c: + switch to using GESTimelineBackgroundSource objects for patterns + +2010-06-11 15:18:17 +0200 Brandon Lewis + + * ges/ges-timeline-background-source.c: + set pattern on newly-created video track objects + +2010-06-11 15:17:42 +0200 Brandon Lewis + + * ges/ges-timeline-background-source.c: + implement vpattern gobject property of tl background source + +2010-06-11 15:16:40 +0200 Brandon Lewis + + * ges/ges-timeline-background-source.c: + add big blob of pattern enum values copied from videotestsrc + +2010-06-11 15:15:59 +0200 Brandon Lewis + + * ges/ges-timeline-background-source.h: + add vpattern field to GESTimelineBackground source + +2010-06-11 15:14:40 +0200 Brandon Lewis + + * ges/ges-track-video-background-source.c: + * ges/ges-track-video-background-source.h: + implement setting pattern on video background sources + +2010-06-11 13:44:40 +0200 Brandon Lewis + + * ges/ges-track-video-background-source.c: + * ges/ges-track-video-background-source.h: + add routines to set track object pattern + +2010-06-11 13:41:44 +0200 Brandon Lewis + + * ges/ges-track-video-background-source.h: + add enum for video patterns + +2010-06-11 13:40:54 +0200 Brandon Lewis + + * tests/check/ges/backgroundsource.c: + test Backgroudn sources in layers + +2010-06-11 10:42:00 +0200 Brandon Lewis + + * ges/ges-timeline-background-source.c: + implment GESTimelineBackground source create_track_object + +2010-06-11 10:40:02 +0200 Brandon Lewis + + * ges/Makefile.am: + add audio background source to build system + +2010-06-11 10:39:14 +0200 Brandon Lewis + + * ges/ges-types.h: + add audio background source to types.h + +2010-06-11 10:37:49 +0200 Brandon Lewis + + * ges/ges-track-audio-background-source.c: + * ges/ges-track-audio-background-source.h: + check in audio background source + +2010-06-10 17:44:17 +0200 Brandon Lewis + + * ges/Makefile.am: + * ges/ges-types.h: + add ges video-track background to build system + +2010-06-10 13:21:47 +0200 Brandon Lewis + + * ges/ges-track-video-background-source.c: + * ges/ges-track-video-background-source.h: + check in ges-track-video-background-source.{c,h} + +2010-06-10 13:29:22 +0200 Brandon Lewis + + * ges/Makefile.am: + add GESTrackBackgroundSource to build system + +2010-06-10 17:42:09 +0200 Brandon Lewis + + * ges/ges.h: + add track background source to header + +2010-06-10 17:43:56 +0200 Brandon Lewis + + * ges/ges-types.h: + add track background source to ges-types.h + +2010-06-10 13:51:33 +0200 Brandon Lewis + + * ges/ges-track-background-source.c: + * ges/ges-track-background-source.h: + check in GESTrackBackgroundSource + +2010-06-10 13:23:59 +0200 Brandon Lewis + + * ges/Makefile.am: + * ges/ges-types.h: + add GESTimelineBackgroundSource to build system + +2010-06-10 17:41:57 +0200 Brandon Lewis + + * ges/ges.h: + add timeline background source to header + +2010-06-10 13:22:36 +0200 Brandon Lewis + + * tests/check/Makefile.am: + * tests/check/ges/backgroundsource.c: + check in background source unit test + +2010-06-10 13:21:06 +0200 Brandon Lewis + + * ges/ges-timeline-background-source.c: + * ges/ges-timeline-background-source.h: + check in ges-timeline-background-source.{c,h} + +2010-06-28 17:23:49 +0200 Edward Hervey + + * tests/examples/simple1.c: + * tests/examples/transition.c: + tests: Add for exit usage + +2010-07-07 01:21:38 -0300 Thiago Santos + + * docs/libs/Makefile.am: + * tests/examples/simple1.c: + * tests/examples/transition.c: + * tools/ges-launch.c: + Fix building issues + Adds missing headers to some files and needed cflags to gtk-doc + scanner build + +2010-06-28 17:08:08 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Fix leaked caps + +2010-06-21 11:54:01 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: unref all pads + +2010-06-21 11:53:30 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Make a copy of the provided GstEncodingProfile + +2010-06-21 11:52:49 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Properly release playsink and encodebin + +2010-06-21 11:52:01 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: finalize => dispose + We want to release our objects before the parent GstBin class does so. + +2010-06-21 11:47:44 +0200 Edward Hervey + + * tools/ges-launch.c: + ges-launch: Don't leak caps + +2010-06-21 11:47:21 +0200 Edward Hervey + + * tools/ges-launch.c: + ges-launch: Properly free profile and outputuri + +2010-06-17 11:45:27 +0200 Edward Hervey + + * tools/ges-launch.c: + ges-launch: Cleanup profile + +2010-06-14 19:40:50 +0200 Edward Hervey + + * ges/ges-track-transition.c: + GESTrackTransition: Release all pads. + Whether calling get_request_pad or get_static_pad we always end up + with an extra reference. + Also keep a reference on videomixer so it doesn't go away before we + call _release_request_pad() on it with the proper pads to release. + +2010-06-14 19:12:42 +0200 Edward Hervey + + * common: + Update to latest common + +2010-06-11 19:34:39 +0200 Edward Hervey + + * tests/examples/transition.c: + examples: Fix debug statement + +2010-06-10 16:19:11 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + release the request pads in dispose + +2010-06-10 16:14:20 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + don't keep an extra reference to vsmpte + +2010-06-10 12:52:41 +0200 Brandon Lewis + + * tests/check/ges/simplelayer.c: + add elemt. to bin in arbitrary_fill_track_func + +2010-06-09 18:57:59 +0200 Brandon Lewis + + * tests/check/ges/simplelayer.c: + fix typos in comment block + +2010-06-09 18:56:55 +0200 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + gstl_recalculate() won't set priorities to -1 + +2010-06-09 16:35:17 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-track-transition.h: + ges/ges-track-transition.h: add missing function prototype + +2010-06-09 17:11:56 +0200 Brandon Lewis + + * tools/ges-launch.c: + tools/ges-launch.c: C90 fixes + +2010-06-09 17:09:10 +0200 Brandon Lewis + + * tests/examples/transition.c: + tests/examples/transition.c: C90 fixes + +2010-06-09 17:08:31 +0200 Brandon Lewis + + * tests/examples/test4.c: + tests/examples/test4.c: C90 fixes + +2010-06-09 16:27:43 +0200 Brandon Lewis + + * tests/examples/concatenate.c: + tests/check/ges/concatenate.c: C90 fixes + +2010-06-09 16:27:43 +0200 Brandon Lewis + + * tests/check/ges/transition.c: + tests/check/ges/transition.c: C90 fixes + +2010-06-09 16:27:43 +0200 Brandon Lewis + + * tests/check/ges/simplelayer.c: + tests/check/ges/simplelayer.c: C90 fixes + +2010-06-09 16:27:43 +0200 Brandon Lewis + + * tests/check/ges/filesource.c: + tests/check/ges/filesource.c: C90 fixes + +2010-06-09 16:27:43 +0200 Brandon Lewis + + * ges/ges-utils.h: + ges/ges-utils.h: C90 fixes + +2010-06-09 16:27:43 +0200 Brandon Lewis + + * ges/ges-track.h: + ges/ges-track.h: C90 fixes + +2010-06-09 16:27:43 +0200 Brandon Lewis + + * ges/ges-track.c: + ges/ges-track.c: C90 fixes + +2010-06-09 16:27:43 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + ges/ges-track-transition.c: C90 fixes + +2010-06-09 16:27:43 +0200 Brandon Lewis + + * ges/ges-track-object.c: + ges/ges-track-object.c: C90 fixes + +2010-06-09 16:27:43 +0200 Brandon Lewis + + * ges/ges-timeline-transition.c: + ges/ges-timeline-transition.c: C90 fixes + +2010-06-09 16:27:43 +0200 Brandon Lewis + + * ges/ges-timeline-object.c: + ges/ges-timeline-object.c: C90 fixes + +2010-06-09 16:27:43 +0200 Brandon Lewis + + * ges/ges-timeline-layer.c: + ges/ges-timeline-layer.c: C90 fixes + +2010-06-09 16:27:43 +0200 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + ges/ges-simple-timeline-layer.c: C90 fixes + +2010-06-09 13:53:32 +0200 Edward Hervey + + * configure.ac: + configure.ac: Actually use the WARNING/ERROR CFLAGS + We weren't detecting all these issues previously + +2010-06-09 13:53:07 +0200 Edward Hervey + + * ges/ges-track-transition.c: + GESTrackTransition: Fix debug statement + +2010-06-09 13:52:35 +0200 Edward Hervey + + * ges/ges-timeline.c: + GESTimeline: Remove all tracks/layers when being disposed + +2010-06-09 13:52:08 +0200 Edward Hervey + + * ges/ges-timeline-layer.c: + GESTimelineLayer: Release all layer/tracks when being disposed + +2010-06-09 11:22:05 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + ges/ges-track-transition.c: set referece to vsmpte to NULL after freeing + +2010-06-09 11:21:26 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + ges/ges-track-transition.c: was freeing same GstController twice in _dispose() + +2010-06-09 11:17:08 +0200 Edward Hervey + + * common: + common: Update to latest submodule revision + +2010-06-08 18:38:44 +0200 Edward Hervey + + * tests/check/ges/simplelayer.c: + check: Check that all objects are removed from the layer + This currently fails + +2010-06-08 18:37:49 +0200 Edward Hervey + + * tests/check/ges/transition.c: + check: Use release_track_object instead of unref + +2010-06-08 18:37:01 +0200 Edward Hervey + + * ges/ges-track-transition.c: + GESTrackTransition: Unref the ControlSource in dispose + +2010-06-08 18:36:37 +0200 Edward Hervey + + * ges/ges.c: + ges: Initialize GstController in ges_init + +2010-06-04 19:53:35 +0200 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + ges/ges-simple-timeline-layer.c: print a warning when transitions overlap + +2010-06-04 18:31:25 +0200 Brandon Lewis + + * ges/ges-timeline-transition.c: + ges/ges-timeline-transition.c: can't set enums by nick + +2010-06-04 18:07:39 +0200 Brandon Lewis + + * ges/ges-timeline-transition.c: + ges/ges-timeline-transition.c: initialize vtype enum type from static list of GEnumValues + +2010-06-04 17:53:15 +0200 Edward Hervey + + * tests/examples/.gitignore: + * tools/.gitignore: + tools/examples: Ignore more files + +2010-06-04 17:50:42 +0200 Edward Hervey + + * Makefile.am: + * configure.ac: + * tests/examples/Makefile.am: + * tools/Makefile.am: + * tools/ges-launch.c: + tools: Moving playlist from examples and making it installable + It is now called ges-launch + +2010-06-04 12:17:56 +0200 Brandon Lewis + + * tests/examples/playlist.c: + tests/examples/playlist.c: allow file / pattern durations to be 0 (but not transitions) + +2010-06-04 12:17:28 +0200 Brandon Lewis + + * tests/examples/playlist.c: + tests/examples/playlist.c: clean up playlist help text + +2010-06-03 19:14:41 +0200 Brandon Lewis + + * tests/examples/playlist.c: + tests/examples/playlist.c: better sanity checking of arguments + +2010-06-03 19:13:42 +0200 Brandon Lewis + + * tests/examples/playlist.c: + tests/examples/playlist.c: add option to print avail. transitions/patterns. update help strings + +2010-06-03 19:04:11 +0200 Brandon Lewis + + * tests/check/ges/simplelayer.c: + * tests/check/ges/transition.c: + tests/check/ges/{simplelayer.c,transition.c}: update unit tests + +2010-06-03 19:02:58 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + ges/ges-track-transition.c: adjust to the change in VTYPE_CROSSFADE + +2010-06-03 19:01:21 +0200 Brandon Lewis + + * ges/ges-timeline-transition.c: + * ges/ges-timeline-transition.h: + ges/ges-timeline-transition.{c,h}: value for VTYPE_CROSSFADE changed to 512 and exported in ges-timeline-transition.h + +2010-06-02 18:58:14 +0200 Brandon Lewis + + * tests/check/ges/transition.c: + tests/check/ges/transition.c: test that changing timeline vtype sets trackobj vtype + +2010-06-02 18:57:10 +0200 Brandon Lewis + + * ges/ges-timeline-transition.c: + ges/ges-timeline-transition.c: implement vtype gobject property on GESTimelineTransitions + +2010-06-02 18:55:52 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + ges/ges-track-transition.{c,h}: add ability to change smptealpha type + +2010-06-02 16:52:02 +0200 Brandon Lewis + + * tests/check/ges/simplelayer.c: + * tests/check/ges/transition.c: + tests/check/ges/{simplelayer.c,tests/check/ges/transition.c}: sync with previous api change + +2010-06-02 16:50:07 +0200 Brandon Lewis + + * ges/ges-timeline-transition.c: + * ges/ges-timeline-transition.h: + ges/ges-timeline-transition.{c,h}: api change, pass gint instead of GEnumValue to new() + +2010-06-02 16:43:10 +0200 Brandon Lewis + + * ges/ges-timeline-transition.c: + * ges/ges-timeline-transition.h: + * tests/check/ges/transition.c: + ges/ges-timeline-transition.{c,h},tests/.../transition.c: type change of vtype to gint from GEnumValue + +2010-06-02 16:35:57 +0200 Brandon Lewis + + * ges/ges-timeline-transition.c: + ges/ges-timeline-transition.c: sync with API change in previous commit + +2010-06-02 16:27:58 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + ges/ges-track-transition.{c,h}: api change: pass gint instead of GEnumValue + +2010-06-02 15:18:55 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + * tests/check/ges/transition.c: + ges/ges-track-transition.{c,h}, tests/check/ges/transition.c: change vtype from GEnumValue to simple gint; + +2010-06-02 13:50:06 +0200 Brandon Lewis + + * tests/check/ges/transition.c: + tests/check/ges/transition.c: make sure unit tests work properly + +2010-06-02 13:20:09 +0200 Brandon Lewis + + * tests/check/ges/transition.c: + tests/check/ges/transition.c: oops, unit tests using wrong api + +2010-06-02 12:46:05 +0200 Brandon Lewis + + * tests/check/Makefile.am: + tests/check/Makefile.am: add transition unit tests to make check + +2010-06-02 12:34:57 +0200 Brandon Lewis + + * tests/check/ges/transition.c: + tests/check/ges/transition.c: check in transition unit tests + +2010-06-01 13:22:05 +0200 Brandon Lewis + + * ges/ges-track-filesource.h: + ges/ges-track-filesource.h: fix typo in documentation commments + +2010-06-01 11:57:42 +0200 Brandon Lewis + + * ges/ges-track-filesource.h: + ges/ges-track-filesource.h: fix incorrect definition of GESTrackFileSource structs. + +2010-05-31 18:59:12 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-custom-timeline-source.h: + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-file-source.h: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline-pipeline.h: + * ges/ges-timeline-source.h: + * ges/ges-timeline-transition.h: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-track-filesource.h: + * ges/ges-track-object.h: + * ges/ges-track-source.h: + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + * ges/ges-track.h: + Add missing documentation + +2010-05-31 15:42:23 +0200 Brandon Lewis + + * tests/check/ges/simplelayer.c: + * tests/examples/transition.c: + tests/check/ges/{simplelayer.c,transition.c}: create audio tracks in demos + +2010-05-31 15:40:52 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + ges/ges-track-transition.c: implement audio crossfades + +2010-05-31 15:38:14 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + ges/ges-track-transition.{c,h}: add fields for audio interpolation to obj + +2010-05-28 11:42:29 +0200 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + * tests/check/ges/simplelayer.c: + fix bugs + +2010-05-28 03:02:49 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + ges/ges-track-transition.{c,h}: rename some members of ges-track-transition struct to separate between audio and video objects. + +2010-05-28 02:31:42 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + ges/ges-track-transition.c: factor out code which produces video bin into a seprate routine + +2010-05-28 00:19:24 +0200 Brandon Lewis + + * tests/examples/playlist.c: + tests/examples/playlist.c: make audio stream of audiotestsrc silent (it's much less annoying). + +2010-05-28 00:16:28 +0200 Brandon Lewis + + * tests/examples/playlist.c: + tests/examples/playlist.c: fix inappropriate down-casts in playlist.py + +2010-05-28 00:12:45 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + ges/ges-track-transition.c: give gnloperations a unique name + +2010-05-28 00:11:51 +0200 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + ges/ges-simple-timeline-layer.c: also error when transition duration exceeds that of its neighbors + +2010-05-27 23:37:11 +0200 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + ges/ges-simple-timeline-layer.c: don't allow user to create timelines with adjacent transitions + +2010-05-27 23:36:10 +0200 Brandon Lewis + + * ges/ges-simple-timeline-layer.c: + ges/ges-simple-timeline-layer.c: implement simple stair-step like priority management scheme. + +2010-05-27 23:10:04 +0200 Brandon Lewis + + * tests/check/ges/simplelayer.c: + tests/check/ges/simplelayer.c: check in massive unit test case for GSTL with transitions + +2010-05-27 12:06:00 +0200 Brandon Lewis + + * tests/examples/playlist.c: + tests/examples/playlist.c: add transitions to playlist example + +2010-05-27 12:04:05 +0200 Brandon Lewis + + * tests/examples/transition.c: + tests/examples/transition.c: use ges_timeline_transition_new_from_nick() + +2010-05-27 12:02:10 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-transition.c: + * ges/ges-timeline-transition.h: + ges/ges-timeline-transition.{c,h}: add convenience routine for creating transitions docs/libs/ges-sections.txt: add routine to documentation + +2010-05-26 18:19:41 +0200 Brandon Lewis + + * tests/examples/playlist.c: + tests/examples/playlist.c: re-work pattern command line syntax + +2010-05-26 16:57:59 +0200 Brandon Lewis + + * tests/examples/transition.c: + ests/examples/transition.c: create transition with specified type + +2010-05-26 16:36:24 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + ges/ges-track-transition.{c,h}: add support for other wipes with smptealpha + +2010-05-26 16:33:44 +0200 Brandon Lewis + + * ges/ges-timeline-transition.c: + * ges/ges-timeline-transition.h: + ges/ges-timeline-transition.{c,h}: add a type field + +2010-05-26 13:27:46 +0200 Brandon Lewis + + * tests/examples/transition.c: + tests/examples/transition.c: pass transition type to make_timeline + +2010-05-26 13:05:18 +0200 Brandon Lewis + + * tests/examples/transition.c: + tests/examples/transition.c: make -t option work with values supported by smpte + "crossfade" + +2010-05-26 13:04:06 +0200 Brandon Lewis + + * tests/examples/transition.c: + tests/examples/transition.c: add routines for identifying transitions + +2010-05-26 11:38:19 +0200 Brandon Lewis + + * tests/examples/transition.c: + tests/examples/transition.c: split out "make_timeline" into separate routine + +2010-05-26 10:48:13 +0200 Brandon Lewis + + * tests/examples/transition.c: + tests/examples/transition.c: remove some cruft from transition example + +2010-05-25 19:10:27 +0200 Brandon Lewis + + * tests/examples/transition.c: + tests/examples/transition.c: print values in seconds not nseconds + +2010-05-25 19:07:21 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + ges-track-transition.c: set interpolation control points properly from gnlobject properties + +2010-05-25 19:06:10 +0200 Brandon Lewis + + * ges/ges-track-transition.h: + ges-track-transition.c: GstControlSource -> GstInterpolationControlSource + +2010-05-25 16:44:58 +0200 Brandon Lewis + + * tests/examples/transition.c: + tests/examples/transition.c: cast arguments to g_object_set + +2010-05-25 16:42:47 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + ges-track-transition.c: create gst-controller for transition + +2010-05-25 16:41:53 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + ges-track-transition.c: add controller and control-source members + +2010-05-25 16:35:16 +0200 Brandon Lewis + + * configure.ac: + * ges/Makefile.am: + depend on GST_CONTROLLER + +2010-05-25 13:44:57 +0200 Brandon Lewis + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-transition.c: + * ges/ges-track-transition.c: + * tests/examples/transition.c: + hacking + +2010-05-24 17:51:31 +0200 Brandon Lewis + + * tests/examples/transition.c: + tests/examples/transition.c: create transition when duration > 0 + +2010-05-24 17:39:45 +0200 Brandon Lewis + + * tests/examples/Makefile.am: + dist transition.c + +2010-05-24 17:39:07 +0200 Brandon Lewis + + * tests/examples/transition.c: + check in single transition demo + +2010-05-24 14:58:55 +0200 Brandon Lewis + + * ges/ges-types.h: + ges/ges-types.h: add typedefs for GESTrackTransition[Class] structs + +2010-05-24 14:57:12 +0200 Brandon Lewis + + * ges/ges-track-transition.h: + ges-track-transition.h: fix typo + +2010-05-24 14:55:53 +0200 Brandon Lewis + + * ges/Makefile.am: + ges/Makefile.am: dist ges-track-transition.c,h + +2010-05-24 13:08:32 +0200 Brandon Lewis + + * ges/ges-track-transition.c: + * ges/ges-track-transition.h: + skeletal implementation of GESTrackTransition + +2010-05-24 12:34:36 +0200 Brandon Lewis + + * ges/ges-track-transition.h: + check-in ges-track-transition.h + +2010-05-24 10:59:43 +0200 Brandon Lewis + + * tests/examples/playlist.c: + playlist.c: working pattern sources + +2010-06-02 11:49:08 +0200 Edward Hervey + + * tests/examples/playlist.c: + examples: Add a looping feature to playlist example + Allows playing the timeline repeatedly a certain number of times + +2010-05-25 16:22:58 +0200 Edward Hervey + + * ges/ges-timeline.c: + GESTimeline: Freeze state of Tracks when doing an async state change + +2010-05-20 10:46:38 +0200 Edward Hervey + + * tests/examples/.gitignore: + * tests/examples/Makefile.am: + * tests/examples/concatenate.c: + examples: New concatenate examples. + Allows concatenating several files of the same type together + +2010-05-20 10:44:01 +0200 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-utils.c: + * ges/ges-utils.h: + * ges/ges.h: + GES: Add a new utility file + API : ges_timeline_new_audio_video() + +2010-05-11 15:03:33 +0200 Edward Hervey + + * tests/examples/.gitignore: + examples: Ignore some files + +2010-05-20 12:29:30 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Only remove the playsink if it was used + +2010-05-19 15:50:51 +0200 Edward Hervey + + * docs/libs/Makefile.am: + docs: Use proper CFLAGS/LIBS when building docs + +2010-05-19 15:50:41 +0200 Edward Hervey + + * ges/ges-timeline.c: + GESTimeline: Remove unused variable + +2010-05-19 12:39:23 +0200 Edward Hervey + + * tests/check/ges/.gitignore: + * tests/examples/.gitignore: + tests: ignore more files + +2010-05-19 12:38:21 +0200 Edward Hervey + + * tests/examples/playlist.c: + examples: Allow setting null duration on files + This will make the timeline use GstDiscoverer to analyze the file. + +2010-05-19 12:36:11 +0200 Edward Hervey + + * ges/ges-timeline.c: + * ges/ges-timeline.h: + GESTimeline: Use GstDiscoverer for incomplete filesources + If a GESTimelineFileSource is added to a layer and: + * It doesn't have specified supported formats + * OR it doesn't have a specified maximum duration + * OR it doesn't have a specifed duration + Then we asynchronously send it to the GstDiscoverer. + If this happens, the state change of the timeline from READY to + PAUSED will happen asynchronously and be completed when everything + has been properly discovered. + Part 2 of GstDiscoverer integration + +2010-05-19 12:24:44 +0200 Edward Hervey + + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-file-source.h: + * tests/check/ges/filesource.c: + GESTimelineFileSource: Add 'max-duration' and 'supported-formats' properties + * max-duration is the total length of the File. + * supported-formats is the various track types this filesource can produce + trackobjects for. This should maybe be moved to parent classes in the + future + Step 1 of GstDiscoverer integration + +2010-05-19 12:19:37 +0200 Edward Hervey + + * ges/ges-timeline-object.c: + GESTimelineObject: Properly set default duration + Set it in the instance_init to GST_SECOND, But let the subclasses override + it. + This allows subclasses to set a different default duration + +2010-05-19 12:14:34 +0200 Edward Hervey + + * ges/ges-simple-timeline-layer.c: + GESSimpleTimelineLayer: Recalculate positions when object duration change + This ensures that if someone changes the duration, the clips still remain + contiguous and in the proper order. + +2010-05-18 19:07:27 +0200 Edward Hervey + + * configure.ac: + * ges/Makefile.am: + configure: Depend on gstreamer-discoverer + +2010-05-18 17:43:28 +0200 Edward Hervey + + * ges/ges-track.c: + * ges/ges-track.h: + GESTrack: Make GESTrackType a flag, and add GES_TRACK_TYPE_UNKNOWN + Also add a bit more documentation about it. + +2010-05-18 15:19:06 +0200 Edward Hervey + + * docs/libs/.gitignore: + * docs/libs/doc-registry.xml: + * docs/libs/ges-decl-list.txt.bak: + * docs/libs/ges-decl.txt.bak: + * docs/libs/html/GESCustomTimelineSource.html: + * docs/libs/html/GESSimpleTimelineLayer.html: + * docs/libs/html/GESTimeline.html: + * docs/libs/html/GESTimelineFileSource.html: + * docs/libs/html/GESTimelineLayer.html: + * docs/libs/html/GESTimelineObject.html: + * docs/libs/html/GESTimelinePipeline.html: + * docs/libs/html/GESTimelineSource.html: + * docs/libs/html/GESTimelineTransition.html: + * docs/libs/html/GESTrack.html: + * docs/libs/html/GESTrackFileSource.html: + * docs/libs/html/GESTrackObject.html: + * docs/libs/html/GESTrackSource.html: + * docs/libs/html/api-index-full.html: + * docs/libs/html/architecture.xml: + * docs/libs/html/ch01.html: + * docs/libs/html/ch02.html: + * docs/libs/html/ch03.html: + * docs/libs/html/ch04.html: + * docs/libs/html/ges-Initialization.html: + * docs/libs/html/ges-architecture.html: + * docs/libs/html/ges-hierarchy.html: + * docs/libs/html/ges.devhelp: + * docs/libs/html/ges.devhelp2: + * docs/libs/html/home.png: + * docs/libs/html/index.html: + * docs/libs/html/index.sgml: + * docs/libs/html/layer_track_overview.png: + * docs/libs/html/left.png: + * docs/libs/html/right.png: + * docs/libs/html/style.css: + * docs/libs/html/up.png: + * docs/libs/tmpl/ges-common.sgml: + * docs/libs/tmpl/ges-common.sgml.bak: + * docs/libs/tmpl/ges-custom-timeline-source.sgml: + * docs/libs/tmpl/ges-custom-timeline-source.sgml.bak: + * docs/libs/tmpl/ges-simple-timeline-layer.sgml: + * docs/libs/tmpl/ges-simple-timeline-layer.sgml.bak: + * docs/libs/tmpl/ges-timeline-filesource.sgml: + * docs/libs/tmpl/ges-timeline-filesource.sgml.bak: + * docs/libs/tmpl/ges-timeline-layer.sgml: + * docs/libs/tmpl/ges-timeline-layer.sgml.bak: + * docs/libs/tmpl/ges-timeline-object.sgml: + * docs/libs/tmpl/ges-timeline-object.sgml.bak: + * docs/libs/tmpl/ges-timeline-pipeline.sgml: + * docs/libs/tmpl/ges-timeline-pipeline.sgml.bak: + * docs/libs/tmpl/ges-timeline-source.sgml: + * docs/libs/tmpl/ges-timeline-source.sgml.bak: + * docs/libs/tmpl/ges-timeline-transition.sgml: + * docs/libs/tmpl/ges-timeline-transition.sgml.bak: + * docs/libs/tmpl/ges-timeline.sgml: + * docs/libs/tmpl/ges-timeline.sgml.bak: + * docs/libs/tmpl/ges-track-filesource.sgml: + * docs/libs/tmpl/ges-track-filesource.sgml.bak: + * docs/libs/tmpl/ges-track-object.sgml: + * docs/libs/tmpl/ges-track-object.sgml.bak: + * docs/libs/tmpl/ges-track-source.sgml: + * docs/libs/tmpl/ges-track-source.sgml.bak: + * docs/libs/tmpl/ges-track.sgml: + * docs/libs/tmpl/ges-track.sgml.bak: + * docs/libs/tmpl/ges-unused.sgml: + * docs/libs/xml/api-index-deprecated.xml: + * docs/libs/xml/api-index-full.xml: + * docs/libs/xml/ges-common.xml: + * docs/libs/xml/ges-custom-timeline-source.xml: + * docs/libs/xml/ges-doc.bottom: + * docs/libs/xml/ges-doc.top: + * docs/libs/xml/ges-simple-timeline-layer.xml: + * docs/libs/xml/ges-timeline-filesource.xml: + * docs/libs/xml/ges-timeline-layer.xml: + * docs/libs/xml/ges-timeline-object.xml: + * docs/libs/xml/ges-timeline-pipeline.xml: + * docs/libs/xml/ges-timeline-source.xml: + * docs/libs/xml/ges-timeline-transition.xml: + * docs/libs/xml/ges-timeline.xml: + * docs/libs/xml/ges-track-filesource.xml: + * docs/libs/xml/ges-track-object.xml: + * docs/libs/xml/ges-track-source.xml: + * docs/libs/xml/ges-track.xml: + * docs/libs/xml/object_index.sgml: + * docs/libs/xml/tree_index.sgml: + * docs/libs/xml/version.entities: + docs: And remove all the stuff that's meant to be generated at runtime + +2010-05-18 12:56:24 +0200 Edward Hervey + + * docs/libs/doc-registry.xml: + * docs/libs/ges-decl-list.txt.bak: + * docs/libs/ges-decl.txt.bak: + * docs/libs/ges-sections.txt: + * docs/libs/html/GESCustomTimelineSource.html: + * docs/libs/html/GESSimpleTimelineLayer.html: + * docs/libs/html/GESTimeline.html: + * docs/libs/html/GESTimelineFileSource.html: + * docs/libs/html/GESTimelineLayer.html: + * docs/libs/html/GESTimelineObject.html: + * docs/libs/html/GESTimelinePipeline.html: + * docs/libs/html/GESTimelineSource.html: + * docs/libs/html/GESTimelineTransition.html: + * docs/libs/html/GESTrack.html: + * docs/libs/html/GESTrackFileSource.html: + * docs/libs/html/GESTrackObject.html: + * docs/libs/html/GESTrackSource.html: + * docs/libs/html/api-index-full.html: + * docs/libs/html/architecture.xml: + * docs/libs/html/ch01.html: + * docs/libs/html/ch02.html: + * docs/libs/html/ch03.html: + * docs/libs/html/ch04.html: + * docs/libs/html/ges-Initialization.html: + * docs/libs/html/ges-architecture.html: + * docs/libs/html/ges-hierarchy.html: + * docs/libs/html/ges.devhelp: + * docs/libs/html/ges.devhelp2: + * docs/libs/html/home.png: + * docs/libs/html/index.html: + * docs/libs/html/index.sgml: + * docs/libs/html/layer_track_overview.png: + * docs/libs/html/left.png: + * docs/libs/html/right.png: + * docs/libs/html/style.css: + * docs/libs/html/up.png: + * docs/libs/tmpl/ges-common.sgml: + * docs/libs/tmpl/ges-common.sgml.bak: + * docs/libs/tmpl/ges-custom-timeline-source.sgml: + * docs/libs/tmpl/ges-custom-timeline-source.sgml.bak: + * docs/libs/tmpl/ges-simple-timeline-layer.sgml: + * docs/libs/tmpl/ges-simple-timeline-layer.sgml.bak: + * docs/libs/tmpl/ges-timeline-filesource.sgml: + * docs/libs/tmpl/ges-timeline-filesource.sgml.bak: + * docs/libs/tmpl/ges-timeline-layer.sgml: + * docs/libs/tmpl/ges-timeline-layer.sgml.bak: + * docs/libs/tmpl/ges-timeline-object.sgml: + * docs/libs/tmpl/ges-timeline-object.sgml.bak: + * docs/libs/tmpl/ges-timeline-pipeline.sgml: + * docs/libs/tmpl/ges-timeline-pipeline.sgml.bak: + * docs/libs/tmpl/ges-timeline-source.sgml: + * docs/libs/tmpl/ges-timeline-source.sgml.bak: + * docs/libs/tmpl/ges-timeline-transition.sgml: + * docs/libs/tmpl/ges-timeline-transition.sgml.bak: + * docs/libs/tmpl/ges-timeline.sgml: + * docs/libs/tmpl/ges-timeline.sgml.bak: + * docs/libs/tmpl/ges-track-filesource.sgml: + * docs/libs/tmpl/ges-track-filesource.sgml.bak: + * docs/libs/tmpl/ges-track-object.sgml: + * docs/libs/tmpl/ges-track-object.sgml.bak: + * docs/libs/tmpl/ges-track-source.sgml: + * docs/libs/tmpl/ges-track-source.sgml.bak: + * docs/libs/tmpl/ges-track.sgml: + * docs/libs/tmpl/ges-track.sgml.bak: + * docs/libs/tmpl/ges-unused.sgml: + * docs/libs/xml/api-index-deprecated.xml: + * docs/libs/xml/api-index-full.xml: + * docs/libs/xml/ges-common.xml: + * docs/libs/xml/ges-custom-timeline-source.xml: + * docs/libs/xml/ges-doc.bottom: + * docs/libs/xml/ges-doc.top: + * docs/libs/xml/ges-simple-timeline-layer.xml: + * docs/libs/xml/ges-timeline-filesource.xml: + * docs/libs/xml/ges-timeline-layer.xml: + * docs/libs/xml/ges-timeline-object.xml: + * docs/libs/xml/ges-timeline-pipeline.xml: + * docs/libs/xml/ges-timeline-source.xml: + * docs/libs/xml/ges-timeline-transition.xml: + * docs/libs/xml/ges-timeline.xml: + * docs/libs/xml/ges-track-filesource.xml: + * docs/libs/xml/ges-track-object.xml: + * docs/libs/xml/ges-track-source.xml: + * docs/libs/xml/ges-track.xml: + * docs/libs/xml/object_index.sgml: + * docs/libs/xml/tree_index.sgml: + * docs/libs/xml/version.entities: + * ges/ges-timeline-object.h: + GESTimelineObject: Document CreateTrackObjectFunc vmethod + +2010-05-18 12:32:31 +0200 Edward Hervey + + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * docs/libs/scanobj-build.stamp: + docs: Make sure hierarchy/properties/signals get built for all classes + +2010-05-10 12:44:56 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Fix 32bit runtime issues + +2010-05-07 13:30:07 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Limit encodebin buffering to 1 buffer + We don't need to queue more than that since we only need thread decoupling + and the various streams going into encodebin are guaranteed to come + from different streaming threads (since they're separate gnlcomposition). + +2010-05-06 19:57:25 +0200 Edward Hervey + + * tests/examples/playlist.c: + examples: Add option to specify video restriction + Some encoders don't handle _get_caps() properly :( + +2010-04-27 11:45:15 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Remove unused variable/label. + +2010-04-20 13:41:20 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Cleanup properly when pads are removed + +2010-04-20 13:26:00 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Implement smart rendering + +2010-04-20 13:08:27 +0200 Edward Hervey + + * tests/examples/Makefile.am: + * tests/examples/playlist.c: + examples: New playlist examples + Allows giving lists of file/start/duration triplets and testing the + various timeline-pipeline modes (preview, render, smart-render) + +2010-04-20 13:04:31 +0200 Edward Hervey + + * tests/check/Makefile.am: + check: Use GST_CFLAGS so we get new compilation flags + +2010-04-20 13:00:38 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + GESTimelinePipeline: Store encoding profile + +2010-04-20 12:59:26 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.h: + GESTimelinePipeline: New Smart Render mode + +2010-04-20 12:57:53 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: new functions to search/create OutputChain + +2010-04-20 12:53:51 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Remove unused code + +2010-04-20 12:50:34 +0200 Edward Hervey + + * ges/ges-track.c: + GESTrack: set caps on the composition + This will allow them to be propagated to all objects contained within + +2010-04-20 12:50:09 +0200 Mark Nauwelaerts + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline.c: + GESTimeLine(PipeLine): remove additional unref + +2010-04-20 12:47:22 +0200 Edward Hervey + + * configure.ac: + configure.ac : round of cleanup + Add extra CFLAGS + Change GST_CVS to GST_GIT + Add -DGST_USE_UNSTABLE_API for gstprofile, since we know it's unstable. + +2010-04-20 12:28:59 +0200 Edward Hervey + + * ges/Makefile.am: + ges: Link gstprofile + +2010-04-20 11:48:21 +0200 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + GESTimeline: New method ges_timeline_get_tracks + +2010-03-13 16:43:59 +0100 Edward Hervey + + * tests/examples/Makefile.am: + * tests/examples/simple1.c: + examples: Simple Audio/Video example + Currently one can: + * Give a multimedia file + * modify the inpoint + * modify the duration + * mute the audio + +2010-03-13 16:05:37 +0100 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Fix minor bug in get_compatible_unlinked_pad + We weren't breaking and ended up doing a fallthrough to the loop + completion. + +2010-03-13 15:53:16 +0100 Edward Hervey + + * ges/ges-timeline-object.h: + GESTimelineObject: Fix doc of priority property + +2010-03-13 15:51:16 +0100 Edward Hervey + + * ges/ges-track-filesource.c: + GESTrackFileSource: Don't forget to free the URI string + +2010-03-12 19:07:15 +0100 Edward Hervey + + * tests/check/Makefile.am: + * tests/check/ges/layer.c: + tests: Add unit test for layer property. + Still needs more work though + +2010-03-12 19:06:42 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + GESTimelineLayer: Add a 'priority' property + +2010-03-12 18:42:28 +0100 Edward Hervey + + * ges/ges-timeline.c: + GESTimeline: Properly iterate TrackObject lists when removing them + +2010-03-12 19:05:36 +0100 Edward Hervey + + * tests/check/ges/basic.c: + tests: Check refcount of created trackobjects + +2010-03-13 15:52:14 +0100 Edward Hervey + + * ges/ges-timeline-object.c: + * tests/check/ges/filesource.c: + GESTimelineObject: Don't leak a reference when creating TrackObject + +2010-03-12 17:17:30 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + GESTimelineObject: new API : _find_track_object + This allows getting the TrackObject for a corresponding TimelineObject + and Track combination + +2010-03-12 17:08:00 +0100 Edward Hervey + + * Makefile.am: + * configure.ac: + configure: use automake 1. 11 silent rules instead of shave if available + +2010-03-12 17:09:03 +0100 Edward Hervey + + * common: + common: Update to latest common + +2010-03-11 11:06:50 +0100 Edward Hervey + + * .gitignore: + * tests/check/ges/.gitignore: + * tests/examples/.gitignore: + ignore more files + +2010-03-05 16:10:13 +0100 Edward Hervey + + * tests/examples/Makefile.am: + examples: Fix linking/include order + +2010-03-05 15:50:49 +0100 Edward Hervey + + * tests/check/ges/filesource.c: + check: Add a test for checking timelinefilesource properties + This include the mute feature + +2010-02-09 17:45:42 +0100 Edward Hervey + + * tests/examples/Makefile.am: + examples: Use profile LIBS + and fix a typo with GST_LIBS + +2010-02-09 17:44:54 +0100 Edward Hervey + + * configure.ac: + configure.ac: gst-profile is now a standalone pkgconfig + +2010-01-20 11:09:56 +0100 Jarkko Pallviainen + + * tests/examples/test1.c: + examples: Fix build on 32bit systems + +2010-01-08 18:21:08 +0100 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Add comment for _set_render_settings + +2010-01-08 18:16:16 +0100 Edward Hervey + + * tests/examples/Makefile.am: + * tests/examples/test4.c: + examples: test4: variant of test3 with rendering. + Usage: ./test4 output_uri audio_files + This will render in ogg/vorbis the first seconds of all the provided + audio files to the output_uri + Ex : ./test4 file:///data/audio1s.ogg /data/music/*.ogg + +2010-01-08 18:14:46 +0100 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Also get static pads for static pads from encodebin. + This is for the cases where the provided GstStreamEncodingProfile has + a non-zero presence. + +2010-01-08 17:05:01 +0100 Edward Hervey + + * configure.ac: + configure.ac: detect gst-convenience + +2009-12-11 15:24:56 +0100 Edward Hervey + + * ges/ges-custom-timeline-source.h: + * ges/ges-timeline-pipeline.c: + * ges/ges-track-object.h: + ges: Small doc fixups + +2009-12-11 15:17:02 +0100 Edward Hervey + + * tests/check/ges/timelineobject.c: + tests: Fix macro by making it use the proper argument types + +2009-12-11 15:16:26 +0100 Edward Hervey + + * tests/check/Makefile.am: + tests: Fix linking order. + This ensures that "make check" will run with the local libraries and not + the system-wide ones + +2009-12-11 15:15:29 +0100 Edward Hervey + + * ges/ges-timeline-object.c: + GESTimelineObject: Move property setting to an earlier stage. + This ensures that any properties set on the TimelineObject will be + propagated to the created TrackObjects just after they're created + +2009-12-11 15:13:19 +0100 Edward Hervey + + * ges/ges-track-object.c: + * ges/ges-track-object.h: + GESTrackObject: Store pending values when GnlObject isn't created yet + +2009-12-11 15:17:25 +0100 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Sync state of newly added element to container + +2009-12-09 15:03:30 +0100 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Don't forget to remember the mode + +2009-12-09 15:03:15 +0100 Edward Hervey + + * ges/ges-timeline-file-source.c: + GESTimelineFileSource: Properly handle mute + mute != active + +2009-12-09 12:22:34 +0100 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: More render support + +2009-12-04 10:49:32 +0100 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + GESTimelinePipeline: beginning of render support + +2009-11-30 15:14:25 +0100 Edward Hervey + + * ges/ges-custom-timeline-source.c: + * ges/ges-custom-timeline-source.h: + * ges/ges-internal.h: + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-file-source.h: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + * ges/ges-timeline-source.c: + * ges/ges-timeline-source.h: + * ges/ges-timeline-transition.c: + * ges/ges-timeline-transition.h: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-track-filesource.c: + * ges/ges-track-filesource.h: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track-source.c: + * ges/ges-track-source.h: + * ges/ges-track.c: + * ges/ges-track.h: + * ges/ges-types.h: + * ges/ges.c: + * ges/ges.h: + ges/: Fix copyright in headers + +2009-11-30 15:14:06 +0100 Edward Hervey + + * AUTHORS: + AUTHORS: Add myself + +2009-11-25 13:13:49 +0100 Edward Hervey + + * ges/ges-timeline-file-source.c: + timelinefilesource: Free URI when finalizing + +2009-11-25 13:11:32 +0100 Edward Hervey + + * ges/ges-track-object.c: + trackobject: priority is a uint32 + +2009-11-25 12:53:13 +0100 Edward Hervey + + * tests/check/ges/timelineobject.c: + tests: release TrackObject when we're done with it + +2009-11-25 12:52:50 +0100 Edward Hervey + + * tests/check/ges/simplelayer.c: + * tests/check/ges/timelineobject.c: + tests: Don't forget to cast to guint64 when using g_object_set + ... else total failure ensues on 32bit machines + +2009-11-25 11:56:58 +0100 Edward Hervey + + * ges/ges-timeline-object.c: + TimelineObject: Add missing argument to printf statement + +2009-11-25 11:55:50 +0100 Edward Hervey + + * ges/ges-custom-timeline-source.c: + customtimelinesource: Fix indentation + +2009-11-25 11:14:02 +0100 Edward Hervey + + * docs/working-diagrams.svg: + docs: updates to working diagram, still needs more love + +2009-11-15 18:23:33 +0100 Edward Hervey + + * configure.ac: + * docs/libs/Makefile.am: + * docs/libs/architecture.xml: + * docs/libs/layer_track_overview.png: + * docs/working-diagrams.svg: + docs: Improve docs some more + +2009-11-12 20:11:28 +0100 Edward Hervey + + * common: + * docs/libs/Makefile.am: + * docs/libs/architecture.xml: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/ges.types: + * ges/ges.c: + docs: Add overview and architecture document and cleanup docs more. + +2009-11-12 19:14:35 +0100 Edward Hervey + + * docs/design/gstprofile.h: + docs: Update to gstprofile header + +2009-11-09 15:55:06 +0100 Edward Hervey + + * .gitignore: + ignore more files + +2009-11-09 15:54:18 +0100 Edward Hervey + + * m4/Makefile.am: + * m4/codeset.m4: + * m4/gettext.m4: + * m4/glibc2.m4: + * m4/glibc21.m4: + * m4/iconv.m4: + * m4/intdiv0.m4: + * m4/intl.m4: + * m4/intldir.m4: + * m4/intlmacosx.m4: + * m4/intmax.m4: + * m4/inttypes-pri.m4: + * m4/inttypes_h.m4: + * m4/lcmessage.m4: + * m4/lib-ld.m4: + * m4/lib-link.m4: + * m4/lib-prefix.m4: + * m4/libtool.m4: + * m4/lock.m4: + * m4/longlong.m4: + * m4/ltoptions.m4: + * m4/ltsugar.m4: + * m4/ltversion.m4: + * m4/lt~obsolete.m4: + * m4/nls.m4: + * m4/po.m4: + * m4/printf-posix.m4: + * m4/progtest.m4: + * m4/size_max.m4: + * m4/stdint_h.m4: + * m4/uintmax_t.m4: + * m4/visibility.m4: + * m4/wchar_t.m4: + * m4/wint_t.m4: + * m4/xsize.m4: + remove m4/*.m4, will be automatically created by autogen.sh + +2009-11-05 10:22:57 +0100 Edward Hervey + + * common: + update common submodule version used + +2009-10-22 17:37:54 +0200 Edward Hervey + + * docs/libs/ges-sections.txt: + docs: Add missing symbol to documentation + +2009-10-19 18:32:23 +0200 Edward Hervey + + * docs/design/encoding-research.txt: + * docs/design/encoding.txt: + * docs/design/gstprofile.h: + docs/design: Fixups/additions based on feedback + +2009-10-07 16:23:22 +0200 Edward Hervey + + * docs/design/encoding-research.txt: + * docs/design/encoding.txt: + * docs/design/gstencodebin.h: + * docs/design/gstprofile.h: + docs/design: Add encoding/profile proposal/design + +2009-09-30 16:45:13 +0200 Edward Hervey + + * tests/check/Makefile.am: + * tests/check/ges/timelineobject.c: + tests: New unit test for GESTimelineObject + +2009-09-30 16:44:41 +0200 Edward Hervey + + * ges/ges-timeline-object.c: + GESTimelineObject: First set the duration and priority before the inpoint. + +2009-09-30 16:44:12 +0200 Edward Hervey + + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-file-source.h: + GESTimelineFileSource: Add a 'mute' property. + This property deactivates the audio trackobjects if set to TRUE + +2009-09-30 16:43:12 +0200 Edward Hervey + + * ges/ges-track-object.c: + GESTrackObject: Listen to property change from gnlobject + TODO: add/emit the 'changed' signal + +2009-09-30 16:42:31 +0200 Edward Hervey + + * ges/ges-track-object.h: + GESTrackObject: Add convenience macros for accessing properties + +2009-09-30 16:42:08 +0200 Edward Hervey + + * ges/ges-track-object.c: + GESTrackObject: Set sane default values + +2009-09-30 16:40:59 +0200 Edward Hervey + + * ges/ges-track-object.c: + * ges/ges-track-object.h: + GESTrackObject: Add 'active' property. + This property allows (de)activating a track object + +2009-09-29 15:32:23 +0200 Edward Hervey + + * tests/examples/Makefile.am: + * tests/examples/test3.c: + tests/examples: test3, same as test2, but uses a SimpleTimelineLayer + +2009-09-29 15:29:11 +0200 Edward Hervey + + * tests/check/Makefile.am: + * tests/check/ges/simplelayer.c: + tests/check: Add test for GESSimpleTimelineLayer + +2009-09-29 15:27:55 +0200 Edward Hervey + + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + GESSimpleTimelineLayer: Implement add()/move() and parent class changes + If changes happen when accessing the GESTimelineLayer API, they will be taken + into account. + +2009-09-29 15:27:17 +0200 Edward Hervey + + * ges/ges-timeline-layer.h: + ges-timeline-layer.h: Add comment + +2009-09-29 15:25:54 +0200 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-timeline-object.h: + ges-timeline-object: Add convenience macros + +2009-09-21 18:11:19 +0200 Edward Hervey + + * tests/examples/Makefile.am: + * tests/examples/test2.c: + tests/example: New small example of timeline file sources. + This examples takes a list of files with audio tracks, and plays the first + second of each. + This demonstrates the usage of GESTimelineFileSource + +2009-09-21 18:08:51 +0200 Edward Hervey + + * tests/check/Makefile.am: + * tests/check/ges/filesource.c: + tests/check: New test for GESTimelineFileSource + +2009-09-21 12:51:16 +0200 Edward Hervey + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/Makefile.am: + * ges/ges-timeline-file-source.c: + * ges/ges-timeline-file-source.h: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-track-filesource.c: + * ges/ges-track-filesource.h: + * ges/ges-types.h: + * ges/ges.h: + New GESTimelineFileSource and GESTrackFileSource classes + +2009-09-16 12:37:45 +0200 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-custom-timeline-source.c: + * ges/ges-custom-timeline-source.h: + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline.c: + Finish public API documentation + +2009-09-16 12:37:13 +0200 Edward Hervey + + * docs/libs/ges-docs.sgml: + docs: Add index and object hierarchy + +2009-09-14 19:45:43 +0200 Edward Hervey + + * tests/check/ges/basic.c: + tests: Make basic test check for proper reference counting. + +2009-09-14 19:44:03 +0200 Edward Hervey + + * ges/ges-track.c: + GESTrack: The track steals the refcount to the caps. document that. + +2009-09-14 19:42:58 +0200 Edward Hervey + + * docs/libs/.gitignore: + docs/libs: ignore more files + +2009-09-14 19:24:28 +0200 Edward Hervey + + * ges/ges-timeline.c: + GESTimeline: Fix reference counting of tracks, add docs. + +2009-09-14 19:23:52 +0200 Edward Hervey + + * ges/ges-timeline-layer.c: + GESTimelineLayer: Fix reference handling of objects, add docs. + +2009-09-14 19:23:21 +0200 Edward Hervey + + * configure.ac: + configure.ac: Require latest gst-plugins-base for 'playsink' + +2009-09-14 17:00:13 +0200 Edward Hervey + + * ges/ges-timeline.h: + GESTimeline: start more documentation + +2009-09-14 16:33:25 +0200 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + GESTrackObject: Document some more + +2009-09-14 15:51:49 +0200 Edward Hervey + + * docs/libs/ges-sections.txt: + * ges/ges-track.c: + GESTrack: document more + +2009-09-10 18:17:38 +0100 Tim-Philipp Müller + + * docs/libs/Makefile.am: + * docs/libs/ges.types: + docs: fix gtk-doc build and make distcheck for me + +2009-09-10 18:53:31 +0200 Edward Hervey + + * tests/examples/test1.c: + Document first high-level demo. + +2009-09-10 18:40:51 +0200 Edward Hervey + + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * ges/ges-custom-timeline-source.c: + * ges/ges-simple-timeline-layer.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-source.c: + * ges/ges-timeline-transition.c: + * ges/ges-timeline.c: + * ges/ges-track-object.c: + * ges/ges-track-source.c: + * ges/ges-track.c: + * ges/ges-track.h: + * ges/ges.c: + More documentation. Coverage now at 25% + +2009-09-10 16:23:12 +0200 Edward Hervey + + * docs/random/scenarios: + docs: move working document + +2009-09-10 16:22:00 +0200 Edward Hervey + + * Makefile.am: + * configure.ac: + * docs/Makefile.am: + * docs/libs/Makefile.am: + * docs/libs/ges-docs.sgml: + * docs/libs/ges-sections.txt: + * docs/libs/scanobj-build.stamp: + * docs/version.entities.in: + docs: Add gtk-doc API documentation + current coverage : 8% + +2009-09-09 15:53:53 +0200 Edward Hervey + + * tests/examples/test1.c: + test1: Expand example to also use an audio track. + +2009-09-09 15:51:52 +0200 Edward Hervey + + * ges/ges-timeline.c: + GESTimeline: Make sure added ghostpads are unique. + +2009-09-09 13:57:37 +0200 Edward Hervey + + * tests/examples/test1.c: + test1: Actually change videotestsrc patterns to make changes obvious. + +2009-09-09 13:55:30 +0200 Edward Hervey + + * tests/examples/test1.c: + test1: Use mainloop, required for proper gnonlin behaviour. + +2009-09-09 12:42:29 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + GESTimelinePipeline: Reconfigure playsink once we've added a pad. + +2009-09-08 19:46:54 +0200 Edward Hervey + + * tests/examples/test1.c: + examples: Add timeline to pipeline and set it to playing. + ... and now we need to go an fix playsink :) + +2009-09-08 19:46:26 +0200 Edward Hervey + + * tests/check/ges/basic.c: + test/ges/basic: Adapt to API changes. + +2009-09-08 19:45:08 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + GESTimelinePipeline: Use 'playsink', track added/removed pads. + Also add a method to set a GESTimeline on the pipeline. + +2009-09-08 19:44:03 +0200 Edward Hervey + + * ges/ges-timeline.c: + * ges/ges-timeline.h: + GESTimeline: Track internal pads and tracks in sync. + Add method to get the Track associated to a ghostpad. + +2009-09-08 18:55:41 +0200 Edward Hervey + + * .gitignore: + gitignore: Ignore more files + +2009-09-08 18:49:22 +0200 Edward Hervey + + * ges/ges-track.c: + * ges/ges-track.h: + GESTrack: Add TrackType enum and constructor property. + This allows us to speed up detection of track content type. + +2009-09-08 18:47:46 +0200 Edward Hervey + + * ges/ges-custom-timeline-source.c: + CustomTimelineSource: Fix typo in debug statement + +2009-09-07 15:46:44 +0200 Edward Hervey + + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + GESTimelinePipeline: Fix typo + +2009-08-07 20:33:40 +0200 Edward Hervey + + * ges/ges-track-object.c: + TrackObject: Add debugging and make default duration 1s + +2009-08-07 20:33:18 +0200 Edward Hervey + + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + TimelineObject: Add start/inpoint/duration/priority properties and propagate them + +2009-08-07 20:32:47 +0200 Edward Hervey + + * ges/ges-track.c: + * ges/ges-track.h: + Track: Handle pads + +2009-08-07 20:32:29 +0200 Edward Hervey + + * ges/ges-track.c: + * ges/ges-track.h: + Track: Add convenience methods for creating a raw Audio or Video track. + +2009-08-07 20:31:11 +0200 Edward Hervey + + * ges/ges-custom-timeline-source.c: + * ges/ges-custom-timeline-source.h: + * tests/check/ges/basic.c: + CustomTimelineSource: Allow giving user_data to the callback + +2009-08-07 20:29:35 +0200 Edward Hervey + + * configure.ac: + * tests/Makefile.am: + * tests/examples/Makefile.am: + * tests/examples/test1.c: + Add directory for examples along with a minimalistic first example. + +2009-08-07 18:18:42 +0200 Edward Hervey + + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track.c: + * ges/ges-track.h: + * tests/check/ges/basic.c: + Add 'caps' property on Track + +2009-08-07 17:09:59 +0200 Edward Hervey + + * COPYING: + * COPYING.LIB: + * INSTALL: + * Makefile.am: + * RELEASE: + * ges/Makefile.am: + * gst-editing-services.doap: + Fix build. Can now run make distcheck. + +2009-08-07 16:51:49 +0200 Edward Hervey + + * ges/ges-track.c: + Track: Add/Remove the GnlComposition from ourself. + +2009-08-07 16:47:18 +0200 Edward Hervey + + * tests/check/ges/basic.c: + tests/ges/basic: Extend test to remove the object. + +2009-08-07 16:46:57 +0200 Edward Hervey + + * ges/ges-track-source.h: + TrackSource: Include TrackObject header file + +2009-08-07 16:46:35 +0200 Edward Hervey + + * ges/ges.c: + ges.c: Add debug line to inform of initialization + +2009-08-07 16:45:16 +0200 Edward Hervey + + * ges/Makefile.am: + * ges/ges-custom-timeline-source.c: + * ges/ges-custom-timeline-source.h: + * ges/ges-types.h: + * ges/ges.h: + New CustomTimelineSource class. + +2009-08-07 16:43:49 +0200 Edward Hervey + + * ges/ges-track.c: + * ges/ges-track.h: + Track: Implement remove_object() + +2009-08-07 16:43:01 +0200 Edward Hervey + + * ges/ges-track-object.c: + * ges/ges-track-object.h: + TrackObject: Add 'valid' property, Make _set_track() return a bool + +2009-08-07 16:41:23 +0200 Edward Hervey + + * ges/ges-timeline.c: + Timeline: Implement remove_track, remove_layer, and _layer_object_removed_cb + +2009-08-07 16:40:51 +0200 Edward Hervey + + * ges/ges-timeline-source.c: + * ges/ges-timeline-source.h: + TimelineSource: Implenent _create_track_object() virtual-method + +2009-08-07 16:39:45 +0200 Edward Hervey + + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + TimelineObject: Implement _release_track_object() + +2009-08-07 16:39:09 +0200 Edward Hervey + + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + TimelineLayer: Implement _remove_object() + +2009-08-06 20:04:59 +0200 Edward Hervey + + * tests/check/ges/basic.c: + tests: Add a simple scenario + +2009-08-06 19:59:25 +0200 Edward Hervey + + * ges/ges-simple-timeline-layer.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-source.c: + * ges/ges-timeline-transition.c: + * ges/ges-timeline.c: + * ges/ges-track-object.c: + * ges/ges-track.c: + ges: Remove unused private structures. We'll re-add on a per-case basis. + +2009-08-06 19:51:29 +0200 Edward Hervey + + * Makefile.am: + * configure.ac: + * ges/Makefile.am: + * ges/ges-internal.h: + * ges/ges-simple-timeline-layer.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-object.c: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-source.c: + * ges/ges-timeline-transition.c: + * ges/ges-timeline.c: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track-source.c: + * ges/ges-track.c: + * ges/ges.c: + * ges/ges.h: + * tests/Makefile.am: + * tests/check/Makefile.am: + * tests/check/ges/basic.c: + Add unit test system. Adjust GST_DEBUG usage. + +2009-08-06 18:54:01 +0200 Edward Hervey + + * ges/Makefile.am: + * ges/ges-track-source.c: + * ges/ges-track-source.h: + * ges/ges-types.h: + * ges/ges.h: + Add new GESTrackSource + +2009-08-06 17:38:43 +0200 Edward Hervey + + * docs/scenarios: + * ges/ges-simple-timeline-layer.c: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-source.c: + * ges/ges-timeline-source.h: + * ges/ges-timeline-transition.c: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track.c: + * ges/ges-track.h: + intermediary commit. Still need to fill in more blanks :( + +2009-08-06 12:47:38 +0200 Edward Hervey + + * ges/Makefile.am: + * ges/gesmarshal.list: + Add signal marshalling code + +2009-08-06 12:14:37 +0200 Edward Hervey + + * Makefile.am: + * autogen.sh: + * configure.ac: + * ges/Makefile.am: + * ges/ges-simple-timeline-layer.h: + * ges/ges-timeline-layer.h: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline-pipeline.h: + * ges/ges-timeline-source.h: + * ges/ges-timeline-transition.h: + * ges/ges-timeline.h: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track.h: + * ges/ges-types.h: + * ges/ges.h: + build fixed again. Moved type declarations in a standalone file. + +2009-08-06 11:24:04 +0200 Edward Hervey + + * ges/Makefile.am: + * ges/ges-simple-timeline-layer.c: + * ges/ges-simple-timeline-layer.h: + * ges/ges-timeline-layer.c: + * ges/ges-timeline-layer.h: + * ges/ges-timeline-object.c: + * ges/ges-timeline-object.h: + * ges/ges-timeline-pipeline.c: + * ges/ges-timeline-pipeline.h: + * ges/ges-timeline-source.c: + * ges/ges-timeline-source.h: + * ges/ges-timeline-transition.c: + * ges/ges-timeline-transition.h: + * ges/ges-timeline.c: + * ges/ges-timeline.h: + * ges/ges-track-object.c: + * ges/ges-track-object.h: + * ges/ges-track.c: + * ges/ges-track.h: + * ges/ges.c: + * ges/ges.h: + src/ => ges/ + +2009-08-06 11:23:01 +0200 Edward Hervey + + * docs/scenarios: + * src/Makefile.am: + * src/ges-simple-timeline-layer.h: + * src/ges-timeline-object.c: + * src/ges-timeline-object.h: + * src/ges-timeline.h: + * src/ges-track-object.c: + * src/ges-track-object.h: + * src/ges-track.h: + * src/ges.c: + * src/ges.h: + MORE HACKING + +2009-08-04 19:27:07 +0200 Edward Hervey + + * README: + README: Mention license + +2009-08-04 19:21:49 +0200 Edward Hervey + + * .gitignore: + .gitignore: ignore cruft + +2009-08-04 17:16:31 +0200 Edward Hervey + + * .gitmodules: + * AUTHORS: + * ChangeLog: + * Makefile.am: + * NEWS: + * autogen.sh: + * common: + * configure.ac: + * gst-editing-services.spec.in: + * m4/Makefile.am: + * m4/codeset.m4: + * m4/gettext.m4: + * m4/glibc2.m4: + * m4/glibc21.m4: + * m4/iconv.m4: + * m4/intdiv0.m4: + * m4/intl.m4: + * m4/intldir.m4: + * m4/intlmacosx.m4: + * m4/intmax.m4: + * m4/inttypes-pri.m4: + * m4/inttypes_h.m4: + * m4/lcmessage.m4: + * m4/lib-ld.m4: + * m4/lib-link.m4: + * m4/lib-prefix.m4: + * m4/libtool.m4: + * m4/lock.m4: + * m4/longlong.m4: + * m4/ltoptions.m4: + * m4/ltsugar.m4: + * m4/ltversion.m4: + * m4/lt~obsolete.m4: + * m4/nls.m4: + * m4/po.m4: + * m4/printf-posix.m4: + * m4/progtest.m4: + * m4/size_max.m4: + * m4/stdint_h.m4: + * m4/uintmax_t.m4: + * m4/visibility.m4: + * m4/wchar_t.m4: + * m4/wint_t.m4: + * m4/xsize.m4: + * src/Makefile.am: + * src/ges-simple-timeline-layer.c: + * src/ges-simple-timeline-layer.h: + * src/ges-timeline-layer.c: + * src/ges-timeline-layer.h: + * src/ges-timeline-object.c: + * src/ges-timeline-object.h: + * src/ges-timeline-pipeline.c: + * src/ges-timeline-pipeline.h: + * src/ges-timeline-source.c: + * src/ges-timeline-source.h: + * src/ges-timeline-transition.c: + * src/ges-timeline-transition.h: + * src/ges-timeline.c: + * src/ges-timeline.h: + * src/ges-track-object.c: + * src/ges-track-object.h: + * src/ges-track.c: + * src/ges-track.h: + It builds !!!! :) + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000000..0e581c39b8 --- /dev/null +++ b/NEWS @@ -0,0 +1,299 @@ +GStreamer 1.20 Release Notes + +GStreamer 1.20 has not been released yet. It is scheduled for release +around October/November 2021. + +1.19.x is the unstable development version that is being developed in +the git main branch and which will eventually result in 1.20, and 1.19.2 +is the current development release in that series + +It is expected that feature freeze will be in early October 2021, +followed by one or two 1.19.9x pre-releases and the new 1.20 stable +release around October/November 2021. + +1.20 will be backwards-compatible to the stable 1.18, 1.16, 1.14, 1.12, +1.10, 1.8, 1.6,, 1.4, 1.2 and 1.0 release series. + +See https://gstreamer.freedesktop.org/releases/1.20/ for the latest +version of this document. + +Last updated: Wednesday 22 September 2021, 18:00 UTC (log) + +Introduction + +The GStreamer team is proud to announce a new major feature release in +the stable 1.x API series of your favourite cross-platform multimedia +framework! + +As always, this release is again packed with many new features, bug +fixes and other improvements. + +Highlights + +- this section will be completed in due course + +Major new features and changes + +Noteworthy new features and API + +- this section will be filled in in due course + +New elements + +- this section will be filled in in due course + +New element features and additions + +- this section will be filled in in due course + +Plugin and library moves + +- this section will be filled in in due course + +- There were no plugin moves or library moves in this cycle. + +Plugin removals + +The following elements or plugins have been removed: + +- this section will be filled in in due course + +Miscellaneous API additions + +- this section will be filled in in due course + +Miscellaneous performance, latency and memory optimisations + +- this section will be filled in in due course + +Miscellaneous other changes and enhancements + +- this section will be filled in in due course + +Tracing framework and debugging improvements + +- this section will be filled in in due course + +Tools + +- this section will be filled in in due course + +GStreamer RTSP server + +- this section will be filled in in due course + +GStreamer VAAPI + +- this section will be filled in in due course + +GStreamer OMX + +- this section will be filled in in due course + +GStreamer Editing Services and NLE + +- this section will be filled in in due course + +GStreamer validate + +- this section will be filled in in due course + +GStreamer Python Bindings + +- this section will be filled in in due course + +GStreamer C# Bindings + +- this section will be filled in in due course + +GStreamer Rust Bindings and Rust Plugins + +The GStreamer Rust bindings are released separately with a different +release cadence that’s tied to gtk-rs, but the latest release has +already been updated for the upcoming new GStreamer 1.20 API. + +gst-plugins-rs, the module containing GStreamer plugins written in Rust, +has also seen lots of activity with many new elements and plugins. + +What follows is a list of elements and plugins available in +gst-plugins-rs, so people don’t miss out on all those potentially useful +elements that have no C equivalent. + +- FIXME: add new elements + +Rust audio plugins + +- audiornnoise: New element for audio denoising which implements the + noise removal algorithm of the Xiph RNNoise library, in Rust +- rsaudioecho: Port of the audioecho element from gst-plugins-good + rsaudioloudnorm: Live audio loudness normalization element based on + the FFmpeg af_loudnorm filter +- claxondec: FLAC lossless audio codec decoder element based on the + pure-Rust claxon implementation +- csoundfilter: Audio filter that can use any filter defined via the + Csound audio programming language +- lewtondec: Vorbis audio decoder element based on the pure-Rust + lewton implementation + +Rust video plugins + +- cdgdec/cdgparse: Decoder and parser for the CD+G video codec based + on a pure-Rust CD+G implementation, used for example by karaoke CDs +- cea608overlay: CEA-608 Closed Captions overlay element +- cea608tott: CEA-608 Closed Captions to timed-text (e.g. VTT or SRT + subtitles) converter +- tttocea608: CEA-608 Closed Captions from timed-text converter +- mccenc/mccparse: MacCaption Closed Caption format encoder and parser +- sccenc/sccparse: Scenarist Closed Caption format encoder and parser +- dav1dec: AV1 video decoder based on the dav1d decoder implementation + by the VLC project +- rav1enc: AV1 video encoder based on the fast and pure-Rust rav1e + encoder implementation +- rsflvdemux: Alternative to the flvdemux FLV demuxer element from + gst-plugins-good, not feature-equivalent yet +- rsgifenc/rspngenc: GIF/PNG encoder elements based on the pure-Rust + implementations by the image-rs project + +Rust text plugins + +- textwrap: Element for line-wrapping timed text (e.g. subtitles) for + better screen-fitting, including hyphenation support for some + languages + +Rust network plugins + +- reqwesthttpsrc: HTTP(S) source element based on the Rust + reqwest/hyper HTTP implementations and almost feature-equivalent + with the main GStreamer HTTP source souphttpsrc +- s3src/s3sink: Source/sink element for the Amazon S3 cloud storage +- awstranscriber: Live audio to timed text transcription element using + the Amazon AWS Transcribe API + +Generic Rust plugins + +- sodiumencrypter/sodiumdecrypter: Encryption/decryption element based + on libsodium/NaCl +- togglerecord: Recording element that allows to pause/resume + recordings easily and considers keyframe boundaries +- fallbackswitch/fallbacksrc: Elements for handling potentially + failing (network) sources, restarting them on errors/timeout and + showing a fallback stream instead +- threadshare: Set of elements that provide alternatives for various + existing GStreamer elements but allow to share the streaming threads + between each other to reduce the number of threads +- rsfilesrc/rsfilesink: File source/sink elements as replacements for + the existing filesrc/filesink elements + +Build and Dependencies + +- this section will be filled in in due course + +gst-build + +- this section will be filled in in due course + +Cerbero + +Cerbero is a meta build system used to build GStreamer plus dependencies +on platforms where dependencies are not readily available, such as +Windows, Android, iOS and macOS. + +General improvements + +- this section will be filled in in due course + +macOS / iOS + +- this section will be filled in in due course + +Windows + +- this section will be filled in in due course + +Windows MSI installer + +- this section will be filled in in due course + +Linux + +- this section will be filled in in due course + +Android + +- this section will be filled in in due course + +Platform-specific changes and improvements + +Android + +- this section will be filled in in due course + +macOS and iOS + +- this section will be filled in in due course + +Windows + +- this section will be filled in in due course + +Linux + +- this section will be filled in in due course + +Documentation improvements + +- this section will be filled in in due course + +Possibly Breaking Changes + +- this section will be filled in in due course +- MPEG-TS SCTE-35 API changes (FIXME: flesh out) +- gst_parse_launch() and friends now error out on non-existing + properties on top-level bins where they would silently fail and + ignore those before. + +Known Issues + +- this section will be filled in in due course + +- There are a couple of known WebRTC-related regressions/blockers: + + - webrtc: DTLS setup with Chrome is broken + - webrtcbin: First keyframe is usually lost + +Contributors + +- this section will be filled in in due course + +… and many others who have contributed bug reports, translations, sent +suggestions or helped testing. + +Stable 1.20 branch + +After the 1.20.0 release there will be several 1.20.x bug-fix releases +which will contain bug fixes which have been deemed suitable for a +stable branch, but no new features or intrusive changes will be added to +a bug-fix release usually. The 1.20.x bug-fix releases will be made from +the git 1.20 branch, which will be a stable branch. + +1.20.0 + +1.20.0 is scheduled to be released around October/November 2021. + +Schedule for 1.22 + +Our next major feature release will be 1.22, and 1.21 will be the +unstable development version leading up to the stable 1.22 release. The +development of 1.21/1.22 will happen in the git main branch. + +The plan for the 1.22 development cycle is yet to be confirmed. + +1.22 will be backwards-compatible to the stable 1.20, 1.18, 1.16, 1.14, +1.12, 1.10, 1.8, 1.6, 1.4, 1.2 and 1.0 release series. + +------------------------------------------------------------------------ + +These release notes have been prepared by Tim-Philipp Müller with +contributions from … + +License: CC BY-SA 4.0 diff --git a/README b/README new file mode 100644 index 0000000000..fa53e6662d --- /dev/null +++ b/README @@ -0,0 +1,18 @@ +GStreamer Editing Services +-------------------------- + + This is a high-level library for facilitating the creation of audio/video +non-linear editors. + +License: +-------- + + This package and its contents are licensend under the GNU Lesser General +Public License (LGPL). + +Dependencies: +------------- + + * GStreamer core + * gst-plugins-base + diff --git a/RELEASE b/RELEASE new file mode 100644 index 0000000000..e0eca800d6 --- /dev/null +++ b/RELEASE @@ -0,0 +1,96 @@ +This is GStreamer gst-editing-services 1.19.2. + +GStreamer 1.19 is the development branch leading up to the next major +stable version which will be 1.20. + +The 1.19 development series adds new features on top of the 1.18 series and is +part of the API and ABI-stable 1.x release series of the GStreamer multimedia +framework. + +Full release notes will one day be found at: + + https://gstreamer.freedesktop.org/releases/1.20/ + +Binaries for Android, iOS, Mac OS X and Windows will usually be provided +shortly after the release. + +This module will not be very useful by itself and should be used in conjunction +with other GStreamer modules for a complete multimedia experience. + + - gstreamer: provides the core GStreamer libraries and some generic plugins + + - gst-plugins-base: a basic set of well-supported plugins and additional + media-specific GStreamer helper libraries for audio, + video, rtsp, rtp, tags, OpenGL, etc. + + - gst-plugins-good: a set of well-supported plugins under our preferred + license + + - gst-plugins-ugly: a set of well-supported plugins which might pose + problems for distributors + + - gst-plugins-bad: a set of plugins of varying quality that have not made + their way into one of core/base/good/ugly yet, for one + reason or another. Many of these are are production quality + elements, but may still be missing documentation or unit + tests; others haven't passed the rigorous quality testing + we expect yet. + + - gst-libav: a set of codecs plugins based on the ffmpeg library. This is + where you can find audio and video decoders and encoders + for a wide variety of formats including H.264, AAC, etc. + + - gstreamer-vaapi: hardware-accelerated video decoding and encoding using + VA-API on Linux. Primarily for Intel graphics hardware. + + - gst-omx: hardware-accelerated video decoding and encoding, primarily for + embedded Linux systems that provide an OpenMax + implementation layer such as the Raspberry Pi. + + - gst-rtsp-server: library to serve files or streaming pipelines via RTSP + + - gst-editing-services: library an plugins for non-linear editing + +==== Download ==== + +You can find source releases of gstreamer in the download +directory: https://gstreamer.freedesktop.org/src/gstreamer/ + +The git repository and details how to clone it can be found at +https://gitlab.freedesktop.org/gstreamer/ + +==== Homepage ==== + +The project's website is https://gstreamer.freedesktop.org/ + +==== Support and Bugs ==== + +We have recently moved from GNOME Bugzilla to GitLab on freedesktop.org +for bug reports and feature requests: + + https://gitlab.freedesktop.org/gstreamer + +Please submit patches via GitLab as well, in form of Merge Requests. See + + https://gstreamer.freedesktop.org/documentation/contribute/ + +for more details. + +For help and support, please subscribe to and send questions to the +gstreamer-devel mailing list (see below for details). + +There is also a #gstreamer IRC channel on the Freenode IRC network. + +==== Developers ==== + +GStreamer source code repositories can be found on GitLab on freedesktop.org: + + https://gitlab.freedesktop.org/gstreamer + +and can also be cloned from there and this is also where you can submit +Merge Requests or file issues for bugs or feature requests. + +Interested developers of the core library, plugins, and applications should +subscribe to the gstreamer-devel list: + + https://lists.freedesktop.org/mailman/listinfo/gstreamer-devel diff --git a/bindings/python/examples/simple.py b/bindings/python/examples/simple.py new file mode 100644 index 0000000000..9628e1ab43 --- /dev/null +++ b/bindings/python/examples/simple.py @@ -0,0 +1,71 @@ +# GStreamer +# +# Copyright (C) 2013 Thibault Saunier +# +# This program 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 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. + +import sys +from ..overrides import override +from ..importer import modules +from gi.repository import GObject + + +if sys.version_info >= (3, 0): + _basestring = str + _callable = lambda c: hasattr(c, '__call__') +else: + _basestring = basestring + _callable = callable + +GES = modules['GES']._introspection_module +__all__ = [] + +if GES._version == '0.10': + import warnings + warn_msg = "You have imported the GES 0.10 module. Because GES 0.10 \ +was not designed for use with introspection some of the \ +interfaces and API will fail. As such this is not supported \ +by the GStreamer development team and we encourage you to \ +port your app to GES 1 or greater. static python bindings is the recomended \ +python module to use with GES 0.10" + + warnings.warn(warn_msg, RuntimeWarning) + +def __timeline_element__repr__(self): + return "%s [%s (%s) %s]" % ( + self.props.name, + Gst.TIME_ARGS(self.props.start), + Gst.TIME_ARGS(self.props.in_point), + Gst.TIME_ARGS(self.props.duration), + ) + +__prev_set_child_property = GES.TimelineElement.set_child_property +def __timeline_element_set_child_property(self, prop_name, prop_value): + res, _, pspec = GES.TimelineElement.lookup_child(self, prop_name) + if not res: + return res + + v = GObject.Value() + v.init(pspec.value_type) + v.set_value(prop_value) + + return __prev_set_child_property(self, prop_name, v) + + +GES.TimelineElement.__repr__ = __timeline_element__repr__ +GES.TimelineElement.set_child_property = __timeline_element_set_child_property +GES.TrackElement.set_child_property = GES.TimelineElement.set_child_property +GES.Container.edit = GES.TimelineElement.edit + +__prev_asset_repr = GES.Asset.__repr__ +def __asset__repr__(self): + return "%s(%s)" % (__prev_asset_repr(self), self.props.id) + +GES.Asset.__repr__ = __asset__repr__ + +def __timeline_iter_clips(self): + """Iterate all clips in a timeline""" + for layer in self.get_layers(): + for clip in layer.get_clips(): + yield clip + +GES.Timeline.iter_clips = __timeline_iter_clips + +try: + from gi.repository import Gst + Gst +except: + raise RuntimeError("GSt couldn't be imported, make sure you have gst-python installed") diff --git a/bindings/python/gi/overrides/__init__.py b/bindings/python/gi/overrides/__init__.py new file mode 100644 index 0000000000..6471f87b91 --- /dev/null +++ b/bindings/python/gi/overrides/__init__.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# +# __init__.py +# +# Copyright (C) 2012 Thibault Saunier +# +# This program 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 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. + +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) diff --git a/bindings/python/meson.build b/bindings/python/meson.build new file mode 100644 index 0000000000..f0e79c14f7 --- /dev/null +++ b/bindings/python/meson.build @@ -0,0 +1 @@ +install_data(['gi/overrides/GES.py'], install_dir: pygi_override_dir) \ No newline at end of file diff --git a/data/completions/ges-launch-1.0 b/data/completions/ges-launch-1.0 new file mode 100644 index 0000000000..798c145a5f --- /dev/null +++ b/data/completions/ges-launch-1.0 @@ -0,0 +1,235 @@ +# GStreamer +# Copyright (C) 2015 Mathieu Duponchelle +# +# bash/zsh completion support for ges-launch +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library 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 +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +HELPERDIR="${BASH_SOURCE[0]%/*}/../helpers" + +if [[ ! -d "$HELPERDIR" ]]; then + HELPERDIR="$(pkg-config --variable=bashhelpersdir gstreamer-1.0)" +else + HELPERDIR=`cd "$HELPERDIR"; pwd` +fi + +# Common definitions +. "$HELPERDIR"/gst + +HELPER="$_GST_HELPER" + +_list_commands () +{ + ges-launch-1.0 help | grep '^ +' | cut -d' ' -f3 +} + +_ges___inspect_action_type () +{ + COMPREPLY=( $(compgen -W "$(ges-launch-1.0 --inspect-action-type | grep '^[^ ]' | cut -d':' -f2)" -- $cur) ) +} + +_ges___track_types () +{ + COMPREPLY=( $(compgen -W "audio video audio+video" -- $cur) ) +} + +_ges___set_scenario () { + COMPREPLY=( $(compgen -W "*.scenario $(gst-validate-1.0 -l | awk '$0=$2' FS=[ RS=])" -- $cur) ) +} + +_ges___load () { + COMPREPLY=( $(compgen -W "*.xges" -- $cur) ) +} + +_ges___outputuri () { + COMPREPLY=( $(compgen -W "file://" -- $cur) ) +} + +_ges___audiosink () { + COMPREPLY=( $(compgen -W "$($HELPER --klass=Sink --sinkcaps='audio/x-raw')" -- $cur) ) +} + +_ges___videosink () { + COMPREPLY=( $(compgen -W "$($HELPER --klass=Sink --sinkcaps='video/x-raw')" -- $cur) ) +} + +_ges_clip () { + if [[ "$prev" == "$command" ]]; + then + _gst_mandatory_argument + else + COMPREPLY=( $(compgen -W "duration= inpoint= start= layer= $(_list_commands)" -- $cur) ) + fi +} + +_ges_test_clip () { + if [[ "$prev" == "$command" ]]; + then + _gst_mandatory_argument + else + COMPREPLY=( $(compgen -W "duration= inpoint= start= layer= $(_list_commands)" -- $cur) ) + fi +} + +_ges_effect () { + if [[ "$prev" == "$command" ]]; + then + _gst_mandatory_argument + else + COMPREPLY=( $(compgen -W "duration= start= layer= $(_list_commands)" -- $cur) ) + fi +} + +_ges_list_options () { + _gst_all_arguments ges-launch-1.0 +} + +_ges_list_commands () { + COMPREPLY=( $(compgen -W "$(_list_commands)" -- $cur) ) +} + +_ges_list_properties () { + local props + + if [[ "$real_command" == "" ]] + then + _gst_mandatory_argument + elif [[ "$real_command" == "+clip" ]] + then + COMPREPLY=( $(compgen -W "set-alpha set-posx set-posy set-width set-height set-volume set-mute" -- $cur) ) + elif [[ "$real_command" == "+test-clip" ]] + then + COMPREPLY=( $(compgen -W "set-alpha set-posx set-posy set-width set-height set-volume set-mute" -- $cur) ) + elif [[ "$real_command" == "+effect" ]] + then + COMPREPLY=() + effect_bin_description="${effect_bin_description//\"/ }" + array=(${effect_bin_description//!/ }) + for i in "${array[@]}"; do + props=("$($HELPER --element-properties $i)") + for j in $props; do + j="${j//=/ }" + COMPREPLY+=( $(compgen -W "set-$j" -- $cur) ) + done + done + else + _gst_mandatory_argument + fi +} + +_ges___exclude_ () { _gst_mandatory_argument; } +_ges___encoding_profile () { _gst_mandatory_argument; } +_ges___ges_sample_path () { _gst_mandatory_argument; } +_ges___ges_sample_path_recurse () { _gst_mandatory_argument; } +_ges___thumbnail () { _gst_mandatory_argument; } +_ges___repeat () { _gst_mandatory_argument; } +_ges___save () { _gst_mandatory_argument; } + +containsElement () { + local e + for e in "${@:2}"; + do + [[ "$e" == "$1" ]] && return 0; + done + return 1 +} + +__ges_main () +{ + local i=1 c=1 command function_exists completion_func commands real_command effect_bin_description + + commands=($(_list_commands)) + real_command="" + effect_bin_description="" + + if [[ "$cur" == "=" ]]; then + _gst_mandatory_argument + return + fi + + while [[ $i -ne $COMP_CWORD ]]; + do + local var + var="${COMP_WORDS[i]}" + if [[ "$var" == "--"* ]] + then + command="$var" + elif containsElement "$var" "${commands[@]}"; + then + real_command="$var" + command="$var" + if [[ "$var" == "+effect" ]] + then + effect_bin_description="${COMP_WORDS[i+1]}" + fi + fi + i=$[$i+1] + done + + if [[ "$command" == "--gst"* ]]; then + completion_func="_${command//-/_}" + else + completion_func="_ges_${command//-/_}" + completion_func="${completion_func//+/}" + fi + + declare -f $completion_func >/dev/null 2>&1 + + function_exists=$? + + if [[ "$cur" == "-"* ]]; then + _ges_list_options + elif [[ "$cur" == "+"* ]]; then + _ges_list_commands + elif [[ "$cur" == "="* ]] + then + _gst_mandatory_argument + elif [[ "$cur" == "set-"* ]] + then + _ges_list_properties + elif [ $function_exists -eq 0 ] + then + $completion_func + else + _ges_list_commands + fi +} + +__ges_func_wrap () +{ + local cur prev + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + $1 +} + +# Setup completion for certain functions defined above by setting common +# variables and workarounds. +# This is NOT a public function; use at your own risk. +__ges_complete () +{ + local wrapper="__ges_wrap${2}" + eval "$wrapper () { __ges_func_wrap $2 ; }" + complete -o bashdefault -o default -o nospace -F $wrapper $1 2>/dev/null \ + || complete -o default -o nospace -F $wrapper $1 +} + +_ges () +{ + __ges_wrap__ges_main +} + +__ges_complete ges-launch-1.0 __ges_main diff --git a/docs/base-classes.md b/docs/base-classes.md new file mode 100644 index 0000000000..8efcd967ec --- /dev/null +++ b/docs/base-classes.md @@ -0,0 +1 @@ +# Base classes diff --git a/docs/deprecated.md b/docs/deprecated.md new file mode 100644 index 0000000000..6ac4a90f50 --- /dev/null +++ b/docs/deprecated.md @@ -0,0 +1,3 @@ +# Deprecated APIS + +Those APIs have been deprecated and shouldn't be used in newly written code. \ No newline at end of file diff --git a/docs/design/asset.txt b/docs/design/asset.txt new file mode 100644 index 0000000000..548908b730 --- /dev/null +++ b/docs/design/asset.txt @@ -0,0 +1,321 @@ +Assets +~~~~~~~~~ + +This draft document describes a possible design for asset objects. + +The assets should be used in order to instantiate objects of differents +types. + +Terminology: A asset is an object from which objects can be extracted. + +Summary +~~~~~~~~~ + +1. Basic ideas +2. Problems +3. Propositions to solve those problems +4. Use-cases +5. API draft + A. Asset API draft + B. Source asset API draft + C. Project asset API draft + D. Extractable/Asset Interface API draft + E. Methods that should be added to other classes + +1. Basic ideas +~~~~~~~~~~~~~~~ + +Basically, asset is a way of avoiding duplicating data between object and avoid +processing when the same processing would happen several times for an 2 different +objects of a same type. + + * There will be a listing of avalaible, ready to use assets + * Asset allow to create some particular types of object that implement the GESExtractable + interface + * Assets will hold metadatas + * Assets can be either, created by the user, or will be created by GES itself + when initializing, there should be a way to disable that feature on demand. + +Some ideas of asset(especially for TimelineSource objects) can be found in docs/random/design. + +2. Problems (Not in any particular order) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 1) We have avoid to various times the same file on the system + 2) We must be able to query assets by some criteria + a. By type of TimelineObject that it can produce + b. By type of supported tracks + c. Should we have filters by some specific properties of source asset + - like duration, width, height, etc? + 3) We must be able to get reference to origin asset of any extracted object + 4) We need a way to describe projects + 5) GESAssets can be instantiated asynchronously + 6) The instantiation of a asset can fail + 7) Users need to get informations about the instantiation failures + 8) User should be able to cancel the creation of a GESAsset (especially + in case of asynchronous Asset creation) + +3. Propositions to solve those problems +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + 1) We should have an interface that needs to be implemented by classes that need to be extractable. + We can call it GESExtractable. It should be responsible for: + * letting the user get the Asset from which an extracted object comes from + * Making it possible to instantiate a GESAsset only from a GType which means that: + - It needs to contain a reference to a GES_TYPE_ASSET (or subclass) so the proper GESAsset type will be instantiated. + - It need to contain some mapping between the ID (string) of the asset, and the property of the object that is used as its ID. + For a property to be usable as an ID for its asset, each objects extracted from a same asset must have the same value for the property + Examples: + GESTimelineFileSource -> URI + GESTrackParseLaunchEffect -> bin_description + GESProject -> project name / uri of the stored serialized + + 2) A list of all available, ready to be used assets should be cached and + reused whenever it is possible. + Basically it will look like: + GESAsset.id -> asset + (the ID is computed thanks to the mapping) + + 4) To allow users to implement some sort of library (media, effects, transitions...) + we must be able to query assets by using some criteria, + e.g. GType of the extractable object, URI, supported track types, etc... + + 5) We can instantiate a GESAsset only from a GType, the appropriate checks need + to be done and it can return subclasses of GESAsset thanks to the + information included in the GESExtractable interface. + + 6) Instanciation can happen asyncronously in some cases. For example, a + asset that needs to discover a file to be properly filled needs. + +4. Use cases +~~~~~~~~~~~~~ + UC-1. Define media files and discover them + UC-2. Define project - reference all assets + UC-3. Define titles + UC-4. Define operations + - Transitions - 1 asset per transition type + - Effects - 1 asset per effects type + - TextOverlay + UC-5. Handle metadata + UC-6. Add operations (only effects?) to a GESTimelineObject + UC-7. User want to 'invent' a new operation, we need to be able + to let him define it + UC-8. The user want to make an object from a GESAsset + + +5. API Draft +~~~~~~~~~~~~ + +A. GESExtractable API +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +GESExtractable is responsible for telling what GESAsset subclass need to +be instantiated. + +/** + * ges_extractable_object_get_asset: + * @object: Target object + * Method to get asset which was used to instaniate specified object + * Returns: origin asset + */ +GESAsset * +ges_extractable_get_asset(GESExtractable *extractable); + +/** + * ges_extractable_object_set_asset: + * @object: Target object + * @asset: (transfer none): The #GESAsset to set + * + * Method to set asset which was used to instaniate specified object + */ +void +ges_extractable_set_asset (GESExtractable * self, GESAsset * asset) + +/** + * ges_extractable_get_asset_type: + * @class: Get the #GType of the GESAsset that should be used to extract + * the object that implements that #GESExtractable interface + * + * Lets user know the type of GESAsset that should be used to extract the + * object that implement that interface. + */ +GType +ges_extractable_get_asset_type (GESExtractableClass *class) + +/** + * ges_extractable_get_id: + * @self: The #GESExtractable + * + * Returns: The #id of the associated #GESAsset + */ +const gchar * +ges_extractable_get_id (GESExtractable * self) + +/** + * ges_extractable_type_get_parameters_for_id: + * @type: The #GType implementing #GESExtractable + * @id: The ID of the Extractable + * @n_params: (out): Return location for the returned array + * + * Returns: (transfer full) (array length=n_params): an array of #GParameter + * needed to extract the #GESExtractable from a #GESAsset of @id + */ +GParameter * +ges_extractable_type_get_parameters_from_id (GType type, const gchar * id, + guint * n_params) + +/** + * ges_extractable_type_get_asset_type: + * @type: The #GType implementing #GESExtractable + * + * Get the #GType, subclass of #GES_TYPE_ASSET to instanciate + * to be able to extract a @type + * + * Returns: the #GType to use to create a asset to extract @type + */ +GType +ges_extractable_type_get_asset_type (GType type) + +/** + * ges_extractable_type_check_id: + * @type: The #GType implementing #GESExtractable + * @id: The ID to check + * + * Check if @id is valid for @type + * + * Returns: Return %TRUE if @id is valid, %FALSE otherwise + */ +gchar * +ges_extractable_type_check_id (GType type, const gchar * id) + + +A. Asset And subclasses API draft +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +a) GESAsset +| ~~~~~~~~~~~ +| +| Will implement GESMetdata +| +| Virtual method type: +| ------------------- +| +| /** +| * GESAssetCreatedCallback: +| * @asset: the #newly created #GESAsset or %NULL if something went wrong +| * @error: The #GError filled if previsouly provided in the constructor or %NULL +| * @user_data: The user data pointer +| * +| * A function that will be called when a #GESAsset is ready to be used. +| */ +| typedef void (*GESAssetCreatedCallback)(GESAsset *asset, GError *error, gpointer user_data); +| +| +| Methods prototypes: +| ------------------- +| /** +| * ges_asset_request: +| * @extractable_type: The #GType of the object that can be extracted from the new asset. +| * The class must implement the #GESExtractable interface. +| * @callback: a #GAsyncReadyCallback to call when the initialization is finished +| * @id: The Identifier of the asset we want to create. This identifier depends of the extractable +| * type you want. By default it is the name of the class itself (or %NULL), but for example for a +| * GESTrackParseLaunchEffect, it will be the pipeline description, for a GESTimelineFileSource it +| * will be the name of the file, etc... You should refer to the documentation of the #GESExtractable +| * type you want to create a #GESAsset for. +| * +| * Creates a new #GESAsset asyncronously, @callback will be called when the materail is loaded +| * +| * Returns: %TRUE if the asset could be loaded to load %FALSE otherwize +| */ +| gboolean +| ges_asset_request (GType extractable_type, GESAssetCreatedCallback callback, +| gpointer user_data, const gchar *id); +| +|->b) GESAssetTimelineObject +| | ~~~~~~~~~~~~~~~~~~~~~~~~~ +| | /** +| | * ges_asset_timeline_object_get_track_types: +| | * @asset: a #GESAssetTimelineObject +| | * +| | * Method that returns track types that are supported by given asset +| | * +| | * Returns: Track types that are supported by asset +| | */ +| | GESTrackType +| | ges_asset_timeline_object_get_track_types (GESAssetTimelineObject *asset); +| | +| | +| |-> c) GESAssetFileSource +| ~~~~~~~~~~~~~~~~~~~~~ +| /** +| * ges_asset_file_source_get_stream_info: +| * @asset: a #GESAsset of extractable_type GES_TIMELINE_FILE_SOURCE +| * Method that returns discoverer data of specified asset so user could work with +| * it directly +| * Returns: discover info of asset +| */ +| GstDiscovererStreamInfo * +| ges_asset_file_source_get_stream_info (GESAssetFileSource *asset); +| +| +|-> d) GESProjectAsset asset API + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + A project is a GESAsset that has GES_TYPE_TIMELINE or subclasses as extractable_type + + FIXME: This special case that should be thought thoroughly. + + /** + * ges_asset_project_list_assets: + * @asset: Project asset + * @type: Type of asset to list + * Method for listing assets of specified type that are available in + * particular project. + * + * Returns: list of available assets of given type in project + */ + ges_asset_project_list_assets (GESAsset *project, + GType type) + +E. Methods that should be added to other classes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +/** + * ges_timeline_layer_add_asset: + * + * Creates TimelineObject from asset, adds it to layer and + * returns reference to it. + * + * Returns: Created #GESTimelineObject + */ +GESTimelineObject * ges_timeline_layer_add_asset (GESTimelineLayer *layer, + GESAssetTimelineObject *asset, + GstClockTime start, + GstClockTime inpoint, + GstClockTime duration, + +/** + * ges_timeline_remove_extracted_from_asset: + * @timeline: A #GESTimeline from which to remove objects + * @asset: The #GESAssetTimelineObject to remove from @timeline + * + * Removes all asset in @timeline that have been extracted from @asset + * + * Returns: %TRUE if everything could be done properly %FALSE otherwize + */ +gboolean ges_timeline_layer_add_asset (GESTimeline *timeline, GESAsset *asset); + +/** + * ges_timeline_object_add_asset: + * @object: Target #GESTimelineObject + * @asset: a #GESAsset that must have a GES_TYPE_TRACK_OPERATION as extractable_type + * @priority: The priority of the new #GESTrackObject + * + * Adds an operation (GESTrackObject(s)) to a GESTimelineObject + * + * Returns: (transfer full):The newly created #GESTrackObject. + */ +GESTrackObject +ges_timeline_object_add_asset (GESTimelineObject *object, + GESAsset *asset, + guint32 priority); diff --git a/docs/design/effects.txt b/docs/design/effects.txt new file mode 100644 index 0000000000..59450896f3 --- /dev/null +++ b/docs/design/effects.txt @@ -0,0 +1,370 @@ +Effects +------- + +Summary +------- +1. Basic ideas +2. Problems +3. Propositions to solve those problems + A. The registry + B. Effects configurability + C. Keyframes +4. Use-cases +5. API draft + +The goal of this proposal is to design a simple way to handle effects through an +API which would allow developers to handle any use-cases. + +1. Basic ideas +---------------- + + * GESTrackEffects are subclasses of GESTrackOperation + + * You can add effects on any clip or layer + + * You can add effects over several clips and control them as a unique effect. + + * Effects are configurable and those properties can change during time + + * We must be able to handle third-party effect providers, like the + gnome-video-effects standard. + + * We must be able to implement complex effects. This means effects that are + more than adding GstElement-s to the timeline. It can also mean effects + that apply both video and audio changes. + +2. Problems +---------- + * We must be able to provide a list of effects available on the system at + runtime. + + * We must be able to configure effects through an API in GES + withtout having to access the GstElements properties directly. + + * We should also expose the GstElement-s contained in an effect so + it is possible for people to control their properties as they wish. + + * We must be able to implement and handle complexe effects directly in GES + + * We must be able to configure effects through time -> Keyframes without + duplicating code from GStreamer + + +3. Propositions to solve those problems +--------------------------------------- + +A. The registry => Still to design + + We could implement a GESRegistry which would actually + retrieve elements (effects) from the GSTRegistry and any other mean + such as gnome-video-effects to let us get all the effects that are present + on the system.... + This way the developers could have the list of all the effects + that are installed on the system pretty easily. + +B. Effects configurability + + The idea to be able to configure effects through a simple API in GES would + be to add an API in GESTrackObject to access the gst-object properties that + user would like to configure. + We would also have a method to set those properties easily. + + We should also find a way to handle that in the case of systems such as + gnome-effects + +C. Keyframes + + We may want to handle this use-case directly in GES and for any kind of + time related configuration? FIXME + => Special specifications for that? + +4. Use-cases +----------- + + UC-1. The user wants to add an effect to an entire clip => GESTimelineObject + new API + + UC-2. The developer wants to allow users to configure effects => New + GESTrackOperation API + + UC-3. The user wants to add an effect on a specific portion of a clip, we + should allow him to specify a portion of the clip where the effect should be + applied. + + UC-4. We want to implement an effect which isn't only composed by a bin, but + is more complexe than that (ex: "effect '24'") => we have the + GESTrackOperation which is the base class (abstract) for this kind of + implementation. This class should implement vmethods to get/set configurable + properties. + + UC-5. A developer wants to implement effect which handle music and video at + the same time, Would the solution be to implement a GESTimelineEffect + to handle this special usecase? FIXME + + UC-6. The developers wants to configure each elements of an effect the way + he wants + with a full control over it. + + UC-7. Developers want to expose all effects present on the system to the + end-user + +5. API draft +------------ + + + A. GESTrackObject new API + + signals: + ------- + * deep-notify: emited when a usefull property of a GstElement + contained in the GESTrackObject changes + => DONE + + /** + * ges_track_object_list_children_properties: + * + * @object: The origin #GESTrackObject + * + * A convenience method that lists all the usefull configurable properties + * of the GstElement-s contained in @object. + * + * Returns: an array of GParamSpec of the configurable properties of the + * GstElement-s contained in @object or %NULL if a problem occurred. + */ + GParamSpec ** + ges_track_object_list_children_properties (GESTrackObject *object); + + -> Usecases: Let user know all the property he can configure. + => Waiting for GESMaterial + + /** + * ges_track_object_set_child_property: + * + * @object: The origin #GESTrackObject + * @property_name: The name of the property + * @value: the value + * + * Sets a property of a GstElement contained in @object. + * + */ + void ges_track_object_set_child_property (GESTrackObject *object, + const gchar *property_name, + GValue * value); + -> Usecases: + + Let user configure effects easily (UC-3) + => DONE + + /** + * ges_track_object_get_child_property: + * + * @object: The origin #GESTrackObject + * @property_name: The name of the property + * @value: return location for the property value + * + * Gets a property of a GstElement contained in @object. + */ + void ges_track_object_get_child_property (GESTrackObject *object, + const gchar *property_name, + GValue * value); + => DONE + + /** + * ges_track_object_get_material: + * + * @object: The origin #GESTrackObject + * + * This is a convenience method to get the #GESMaterial + * from which @object has been made. + * + * Returns: The material from which @object has been made or %NULL + * if @object has been made by another mean + */ + GESMaterial *ges_track_object_get_material (GESTrackObject *object); + => Waiting for GESMaterial + + B. GESTimelineObject new API + + signals: + ------- + * effect-added: emited when an effect is added + * effect-removed: emited when an effect is removed + => DONE + + /** + * ges_timeline_object_add_effect: + * + * @object: The origin #GESTimelineObject + * @effect_material: The #GESEffect from which to create the effect + * @position: The top position you want to give to the effect, + * -1 if you want it to be added at the end of effects. + * + * Adds a new effect corresponding to @effect_material to the + * #GESTimelineObject + * + * Returns: The newly created #GESTrackEffect, or %NULL if there was an + * error. + */ + GESTrackEffect *ges_timeline_object_add_effect (GESTimelineObject *object, + GESEffect *effect_material, + gint position); + => Waiting for GESMaterial + + /** + * ges_timeline_object_get_effects: + * + * @object: The origin #GESTimelineObject + * + * Returns: a #GList of the #GESTrackEffect that are applied on + * @object order by ascendant priorities. + * The refcount of the objects will be increased. The user will have to + * unref each #GESTrackOperation and free the #GList. + */ + GList * + ges_timeline_object_get_effects (GESTimelineObject *object); + -> Usecases: + + First step to allow the configuration of effects (UC-3) + => DONE + + /** + * ges_timeline_object_set_top_effect_position: + * + * @object: The origin #GESTimelineObject + * @effect: The #GESTrackEffect to move + * @newposition: the new position at which to move the @effect + * + * Returns: %TRUE if @effect was successfuly moved, %FALSE otherwize. + */ + gboolean + ges_timeline_object_set_top_effect_position (GESTimelineObject *object, + GESTrackEffect *effect, guint newposition); + => DONE + + /** + * ges_timeline_object_get_top_effect_position: + * + * @object: The origin #GESTimelineObject + * @effect: The #GESTrackEffect we want to get the top position from + * + * Gets the top position of an effect. + * + * Returns: The top position of the effect, -1 if something went wrong. + */ + gint + ges_timeline_object_get_top_effect_position (GESTimelineObject *object, + GESTrackEffect *effect); + => DONE + + C - The GESTrackEffect API: + -> This is an empty abstract class + => DONE + + D - The GESTrackParseLaunchEffect API: + This is a parse-launch based implementation of TrackEffect. + + /** + * ges_track_parse_launch_effect_new: + * + * @bin_dec: The gst-launch like bin description of the effect + * + * Creates a new #GESTrackEffect from the description of the bin. This is + * a convenience method for testing puposes. + * + * Returns: a newly created #GESTrackEffect, or %NULL if something went + * wrong. + */ + GESTrackEffect *ges_track_parse_launch_effect_new (GESTrackEffect *effect, + const gchar *bin_desc); + => DONE + + E - The GESTrackMaterialEffect API: + /** + * ges_track_material_effect: + * + * @effect_material: The #GESEffect from which to create this + * #GESTrackEffect + * + * Creates a new #GESTrackEffect from a #GESEffect + * + * Returns: a newly created #GESTrackEffect, or %NULL if something went + * wrong. + */ + GESTrackEffect *ges_track_material_effect_new (GESTrackEffect *effect, + GESEffect *effect_material); + => Waiting for GESMaterial + + F - The GESTimelineEffect API: + -> This is an empty abstract class + => DONE + + -> Usecases: The user wants to control multiple effects in sync. The user + wants to add an effect to the whole timeline. The user wants + to had an effect to a segment of the timeline without caring + bout what clip it is applied on. + + G - The GESTimelineParseLaunchEffect API: + This is a parse-launch based implementation of TimelineEffect. + + /** + * ges_timeline_parse_launch_effect_new_from_bin_desc: + * @video_bin_description: The gst-launch like bin description of the effect + * @audio_bin_description: The gst-launch like bin description of the effect + * + * Creates a new #GESTimelineParseLaunchEffect from the description of the bin. + * + * Returns: a newly created #GESTimelineParseLaunchEffect, or %NULL if something went + * wrong. + */ + GESTimelineParseLaunchEffect * + ges_timeline_parse_launch_effect_new (const gchar * video_bin_description, + const gchar * audio_bin_description) + + => DONE + + + H - The GESEffect: + + The GESEffect class is a subclass of GESMaterial, it is used to describe + effects independently of the usage which is made of it in the timeline. + + A GESEffect can specify a GESTrackOperation class to use in a + TimelineObject. + + All important properties are inherited from GESMaterial such as: + * Name + * Description + * Tags + * ... + + We should also be able to list properties of the effect from the GESMaterial. + + => Waiting for GESMaterial + +================= + TODO GESRegistry API: + This should be a singleton since we don't want an app to instanciate more + than one registry. It must be able to get effects from various sources. + We should also make sure any custom effect is detected. + + /** + * ges_registry_get_default: + * + * Returns a newly created #GESEffectRegistry or the existing one + * increasing + * its refcount + */ + GESEffectRegistry * + ges_registry_get_default (void); + -> Usecases: + + Have a registry of all effects that are on the system (UC-8) + + /** + * ges_effect_registry_get_effect_list: + * + * @self: The origin #GESEffectRegistry + * + * Returns a #GList of #GESEffectDescriptors. The + */ + GList * + ges_registry_get_effect_list (GESEffectRegistry *self); + -> Usecases: + + Get all effects descriptors that are on the system (UC-8) diff --git a/docs/design/encoding-research.txt b/docs/design/encoding-research.txt new file mode 100644 index 0000000000..fb51a0816a --- /dev/null +++ b/docs/design/encoding-research.txt @@ -0,0 +1,110 @@ +GStreamer: Research into encoding and muxing +-------------------------------------------- + +Use Cases +--------- + + This is a list of various use-cases where encoding/muxing is being + used. + +* Transcoding + + The goal is to convert with as minimal loss of quality any input + file for a target use. + A specific variant of this is transmuxing (see below). + + Example applications: Arista, Transmageddon + +* Rendering timelines + + The incoming streams are a collection of various segments that need + to be rendered. + Those segments can vary in nature (i.e. the video width/height can + change). + This requires the use of identiy with the single-segment property + activated to transform the incoming collection of segments to a + single continuous segment. + + Example applications: Pitivi, Jokosher + +* Encoding of live sources + + The major risk to take into account is the encoder not encoding the + incoming stream fast enough. This is outside of the scope of + encodebin, and should be solved by using queues between the sources + and encodebin, as well as implementing QoS in encoders and sources + (the encoders emitting QoS events, and the upstream elements + adapting themselves accordingly). + + Example applications: camerabin, cheese + +* Screencasting applications + + This is similar to encoding of live sources. + The difference being that due to the nature of the source (size and + amount/frequency of updates) one might want to do the encoding in + two parts: + * The actual live capture is encoded with a 'almost-lossless' codec + (such as huffyuv) + * Once the capture is done, the file created in the first step is + then rendered to the desired target format. + + Fixing sources to only emit region-updates and having encoders + capable of encoding those streams would fix the need for the first + step but is outside of the scope of encodebin. + + Example applications: Istanbul, gnome-shell, recordmydesktop + +* Live transcoding + + This is the case of an incoming live stream which will be + broadcasted/transmitted live. + One issue to take into account is to reduce the encoding latency to + a minimum. This should mostly be done by picking low-latency + encoders. + + Example applications: Rygel, Coherence + +* Transmuxing + + Given a certain file, the aim is to remux the contents WITHOUT + decoding into either a different container format or the same + container format. + Remuxing into the same container format is useful when the file was + not created properly (for example, the index is missing). + Whenever available, parsers should be applied on the encoded streams + to validate and/or fix the streams before muxing them. + + Metadata from the original file must be kept in the newly created + file. + + Example applications: Arista, Transmaggedon + +* Loss-less cutting + + Given a certain file, the aim is to extract a certain part of the + file without going through the process of decoding and re-encoding + that file. + This is similar to the transmuxing use-case. + + Example applications: Pitivi, Transmageddon, Arista, ... + +* Multi-pass encoding + + Some encoders allow doing a multi-pass encoding. + The initial pass(es) are only used to collect encoding estimates and + are not actually muxed and outputted. + The final pass uses previously collected information, and the output + is then muxed and outputted. + +* Archiving and intermediary format + + The requirement is to have lossless + +* CD ripping + + Example applications: Sound-juicer + +* DVD ripping + + Example application: Thoggen diff --git a/docs/design/encoding.txt b/docs/design/encoding.txt new file mode 100644 index 0000000000..1bce7ac64e --- /dev/null +++ b/docs/design/encoding.txt @@ -0,0 +1,463 @@ +Encoding and Muxing +------------------- + +Summary +------- + A. Problems + B. Goals + 1. EncodeBin + 2. Encoding Profile System + 3. Helper Library for Profiles + + + +A. Problems this proposal attempts to solve +------------------------------------------- + +* Duplication of pipeline code for gstreamer-based applications + wishing to encode and or mux streams, leading to subtle differences + and inconsistencies accross those applications. + +* No unified system for describing encoding targets for applications + in a user-friendly way. + +* No unified system for creating encoding targets for applications, + resulting in duplication of code accross all applications, + differences and inconsistencies that come with that duplication, + and applications hardcoding element names and settings resulting in + poor portability. + + + +B. Goals +-------- + +1. Convenience encoding element + + Create a convenience GstBin for encoding and muxing several streams, + hereafter called 'EncodeBin'. + + This element will only contain one single property, which is a + profile. + +2. Define a encoding profile system + +2. Encoding profile helper library + + Create a helper library to: + * create EncodeBin instances based on profiles, and + * help applications to create/load/save/browse those profiles. + + + + +1. EncodeBin +------------ + +1.1 Proposed API +---------------- + + EncodeBin is a GstBin subclass. + + It implements the GstTagSetter interface, by which it will proxy the + calls to the muxer. + + Only two introspectable property (i.e. usable without extra API): + * A GstEncodingProfile* + * The name of the profile to use + + When a profile is selected, encodebin will: + * Add REQUEST sinkpads for all the GstStreamProfile + * Create the muxer and expose the source pad + + Whenever a request pad is created, encodebin will: + * Create the chain of elements for that pad + * Ghost the sink pad + * Return that ghost pad + + This allows reducing the code to the minimum for applications + wishing to encode a source for a given profile: + + ... + + encbin = gst_element_factory_make("encodebin, NULL); + g_object_set (encbin, "profile", "N900/H264 HQ", NULL); + gst_element_link (encbin, filesink); + + ... + + vsrcpad = gst_element_get_src_pad(source, "src1"); + vsinkpad = gst_element_request_pad_simple (encbin, "video_%d"); + gst_pad_link(vsrcpad, vsinkpad); + + ... + + +1.2 Explanation of the Various stages in EncodeBin +-------------------------------------------------- + + This describes the various stages which can happen in order to end + up with a multiplexed stream that can then be stored or streamed. + +1.2.1 Incoming streams + + The streams fed to EncodeBin can be of various types: + + * Video + * Uncompressed (but maybe subsampled) + * Compressed + * Audio + * Uncompressed (audio/x-raw-{int|float}) + * Compressed + * Timed text + * Private streams + + +1.2.2 Steps involved for raw video encoding + +(0) Incoming Stream + +(1) Transform raw video feed (optional) + + Here we modify the various fundamental properties of a raw video + stream to be compatible with the intersection of: + * The encoder GstCaps and + * The specified "Stream Restriction" of the profile/target + + The fundamental properties that can be modified are: + * width/height + This is done with a video scaler. + The DAR (Display Aspect Ratio) MUST be respected. + If needed, black borders can be added to comply with the target DAR. + * framerate + * format/colorspace/depth + All of this is done with a colorspace converter + +(2) Actual encoding (optional for raw streams) + + An encoder (with some optional settings) is used. + +(3) Muxing + + A muxer (with some optional settings) is used. + +(4) Outgoing encoded and muxed stream + + +1.2.3 Steps involved for raw audio encoding + + This is roughly the same as for raw video, expect for (1) + +(1) Transform raw audo feed (optional) + + We modify the various fundamental properties of a raw audio stream to + be compatible with the intersection of: + * The encoder GstCaps and + * The specified "Stream Restriction" of the profile/target + + The fundamental properties that can be modifier are: + * Number of channels + * Type of raw audio (integer or floating point) + * Depth (number of bits required to encode one sample) + + +1.2.4 Steps involved for encoded audio/video streams + + Steps (1) and (2) are replaced by a parser if a parser is available + for the given format. + + +1.2.5 Steps involved for other streams + + Other streams will just be forwarded as-is to the muxer, provided the + muxer accepts the stream type. + + + + +2. Encoding Profile System +-------------------------- + + This work is based on: + * The existing GstPreset system for elements [0] + * The gnome-media GConf audio profile system [1] + * The investigation done into device profiles by Arista and + Transmageddon [2 and 3] + +2.2 Terminology +--------------- + +* Encoding Target Category + A Target Category is a classification of devices/systems/use-cases + for encoding. + + Such a classification is required in order for: + * Applications with a very-specific use-case to limit the number of + profiles they can offer the user. A screencasting application has + no use with the online services targets for example. + * Offering the user some initial classification in the case of a + more generic encoding application (like a video editor or a + transcoder). + + Ex: + Consumer devices + Online service + Intermediate Editing Format + Screencast + Capture + Computer + +* Encoding Profile Target + A Profile Target describes a specific entity for which we wish to + encode. + A Profile Target must belong to at least one Target Category. + It will define at least one Encoding Profile. + + Ex (with category): + Nokia N900 (Consumer device) + Sony PlayStation 3 (Consumer device) + Youtube (Online service) + DNxHD (Intermediate editing format) + HuffYUV (Screencast) + Theora (Computer) + +* Encoding Profile + A specific combination of muxer, encoders, presets and limitations. + + Ex: + Nokia N900/H264 HQ + Ipod/High Quality + DVD/Pal + Youtube/High Quality + HTML5/Low Bandwith + DNxHD + +2.3 Encoding Profile +-------------------- + +An encoding profile requires the following information: + + * Name + This string is not translatable and must be unique. + A recommendation to guarantee uniqueness of the naming could be: + / + * Description + This is a translatable string describing the profile + * Muxing format + This is a string containing the GStreamer media-type of the + container format. + * Muxing preset + This is an optional string describing the preset(s) to use on the + muxer. + * Multipass setting + This is a boolean describing whether the profile requires several + passes. + * List of Stream Profile + +2.3.1 Stream Profiles + +A Stream Profile consists of: + + * Type + The type of stream profile (audio, video, text, private-data) + * Encoding Format + This is a string containing the GStreamer media-type of the encoding + format to be used. If encoding is not to be applied, the raw audio + media type will be used. + * Encoding preset + This is an optional string describing the preset(s) to use on the + encoder. + * Restriction + This is an optional GstCaps containing the restriction of the + stream that can be fed to the encoder. + This will generally containing restrictions in video + width/heigh/framerate or audio depth. + * presence + This is an integer specifying how many streams can be used in the + containing profile. 0 means that any number of streams can be + used. + * pass + This is an integer which is only meaningful if the multipass flag + has been set in the profile. If it has been set it indicates which + pass this Stream Profile corresponds to. + +2.4 Example profile +------------------- + +The representation used here is XML only as an example. No decision is +made as to which formatting to use for storing targets and profiles. + + + Nokia N900 + Consumer Device + + Nokia N900/H264 HQ + Nokia N900/MP3 + Nokia N900/AAC + + + + + Nokia N900/H264 HQ + + High Quality H264/AAC for the Nokia N900 + + video/quicktime,variant=iso + + + audio + audio/mpeg,mpegversion=4 + Quality High/Main + audio/x-raw-int,channels=[1,2] + 1 + + + video + video/x-h264 + Profile Baseline/Quality High + + video/x-raw-yuv,width=[16, 800],\ + height=[16, 480],framerate=[1/1, 30000/1001] + + 1 + + + + + +2.5 API +------- + A proposed C API is contained in the gstprofile.h file in this directory. + + +2.6 Modifications required in the existing GstPreset system +----------------------------------------------------------- + +2.6.1. Temporary preset. + + Currently a preset needs to be saved on disk in order to be + used. + + This makes it impossible to have temporary presets (that exist only + during the lifetime of a process), which might be required in the + new proposed profile system + +2.6.2 Categorisation of presets. + + Currently presets are just aliases of a group of property/value + without any meanings or explanation as to how they exclude each + other. + + Take for example the H264 encoder. It can have presets for: + * passes (1,2 or 3 passes) + * profiles (Baseline, Main, ...) + * quality (Low, medium, High) + + In order to programmatically know which presets exclude each other, + we here propose the categorisation of these presets. + + This can be done in one of two ways + 1. in the name (by making the name be [:]) + This would give for example: "Quality:High", "Profile:Baseline" + 2. by adding a new _meta key + This would give for example: _meta/category:quality + +2.6.3 Aggregation of presets. + + There can be more than one choice of presets to be done for an + element (quality, profile, pass). + + This means that one can not currently describe the full + configuration of an element with a single string but with many. + + The proposal here is to extend the GstPreset API to be able to set + all presets using one string and a well-known separator ('/'). + + This change only requires changes in the core preset handling code. + + This would allow doing the following: + gst_preset_load_preset (h264enc, + "pass:1/profile:baseline/quality:high"); + +2.7 Points to be determined +--------------------------- + + This document hasn't determined yet how to solve the following + problems: + +2.7.1 Storage of profiles + + One proposal for storage would be to use a system wide directory + (like $prefix/share/gstreamer-0.10/profiles) and store XML files for + every individual profiles. + + Users could then add their own profiles in ~/.gstreamer-0.10/profiles + + This poses some limitations as to what to do if some applications + want to have some profiles limited to their own usage. + + +3. Helper library for profiles +------------------------------ + + These helper methods could also be added to existing libraries (like + GstPreset, GstPbUtils, ..). + + The various API proposed are in the accompanying gstprofile.h file. + +3.1 Getting user-readable names for formats + + This is already provided by GstPbUtils. + +3.2 Hierarchy of profiles + + The goal is for applications to be able to present to the user a list + of combo-boxes for choosing their output profile: + + [ Category ] # optional, depends on the application + [ Device/Site/.. ] # optional, depends on the application + [ Profile ] + + Convenience methods are offered to easily get lists of categories, + devices, and profiles. + +3.3 Creating Profiles + + The goal is for applications to be able to easily create profiles. + + The applications needs to be able to have a fast/efficient way to: + * select a container format and see all compatible streams he can use + with it. + * select a codec format and see which container formats he can use + with it. + + The remaining parts concern the restrictions to encoder + input. + +3.4 Ensuring availability of plugins for Profiles + + When an application wishes to use a Profile, it should be able to + query whether it has all the needed plugins to use it. + + This part will use GstPbUtils to query, and if needed install the + missing plugins through the installed distribution plugin installer. + + + + +* Research links + + Some of these are still active documents, some other not + +[0] GstPreset API documentation + http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/GstPreset.html + +[1] gnome-media GConf profiles + http://www.gnome.org/~bmsmith/gconf-docs/C/gnome-media.html + +[2] Research on a Device Profile API + http://gstreamer.freedesktop.org/wiki/DeviceProfile + +[3] Research on defining presets usage + http://gstreamer.freedesktop.org/wiki/PresetDesign + diff --git a/docs/design/gstencodebin.h b/docs/design/gstencodebin.h new file mode 100644 index 0000000000..dc6a259771 --- /dev/null +++ b/docs/design/gstencodebin.h @@ -0,0 +1,46 @@ +/* GStreamer encoding bin + * Copyright (C) 2009 Edward Hervey + * (C) 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_ENCODEBIN_H__ +#define __GST_ENCODEBIN_H__ + +#include +#include + +#define GST_TYPE_ENCODE_BIN (gst_encode_bin_get_type()) +#define GST_ENCODE_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ENCODE_BIN,GstPlayBin)) +#define GST_ENCODE_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ENCODE_BIN,GstPlayBinClass)) +#define GST_IS_ENCODE_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ENCODE_BIN)) +#define GST_IS_ENCODE_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ENCODE_BIN)) + +typedef struct _GstEncodebin GstEncodeBin; + +struct _GstEncodeBin { + GstBin parent; + + GstProfile *profile; +}; + +GType gst_encode_bin_get_type(void); + +GstElement *gst_encode_bin_new (GstProfile *profile, gchar *name); +gboolean gst_encode_bin_set_profile (GstEncodeBin *ebin, GstProfile *profile); + +#endif __GST_ENCODEBIN_H__ diff --git a/docs/design/gstprofile.h b/docs/design/gstprofile.h new file mode 100644 index 0000000000..bfca568610 --- /dev/null +++ b/docs/design/gstprofile.h @@ -0,0 +1,231 @@ +/* GStreamer encoding profiles library + * Copyright (C) 2009 Edward Hervey + * (C) 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_PROFILE_H__ +#define __GST_PROFILE_H__ + +#include + +typedef enum { + GST_ENCODING_PROFILE_UNKNOWN, + GST_ENCODING_PROFILE_VIDEO, + GST_ENCODING_PROFILE_AUDIO, + GST_ENCODING_PROFILE_TEXT + /* Room for extenstion */ +} GstEncodingProfileType; + +typedef struct _GstEncodingTarget GstEncodingTarget; +typedef struct _GstEncodingProfile GstEncodingProfile; +typedef struct _GstStreamEncodingProfile GstStreamEncodingProfile; +typedef struct _GstVideoEncodingProfile GstVideoEncodingProfile; + +/* FIXME/UNKNOWNS + * + * Should encoding categories be well-known strings/quarks ? + * + */ + +/** + * GstEncodingTarget: + * @name: The name of the target profile. + * @category: The target category (device, service, use-case). + * @profiles: A list of #GstProfile this device supports. + * + */ +struct _GstEncodingTarget { + gchar *name; + gchar *category; + GList *profiles; +} + +/** + * GstEncodingProfile: + * @name: The name of the profile + * @format: The GStreamer mime type corresponding to the muxing format. + * @preset: The name of the #GstPreset(s) to be used on the muxer. This is optional. + * @multipass: Whether this profile is a multi-pass profile or not. + * @encodingprofiles: A list of #GstStreamEncodingProfile for the various streams. + * + */ + +struct _GstEncodingProfile { + gchar *name; + gchar *format; + gchar *preset; + gboolean multipass; + GList *encodingprofiles; +}; + +/** + * GstStreamEncodingProfile: + * @type: Type of profile + * @format: The GStreamer mime type corresponding to the encoding format. + * @preset: The name of the #GstPreset to be used on the encoder. This is optional. + * @restriction: The #GstCaps restricting the input. This is optional. + * @presence: The number of streams that can be created. 0 => any. + */ +struct _GstStreamEncodingProfile { + GstEncodingProfileType type; + gchar *format; + gchar *preset; + GstCaps *restriction; + guint presence; +}; + +/** + * GstVideoEncodingProfile: + * @profile: common #GstEncodingProfile part. + * @pass: The pass number if this is part of a multi-pass profile. Starts at 1 + * for multi-pass. Set to 0 if this is not part of a multi-pass profile. + * @variable_framerate: Do not enforce framerate on incoming raw stream. Default + * is FALSE. + */ +struct _GstVideoEncodingProfile { + GstStreamEncodingProfile profile; + guint pass; + gboolean variable_framerate; +}; + +/* Generic helper API */ +/** + * gst_encoding_category_list_target: + * @category: a profile target category name. Can be NULL. + * + * Returns the list of all available #GstProfileTarget for the given @category. + * If @category is #NULL, then all available #GstProfileTarget are returned. + */ +GList *gst_encoding_category_list_target (gchar *category); + +/** + * list available profile target categories + */ +GList *gst_profile_list_target_categories (); + +gboolean gst_profile_target_save (GstProfileTarget *target); + +/** + * gst_encoding_profile_get_input_caps: + * @profile: a #GstEncodingProfile + * + * Returns: the list of all caps the profile can accept. Caller must call + * gst_cap_unref on all unwanted caps once it is done with the list. + */ +GList * gst_profile_get_input_caps (GstEncodingProfile *profile); + +/* + * Application convenience methods (possibly to be added in gst-pb-utils) + */ + +/** + * gst_pb_utils_create_encoder: + * @caps: The #GstCaps corresponding to a codec format + * @preset: The name of a preset + * @name: The name to give to the returned instance, can be #NULL. + * + * Creates an encoder which can output the given @caps. If several encoders can + * output the given @caps, then the one with the highest rank will be picked. + * If a @preset is specified, it will be applied to the created encoder before + * returning it. + * If a @preset is specified, then the highest-ranked encoder that can accept + * the givein preset will be returned. + * + * Returns: The encoder instance with the preset applied if it is available. + * #NULL if no encoder is available. + */ +GstElement *gst_pb_utils_create_encoder(GstCaps *caps, gchar *preset, gchar *name); +/** + * gst_pb_utils_create_encoder_format: + * + * Convenience version of @gst_pb_utils_create_encoder except one does not need + * to create a #GstCaps. + */ +GstElement *gst_pb_utils_create_encoder_format(gchar *format, gchar *preset, + gchar *name); + +/** + * gst_pb_utils_create_muxer: + * @caps: The #GstCaps corresponding to a codec format + * @preset: The name of a preset + * + * Creates an muxer which can output the given @caps. If several muxers can + * output the given @caps, then the one with the highest rank will be picked. + * If a @preset is specified, it will be applied to the created muxer before + * returning it. + * If a @preset is specified, then the highest-ranked muxer that can accept + * the givein preset will be returned. + * + * Returns: The muxer instance with the preset applied if it is available. + * #NULL if no muxer is available. + */ +GstElement *gst_pb_utils_create_muxer(GstCaps *caps, gchar *preset); +/** + * gst_pb_utils_create_muxer_format: + * + * Convenience version of @gst_pb_utils_create_muxer except one does not need + * to create a #GstCaps. + */ +GstElement *gst_pb_utils_create_muxer_format(gchar *format, gchar *preset, + gchar *name); + +/** + * gst_pb_utils_encoders_compatible_with_muxer: + * @muxer: a muxer instance + * + * Finds a list of available encoders whose output can be fed to the given + * @muxer. + * + * Returns: A list of compatible encoders, or #NULL if none can be found. + */ +GList *gst_pb_utils_encoders_compatible_with_muxer(GstElement *muxer); + +GList *gst_pb_utils_muxers_compatible_with_encoder(GstElement *encoder); + + +/* + * GstPreset modifications + */ + +/** + * gst_preset_create: + * @preset: The #GstPreset on which to create the preset + * @name: A name for the preset + * @properties: The properties + * + * Creates a new preset with the given properties. This preset will only + * exist during the lifetime of the process. + * If you wish to use it after the lifetime of the process, you must call + * @gst_preset_save_preset. + * + * Returns: #TRUE if the preset could be created, else #FALSE. + */ +gboolean gst_preset_create (GstPreset *preset, gchar *name, + GstStructure *properties); + +/** + * gst_preset_reset: + * @preset: a #GstPreset + * + * Sets all the properties of the element back to their default values. + */ +/* FIXME : This could actually be put at the GstObject level, or maybe even + * at the GObject level. */ +void gst_preset_reset (GstPreset *preset); + +#endif /* __GST_PROFILE_H__ */ diff --git a/docs/design/metadata.txt b/docs/design/metadata.txt new file mode 100644 index 0000000000..9e45006b9d --- /dev/null +++ b/docs/design/metadata.txt @@ -0,0 +1,263 @@ +Metadata +~~~~~~~~ + +Summary +~~~~~~~ + +1. Basic ideas +2. Problems +3. Ways of solving problems +4. Use-cases +5. API draft + +1. Basic ideas +~~~~~~~~~~~~~~ + +If we look at entities that are present in GES we can see that almost all of +them need some sort of metadata: + * GESTimeline + * GESTimelineLayer + * GESTimelineObject + * GESTrackObject + * Yet to be implemented GESProject + +For all those classes to be able to contain metadatas and to avoid code +duplication as much as possible, we should have an interface to handle Metadata. +Let's call the interface GESMetaContainer for now (name to be defined). + +2. Problems +~~~~~~~~~~~ + + 1) We must be able to discover all metadata items that are + attached to object + 2) We must be able to hold metadata of any type user wants + 3) Some metadatas are read only, others are writable + 4) User should be able to query metadata easily using various criteria + 5) Metadatas should be serializable + 6) User should be able to define read only metadatas with a default value + 7) User should be able to define metadatas that have a specific type which can not + be changed when setting a new value + + +3. Possible solution +~~~~~~~~~~~~~~~~~~~~~ + + 1) To implement metadata GstStructure will be used. It allows to get list of + all available tags in specified list by calling "gst_structure_foreach". + 2) We will have methods to register metas + +4. Use-cases +~~~~~~~~~~~~ + + UC-1. Hold tag information about file source asset. + - TS: I think some of them are TrackObject specific... so we should be + able to get them from the 2 types of objects + UC-2. Hold descriptions of operations + UC-3. Hold information about projects (title, author, description) + UC-4. Hold user comments about any of TimelineLayer/Timeline/Project/TimelineObjects + UC-5. Hold application specific settings (i.e. layer height, folding state + in Pitivi) + UC-6. Serialize a timeline, project and keep metadatas + +5. API +~~~~~~ + +We have a GESMetdata class that controls metadata. + +gboolean +ges_meta_container_set_boolean (GESMetaContainer *container, + const gchar* meta_item, + gboolean value); + +gboolean +ges_meta_container_set_int (GESMetaContainer *container, + const gchar* meta_item, + gint value); + +gboolean +ges_meta_container_set_uint (GESMetaContainer *container, + const gchar* meta_item, + guint value); + +gboolean +ges_meta_container_set_int64 (GESMetaContainer *container, + const gchar* meta_item, + gint64 value); + +gboolean +ges_meta_container_set_uint64 (GESMetaContainer *container, + const gchar* meta_item, + guint64 value); + +gboolean +ges_meta_container_set_float (GESMetaContainer *container, + const gchar* meta_item, + gfloat value); + +gboolean +ges_meta_container_set_double (GESMetaContainer *container, + const gchar* meta_item, + gdouble value); + +gboolean +ges_meta_container_set_date (GESMetaContainer *container, + const gchar* meta_item, + const GDate* value); + +gboolean +ges_meta_container_set_date_time (GESMetaContainer *container, + const gchar* meta_item, + const GstDateTime* value); + +gboolean +ges_meta_container_set_string (GESMetaContainer *container, + const gchar* meta_item, + const gchar* value); + +gboolean +ges_meta_container_set_meta (GESMetaContainer * container, + const gchar* meta_item, + const GValue *value); + +gboolean +ges_meta_container_register_meta_boolean (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + gboolean value); + +gboolean +ges_meta_container_register_meta_int (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + gint value); + +gboolean +ges_meta_container_register_meta_uint (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + guint value); + +gboolean +ges_meta_container_register_meta_int64 (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + gint64 value); + +gboolean +ges_meta_container_register_meta_uint64 (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + guint64 value); + +gboolean +ges_meta_container_register_meta_float (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + gfloat value); + +gboolean +ges_meta_container_register_meta_double (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + gdouble value); + +gboolean +ges_meta_container_register_meta_date (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + const GDate* value); + +gboolean +ges_meta_container_register_meta_date_time (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + const GstDateTime* value); + +gboolean +ges_meta_container_register_meta_string (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + const gchar* value); + +gboolean +ges_meta_container_register_meta (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + const GValue * value); + +gboolean +ges_meta_container_check_meta_registered (GESMetaContainer *container, + const gchar * meta_item, + GESMetaFlag * flags, + GType * type); + +gboolean +ges_meta_container_get_boolean (GESMetaContainer *container, + const gchar* meta_item, + gboolean* dest); + +gboolean +ges_meta_container_get_int (GESMetaContainer *container, + const gchar* meta_item, + gint* dest); + +gboolean +ges_meta_container_get_uint (GESMetaContainer *container, + const gchar* meta_item, + guint* dest); + +gboolean +ges_meta_container_get_int64 (GESMetaContainer *container, + const gchar* meta_item, + gint64* dest); + +gboolean +ges_meta_container_get_uint64 (GESMetaContainer *container, + const gchar* meta_item, + guint64* dest); + +gboolean +ges_meta_container_get_float (GESMetaContainer *container, + const gchar* meta_item, + gfloat* dest); + +gboolean +ges_meta_container_get_double (GESMetaContainer *container, + const gchar* meta_item, + gdouble* dest); + +gboolean +ges_meta_container_get_date (GESMetaContainer *container, + const gchar* meta_item, + GDate** dest); + +gboolean +ges_meta_container_get_date_time (GESMetaContainer *container, + const gchar* meta_item, + GstDateTime** dest); + +const gchar * +ges_meta_container_get_string (GESMetaContainer * container, + const gchar * meta_item); + +const GValue * +ges_meta_container_get_meta (GESMetaContainer * container, + const gchar * key); + +typedef void +(*GESMetaForeachFunc) (const GESMetaContainer *container, + const gchar *key, + const GValue *value, + gpointer user_data); + +void +ges_meta_container_foreach (GESMetaContainer *container, + GESMetaForeachFunc func, + gpointer user_data); + +gchar * +ges_meta_container_metas_to_string (GESMetaContainer *container); + +gboolean +ges_meta_container_add_metas_from_string (GESMetaContainer *container, + const gchar *str); diff --git a/docs/design/time_notes.md b/docs/design/time_notes.md new file mode 100644 index 0000000000..46180de9ad --- /dev/null +++ b/docs/design/time_notes.md @@ -0,0 +1,412 @@ +# Time Notes + +Some notes on time coordinates and time effects in GES. + +## Time Coordinate Definitions + +A timeline will have a single time coordinate, which runs from `0` onwards in `GstClockTime`. Each track in the timeline will share the same time. + +For a given track, at any given timeline time `time`, we have a stack of `GESTrackElement`s whose interval `[start, start + duration]` contains `time`. The elements are linked in order or priority. Each element will have four time coordinates per *each* unique stack it is part of: + ++ external sink coordinates: the coordinates used at the boundary between the upstream element and itself. This is the external source coordinates of the upstream element minus `(downstream-start - upstream-start)`. If it has no upstream element, these coordinates do not exist. ++ external source coordinates: the coordinates used at the boundary between the downstream element and itself. This is the external sink coordinates of the downstream element minus `(upstream-start - downstream-start)`. If it has no downstream element, these coordinates can be translated to the timeline coordinates by adding the `start` of the element. ++ internal sink coordinates: the coordinates used for the sink of the first internal `GstElement`. This is the external sink coordinates plus `in-point`. ++ internal source coordinates: the coordinates used at the source of the last internal `GstElement`. This will differ from the internal sink coordinates if one of the `GstElement`s applies a rate-changing effects. This is the external source coordinates plus `in-point`. Note that an element that changes the consumption rate should always have its in-point set to `0`. This is because nleghostpad is not able to 'undo' this shift by `in-point` at the opposite pad. + +The following diagram shows where these coordinates are used, and how they are transformed. Below we have a `GESSource`, followed by a `GESOperation` that does not perform any rate-changing effect, followed by a `GESEffect` that does apply a rate-changing effect (and so its `in-point` is `0`). + +``` +time coordinate coordinate +coords object transformation transformation +used upwards downwards +______________________________________________________________________________________ + + | source (1) | +int. src |---------------------------| + | | + in-point-1 - in-point-1 +ex. src '===========================' + - start-1 + start-2 + start-1 - start-2 +ex. sink .===========================. + | | - in-point-2 + in-point-2 +int. sink |---------------------------| + | operation (2) | identity () identity () +int. src |---------------------------| + | | + in-point-2 - in-point-2 +ex. src '===========================' + - start-2 + start-3 + start-2 - start-3 +ex. sink .===========================. + | | - 0 + 0 +int. sink |---------------------------| + | time effect (3) | f () f^-1 () +int. src |---------------------------| + | | + 0 - 0 +ex. src '===========================' + - start-3 + start-3 +timeline +++++++++++++++++++++++++++++ + timeline +``` + +The given function `f` summarises how a seek will transform as it goes from from the source to the sink of the internal `GstElement`, and `f^-1` summarises how a segment stream time will transform. + +In particular, `f` will be a function + +``` +f: [0, MAX] -> [0, G_MAXUINT64 - 1] +``` + +where `MAX` is some `guint64`. For what follows, we will only fully support time effects whose function `f`: + ++ is monotonically increasing. This would exclude time effects that play some later content, and then jump back to earlier content. ++ is 'continuously reversible'. We define `T_d` for the time `t` as the set of all the times `t'` such that `|f (t') - t| <= d`. This property requires that, for any `t` between `f`'s minimum and maximum values, we can choose a small `d` such that `T_d` is not empty, is small and has no gaps. The word "small" refers to an unnoticeable difference (the times are in nanoseconds). This means that `f` can be approximately reversed at all points between its minimum and maximum, which means that `f^-1` can act as a close inverse of `f`. For a monotonically increasing function, this means that `f` is *steadily* increasing. + + For example, if `f` simply doubles the time, then for time `t = 501`, we can choose `d=1`, and `T_d` would be `{250, 251}`. + + This would exclude a time effect which has a large jump, because there would be a time `t` between this jump, whose `T_d` would be empty for all small `d`. + + This would also exclude a time effect that creates a freeze-frame effect by always seeking to the same spot, because at the time `t` of this freeze-frame, `T_d` would be large for all `d`. + ++ obeys `f (0) = 0`. This would exclude a time effect that introduces an initial shift in the requested source time. ++ has a `MAX` that is large enough. For example, 24 hours would be fine for a timeline. This would exclude a rate effect with a very large speedup. ++ does not depend on any property outside of the effect element, or on the data it receives. This would exclude a time effect that, say, goes faster if there is more red in the image. + +In what follows, a time effect that breaks one of these can still be used, but not all the features will work. + +### Translations Handled by `nleobject` Pads + +An `nleobject` source pad will translate outgoing times by applying + +``` +time-out = time-in + start - in-point +``` + +This will translate from the internal source coordinates to the timeline coordinates *if* it is the most downstream element. Similarly, an `nleobject` sink pad will translate incoming times by applying + +``` +time-out = time-in - start + in-point +``` + +If we have two `nle-object`s, `object-up` and `object-down`, that have their pads linked, then a time `time-up` from `object-up`'s internal `GstElement`, would be translated at the link to + +``` +time-down + = time-up + start-up - in-point-up - start-down + in-point-down +``` + +So the pads will overall translate from the internal source coordinates of the upstream element to the internal sink coordinates of the downstream element. + +### Undefined Translations + +Note that the coordinate transformation from the timeline time to an upstream time may be undefined depending on the configuration of elements in the timeline. For example, consider the earlier example stack, with the operation starting later than the time effect, such that + +``` +d = (start-2 - start-3) > 0 +``` + +And we choose the `time` + +``` +time = start-2 + = start-3 + d +``` + +Then, when `time` is transformed to the external source coordinates of the operation, we have + +``` +operation-source-time = f (time - start-3) - start-2 + start-3 + = f (d) - d +``` + +If the time effect slows down the consumption rate, then `f (d) < d`, which would make the time undefined in the external source coordinates (we can not have a negative `GstClockTime`). Basically, the effect is trying to access content that is before the operation. + +We can similarly have an effect that tries to access content that is later than the operation, but this wouldn't lead to an underflow of the time. It can however lead to a request for data that is outside the internal content of the operation. + +### Mismatched Coordinates + +The coordinates of an element are only defined relative to the stack that they are in. However, if we have no time effects, these coordinates will line up. Consider the following source and operation configuration. + +``` + | source (1) | + |---------------------------| + | | + '===========================' + +.===========================. +| | +|---------------------------| +| operation (2) | +|---------------------------| +| | +'===========================' + ++++++++++++++++++++++++++++++++++++++++++ + timeline +``` + +This gives us three stacks + +``` + | (1) | | (1) | + |---------------| |-----------| + | | | | + '===============. '===========' + 0 (s1-s2+d1) (s1-s2+d1) d2 + + 0 (s2-s1) (s2-s1) d1 + .===========. .===============. + | | | | + |-----------| |---------------| + | (2) | | (2) | + |-----------| |---------------| + | | | | + '===========' '===============' + 0 (s2-s1) (s2-s1) d1 + + +++++++++++++ +++++++++++++++++ +++++++++++++ +s1 s2 s2 (s1+d1) (s1+d1) (s2+d2) +``` + +where we have written in times in the external coordinates of the elements, where `s1` and `d1` are the `start` and `duration` of the source, and similarly for `s2` and `d2` for the operation. We can see that the edge times of all coordinates match up with their neighbours. Therefore, for both elements, there coordinates across each stack can be combined into a single coordinate system. + +Consider that instead of the operation we have a time effect, then we would have + +``` + | (1) | | (1) | + |---------------| |-----------| + | | | | + '===============. '===========' + f(s2-s1) f(d1) (s1-s2+d1) d2 + -(s2-s1) -(s2-s1) + +f(0) f(s2-s1) f(s2-s1) f(d1) + .===========. .===============. + | | | | + |-----------| |---------------| + | (2) | | (2) | + |-----------| |---------------| + | | | | + '===========' '===============' + 0 (s2-s1) (s2-s1) d1 + + +++++++++++++ +++++++++++++++++ +++++++++++++ +s1 s2 s2 (s1+d1) (s1+d1) (s2+d2) +``` + +We can see that the coordinates of the source now start at `f(s2-s1) - (s2-s1)`, rather than `0`. We can also see that the external source coordinates of the source jump by `(d1 - f(d1))` when the time effect ends. Therefore, most time effects will prevent the coordinates from different stacks from being combined. This can lead to counter-intuitive behaviour. + +A further example would be a rate effect with `rate=3` that covers two sources that are side by side. The rate effect will **not** treat this as playing the sources concatenated, at triple speed. Instead, it would play the first source at triple speed, and once it reaches the starting timeline time of the second source, it will start playing the second source instead, but starting from the internal source coordinates + +``` +3 * (source-start - rate-start) - (source-start - rate-start) + source-in-point + = 2 * (source-start - rate-start) + source-in-point +``` + +Note that if this was a slowed down rate, this would have been an undefined (negative) time, as we mentioned earlier. + +Therefore, in general, time effects should only be placed at a higher priority than elements that share the same `start` and `duration` as it. Note that it is fine to place an operation with a higher priority on top of a time effect with a different `start` or `duration` because this will not lead to a change in the coordinates. + +This is why only a `GESSourceClip` can have time effects added to it. + +There is a general exception to this: if a time effect obeys `f (0) = 0`, then it will not introduce mismatched coordinates downstream if it has a later `start` than all the elements it has a higher priority than, **and** its end timeline time matches all of theirs. Note that this is because the effect would only exist in a single stack, and starts by apply no change to the times it receives. + +## GESTimelineElement times + +The `start` and `duration` of an element use the timeline time coordinates. `in-point` and `max-duration` use the internal source coordinates. These last two should be `0` and `GST_CLOCK_TIME_NONE` respectively for time effects. + +## How to Translate Between Time Coordinates of a Clip + +Consider a `GESTrackElement` `element` in a `GESClip` `clip` in a timeline. It has `n` `active` elements with higher priority in the same `clip` and track, labelled by `i=1,...,n`, where element 1 has a higher priority than element 2, and so on. Each element has an associated function `f_i` that translates from its external source coordinates to its external sink coordinates. Note that for elements that apply no time effect, this will be an identity, regardless of their `in-point`. We can define the function `F`, such that + +``` +F(t) = f_n (f_n-1 ( ... f_1 (t)...)) +``` + +Note that if each `f_i` has the desired properties, then so will `F`, with the exception that the maximum value it can translate may have become too small. For example, if several rate effects accumulate into a very large speedup. + +Given such an `F`, we can translate from the timeline time `t` to the internal source coordinate time of `element` using + +``` +F (t - start) + in-point +``` + +This is what is done in `ges_clip_get_internal_time_from_timeline_time`. + +Note that this works because all the elements in `clip` share the same `start`. Note that this would not work if there existed an overlapping higher priority time effect outside of the clip because the highest priority clip element would **not** be receiving a timeline time at its source pads. This is not a problem if there are non-time effects at higher priority because they will pass through a timeline time unchanged. + +If `F` has the desired properties, it will have a well defined inverse `F^-1`, based on the inverses of `f_i`, which we can use to reverse this translation: + +``` +F^-1 (t - in-point) + start +``` + +This is what is done in `ges_clip_get_timeline_time_from_internal_time`. + +## `duration-limit` + +The `duration-limit` is meant to be the largest value we can set the clip's `duration` to. + +It would be given by the minimum + +``` +ges_clip_get_timeline_time_from_internal_time (clip, child, child-max-duration) - start +``` + +we calculate amongst all its children that have a `max-duration`. Note that the implementation of `_calculate_duration_limit` does not use this method directly, but it should give the same result. + +Note that this would fail if `max-duration` is not reachable through a seek. E.g. if the corresponding function `F` of the time effects acted like + +``` +F (t) = t + max-duration + 1 +``` + +then `F^-1 (t)` will be undefined for `t=max-duartion` because its domain will be `[max-duration + 1, inf)`. Note that this function `F` does not obey `F (0)=0`, so is not supported in GES. + +Note that `duration-limit` may not be *exactly* the largest end time possible. If the corresponding function `F` is monotonically increasing, then there is no source time below `max-duration` that could give a larger value, but there may be some times beyond `max-time` that would correspond to the *same* source time. However, these extra times will only differ from the `max-time` by a small amount if `F` is 'continuously reversible', and so `max-time` would be close enough. Otherwise, we would not have a simple way to know which is the actual largest `duration`. + +## Trimming a clip + +Normally, trimming is meant to keep the internal content in the same position relative to the timeline. If we are applying a non-constant rate effect, it may not be possible to keep all the internal content appearing in the timeline at the same time whilst changing the `start` and `duration`. However, we can keep the start or end frames/samples in the same timeline position. + +#### Trimming the start of a clip to a later time + +When trimming the start edge of a clip from timeline time `old-start` to `new-start`, where `old-start < new-start <= (old-start + duration)`, we set the `in-point` of the clip's children such that the internal content that appeared at `new-start` before the trim, still appears at `new-start` afterwards. + +This would require + +``` +new-in-point = old-in-point + F (new-start - old-start) +``` + +because this is the internal source time corresponding to `new-start`. + +Note that, after we have finished trimming, *assuming* the corresponding `F` has not changed and `F (0) = 0`, + +``` +ges_clip_get_internal_timeline_from_timeline_time (clip, child, new-start) + = F (new-start - new-start) + new-in-point + = new-in-point +``` + +So after trimming, `new-start` will correspond to the same source position as before. Note that this would not work if the time effects changed depending on the data they receive (such as a "go faster if we have more red" time effect) because the corresponding `F` would have changed after setting the `in-point`. However, we already stated earlier that these are not supported in GES. + +#### Trimming the start of a clip to an earlier time + +When trimming the start edge of a clip from timeline time `old-start` to `new-start`, where `new-start < old-start`, we set the `in-point` of the clip's children such that the internal content that appeared at `old-start` before the trim, still appears at `old-start` afterwards. + +``` +new-in-point = old-in-point - F (old-start - new-start) +``` + +Note that this will fail if the second argument is too big, which indicates that it would be before there is any internal content. + +In terms of the function `F` earlier, since this is calculated using the new `start` and old `in-point`, the `source-time` would be + +Note, after we have finished trimming, *assuming* the corresponding `F` has not changed, + +``` +ges_clip_get_internal_time_from_timeline_time (clip, child, old-start) + = F (old-start - new-start) + new-in-point + = F (old-start - new-start) + old-in-point - F (old-start - new-start) + = old-in-point +``` + +So after trimming, `old-start` will correspond to the same source time as before. + +Note that `ges_clip_get_internal_time_from_timeline_time` will perform this same calculation if it receives a timeline time before the `start` of the clip. So timeline-tree is simply able to call `ges_clip_get_internal_time_from_timeline_time (clip, child, new_start, error)` in both cases. + +#### Trimming the end of a clip + +This is as simple as changing the `duration` of the clip since everything will stay at the same timeline position anyway (assuming `F` does not change, as required by GES). It just cannot go above the clip's `duration-limit`. + +## Splitting a clip + +The `in-point` of the new clip is chosen to match the new `out-point` of the split clip. This won't work well if different core children of the clip will end up with very different `out-point`s. But if these differences are within half a source frame, GES will not complain. The same can happen when trimming a clip, since all the core children must share the same `in-point`. + +## Buffer Timestamps + +NOTE: As of 21 May, the recommended changes are not implemented in GES. This delves into why translations will be needed for non-linear time effects. + +Currently, the `nleobject` pads will leave the buffer times unchanged. Which means that an `nlesource` will send out buffer timestamped using its *internal* source coordinates. + +This is in contrast to a `pitch` within an `nleoperation`, which would translate the buffer times from its internal sink coordinates to its internal source coordinates. + +Since the internal source coordinates of the `nlesource` do **not** match the internal sink coordinates of the `nleoperation` (they will differ by `in-point`), this will result in buffer times that are not in **any** coordinates. + +This will make it difficult to use control bindings which are to be given in stream time, which is linked to the buffer timestamps. + +We can explore this in more detail. [According to the GStreamer design docs](https://gstreamer.freedesktop.org/documentation/additional/design/synchronisation.html#stream-time), the stream time is used for + ++ report the POSITION query in the pipeline ++ the position used in seek events/queries ++ the position used to synchronize controller values + +Therefore, in our case, we can say that the stream time at a given position in an nle stack should match the corresponding seek time. + +If we have no applied rate, which shouldn't be the case for a normal uses of a timeline, the `stream-time` is given by + +``` + stream-time = buffer.timestamp - seg.start + seg.time +``` + +Thus, the stream time is basically the internal source or sink coordinates. In `GESTrackElement` control sources are meant to be given in the internal source coordinates. + +We will now look at what these time values **currently** are set to in a stack of an `nlesource` and an `nleoperation` that share the same `start` and `duration`. + +Currently, the `nleobject` pads will only change the `seg.time` of the segments it receives by adding or subtracting `(start - in-point)`. + +We will assume that the `GstElement` that the `nleoperation` wraps is applying its time effect to `seg.time`, `seg.start` and `buffer.timestamp`, which is given by the function `g`. Note that this is what `pitch` currently does. Its not clear to me what `videorate` does to the `buffer.timestamp`, but it does transform `seg.start` the same way as `seg.time`. + +The following is a table of what the `seg.time`, `seg.start` and `buffer.timestamp` values are when *leaving* a pad. The "internal src pad" refers to the source pad of the internal `GstElement`. `s` is the `start` of the objects, and `i` is the `in-point` of the `nlesource`. Following these is what the corresponding stream time would be using these values. The final row is what the corresponding seek position would be coming *into* the pad, if were seeking to the same media time `T`. + +``` + nlesrc nlesrc nleop nleop nleop + internal external external internal external + src pad src pad sink pad src pad src pad + +seg.time i s 0 g (0) g (0) + s + +seg.start i i i g (i) g (i) + +buffer. T T T g (T) g (T) +timestamp +------------------------------------------------------------------------ +stream T T T g (T) g (T) +time - i - i - g (i) - g (i) + + s + g (0) + g (0) + + s +------------------------------------------------------------------------ +seek T T T g (T - i) g (T - i) +time - i - i + s + + s +``` + +We can see that after the `nleoperation`, the seek time and stream time will generally be out of sync. + +Note that if `g` corresponds to a constant rate effect, then + +``` +g (t) = r * t +``` + +for some rate `r`. Then, at the `nleoperation` external source pad. + +``` +stream-time = r * T - r * i + r * 0 + s + = g (T - i) + s + = seek-time +``` + +so the two will match up under the current behaviour for this special case. However, if the rate varies, this will break down. + +If, instead, we translate `seg.start` and `buffer.timestamp` in the same way as `seg.time` on the `nleobject` pads, by adding or subtracting `(start - in-point)`, then we will always have + +``` +seg.start = seg.time +``` + +which means that we would also have + +``` +seek-time = stream-time = buffer.timestamp +``` + +Finally, it would be a good if the convention for a time effect was to use the *output* stream time in `gst_object_sync_values`, rather than the *input* stream time. This would make them compatible with GES's rule that control sources are given in the internal source coordinates. Luckily, it seems that `pitch` already uses the output stream time. `videorate` doesn't currently use `gst_object_sync_values`. diff --git a/docs/gst_plugins_cache.json b/docs/gst_plugins_cache.json new file mode 100644 index 0000000000..b3186450e0 --- /dev/null +++ b/docs/gst_plugins_cache.json @@ -0,0 +1,473 @@ +{ + "ges": { + "description": "GStreamer Editing Services Plugin", + "elements": { + "gesdemux": { + "author": "Thibault Saunier , Edward Hervey , Mathieu Duponchelle , Thibault Saunier ", + "description": "Combines NLE objects", + "hierarchy": [ + "NleComposition", + "NleObject", + "GstBin", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "interfaces": [ + "GstChildProxy" + ], + "klass": "Filter/Editor", + "long-name": "GNonLin Composition", + "pad-templates": { + "src": { + "caps": "ANY", + "direction": "src", + "presence": "always" + } + }, + "properties": { + "drop-tags": { + "blurb": "Whether the composition should drop tags from its children", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "true", + "mutable": "playing", + "readable": true, + "type": "gboolean", + "writable": true + }, + "id": { + "blurb": "The stream-id of the composition", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "NULL", + "mutable": "null", + "readable": true, + "type": "gchararray", + "writable": true + } + }, + "rank": "none", + "signals": { + "commited": { + "args": [ + { + "name": "arg0", + "type": "gboolean" + } + ], + "return-type": "void", + "when": "first" + } + } + }, + "nleoperation": { + "author": "Wim Taymans , Edward Hervey ", + "description": "Encapsulates filters/effects for use with NLE Objects", + "hierarchy": [ + "NleOperation", + "NleObject", + "GstBin", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "interfaces": [ + "GstChildProxy" + ], + "klass": "Filter/Editor", + "long-name": "GNonLin Operation", + "pad-templates": { + "sink%%d": { + "caps": "ANY", + "direction": "sink", + "presence": "request" + }, + "src": { + "caps": "ANY", + "direction": "src", + "presence": "always" + } + }, + "properties": { + "sinks": { + "blurb": "Number of input sinks (-1 for automatic handling)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "1", + "max": "2147483647", + "min": "-1", + "mutable": "null", + "readable": true, + "type": "gint", + "writable": true + } + }, + "rank": "none", + "signals": { + "input-priority-changed": { + "args": [ + { + "name": "arg0", + "type": "GstPad" + }, + { + "name": "arg1", + "type": "guint" + } + ], + "return-type": "void", + "when": "last" + } + } + }, + "nlesource": { + "author": "Wim Taymans , Edward Hervey ", + "description": "Manages source elements", + "hierarchy": [ + "NleSource", + "NleObject", + "GstBin", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "interfaces": [ + "GstChildProxy" + ], + "klass": "Filter/Editor", + "long-name": "GNonLin Source", + "pad-templates": { + "src": { + "caps": "ANY", + "direction": "src", + "presence": "always" + } + }, + "properties": {}, + "rank": "none", + "signals": {} + }, + "nleurisource": { + "author": "Edward Hervey ", + "description": "High-level URI Source element", + "hierarchy": [ + "NleURISource", + "NleSource", + "NleObject", + "GstBin", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "interfaces": [ + "GstChildProxy" + ], + "klass": "Filter/Editor", + "long-name": "GNonLin URI Source", + "pad-templates": { + "src": { + "caps": "ANY", + "direction": "src", + "presence": "sometimes" + } + }, + "properties": { + "uri": { + "blurb": "Uri of the file to use", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "NULL", + "mutable": "null", + "readable": true, + "type": "gchararray", + "writable": true + } + }, + "rank": "none", + "signals": {} + } + }, + "filename": "gstnle", + "license": "LGPL", + "other-types": { + "NleObject": { + "hierarchy": [ + "NleObject", + "GstBin", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "interfaces": [ + "GstChildProxy" + ], + "kind": "object", + "properties": { + "active": { + "blurb": "Use this object in the NleComposition", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "true", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, + "caps": { + "blurb": "Caps used to filter/choose the output stream", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "ANY", + "mutable": "null", + "readable": true, + "type": "GstCaps", + "writable": true + }, + "duration": { + "blurb": "Outgoing duration (in nanoseconds)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "9223372036854775807", + "min": "0", + "mutable": "null", + "readable": true, + "type": "gint64", + "writable": true + }, + "expandable": { + "blurb": "Expand to the full duration of the container composition", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, + "inpoint": { + "blurb": "The media start position (in nanoseconds)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "18446744073709551615", + "max": "18446744073709551615", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint64", + "writable": true + }, + "media-duration-factor": { + "blurb": "The relative rate caused by this object", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "1", + "max": "1.79769e+308", + "min": "0.01", + "mutable": "null", + "readable": true, + "type": "gdouble", + "writable": true + }, + "priority": { + "blurb": "The priority of the object (0 = highest priority)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "-1", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "start": { + "blurb": "The start position relative to the parent (in nanoseconds)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "18446744073709551615", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint64", + "writable": true + }, + "stop": { + "blurb": "The stop position relative to the parent (in nanoseconds)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "18446744073709551615", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint64", + "writable": false + } + }, + "signals": { + "commit": { + "action": true, + "args": [ + { + "name": "arg0", + "type": "gboolean" + } + ], + "return-type": "gboolean", + "when": "last" + } + } + } + }, + "package": "GStreamer Editing Services", + "source": "gst-editing-services", + "tracers": {}, + "url": "Unknown package origin" + } +} \ No newline at end of file diff --git a/docs/images/layer_track_overview.png b/docs/images/layer_track_overview.png new file mode 100644 index 0000000000..8e2851735f Binary files /dev/null and b/docs/images/layer_track_overview.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000000..e2e39da7af --- /dev/null +++ b/docs/index.md @@ -0,0 +1,68 @@ +--- +short-description: GStreamer Editing Services API reference. +... + +# GStreamer Editing Services + +The "GStreamer Editing Services" is a library to simplify the creation +of multimedia editing applications. Based on the GStreamer multimedia framework +and the GNonLin set of plugins, its goals are to suit all types of editing-related +applications. + +The GStreamer Editing Services are cross-platform and work on most UNIX-like +platform as well as Windows. It is released under the GNU Library General Public License +(GNU LGPL). + +## Goals of GStreamer Editing Services + +The GStreamer multimedia framework and the accompanying GNonLin set of +plugins for non-linear editing offer all the building blocks for: + +- Decoding and encoding to a wide variety of formats, through all the + available GStreamer plugins. + +- Easily choosing segments of streams and arranging them through time + through the GNonLin set of plugins. + +But all those building blocks only offer stream-level access, which +results in developers who want to write non-linear editors to write a +consequent amount of code to get to the level of *non-linear editing* +notions which are closer and more meaningful for the end-user (and +therefore the application). + +The GStreamer Editing Services (hereafter GES) aims to fill the gap +between GStreamer/GNonLin and the application developer by offering a +series of classes to simplify the creation of many kind of +editing-related applications. + +## Architecture + +### Timeline and TimelinePipeline + +The most top-level object encapsulating every other object is the +[GESTimeline](GESTimeline). It is the central object for any editing project. + +The `GESTimeline` is a `GstElement`. It can therefore be used in any +GStreamer pipeline like any other object. + +### Tracks and Layers + +The GESTimeline can contain two types of objects (seen in +"Layers and Tracks"): + +- Layers - Corresponds to the user-visible arrangement of clips, and + what you primarily interact with as an application developer. A + minimalistic timeline would only have one layer, but a more complex + editing application could use as many as needed. + +- Tracks - Corresponds to the output streams in GStreamer. A typical + GESTimeline, aimed at a video editing application, would have an + audio track and a video track. A GESTimeline for an audio editing + application would only require an audio track. Multiple layers can + be related to each track. + +![Layers and Tracks](images/layer_track_overview.png) + +In order to reduce even more the amount of GStreamer interaction the +application developer has to deal with, a convenience GstPipeline has +been made available specifically for Timelines : [GESPipeline](GESPipeline). diff --git a/docs/libs/GESAudioTestSource-children-props.md b/docs/libs/GESAudioTestSource-children-props.md new file mode 100644 index 0000000000..382c19c30f --- /dev/null +++ b/docs/libs/GESAudioTestSource-children-props.md @@ -0,0 +1,32 @@ +#### `freq` + +Frequency of test signal. The sample rate needs to be at least 2 times higher. + +Value type: #gdouble + +See #audiotestsrc:freq + +#### `mute` + +mute channel + +Value type: #gboolean + +See #volume:mute + +#### `volume` + +volume factor, 1.0=100% + +Value type: #gdouble + +See #volume:volume + +#### `volume` + +Volume of test signal + +Value type: #gdouble + +See #audiotestsrc:volume + diff --git a/docs/libs/GESAudioUriSource-children-props.md b/docs/libs/GESAudioUriSource-children-props.md new file mode 100644 index 0000000000..0e39a34a3e --- /dev/null +++ b/docs/libs/GESAudioUriSource-children-props.md @@ -0,0 +1,16 @@ +#### `mute` + +mute channel + +Value type: #gboolean + +See #volume:mute + +#### `volume` + +volume factor, 1.0=100% + +Value type: #gdouble + +See #volume:volume + diff --git a/docs/libs/GESTimeOverlayClip-children-props.md b/docs/libs/GESTimeOverlayClip-children-props.md new file mode 100644 index 0000000000..999fe8186b --- /dev/null +++ b/docs/libs/GESTimeOverlayClip-children-props.md @@ -0,0 +1,201 @@ +#### `alpha` + +alpha of the stream + +Value type: #gdouble + +#### `background-color` + +Background color to use (big-endian ARGB) + +Value type: #guint + +See #videotestsrc:background-color + +#### `font-desc` + +Pango font description of font to be used for rendering. See documentation of +pango_font_description_from_string for syntax. + +Value type: #gchararray + +See #GstBaseTextOverlay:font-desc + +#### `foreground-color` + +Foreground color to use (big-endian ARGB) + +Value type: #guint + +See #videotestsrc:foreground-color + +#### `freq` + +Frequency of test signal. The sample rate needs to be at least 2 times higher. + +Value type: #gdouble + +See #audiotestsrc:freq + +#### `halignment` + +Horizontal alignment of the text + +Valid values: + - **left** (0) – left + - **center** (1) – center + - **right** (2) – right + - **position** (4) – Absolute position clamped to canvas + - **absolute** (5) – Absolute position + +See #GstBaseTextOverlay:halignment + +#### `height` + +height of the source + +Value type: #gint + +#### `mute` + +mute channel + +Value type: #gboolean + +See #volume:mute + +#### `pattern` + +Type of test pattern to generate + +Valid values: + - **SMPTE 100% color bars** (0) – smpte + - **Random (television snow)** (1) – snow + - **100% Black** (2) – black + - **100% White** (3) – white + - **Red** (4) – red + - **Green** (5) – green + - **Blue** (6) – blue + - **Checkers 1px** (7) – checkers-1 + - **Checkers 2px** (8) – checkers-2 + - **Checkers 4px** (9) – checkers-4 + - **Checkers 8px** (10) – checkers-8 + - **Circular** (11) – circular + - **Blink** (12) – blink + - **SMPTE 75% color bars** (13) – smpte75 + - **Zone plate** (14) – zone-plate + - **Gamut checkers** (15) – gamut + - **Chroma zone plate** (16) – chroma-zone-plate + - **Solid color** (17) – solid-color + - **Moving ball** (18) – ball + - **SMPTE 100% color bars** (19) – smpte100 + - **Bar** (20) – bar + - **Pinwheel** (21) – pinwheel + - **Spokes** (22) – spokes + - **Gradient** (23) – gradient + - **Colors** (24) – colors + +See #videotestsrc:pattern + +#### `posx` + +x position of the stream + +Value type: #gint + +#### `posy` + +y position of the stream + +Value type: #gint + +#### `text-width` + +Resulting width of font rendering + +Value type: #guint + +See #GstBaseTextOverlay:text-width + +#### `text-x` + +Resulting X position of font rendering. + +Value type: #gint + +See #GstBaseTextOverlay:text-x + +#### `text-y` + +Resulting X position of font rendering. + +Value type: #gint + +See #GstBaseTextOverlay:text-y + +#### `time-mode` + +What time to show + +Valid values: + - **buffer-time** (0) – buffer-time + - **stream-time** (1) – stream-time + - **running-time** (2) – running-time + - **time-code** (3) – time-code + +See #timeoverlay:time-mode + +#### `valignment` + +Vertical alignment of the text + +Valid values: + - **baseline** (0) – baseline + - **bottom** (1) – bottom + - **top** (2) – top + - **position** (3) – Absolute position clamped to canvas + - **center** (4) – center + - **absolute** (5) – Absolute position + +See #GstBaseTextOverlay:valignment + +#### `video-direction` + +Video direction: rotation and flipping + +Valid values: + - **GST_VIDEO_ORIENTATION_IDENTITY** (0) – identity + - **GST_VIDEO_ORIENTATION_90R** (1) – 90r + - **GST_VIDEO_ORIENTATION_180** (2) – 180 + - **GST_VIDEO_ORIENTATION_90L** (3) – 90l + - **GST_VIDEO_ORIENTATION_HORIZ** (4) – horiz + - **GST_VIDEO_ORIENTATION_VERT** (5) – vert + - **GST_VIDEO_ORIENTATION_UL_LR** (6) – ul-lr + - **GST_VIDEO_ORIENTATION_UR_LL** (7) – ur-ll + - **GST_VIDEO_ORIENTATION_AUTO** (8) – auto + - **GST_VIDEO_ORIENTATION_CUSTOM** (9) – custom + +See #GstVideoDirection:video-direction + +#### `volume` + +Volume of test signal + +Value type: #gdouble + +See #audiotestsrc:volume + +#### `volume` + +volume factor, 1.0=100% + +Value type: #gdouble + +See #volume:volume + +#### `width` + +width of the source + +Value type: #gint + diff --git a/docs/libs/GESTitleSource-children-props.md b/docs/libs/GESTitleSource-children-props.md new file mode 100644 index 0000000000..999b092d78 --- /dev/null +++ b/docs/libs/GESTitleSource-children-props.md @@ -0,0 +1,221 @@ +#### `alpha` + +alpha of the stream + +Value type: #gdouble + +#### `color` + +Color to use for text (big-endian ARGB). + +Value type: #guint + +See #GstBaseTextOverlay:color + +#### `font-desc` + +Pango font description of font to be used for rendering. See documentation of +pango_font_description_from_string for syntax. + +Value type: #gchararray + +See #GstBaseTextOverlay:font-desc + +#### `foreground-color` + +Foreground color to use (big-endian ARGB) + +Value type: #guint + +See #videotestsrc:foreground-color + +#### `halignment` + +Horizontal alignment of the text + +Valid values: + - **left** (0) – left + - **center** (1) – center + - **right** (2) – right + - **position** (4) – Absolute position clamped to canvas + - **absolute** (5) – Absolute position + +See #GstBaseTextOverlay:halignment + +#### `height` + +height of the source + +Value type: #gint + +#### `outline-color` + +Color to use for outline the text (big-endian ARGB). + +Value type: #guint + +See #GstBaseTextOverlay:outline-color + +#### `pattern` + +Type of test pattern to generate + +Valid values: + - **SMPTE 100% color bars** (0) – smpte + - **Random (television snow)** (1) – snow + - **100% Black** (2) – black + - **100% White** (3) – white + - **Red** (4) – red + - **Green** (5) – green + - **Blue** (6) – blue + - **Checkers 1px** (7) – checkers-1 + - **Checkers 2px** (8) – checkers-2 + - **Checkers 4px** (9) – checkers-4 + - **Checkers 8px** (10) – checkers-8 + - **Circular** (11) – circular + - **Blink** (12) – blink + - **SMPTE 75% color bars** (13) – smpte75 + - **Zone plate** (14) – zone-plate + - **Gamut checkers** (15) – gamut + - **Chroma zone plate** (16) – chroma-zone-plate + - **Solid color** (17) – solid-color + - **Moving ball** (18) – ball + - **SMPTE 100% color bars** (19) – smpte100 + - **Bar** (20) – bar + - **Pinwheel** (21) – pinwheel + - **Spokes** (22) – spokes + - **Gradient** (23) – gradient + - **Colors** (24) – colors + +See #videotestsrc:pattern + +#### `posx` + +x position of the stream + +Value type: #gint + +#### `posy` + +y position of the stream + +Value type: #gint + +#### `shaded-background` + +Whether to shade the background under the text area + +Value type: #gboolean + +See #GstBaseTextOverlay:shaded-background + +#### `text` + +Text to be display. + +Value type: #gchararray + +See #GstBaseTextOverlay:text + +#### `text-height` + +Resulting height of font rendering + +Value type: #guint + +See #GstBaseTextOverlay:text-height + +#### `text-width` + +Resulting width of font rendering + +Value type: #guint + +See #GstBaseTextOverlay:text-width + +#### `text-x` + +Resulting X position of font rendering. + +Value type: #gint + +See #GstBaseTextOverlay:text-x + +#### `text-y` + +Resulting X position of font rendering. + +Value type: #gint + +See #GstBaseTextOverlay:text-y + +#### `valignment` + +Vertical alignment of the text + +Valid values: + - **baseline** (0) – baseline + - **bottom** (1) – bottom + - **top** (2) – top + - **position** (3) – Absolute position clamped to canvas + - **center** (4) – center + - **absolute** (5) – Absolute position + +See #GstBaseTextOverlay:valignment + +#### `video-direction` + +Video direction: rotation and flipping + +Valid values: + - **GST_VIDEO_ORIENTATION_IDENTITY** (0) – identity + - **GST_VIDEO_ORIENTATION_90R** (1) – 90r + - **GST_VIDEO_ORIENTATION_180** (2) – 180 + - **GST_VIDEO_ORIENTATION_90L** (3) – 90l + - **GST_VIDEO_ORIENTATION_HORIZ** (4) – horiz + - **GST_VIDEO_ORIENTATION_VERT** (5) – vert + - **GST_VIDEO_ORIENTATION_UL_LR** (6) – ul-lr + - **GST_VIDEO_ORIENTATION_UR_LL** (7) – ur-ll + - **GST_VIDEO_ORIENTATION_AUTO** (8) – auto + - **GST_VIDEO_ORIENTATION_CUSTOM** (9) – custom + +See #GstVideoDirection:video-direction + +#### `width` + +width of the source + +Value type: #gint + +#### `x-absolute` + +Horizontal position when using absolute alignment + +Value type: #gdouble + +See #GstBaseTextOverlay:x-absolute + +#### `xpos` + +Horizontal position when using clamped position alignment + +Value type: #gdouble + +See #GstBaseTextOverlay:xpos + +#### `y-absolute` + +Vertical position when using absolute alignment + +Value type: #gdouble + +See #GstBaseTextOverlay:y-absolute + +#### `ypos` + +Vertical position when using clamped position alignment + +Value type: #gdouble + +See #GstBaseTextOverlay:ypos + diff --git a/docs/libs/GESTransitionClip-children-props.md b/docs/libs/GESTransitionClip-children-props.md new file mode 100644 index 0000000000..44507fbba7 --- /dev/null +++ b/docs/libs/GESTransitionClip-children-props.md @@ -0,0 +1,16 @@ +#### `border` + +The border width + +Value type: #guint + +See #GESVideoTransition:border + +#### `invert` + +Whether the transition is inverted + +Value type: #gboolean + +See #GESVideoTransition:invert + diff --git a/docs/libs/GESVideoTestSource-children-props.md b/docs/libs/GESVideoTestSource-children-props.md new file mode 100644 index 0000000000..8d99b051ed --- /dev/null +++ b/docs/libs/GESVideoTestSource-children-props.md @@ -0,0 +1,97 @@ +#### `alpha` + +alpha of the stream + +Value type: #gdouble + +#### `background-color` + +Background color to use (big-endian ARGB) + +Value type: #guint + +See #videotestsrc:background-color + +#### `foreground-color` + +Foreground color to use (big-endian ARGB) + +Value type: #guint + +See #videotestsrc:foreground-color + +#### `height` + +height of the source + +Value type: #gint + +#### `pattern` + +Type of test pattern to generate + +Valid values: + - **SMPTE 100% color bars** (0) – smpte + - **Random (television snow)** (1) – snow + - **100% Black** (2) – black + - **100% White** (3) – white + - **Red** (4) – red + - **Green** (5) – green + - **Blue** (6) – blue + - **Checkers 1px** (7) – checkers-1 + - **Checkers 2px** (8) – checkers-2 + - **Checkers 4px** (9) – checkers-4 + - **Checkers 8px** (10) – checkers-8 + - **Circular** (11) – circular + - **Blink** (12) – blink + - **SMPTE 75% color bars** (13) – smpte75 + - **Zone plate** (14) – zone-plate + - **Gamut checkers** (15) – gamut + - **Chroma zone plate** (16) – chroma-zone-plate + - **Solid color** (17) – solid-color + - **Moving ball** (18) – ball + - **SMPTE 100% color bars** (19) – smpte100 + - **Bar** (20) – bar + - **Pinwheel** (21) – pinwheel + - **Spokes** (22) – spokes + - **Gradient** (23) – gradient + - **Colors** (24) – colors + +See #videotestsrc:pattern + +#### `posx` + +x position of the stream + +Value type: #gint + +#### `posy` + +y position of the stream + +Value type: #gint + +#### `video-direction` + +Video direction: rotation and flipping + +Valid values: + - **GST_VIDEO_ORIENTATION_IDENTITY** (0) – identity + - **GST_VIDEO_ORIENTATION_90R** (1) – 90r + - **GST_VIDEO_ORIENTATION_180** (2) – 180 + - **GST_VIDEO_ORIENTATION_90L** (3) – 90l + - **GST_VIDEO_ORIENTATION_HORIZ** (4) – horiz + - **GST_VIDEO_ORIENTATION_VERT** (5) – vert + - **GST_VIDEO_ORIENTATION_UL_LR** (6) – ul-lr + - **GST_VIDEO_ORIENTATION_UR_LL** (7) – ur-ll + - **GST_VIDEO_ORIENTATION_AUTO** (8) – auto + - **GST_VIDEO_ORIENTATION_CUSTOM** (9) – custom + +See #GstVideoDirection:video-direction + +#### `width` + +width of the source + +Value type: #gint + diff --git a/docs/libs/GESVideoUriSource-children-props.md b/docs/libs/GESVideoUriSource-children-props.md new file mode 100644 index 0000000000..10791aead5 --- /dev/null +++ b/docs/libs/GESVideoUriSource-children-props.md @@ -0,0 +1,83 @@ +#### `alpha` + +alpha of the stream + +Value type: #gdouble + +#### `fields` + +Fields to use for deinterlacing + +Valid values: + - **All fields** (0) – all + - **Top fields only** (1) – top + - **Bottom fields only** (2) – bottom + - **Automatically detect** (3) – auto + +See #deinterlace:fields + +#### `height` + +height of the source + +Value type: #gint + +#### `mode` + +Deinterlace Mode + +Valid values: + - **Auto detection (best effort)** (0) – auto + - **Force deinterlacing** (1) – interlaced + - **Run in passthrough mode** (2) – disabled + - **Auto detection (strict)** (3) – auto-strict + +See #deinterlace:mode + +#### `posx` + +x position of the stream + +Value type: #gint + +#### `posy` + +y position of the stream + +Value type: #gint + +#### `tff` + +Deinterlace top field first + +Valid values: + - **Auto detection** (0) – auto + - **Top field first** (1) – tff + - **Bottom field first** (2) – bff + +See #deinterlace:tff + +#### `video-direction` + +Video direction: rotation and flipping + +Valid values: + - **GST_VIDEO_ORIENTATION_IDENTITY** (0) – identity + - **GST_VIDEO_ORIENTATION_90R** (1) – 90r + - **GST_VIDEO_ORIENTATION_180** (2) – 180 + - **GST_VIDEO_ORIENTATION_90L** (3) – 90l + - **GST_VIDEO_ORIENTATION_HORIZ** (4) – horiz + - **GST_VIDEO_ORIENTATION_VERT** (5) – vert + - **GST_VIDEO_ORIENTATION_UL_LR** (6) – ul-lr + - **GST_VIDEO_ORIENTATION_UR_LL** (7) – ur-ll + - **GST_VIDEO_ORIENTATION_AUTO** (8) – auto + - **GST_VIDEO_ORIENTATION_CUSTOM** (9) – custom + +See #GstVideoDirection:video-direction + +#### `width` + +width of the source + +Value type: #gint + diff --git a/docs/libs/document-children-props.py b/docs/libs/document-children-props.py new file mode 100644 index 0000000000..5c5e2467a9 --- /dev/null +++ b/docs/libs/document-children-props.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +""" +Simple script to update the children properties information for +GESTrackElement-s that add children properties all the time +""" + +import gi +import os +import sys +import textwrap + +gi.require_version("Gst", "1.0") +gi.require_version("GObject", "2.0") +gi.require_version("GES", "1.0") + +from gi.repository import Gst, GES, GObject + +overrides = { + "GstFramePositioner": False, + "GstBaseTextOverlay": "timeoverlay", + "GstVideoDirection": "videoflip", + "GESVideoTestSource": "GESVideoTestSource", + "GESVideoTransition": "GESVideoTransition", +} + +if __name__ == "__main__": + Gst.init(None) + GES.init() + + os.chdir(os.path.realpath(os.path.dirname(__file__))) + tl = GES.Timeline.new_audio_video() + layer = tl.append_layer() + + elements = [] + def add_clip(c, add=True, override_name=None): + c.props.duration = Gst.SECOND + c.props.start = layer.get_duration() + layer.add_clip(c) + if add: + elements.extend(c.children) + else: + if override_name: + elements.append((c, override_name)) + else: + elements.append(c) + + add_clip(GES.UriClipAsset.request_sync(Gst.filename_to_uri(os.path.join("../../", "tests/check/assets/audio_video.ogg"))).extract()) + add_clip(GES.TestClip.new()) + add_clip(GES.TitleClip.new()) + + add_clip(GES.SourceClip.new_time_overlay(), False, "GESTimeOverlaySourceClip") + add_clip(GES.TransitionClip.new_for_nick("crossfade"), False) + + for element in elements: + if isinstance(element, tuple): + element, gtype = element + else: + gtype = element.__gtype__.name + print(gtype) + with open(gtype + '-children-props.md', 'w') as f: + for prop in GES.TimelineElement.list_children_properties(element): + prefix = '#### `%s`\n\n' % (prop.name) + + prefix_len = len(prefix) + lines = textwrap.wrap(prop.blurb, width=80) + + doc = prefix + lines[0] + + if GObject.type_is_a(prop, GObject.ParamSpecEnum.__gtype__): + lines += ["", "Valid values:"] + for value in prop.enum_class.__enum_values__.values(): + lines.append(" - **%s** (%d) – %s" % (value.value_name, + int(value), value.value_nick)) + else: + lines += ["", "Value type: #" + prop.value_type.name] + + typename = overrides.get(prop.owner_type.name, None) + if typename is not False: + if typename is None: + if GObject.type_is_a(prop.owner_type, Gst.Element): + typename = GObject.new(prop.owner_type).get_factory().get_name() + lines += ["", "See #%s:%s" % (typename, prop.name)] + + if len(lines) > 1: + doc += '\n' + doc += '\n'.join(lines[1:]) + + + print(doc + "\n", file=f) diff --git a/docs/low_level.md b/docs/low_level.md new file mode 100644 index 0000000000..fa1f8b58ee --- /dev/null +++ b/docs/low_level.md @@ -0,0 +1,5 @@ +# Low level APIs + +Those APIs should usually not be used unless you know +what you are doing, check other parts of the documentation +before deciding you should use one of those. diff --git a/docs/meson.build b/docs/meson.build new file mode 100644 index 0000000000..c3c91efff2 --- /dev/null +++ b/docs/meson.build @@ -0,0 +1,131 @@ +build_hotdoc = false + +if meson.is_cross_build() + if get_option('doc').enabled() + error('Documentation enabled but building the doc while cross building is not supported yet.') + endif + + message('Documentation not built as building it while cross building is not supported yet.') + subdir_done() +endif + +required_hotdoc_extensions = ['gi-extension', 'gst-extension'] +if gst_dep.type_name() == 'internal' + gst_proj = subproject('gstreamer') + plugins_cache_generator = gst_proj.get_variable('plugins_cache_generator') +else + plugins_cache_generator = find_program(join_paths(gst_dep.get_pkgconfig_variable('libexecdir'), 'gstreamer-' + apiversion, 'gst-plugins-doc-cache-generator'), required: false) +endif + +plugins_cache = join_paths(meson.current_source_dir(), 'gst_plugins_cache.json') + +if plugins_cache_generator.found() + plugins_doc_dep = custom_target('editing-services-doc-cache', + command: [plugins_cache_generator, plugins_cache, '@OUTPUT@', '@INPUT@'], + input: plugins, + output: 'gst_plugins_cache.json', + build_always_stale: true, + ) +else + warning('GStreamer plugin inspector for documentation not found, can\'t update the cache') +endif + +hotdoc_p = find_program('hotdoc', required: get_option('doc')) +if not hotdoc_p.found() + message('Hotdoc not found, not building the documentation') + subdir_done() +endif + +hotdoc_req = '>= 0.11.0' +hotdoc_version = run_command(hotdoc_p, '--version').stdout() +if not hotdoc_version.version_compare(hotdoc_req) + if get_option('doc').enabled() + error('Hotdoc version @0@ not found, got @1@'.format(hotdoc_req, hotdoc_version)) + else + message('Hotdoc version @0@ not found, got @1@, not building documentation'.format(hotdoc_req, hotdoc_version)) + subdir_done() + endif +endif + +hotdoc = import('hotdoc') +foreach extension: required_hotdoc_extensions + if not hotdoc.has_extensions(extension) + if get_option('doc').enabled() + error('Documentation enabled but gi-extension missing') + endif + + message('@0@ extensions not found, not building documentation requiring it'.format(extension)) + endif +endforeach + +if not build_gir + if get_option('doc').enabled() + error('Documentation enabled but introspection not built.') + endif + + message('Introspection not built, can\'t build the documentation') + subdir_done() +endif + +build_hotdoc = true +ges_excludes = [] +foreach f: ['gesmarshal.*', + 'ges-internal.*', + 'ges-auto-transition.*', + 'ges-structured-interface.*', + 'ges-structure-parser.*', + 'ges-version.h', + 'ges-smart-*', + 'ges-command-line-formatter.*', + 'ges-base-xml-formatter.h', + 'gstframepositioner.*', + 'lex.priv_ges_parse_yy.c', + 'ges-parse-lex.[c]'] + ges_excludes += [join_paths(meson.current_source_dir(), '..', '..', 'ges', f)] +endforeach + +hotdoc = import('hotdoc') +libs_doc = [hotdoc.generate_doc('gst-editing-services', + project_version: apiversion, + extra_assets: [join_paths(meson.current_source_dir(), 'images')], + gi_c_sources: ges_sources + ges_headers, + gi_c_source_roots: [join_paths(meson.current_source_dir(), '../ges/')], + gi_sources: [ges_gir[0].full_path()], + gi_c_source_filters: ges_excludes, + sitemap: 'sitemap.txt', + index: 'index.md', + gi_index: 'index.md', + gi_smart_index: true, + gi_order_generated_subpages: true, + dependencies: [ges_dep], + disable_incremental_build: true, +)] + +plugins_doc = [] +list_plugin_res = run_command(python3, '-c', +''' +import sys +import json + +with open("@0@") as f: + print(':'.join(json.load(f).keys()), end='') +'''.format(plugins_cache)) + +assert(list_plugin_res.returncode() == 0, + 'Could not list plugins from @0@\n@1@\n@1@'.format(plugins_cache, list_plugin_res.stdout(), list_plugin_res.stderr())) + + +foreach plugin_name: list_plugin_res.stdout().split(':') + plugins_doc += [hotdoc.generate_doc(plugin_name, + project_version: apiversion, + sitemap: 'plugins/sitemap.txt', + index: 'plugins/index.md', + gst_index: 'plugins/index.md', + gst_smart_index: true, + gst_c_sources: ['../plugins/*/*.[ch]',], + dependencies: [gst_dep, plugins], + gst_order_generated_subpages: true, + gst_cache_file: plugins_cache, + gst_plugin_name: plugin_name, + )] +endforeach diff --git a/docs/plugins/index.md b/docs/plugins/index.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/plugins/nle.md b/docs/plugins/nle.md new file mode 100644 index 0000000000..97b896806f --- /dev/null +++ b/docs/plugins/nle.md @@ -0,0 +1,7 @@ +--- +short-description: Non Linear Engine +... + +# Non Linear Engine + +NLE is a set of elements to implement Non Linear multimedia editing. diff --git a/docs/plugins/sitemap.txt b/docs/plugins/sitemap.txt new file mode 100644 index 0000000000..d82d5199b0 --- /dev/null +++ b/docs/plugins/sitemap.txt @@ -0,0 +1 @@ +gst-index \ No newline at end of file diff --git a/docs/random/design b/docs/random/design new file mode 100644 index 0000000000..a9e2c193ed --- /dev/null +++ b/docs/random/design @@ -0,0 +1,440 @@ +GStreamer Editing Services +-------------------------- + + This is a list of features and goals for the GStreamer Editing + Services. + + Some features are already implemented, and some others not. When the + status is not specified, this means it is still not implemented but + might be investigated. + + +FUNDAMENTAL GOALS: + + 1) API must be easy to use for simple use-cases. Use and abuse + convenience methods. + 2) API must allow as many use-cases as possible, not just the simple + ones. + + +FEATURES + +Index of features: + + * Project file load/save support (GESFormatter) + * Grouping/Linking of Multiple TrackObjects + * Selection support (Extension from Grouping/Linking) + * Effects support + * Source Material object + * Proxy support + * Editing modes (Ripple/Roll/Slip/Slide) + * Coherent handling of Content in different formats + * Video compositing and audio mixing + * Handling of alpha video (i.e. transparency) + * Faster/Tigher interaction with GNonLin elements + * Media Asset Management integration + * Templates + * Plugin system + + + +* Project file load/save support (GESFormatter) + + Status: + Implemented, requires API addition for all use-cases. + + Problems: + Timelines can be stored in many different formats, we need to + ensure it is as easy/trivial as possible for users to load/save + those timelines. + Some timeline formats might have format-specific + sources/objects/effects which need to be handled in certain ways + and therefore provide their own classes. + + The object that can save/load GESTimeline are Formatters. + + Formatters can offer support for load-only/save-only formats. + + There must be a list of well-known GES classes that all formatters + must be able to cope with. If a subclass of one of those classes is + present in a timeline, the formatter will do its best to store a + compatible information. + + A Formatter can ask a pre-render of classes that it doesn't + understand (See Proxy section). + + Formatters can provide subclasses of well-known GES classes when + filling in the timeline to offer format-specific features. + + + +* Grouping/Linking of Multiple TrackObjects + + Status: + Implemented, but doesn't have public API for controlling the + tracked objects or creating groups from TimelineObject(s) + + Problems: + In order to make the usage of timelines at the Layer level as easy + as possible, we must be able to group any TrackObject together as + one TimelineObject. + + The base GESTimelineObject keeps a reference to all the + GESTrackObjects it is controlling. It contains a mapping of the + position of those track objects relatively to the timeline objects. + + TrackObjects will move and be modified synchronously with the + TimelineObject, and vice-versa. + + TrackObjects can be 'unlocked' from the changes of its controlling + TimelineObject. In this case, it will not move and be modified + synchronously with the TimelineObject. + + + +* Selection support (Extension from Grouping/Linking) + + Problems: + In order to make user-interface faster to write, we must have a way + to create selections of user-selected TimelineObject(s) or + TrackObject(s) to move them together. + + This should be able to do by creating a non-visible (maybe not even + inserted in the layer?) TimelineObject. + + + +* Effects support + + Status: + Partially Implemented, requires API addition for all use-cases. + + Problems: + In order for users to apply multimedia effects in their timelines, + we need an API to search, add and control those effects. + + We must be able to provide a list of effects available on the system + at runtime. + We must be able to configure effects through an API in GES without + having to access the GstElements properties directly. + + We should also expose the GstElements contained in an effect so it + is possible for people to control their properties as they wish. + + We must be able to implement and handle complex effects directly in + GES. + + We must be able to configure effects through time -> Keyframes + without duplicating code from GStreamer. + + + +* Source Material object + + Problems: + Several TimelineSource for a same uri actually share a lot + in common. That information will mostly come from GstDiscoverer, + but could also contain extra information provided by 3rd party + modules. + + The information regarding the various streams (and obtained through + optionally running GstDiscoverer) is not stored and has to be + re-analyzed else where. + + Definition: + Material: n, The substance or substances out of which a thing is or + can be made. + + In order to avoid duplicating that information in every single + TimelineSource, a 'Material' object needs to be made available. + + A Material object contains all the information which is independent + of the usage of that material in a timeline. + + A Material contains the list of 'streams' that can be provided with + as much information as possible (ex: contains audio and video + streams with full caps information, or better yet the output of + GstDiscoverer). + + A Material contains the various Metadata (author, title, origin, + copyright ,....). + + A Material object can specify the TimelineSource class to use in a + Layer. + + + +* Proxy support + + Problems: + A certain content might be impossible to edit on a certain setup + due to many reasons (too complex to decode in realtime, not in + digital format, not available locally, ...). + + In order to be able to store/export timelines to some formats, one + might need to have to create a pre-render of some items of the + timeline, while retaining as much information as possible. + + Content here is not limited to single materials, it could very well + be a complex combination of materials/effects like a timeline or a + collection of images. + + To solve this problem, we need a notion of ProxyMaterial. + + It is a subclass of Material and as such provides all the same + features as Material. + + It should be made easy to create one from an existing TimelineSource + (and it's associated Material(s)), with specifiable rendering + settings and output location. + + The user should have the possibility to switch from Proxy materials + to original (in order to use the lower + resolution/quality/... version for the editing phase and the + original material for final rendering phase). + + Requires: + GESMaterial + + + +* Editing modes (Ripple/Roll/Slip/Slide) + + Status: + Not implemented. + + Problems: + Most editing relies on heavy usage of 4 editing tools which editors + will require. Ripple/Roll happen on edit points (between two clips) + and Slip/Slide happen on a clip. + + The Ripple tool allows you to modify the beginning/end of a clip + and move the neighbour accordingly. This will change the overall + timeline duration. + + The Roll tool allows you to modify the position of an editing point + between two clips without modifying the inpoint of the first clip + nor the out-point of the second clip. This will not change the + overall timeline duration. + + The Slip tool allows you to modify the in-point of a clip without + modifying it's duration or position in the timeline. + + The Slide tool allows you to modify the position of a clip in a + timeline without modifying it's duration or it's in-point, but will + modify the out-point of the previous clip and in-point of the + following clip so as not to modify the overall timeline duration. + + These tools can be used both on TimelineObjects and on + TrackObjects, we need to make sure that changes are propagated + properly. + + + +* Coherent handling of Content in different formats + + Problems: + When mixing content in different format (Aspect-Ratio, Size, color + depth, number of audio channels, ...), decisions need to be made on + whether to conform the material to a common format or not, and on + how to conform that material. + + Conforming the material here means bringing it to a common format. + + All the information regarding the contents we are handling are + stored in the various GESMaterial. The target format is also known + through the caps of the various GESTracks involved. The Material and + track output caps will allow us to make decisions on what course of + action to take. + + By default content should be conformed to a good balance of speed + and avoid loss of information. + Ex: If mixing a 4:3 video and a 16:9 video with a target track + aspect ratio of 4:3, we will make the width of the two videos + be equal without distorting their respective aspect-ratios. + + Requires: + GESMaterial + + See also: + Video compositing and audio mixing + + + +* Video compositing and audio mixing + + Status: + Not implemented. The bare minimum to implement are the static + absolute property handling. Relative/variable properties and group + handling can be done once we know how to handle object grouping. + + Problems: + Editing requires not only a linear combination of cuts and + sequences, but also mixing various content/effect at the same + time. + + Audio and Video compositing/mixing requires having a set of base + properties for all sources that indicate their positioning in the + final composition. + + Audio properties + * Volume + * Panning (or more generally positioning and up-/down-mixing for + multi-channel). + + Video properties + * Z-layer (implicit through priority property) + * X,Y position + * Vertical and Horizontal scaling + * Global Alpha (see note below about alpha). + + A big problem with compositing/mixing is handling positioning that + could change due to different input/output format AND avoiding any + quality loss. + + Example 1 (video position and scale/aspect-ratio changes): + A user puts a 32x24 logo video at position 10,10 on a 1280x720 + video. Later on the user decides to render the timeline to a + different resolution (like 1920x1080) or aspect ratio (4:3 instead + of 16:9). + The overlayed logo should stay at the same relative position + regardless of the output format. + + Example 2 (video scaling): + A user decides to overlay a video logo which is originally a + 320x240 video by scaling it down to 32x24 on top of a 1280x720 + video. Later on the user decides to render a 1920x1080 version of + the timeline. + The resulting rendered 1920x1080 video shall have the overlay + video located at the exact relative position and using a 64x48 + downscale of the original overlay video (i.e. avoiding a + 640x480=>32x24=>64x48 double-scaling). + + Example 3 (audio volume): + A user adjusts the commentary audio track and the soundtrack audio + track based on the volume of the various videos playing. Later on + the user wants to adjust the overall audio volume in order for the + final output to conform to a target RMS/peak volume. + The resulting relative volumes of each track should be the same + WITHOUT any extra loss of audio quality (i.e. avoiding a + downscale/upscale lossy volume conversion cycle). + + Example 4 (audio positioning): + A user adjusts the relative panning/positioning of the commentary, + soundtrack and sequence for a 5.1 mixing. Later on he decides to + make a 7.1 and a stereo rendering. + The resulting relative positioning should be kept as much as + possible (left/right downmix and re-positioning for extra 2 + channels in the case of 7.1 upmixing) WITHOUT any extra loss in + quality. + + + Create a new 'CompositingProperties' object for audio and video + which is an extensible set of properties for media-specific + positioning. This contains the properties mentionned above. + + Add the CompositingProperties object to the base GESTrackObject + which points to the audio or video CompositingProperties + object (depending on what format that object is handling). + + Provide convenience functions to retrieve and set the audio or video + compositing properties of a GESTrackObject. Do the same for the + GESTimelineObject, which proxies it to the relevant GESTrackObject. + + Create a new GESTrack{Audio|Video}Compositing GstElement which will + be put in each track as a priority 0 expandable NleOperation. + That object will be able to figure out which + mixing/scaling/conversion elements to use at any given time by + inspecting: + * The various GESTrackObject Compositing Properties + * The various GESTrackObject GESMaterial stream properties + * The GESTrack target output GstCaps + + The properties values could be both set/stored as 'relative' values + and as 'absolute' values in order to handle any input/output formats + or setting. + + Objects that are linked/grouped with others have their properties + move in sync with each other. (Ex: If an overlay logo is locked to a + video, it will scale/move/be-transparent in sync with the video on + which it is overlayed). + + Objects that are not linked/grouped to other objects have their + properties move in sync with the target format. If the target format + changes, all object positioning will change relatively to that + format. + + Requires: + GESMaterial + + See also: + Coherent handling of Content in different formats + + + +* Handling of alpha video (i.e. transparency) + + Problem: + Some streams will contain partial transparency (overlay + logos/videos, bluescreen, ...). + + Those streams need to be handle-able by the user just like + non-alpha videos without losing the transparency regions (i.e. it + should be properly blended with the underlying regions). + + + +* Faster/Tighter interaction with GNonLin elements + + Problems: + A lot of properties/concepts need to be duplicated at the GES level + since the only way to communicate with the GNonLin elements is + through publically available APIs (GObject and GStreamer APIs). + + The GESTrackObject for example has to duplicate exactly the same + properties as NleObject for no reason. + + Other properties are also expensive to re-compute and also become + non-MT-safe (like computing the exact 'tree' of objects at a + certain position in a NleComposition). + + Merge the GES and GNonLin modules together into one single module, + and keep the same previous API for both for backward compatibility. + + Add additional APIs to GNonLin which GES can use. + + + +* Media Asset Management integration + + (Track, Search, Browse, Push content) TBD + + + +* Templates + + Problem: + In order to create as quickly as possible professional-looking + timelines, we need to provide a way to create 'templates' which + users can select and have an automatic timeline 'look' used. + + This will allow users to be able to quickly add their clips, set + titles and have a timeline with a professional look. This is + similar to the same feature that iMovie offers both on desktop and + iOS. + + + +* Plugin system + + Problem: + All of GES classes are made in such a way that creating new + sources, effects, templates, formatters, etc... can be easily added + either to the GES codebase itself or to applications. + + But in order to provide more features without depending on GES + releases, limit those features to a single application, and in + order to provide 'closed'/3rd party features, we need to implement + a plugin system so one can add new features. + + Use a registry system similar to GStreamer. diff --git a/docs/random/lifecycle b/docs/random/lifecycle new file mode 100644 index 0000000000..df02e62c3b --- /dev/null +++ b/docs/random/lifecycle @@ -0,0 +1,18 @@ +Lifecycle of a Timeline/Track Object + +* Adding a TimelineObject to a Layer + +(tlobj:timelineobject, trobj:trackobject) + +ges_timeline_layer_add_object(layer, tlobj) + signal_emit "object-added", layer, tlobj + GESTimeline receives signal + for each TRACK { + ges_timeline_object_create_track_objects(tlobj, TRACK) + trobj = GESTimelineObject::create_track_objects + ges_track_add_object(TRACK, trobj) + ges_track_object_set_track(troj, TRACK) + nleobj = GESTrackObject::create_gnl_object + ges_timeline_object_fill_track_object(tlobj, trobj, nleobj) + GESTimelineObject::fill_track_object + diff --git a/docs/random/mapping.txt b/docs/random/mapping.txt new file mode 100644 index 0000000000..e2a3866d13 --- /dev/null +++ b/docs/random/mapping.txt @@ -0,0 +1,99 @@ +Mapping Timeline position to Track position +------------------------------------------- + +TrackObject/TimelineObject basic properties (hereafter position): + start + duration + in-point + priority + + +Use Cases: + + A TimelineObject tracks one or many TrackObject(s). + + When the TimelineObject position is modified we might need + to cascade those changes to the controlled TrackObject(s) if those + TrackObject(s) are 'locked' to the TimelineObject. + + If we modify the positions of a TrackObject that TrackObject is + 'locked' to the TimelineObject, we need to ensure all the other + co-related TrackObject belong to the same TimelineObject are moved in + the same way. + + A TrackObject can be temporarily 'unlocked' from its TimelineObject, + so as to move it independently, and then 'locked' back to it. This + can allow moves, like shifting audio trackobject in relation to the + video trackobject (to fix sync issues) and then 'lock' them back so + as to be able to move them as one entity thereafter. + + When adding TimelineOverlay(s) or TimelineEffect(s) on a + TimelineObject, we need to ensure the TrackObject(s) that those extra + effects will create can be added with specific priority offsets, in + such a way that they always end up "on top" of the TimelineObject's + existing tracked TrackObject(s). + + When a controlled TrackObject is being moved when 'unlocked', we need + to make sure the duration/height of the TimelineObject is updated + accordingly. Ex : moving a TrackObject down by one priority should + increase the TimelineObject "heigh" property by 1. + + A TimelineObject might want to have a tighter control over which + Track(s) each of the TrackObjects it is controlling are going. This + is more obvious in the case of timeline with multiple Tracks of the + same kind, or if a TimelineObject can produce multiple TrackObjects + of the same media type (ex: file with multiple audio tracks). + + +Main Problem: + + There needs to be a mapping between the TimelineObject basic + properties and its controlled TrackObject(s) position. + +Design: + + The TimelineObject listen to TrackObject 'notify' signals + + When it sets a property on its trackobjects, it 'ignores' all + notifications that happen while setting them. + + Setting a property on a TrackObject will see its property changed, + and then it emits a notify with the modified property. + + TrackObject::locked + ges_track_object_set_locked() + ges_track_object_is_locked() + + Mapping { + GESTrackObject *object; + gint64 start_offset; + gint64 duration_offset; + gint64 inpoint_offset; + gint32 priority_offset; + /* Track ??? */ + } + + P : property + V : value + + TimelineObject set_property(P,V) + ignore_notifies = TRUE + parent.P = V + foreach child in trackobjects: + if child.is_locked(): + child.set_property(P, parent.P + mapping(child).P_offset) + ignore_notifies = FALSE + + TimelineObject child 'notify::P' handler: + if ignore_notifies: + return + if not child.is_locked(): + mapping(child).P_offset = timeline.P - child.P + else: + TimelineObject.set_property(P, child value + mapping(child).P_offset) + + TrackObject set_property(P, V) + update the property locally (P = V) + emit 'notify::P' signal + + TODO : When do we resync the parent values to have minimal offsets ? diff --git a/docs/random/rework_class_hierarchie.html b/docs/random/rework_class_hierarchie.html new file mode 100644 index 0000000000..9d7109b14f --- /dev/null +++ b/docs/random/rework_class_hierarchie.html @@ -0,0 +1,103 @@ + + + + Rework the GStreamer Editing Services class hierarchy + + +Reasoning: +---------- + +All the time (position) related concepts are shared between GESTimelineObject and GESTrackObject +and currently are repeated at the 2 levels. +Moreover, if we want to add the concept of Group we end up with something quite similare to the current +GESTimelineObject but that contains GESTimelineObject-s instead of GESTrackObject-s so we could share +those informations creating a new class aiming at containing the objects that have that +notion of timing. + +At the same time, we want to clarify namings. First we should remove the word Object in class names, +we have been told various times that it sounds just "wrong" for people as Objects are instances and there +we are talking about Classes. + +Class Hierarchy: +------------- + +<pre><code> +<table> +<tr> + <td> + +Before: +------- + +GESTimelineObject + GESTimelineSource + GESCustomTimelineSource + GESTimelineTestSource + GESTimelineFileSource + GESTimelineTitleSource + GESTimelineOperation + GESTimelineOverlay + GESTimelineTextOverlay + GESTimelineTransition + GESTimelineTransition + GESTimelineEffect + GESTimelineParseLaunchEffect +GESTimelineLayer + GESSimpleTimelineLayer +GESTrackObject + GESTrackSource + GESTrackAudioTestSource + GESTrackFileSource + GESTrackImageSource + GESTrackTitleSource + GESTrackVideoTestSource + GESTrackOperation + GESTrackTransition + GESTrackAudioTransition + GESTrackVideoTransition + GESTrackEffect + GESTrackParseLaunchEffect + GESTrackTextOverlay + </td> + <td> + +After: +------- + +GESTimelineElement + GESContainer + GESClip + GESSourceClip + GESCustomSourceClip + GESTestClip + GESUriClip + GESTitleClip + GESOperationClip + GESOverlayClip + GESTextOverlayClip + GESBaseTransitionClip + GESTransitionClip + GESBaseEffectClip + GESEffectClip + GESClipGroup + GESTrackElement + GESSource + GESAudioTestSource + GESUriSource + GESImageSource + GESTitleSource + GESVideoTestSource + GESOperation + GESTransition + GESAudioTransition + GESVideoTransition + GESBaseEffect + GESEffect + GESTextOverlay + </td> + </tr> +</table> +</code></pre> + + + diff --git a/docs/random/scenarios b/docs/random/scenarios new file mode 100644 index 0000000000..9b6425531e --- /dev/null +++ b/docs/random/scenarios @@ -0,0 +1,143 @@ +SCENARIOS + +* Adding a TimelineObject to a TimelineLayer +-------------------------------------------- + + * Create a Timeline + + * Create a Track + + * Add the track to the Timeline (==> ges_timeline_add_track (track);) + The Timeline adds the Track to itself (i.e. gst_bin_add()) + 'track-added' is emitted + + * Create a TimelineLayer + + * Add the TimelineLayer to the Timeline (ges_timeline_add_layer (layer);) + The Timeline takes a reference on the layer and stores it + The Timeline tells the TimelineLayer that it now belongs to the given Timeline (weak reference) + ==> ges_timeline_layer_set_timeline (); + 'layer-added' is emitted + + * Create a TimelineObject + + * Add the TimelineObject to the TimelineLayer (ges_timeline_layer_add_object (object);) + The TimelineLayer takes a reference on the TimelineObject and stores it + The timelineLayer tells the TimelineObject that it now belongs to the given layer (weak reference) + ==> ges_timeline_object_set_layer (); + 'object-added' is emitted by TimelineLayer + The Timeline requests a new TrackObject from the new TimelineObject for each Track + ==> ges_timeline_object_create_track_object (track) + The TimelineObject calls the 'create_track_object' virtual method with the given track + Example implementation + Create a GESTrackSource + (GESTimelineObject is a constructor property of track objects) + A GESTrackObject CAN NOT EXIST WITHOUT A GESTimelineObject ! + The Timeline adds the newly created TrackObject to the Track + ==> ges_track_add_object (track, trackobject); + Set the track on the TrackObject + ==> ges_track_object_set_track (track) + The GESTrackObject can create the NleObject + + + +Methods +------- + +[ GESTimeline ] + +* gboolean + ges_timeline_add_track (GESTimeline * timeline, GESTrack * track); + + * The Timeline adds the track to itself (gst_bin_add ()) # reference implicitely taken + * The Timeline adds the track to its list of tracked tracks + * The Timeline sets the Timeline on the track + => ges_track_set_timeline (GESTrack * track, GESTimeline * timeline); + Just sets the timeline field of the track. + * emits 'track-added' + + +* gboolean + ges_timeline_add_layer (GESTimeline * timeline, GESTimelineLayer * layer); + + * The Timeline takes a reference on the layer and stores it + * The Timeline tells the Layer that it now belongs to the given Timeline + => ges_timeline_layer_set_timeline (GESTimelineLayer * layer, GESTimeline * timeline); + Just sets the timeline field of the layer. + * Connect to the layer's 'object-added' signal + * emits 'layer-added' + + +* GESTimeline's + callback for GESTimelineLayer::object-added (GESTimelineLayer * layer, GESTimelineObject * object); + + * For each GESTrack in the Timeline: + * The timeline requests a new TrackObject for the new TimelineObject for each Track + trackobj = ges_timeline_object_create_track_object (timelineobj, track); + * The timeline adds the newly created TrackObject to the track + ges_track_add_object (track, trackobj); + +[ GESTimelineLayer ] + +* gboolean + ges_timeline_layer_add_object (GESTimelineLayer * layer, GESTimelineObject * object); + + * The TimelineLayer takes a reference on the TimelineObject and stores it + * The TimelineLayer tells the TimelineObject it now belongs to the given Layer + => ges_timeline_object_set_layer (GESTimelineObject * object, GESTimelineLayer * layer); + Just sets the layer field of the timeline object. + * emits 'object-added' + + +[ GESTimelineObject ] + +* GESTrackObject * + ges_timeline_object_create_track_object (GESTimelineObject * object, GESTrack * track); + + * The TimelineObject calls the 'create_track_object' virtual method + * The TimelineObject sets the TimelineObject on the new TrackObject + => ges_track_object_set_timeline_object (track_object, timeline_object); + Just sets the timeline-object field of the TrackObject + * Return the newly created GESTrackObject + + +* Virtual-method for GESTimelineObject::create_track_object (GESTimelineObject * object, GESTrack * track); + + * Create a track object of the proper type + Ex (for a source) : + return ges_track_source_new(); + +* gboolean + ges_timeline_object_fill_track_object (GESTimelineObject *tlo, GESTrackObject *tro, GstElement *nleobj); + + * up to the implementation :) + + +[ GESTrack ] + +* gboolean + ges_track_add_object (GESTrack * track, GESTrackObject * object); + + * Set the track on the track_object + ges_track_object_set_track (object, track); + * Add the NleObject of the TrackObject to the composition + gst_bin_add (track->composition, object->nleobject); + + +[ GESTrackObject ] + +* gboolean + ges_track_object_set_track (GESTrackObject * object, GESTrack * track); + + * Set the track field of the TrackObject + * if no NleObject is available yet: + * Call the 'create_gnl_object' virtual method + + +* Virtual-method for GESTrackObject::create_gnl_object + + * Create a NleObject of the proper type + Ex : nleobject = gst_element_factory_make("nlesource", NULL); + * Ask the TimelineObject to fill in the NleObject + => ges_timeline_object_fill_track_object (GESTimelineObject * tlo, GESTrackObject * tro, GstElement * nleobj); + diff --git a/docs/sitemap.txt b/docs/sitemap.txt new file mode 100644 index 0000000000..c68b6ecbd8 --- /dev/null +++ b/docs/sitemap.txt @@ -0,0 +1,65 @@ +gi-index + ges.h + ges-timeline.h + ges-layer.h + ges-clip.h + ges-uri-clip.h + ges-title-clip.h + ges-test-clip.h + ges-time-overlay-clip.h + ges-effect-clip.h + ges-transition-clip.h + ges-pipeline.h + ges-project.h + base-classes.md + ges-timeline-element.h + ges-container.h + ges-track.h + ges-audio-track.h + ges-video-track.h + ges-asset.h + ges-uri-asset.h + ges-clip-asset.h + ges-effect-asset.h + ges-track-element-asset.h + ges-source-clip-asset.h + ges-effect.h + ges-extractable.h + ges-group.h + ges-meta-container.h + ges-marker-list.h + ges-formatter.h + ges-xml-formatter.h + ges-track-element.h + ges-video-source.h + ges-audio-source.h + ges-audio-test-source.h + ges-audio-uri-source.h + ges-video-uri-source.h + ges-video-test-source.h + ges-title-source.h + ges-text-overlay.h + ges-gerror.h + ges-types.h + ges-enums.h + ges-utils.h + low_level.md + ges-base-xml-formatter.h + ges-command-line-formatter.h + ges-audio-transition.h + ges-base-effect-clip.h + ges-base-effect.h + ges-base-transition-clip.h + ges-operation-clip.h + ges-operation.h + ges-overlay-clip.h + ges-source-clip.h + ges-source.h + ges-text-overlay-clip.h + ges-transition.h + ges-video-transition.h + ges-prelude.h + deprecated.md + ges-pitivi-formatter.h + ges-image-source.h + ges-multi-file-source.h \ No newline at end of file diff --git a/docs/version.entities.in b/docs/version.entities.in new file mode 100644 index 0000000000..606e725c6f --- /dev/null +++ b/docs/version.entities.in @@ -0,0 +1,2 @@ + + diff --git a/docs/working-diagrams.svg b/docs/working-diagrams.svg new file mode 100644 index 0000000000..6525cbd959 --- /dev/null +++ b/docs/working-diagrams.svg @@ -0,0 +1,563 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + GESTimeline + + + Layer + + + Track + + + Track + Uservisible + Medias + + + + + + + + Time + + + + + + + + + + FileSource A + FileSource C(muted) + + FileSource B(+fx) + Video Track + Audio Track + Layer + Source A + Source A + SourceB + BaseEffect + Source C + SourceB + + + diff --git a/examples/c/assets.c b/examples/c/assets.c new file mode 100644 index 0000000000..b1abac4e5b --- /dev/null +++ b/examples/c/assets.c @@ -0,0 +1,69 @@ +/* GStreamer Editing Services + * Copyright (C) 2012 Volodymyr Rudyi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +static void +asset_loaded_cb (GObject * source, GAsyncResult * res, GMainLoop * mainloop) +{ + GESUriClipAsset *mfs = + GES_URI_CLIP_ASSET (ges_asset_request_finish (res, NULL)); + GstDiscovererInfo *discoverer_info = NULL; + discoverer_info = ges_uri_clip_asset_get_info (mfs); + + GST_DEBUG ("Result is %d", gst_discoverer_info_get_result (discoverer_info)); + GST_DEBUG ("Info type is %s", G_OBJECT_TYPE_NAME (mfs)); + GST_DEBUG ("Duration is %" GST_TIME_FORMAT, + GST_TIME_ARGS (ges_uri_clip_asset_get_duration (mfs))); + + gst_object_unref (mfs); + + g_main_loop_quit (mainloop); +} + +int +main (int argc, gchar ** argv) +{ + GMainLoop *mainloop; + + if (argc != 2) { + return 1; + } + /* Initialize GStreamer (this will parse environment variables and commandline + * arguments. */ + gst_init (NULL, NULL); + + /* Initialize the GStreamer Editing Services */ + ges_init (); + + /* ... and we start a GMainLoop. GES **REQUIRES** a GMainLoop to be running in + * order to function properly ! */ + mainloop = g_main_loop_new (NULL, FALSE); + + ges_asset_request_async (GES_TYPE_URI_CLIP, argv[1], NULL, + (GAsyncReadyCallback) asset_loaded_cb, mainloop); + + g_main_loop_run (mainloop); + g_main_loop_unref (mainloop); + + return 0; +} diff --git a/examples/c/concatenate.c b/examples/c/concatenate.c new file mode 100644 index 0000000000..b543cc99c1 --- /dev/null +++ b/examples/c/concatenate.c @@ -0,0 +1,193 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Edward Hervey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +static void +bus_message_cb (GstBus * bus, GstMessage * message, GMainLoop * mainloop); + +static GstEncodingProfile *make_profile_from_info (GstDiscovererInfo * info); + +GESLayer *layer = NULL; +GESPipeline *pipeline = NULL; +GESTimeline *timeline = NULL; +gchar *output_uri = NULL; +guint assetsCount = 0; +guint assetsLoaded = 0; + +static void +asset_loaded_cb (GObject * source_object, GAsyncResult * res, + GMainLoop * mainloop) +{ + GError *error = NULL; + guint64 duration = 0; + + GESUriClipAsset *mfs = + GES_URI_CLIP_ASSET (ges_asset_request_finish (res, &error)); + + if (error) { + GST_WARNING ("error creating asset %s", error->message); + + return; + } + + duration = ges_uri_clip_asset_get_duration (mfs); + ges_layer_add_asset (layer, + GES_ASSET (source_object), + ges_timeline_get_duration (timeline), + 0, duration, ges_clip_asset_get_supported_formats (GES_CLIP_ASSET (mfs))); + + assetsLoaded++; + /* + * Check if we have loaded last asset and trigger concatenating + */ + if (assetsLoaded == assetsCount) { + GstDiscovererInfo *info = ges_uri_clip_asset_get_info (mfs); + GstEncodingProfile *profile = make_profile_from_info (info); + ges_pipeline_set_render_settings (pipeline, output_uri, profile); + /* We want the pipeline to render (without any preview) */ + if (!ges_pipeline_set_mode (pipeline, GES_PIPELINE_MODE_SMART_RENDER)) { + g_main_loop_quit (mainloop); + return; + } + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); + } + + gst_object_unref (mfs); +} + +int +main (int argc, char **argv) +{ + GMainLoop *mainloop = NULL; + GESTimeline *timeline; + GstBus *bus = NULL; + guint i; + + + if (argc < 3) { + gst_print ("Usage: %s \n", argv[0]); + return -1; + } + + gst_init (&argc, &argv); + ges_init (); + + timeline = ges_timeline_new_audio_video (); + + layer = (GESLayer *) ges_layer_new (); + if (!ges_timeline_add_layer (timeline, layer)) + return -1; + + output_uri = argv[1]; + assetsCount = argc - 2; + + for (i = 2; i < argc; i++) { + ges_asset_request_async (GES_TYPE_URI_CLIP, argv[i], + NULL, (GAsyncReadyCallback) asset_loaded_cb, mainloop); + } + + /* In order to view our timeline, let's grab a convenience pipeline to put + * our timeline in. */ + pipeline = ges_pipeline_new (); + + /* Add the timeline to that pipeline */ + if (!ges_pipeline_set_timeline (pipeline, timeline)) + return -1; + + mainloop = g_main_loop_new (NULL, FALSE); + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + gst_bus_add_signal_watch (bus); + g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), mainloop); + + g_main_loop_run (mainloop); + + return 0; + +} + +static void +bus_message_cb (GstBus * bus, GstMessage * message, GMainLoop * mainloop) +{ + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + gst_print ("ERROR\n"); + g_main_loop_quit (mainloop); + break; + case GST_MESSAGE_EOS: + gst_print ("Done\n"); + g_main_loop_quit (mainloop); + break; + default: + break; + } +} + +static GstEncodingProfile * +make_profile_from_info (GstDiscovererInfo * info) +{ + GstEncodingContainerProfile *profile = NULL; + GstDiscovererStreamInfo *sinfo = gst_discoverer_info_get_stream_info (info); + + /* Get the container format */ + if (GST_IS_DISCOVERER_CONTAINER_INFO (sinfo)) { + GList *tmp, *substreams; + + profile = gst_encoding_container_profile_new ((gchar *) "concatenate", NULL, + gst_discoverer_stream_info_get_caps (sinfo), NULL); + + substreams = + gst_discoverer_container_info_get_streams ((GstDiscovererContainerInfo + *) sinfo); + + /* For each on the formats add stream profiles */ + for (tmp = substreams; tmp; tmp = tmp->next) { + GstDiscovererStreamInfo *stream = GST_DISCOVERER_STREAM_INFO (tmp->data); + GstEncodingProfile *sprof = NULL; + + if (GST_IS_DISCOVERER_VIDEO_INFO (stream)) { + sprof = (GstEncodingProfile *) + gst_encoding_video_profile_new (gst_discoverer_stream_info_get_caps + (stream), NULL, NULL, 1); + } else if (GST_IS_DISCOVERER_AUDIO_INFO (stream)) { + sprof = (GstEncodingProfile *) + gst_encoding_audio_profile_new (gst_discoverer_stream_info_get_caps + (stream), NULL, NULL, 1); + } else { + GST_WARNING ("Unsupported streams"); + } + + if (sprof) + gst_encoding_container_profile_add_profile (profile, sprof); + } + if (substreams) + gst_discoverer_stream_info_list_free (substreams); + } else { + GST_ERROR ("No container format !!!"); + } + + if (sinfo) + gst_discoverer_stream_info_unref (sinfo); + + return GST_ENCODING_PROFILE (profile); +} diff --git a/examples/c/ges-ui.c b/examples/c/ges-ui.c new file mode 100644 index 0000000000..5f7d2d5e94 --- /dev/null +++ b/examples/c/ges-ui.c @@ -0,0 +1,1703 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* Application Data ********************************************************/ + +/** + * Contains most of the application data so that signal handlers + * and other callbacks have easy access. + */ + +typedef struct App +{ + /* back-end objects */ + GESTimeline *timeline; + GESPipeline *pipeline; + GESLayer *layer; + GESTrack *audio_track; + GESTrack *video_track; + guint audio_tracks; + guint video_tracks; + + /* application state */ + gchar *pending_uri; + int n_objects; + + int n_selected; + GList *selected_objects; + GType selected_type; + gboolean first_selected; + gboolean last_selected; + + gboolean ignore_input; + GstState state; + + GtkListStore *model; + GtkTreeSelection *selection; + + /* widgets */ + GtkWidget *main_window; + GtkWidget *add_effect_dlg; + GtkWidget *properties; + GtkWidget *filesource_properties; + GtkWidget *text_properties; + GtkWidget *generic_duration; + GtkWidget *background_properties; + GtkWidget *audio_effect_entry; + GtkWidget *video_effect_entry; + + GtkHScale *duration; + GtkHScale *in_point; + GtkHScale *volume; + + GtkAction *add_file; + GtkAction *add_effect; + GtkAction *add_test; + GtkAction *add_title; + GtkAction *add_transition; + GtkAction *delete; + GtkAction *play; + GtkAction *stop; + GtkAction *move_up; + GtkAction *move_down; + GtkToggleAction *audio_track_action; + GtkToggleAction *video_track_action; + + GtkComboBox *halign; + GtkComboBox *valign; + GtkComboBox *background_type; + + GtkEntry *text; + GtkEntry *seconds; + + GtkSpinButton *frequency; +} App; + +static int n_instances = 0; + +/* Prototypes for auto-connected signal handlers ***************************/ + +/** + * These are declared non-static for signal auto-connection + */ + +gboolean window_delete_event_cb (GtkWidget * window, GdkEvent * event, + App * app); +void new_activate_cb (GtkMenuItem * item, App * app); +void open_activate_cb (GtkMenuItem * item, App * app); +void save_as_activate_cb (GtkMenuItem * item, App * app); +void launch_project_activate_cb (GtkMenuItem * item, App * app); +void quit_item_activate_cb (GtkMenuItem * item, App * app); +void delete_activate_cb (GtkAction * item, App * app); +void play_activate_cb (GtkAction * item, App * app); +void stop_activate_cb (GtkAction * item, App * app); +void move_up_activate_cb (GtkAction * item, App * app); +void move_down_activate_cb (GtkAction * item, App * app); +void add_effect_activate_cb (GtkAction * item, App * app); +void add_file_activate_cb (GtkAction * item, App * app); +void add_text_activate_cb (GtkAction * item, App * app); +void add_test_activate_cb (GtkAction * item, App * app); +void audio_track_activate_cb (GtkToggleAction * item, App * app); +void video_track_activate_cb (GtkToggleAction * item, App * app); +void add_transition_activate_cb (GtkAction * item, App * app); +void app_selection_changed_cb (GtkTreeSelection * selection, App * app); +void halign_changed_cb (GtkComboBox * widget, App * app); +void valign_changed_cb (GtkComboBox * widget, App * app); +void background_type_changed_cb (GtkComboBox * widget, App * app); +void frequency_value_changed_cb (GtkSpinButton * widget, App * app); +void on_apply_effect_cb (GtkButton * button, App * app); +void on_cancel_add_effect_cb (GtkButton * button, App * app); +gboolean add_effect_dlg_delete_event_cb (GtkWidget * widget, GdkEvent * event, + gpointer * app); + +gboolean +duration_scale_change_value_cb (GtkRange * range, + GtkScrollType unused, gdouble value, App * app); + +gboolean +in_point_scale_change_value_cb (GtkRange * range, + GtkScrollType unused, gdouble value, App * app); + +gboolean +volume_change_value_cb (GtkRange * range, + GtkScrollType unused, gdouble value, App * app); + +/* UI state functions *******************************************************/ + +/** + * Update properties of UI elements that depend on more than one thing. + */ + +static void +update_effect_sensitivity (App * app) +{ + GList *i; + gboolean ok = TRUE; + + /* effects will work for multiple FileSource */ + for (i = app->selected_objects; i; i = i->next) { + if (!GES_IS_URI_CLIP (i->data)) { + ok = FALSE; + break; + } + } + + gtk_action_set_sensitive (app->add_effect, + ok && (app->n_selected > 0) && (app->state != GST_STATE_PLAYING) + && (app->state != GST_STATE_PAUSED)); +} + +static void +update_delete_sensitivity (App * app) +{ + /* delete will work for multiple items */ + gtk_action_set_sensitive (app->delete, + (app->n_selected > 0) && (app->state != GST_STATE_PLAYING) + && (app->state != GST_STATE_PAUSED)); +} + +static void +update_add_transition_sensitivity (App * app) +{ + gtk_action_set_sensitive (app->add_transition, + (app->state != GST_STATE_PLAYING) && (app->state != GST_STATE_PAUSED)); +} + +static void +update_move_up_down_sensitivity (App * app) +{ + gboolean can_move; + + can_move = (app->n_selected == 1) && + (app->state != GST_STATE_PLAYING) && (app->state != GST_STATE_PAUSED); + + gtk_action_set_sensitive (app->move_up, can_move && (!app->first_selected)); + gtk_action_set_sensitive (app->move_down, can_move && (!app->last_selected)); +} + +static void +update_play_sensitivity (App * app) +{ + gtk_action_set_sensitive (app->play, app->n_objects); +} + +/* Backend callbacks ********************************************************/ + +static void +test_source_notify_volume_changed_cb (GESClip * clip, GParamSpec * + unused G_GNUC_UNUSED, App * app) +{ + gdouble volume; + + g_object_get (G_OBJECT (clip), "volume", &volume, NULL); + + gtk_range_set_value (GTK_RANGE (app->volume), volume); +} + +static void +layer_notify_valid_changed_cb (GObject * object, GParamSpec * unused + G_GNUC_UNUSED, App * app) +{ + update_play_sensitivity (app); +} + +static gboolean +find_row_for_object (GtkListStore * model, GtkTreeIter * ret, GESClip * clip) +{ + gtk_tree_model_get_iter_first ((GtkTreeModel *) model, ret); + + while (gtk_list_store_iter_is_valid (model, ret)) { + GESClip *clip2; + gtk_tree_model_get ((GtkTreeModel *) model, ret, 2, &clip2, -1); + if (clip2 == clip) { + gst_object_unref (clip2); + return TRUE; + } + gst_object_unref (clip2); + gtk_tree_model_iter_next ((GtkTreeModel *) model, ret); + } + return FALSE; +} + +/* this callback is registered for every clip, and updates the + * corresponding duration cell in the model */ +static void +clip_notify_duration_cb (GESClip * clip, + GParamSpec * arg G_GNUC_UNUSED, App * app) +{ + GtkTreeIter iter; + guint64 duration = 0; + + g_object_get (clip, "duration", &duration, NULL); + find_row_for_object (app->model, &iter, clip); + gtk_list_store_set (app->model, &iter, 1, duration, -1); +} + +/* these guys are only connected to filesources that are the target of the + * current selection */ + +static void +filesource_notify_duration_cb (GESClip * clip, + GParamSpec * arg G_GNUC_UNUSED, App * app) +{ + guint64 duration, max_inpoint; + duration = GES_TIMELINE_ELEMENT_DURATION (clip); + max_inpoint = GES_TIMELINE_ELEMENT_MAX_DURATION (clip) - duration; + + gtk_range_set_value (GTK_RANGE (app->duration), duration); + gtk_range_set_fill_level (GTK_RANGE (app->in_point), max_inpoint); + + if (max_inpoint < GES_TIMELINE_ELEMENT_INPOINT (clip)) + g_object_set (clip, "in-point", max_inpoint, NULL); + +} + +static void +filesource_notify_max_duration_cb (GESClip * clip, + GParamSpec * arg G_GNUC_UNUSED, App * app) +{ + gtk_range_set_range (GTK_RANGE (app->duration), 0, (gdouble) + GES_TIMELINE_ELEMENT_MAX_DURATION (clip)); + gtk_range_set_range (GTK_RANGE (app->in_point), 0, (gdouble) + GES_TIMELINE_ELEMENT_MAX_DURATION (clip)); +} + +static void +filesource_notify_in_point_cb (GESClip * clip, + GParamSpec * arg G_GNUC_UNUSED, App * app) +{ + gtk_range_set_value (GTK_RANGE (app->in_point), + GES_TIMELINE_ELEMENT_INPOINT (clip)); +} + +static void +app_update_first_last_selected (App * app) +{ + GtkTreePath *path; + + /* keep track of whether the first or last items are selected */ + path = gtk_tree_path_new_from_indices (0, -1); + app->first_selected = + gtk_tree_selection_path_is_selected (app->selection, path); + gtk_tree_path_free (path); + + path = gtk_tree_path_new_from_indices (app->n_objects - 1, -1); + app->last_selected = + gtk_tree_selection_path_is_selected (app->selection, path); + gtk_tree_path_free (path); +} + +static void +object_count_changed (App * app) +{ + app_update_first_last_selected (app); + update_move_up_down_sensitivity (app); + update_play_sensitivity (app); +} + +static void +title_source_text_changed_cb (GESClip * clip, + GParamSpec * arg G_GNUC_UNUSED, App * app) +{ + GtkTreeIter iter; + gchar *text; + + g_object_get (clip, "text", &text, NULL); + if (text) { + find_row_for_object (app->model, &iter, clip); + gtk_list_store_set (app->model, &iter, 0, text, -1); + } +} + +static void +layer_object_added_cb (GESLayer * layer, GESClip * clip, App * app) +{ + GtkTreeIter iter; + gchar *description; + + GST_INFO ("layer clip added cb %p %p %p", layer, clip, app); + + gtk_list_store_append (app->model, &iter); + + if (GES_IS_URI_CLIP (clip)) { + g_object_get (G_OBJECT (clip), "uri", &description, NULL); + gtk_list_store_set (app->model, &iter, 0, description, 2, clip, -1); + } + + else if (GES_IS_TITLE_CLIP (clip)) { + gtk_list_store_set (app->model, &iter, 2, clip, -1); + g_signal_connect (G_OBJECT (clip), "notify::text", + G_CALLBACK (title_source_text_changed_cb), app); + title_source_text_changed_cb (clip, NULL, app); + } + + else if (GES_IS_TEST_CLIP (clip)) { + gtk_list_store_set (app->model, &iter, 2, clip, 0, "Test Source", -1); + } + + else if (GES_IS_BASE_TRANSITION_CLIP (clip)) { + gtk_list_store_set (app->model, &iter, 2, clip, 0, "Transition", -1); + } + + g_signal_connect (G_OBJECT (clip), "notify::duration", + G_CALLBACK (clip_notify_duration_cb), app); + clip_notify_duration_cb (clip, NULL, app); + + app->n_objects++; + object_count_changed (app); +} + +static void +layer_object_removed_cb (GESLayer * layer, GESClip * clip, App * app) +{ + GtkTreeIter iter; + + GST_INFO ("layer clip removed cb %p %p %p", layer, clip, app); + + if (!find_row_for_object (GTK_LIST_STORE (app->model), &iter, clip)) { + gst_print ("clip deleted but we don't own it"); + return; + } + app->n_objects--; + object_count_changed (app); + + gtk_list_store_remove (app->model, &iter); +} + +static void +layer_object_moved_cb (GESClip * layer, GESClip * clip, + gint old, gint new, App * app) +{ + GtkTreeIter a, b; + GtkTreePath *path; + + /* we can take the old position as given, but the new position might have to + * be adjusted. */ + new = new < 0 ? (app->n_objects - 1) : new; + + path = gtk_tree_path_new_from_indices (old, -1); + gtk_tree_model_get_iter (GTK_TREE_MODEL (app->model), &a, path); + gtk_tree_path_free (path); + + path = gtk_tree_path_new_from_indices (new, -1); + gtk_tree_model_get_iter (GTK_TREE_MODEL (app->model), &b, path); + gtk_tree_path_free (path); + + gtk_list_store_swap (app->model, &a, &b); + app_selection_changed_cb (app->selection, app); + update_move_up_down_sensitivity (app); +} + +static void +pipeline_state_changed_cb (App * app) +{ + gboolean playing_or_paused; + + if (app->state == GST_STATE_PLAYING) + gtk_action_set_stock_id (app->play, GTK_STOCK_MEDIA_PAUSE); + else + gtk_action_set_stock_id (app->play, GTK_STOCK_MEDIA_PLAY); + + update_delete_sensitivity (app); + update_add_transition_sensitivity (app); + update_move_up_down_sensitivity (app); + + playing_or_paused = (app->state == GST_STATE_PLAYING) || + (app->state == GST_STATE_PAUSED); + + gtk_action_set_sensitive (app->add_file, !playing_or_paused); + gtk_action_set_sensitive (app->add_title, !playing_or_paused); + gtk_action_set_sensitive (app->add_test, !playing_or_paused); + gtk_action_set_sensitive ((GtkAction *) app->audio_track_action, + !playing_or_paused); + gtk_action_set_sensitive ((GtkAction *) app->video_track_action, + !playing_or_paused); + gtk_widget_set_sensitive (app->properties, !playing_or_paused); +} + +static void +project_bus_message_cb (GstBus * bus, GstMessage * message, + GMainLoop * mainloop) +{ + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + gst_printerr ("ERROR\n"); + g_main_loop_quit (mainloop); + break; + case GST_MESSAGE_EOS: + gst_printerr ("Done\n"); + g_main_loop_quit (mainloop); + break; + default: + break; + } +} + +static void +bus_message_cb (GstBus * bus, GstMessage * message, App * app) +{ + const GstStructure *s; + s = gst_message_get_structure (message); + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + gst_print ("ERROR\n"); + break; + case GST_MESSAGE_EOS: + gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_READY); + break; + case GST_MESSAGE_STATE_CHANGED: + if (s && GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (app->pipeline)) { + GstState old, new, pending; + gst_message_parse_state_changed (message, &old, &new, &pending); + app->state = new; + pipeline_state_changed_cb (app); + } + break; + default: + break; + } +} + +/* Static UI Callbacks ******************************************************/ + +static gboolean +check_time (const gchar * time) +{ + static GRegex *re = NULL; + + if (!re) { + if (NULL == (re = + g_regex_new ("^[0-9][0-9]:[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?$", + G_REGEX_EXTENDED, 0, NULL))) + return FALSE; + } + + if (g_regex_match (re, time, 0, NULL)) + return TRUE; + return FALSE; +} + +static guint64 +str_to_time (const gchar * str) +{ + guint64 ret; + guint64 h, m; + gdouble s; + gchar buf[15]; + + buf[0] = str[0]; + buf[1] = str[1]; + buf[2] = '\0'; + + h = strtoull (buf, NULL, 10); + + buf[0] = str[3]; + buf[1] = str[4]; + buf[2] = '\0'; + + m = strtoull (buf, NULL, 10); + + strncpy (buf, &str[6], sizeof (buf) - 1); + buf[sizeof (buf) - 1] = '\0'; + s = strtod (buf, NULL); + + ret = (h * 3600 * GST_SECOND) + + (m * 60 * GST_SECOND) + ((guint64) (s * GST_SECOND)); + + return ret; +} + +static void +text_notify_text_changed_cb (GtkEntry * widget, GParamSpec * unused, App * app) +{ + GList *tmp; + const gchar *text; + + if (app->ignore_input) + return; + + text = gtk_entry_get_text (widget); + + for (tmp = app->selected_objects; tmp; tmp = tmp->next) { + g_object_set (G_OBJECT (tmp->data), "text", text, NULL); + } +} + +static void +seconds_notify_text_changed_cb (GtkEntry * widget, GParamSpec * unused, + App * app) +{ + GList *tmp; + const gchar *text; + + if (app->ignore_input) + return; + + text = gtk_entry_get_text (app->seconds); + + if (!check_time (text)) { + gtk_entry_set_icon_from_stock (app->seconds, + GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_DIALOG_WARNING); + } else { + gtk_entry_set_icon_from_stock (app->seconds, + GTK_ENTRY_ICON_SECONDARY, NULL); + for (tmp = app->selected_objects; tmp; tmp = tmp->next) { + g_object_set (GES_CLIP (tmp->data), "duration", + (guint64) str_to_time (text), NULL); + } + } +} + +static void +duration_cell_func (GtkTreeViewColumn * column, GtkCellRenderer * renderer, + GtkTreeModel * model, GtkTreeIter * iter, gpointer user) +{ + gchar buf[30]; + guint64 duration; + + gtk_tree_model_get (model, iter, 1, &duration, -1); + g_snprintf (buf, sizeof (buf), "%u:%02u:%02u.%09u", GST_TIME_ARGS (duration)); + g_object_set (renderer, "text", &buf, NULL); +} + +/* UI Initialization ********************************************************/ + +static void +connect_to_filesource (GESClip * clip, App * app) +{ + g_signal_connect (G_OBJECT (clip), "notify::max-duration", + G_CALLBACK (filesource_notify_max_duration_cb), app); + filesource_notify_max_duration_cb (clip, NULL, app); + + g_signal_connect (G_OBJECT (clip), "notify::duration", + G_CALLBACK (filesource_notify_duration_cb), app); + filesource_notify_duration_cb (clip, NULL, app); + + g_signal_connect (G_OBJECT (clip), "notify::in-point", + G_CALLBACK (filesource_notify_in_point_cb), app); + filesource_notify_in_point_cb (clip, NULL, app); +} + +static void +disconnect_from_filesource (GESClip * clip, App * app) +{ + g_signal_handlers_disconnect_by_func (G_OBJECT (clip), + filesource_notify_duration_cb, app); + + g_signal_handlers_disconnect_by_func (G_OBJECT (clip), + filesource_notify_max_duration_cb, app); +} + +static void +connect_to_title_source (GESClip * clip, App * app) +{ + GESTitleClip *titleclip; + titleclip = GES_TITLE_CLIP (clip); + gtk_combo_box_set_active (app->halign, + ges_title_clip_get_halignment (titleclip)); + gtk_combo_box_set_active (app->valign, + ges_title_clip_get_valignment (titleclip)); + gtk_entry_set_text (app->text, ges_title_clip_get_text (titleclip)); +} + +static void +disconnect_from_title_source (GESClip * clip, App * app) +{ +} + +static void +connect_to_test_source (GESClip * clip, App * app) +{ + GObjectClass *klass; + GParamSpecDouble *pspec; + + GESTestClip *testclip; + testclip = GES_TEST_CLIP (clip); + gtk_combo_box_set_active (app->background_type, + ges_test_clip_get_vpattern (testclip)); + + g_signal_connect (G_OBJECT (testclip), "notify::volume", + G_CALLBACK (test_source_notify_volume_changed_cb), app); + test_source_notify_volume_changed_cb (clip, NULL, app); + + klass = G_OBJECT_GET_CLASS (G_OBJECT (testclip)); + + pspec = G_PARAM_SPEC_DOUBLE (g_object_class_find_property (klass, "volume")); + gtk_range_set_range (GTK_RANGE (app->volume), pspec->minimum, pspec->maximum); + + pspec = G_PARAM_SPEC_DOUBLE (g_object_class_find_property (klass, "freq")); + gtk_spin_button_set_range (app->frequency, pspec->minimum, pspec->maximum); + gtk_spin_button_set_value (app->frequency, + ges_test_clip_get_frequency (GES_TEST_CLIP (clip))); +} + +static void +disconnect_from_test_source (GESClip * clip, App * app) +{ + g_signal_handlers_disconnect_by_func (G_OBJECT (clip), + test_source_notify_volume_changed_cb, app); +} + +static void +connect_to_object (GESClip * clip, App * app) +{ + gchar buf[30]; + guint64 duration; + + app->ignore_input = TRUE; + + duration = GES_TIMELINE_ELEMENT_DURATION (clip); + g_snprintf (buf, sizeof (buf), "%02u:%02u:%02u.%09u", + GST_TIME_ARGS (duration)); + gtk_entry_set_text (app->seconds, buf); + + if (GES_IS_URI_CLIP (clip)) { + connect_to_filesource (clip, app); + } else if (GES_IS_TITLE_CLIP (clip)) { + connect_to_title_source (clip, app); + } else if (GES_IS_TEST_CLIP (clip)) { + connect_to_test_source (clip, app); + } + + app->ignore_input = FALSE; +} + +static void +disconnect_from_object (GESClip * clip, App * app) +{ + if (GES_IS_URI_CLIP (clip)) { + disconnect_from_filesource (clip, app); + } else if (GES_IS_TITLE_CLIP (clip)) { + disconnect_from_title_source (clip, app); + } else if (GES_IS_TEST_CLIP (clip)) { + disconnect_from_test_source (clip, app); + } +} + +static GtkListStore * +get_video_patterns (void) +{ + GEnumClass *enum_class; + GESTestClip *tr; + GESTestClipClass *klass; + GParamSpec *pspec; + GEnumValue *v; + GtkListStore *m; + GtkTreeIter i; + + m = gtk_list_store_new (1, G_TYPE_STRING); + + tr = ges_test_clip_new (); + klass = GES_TEST_CLIP_GET_CLASS (tr); + + pspec = g_object_class_find_property (G_OBJECT_CLASS (klass), "vpattern"); + + enum_class = G_ENUM_CLASS (g_type_class_ref (pspec->value_type)); + + for (v = enum_class->values; v->value_nick != NULL; v++) { + gtk_list_store_append (m, &i); + gtk_list_store_set (m, &i, 0, v->value_name, -1); + } + + g_type_class_unref (enum_class); + gst_object_unref (tr); + + return m; +} + +#define GET_WIDGET(dest,name,type) {\ + if (!(dest =\ + type(gtk_builder_get_object(builder, name))))\ + goto fail;\ +} + + +static void +layer_added_cb (GESTimeline * timeline, GESLayer * layer, App * app) +{ + if (!GES_IS_LAYER (layer)) { + GST_ERROR ("This timeline contains a layer type other than " + "GESLayer. Timeline editing disabled"); + return; + } + + if (!(app->layer)) { + app->layer = layer; + } + + if (layer != app->layer) { + GST_ERROR ("This demo doesn't support editing timelines with multiple" + " layers"); + return; + } + + g_signal_connect (app->layer, "clip-added", + G_CALLBACK (layer_object_added_cb), app); + g_signal_connect (app->layer, "clip-removed", + G_CALLBACK (layer_object_removed_cb), app); + g_signal_connect (app->layer, "object-moved", + G_CALLBACK (layer_object_moved_cb), app); + g_signal_connect (app->layer, "notify::valid", + G_CALLBACK (layer_notify_valid_changed_cb), app); +} + +static void +update_track_actions (App * app) +{ + g_signal_handlers_disconnect_by_func (app->audio_track_action, + audio_track_activate_cb, app); + g_signal_handlers_disconnect_by_func (app->video_track_action, + video_track_activate_cb, app); + gtk_toggle_action_set_active (app->audio_track_action, app->audio_tracks); + gtk_toggle_action_set_active (app->video_track_action, app->video_tracks); + gtk_action_set_sensitive ((GtkAction *) app->audio_track_action, + app->audio_tracks <= 1); + gtk_action_set_sensitive ((GtkAction *) app->video_track_action, + app->video_tracks <= 1); + g_signal_connect (G_OBJECT (app->audio_track_action), "activate", + G_CALLBACK (audio_track_activate_cb), app); + g_signal_connect (G_OBJECT (app->video_track_action), "activate", + G_CALLBACK (video_track_activate_cb), app); +} + +static void +track_added_cb (GESTimeline * timeline, GESTrack * track, App * app) +{ + if (track->type == GES_TRACK_TYPE_AUDIO) { + app->audio_tracks++; + if (!app->audio_track) + app->audio_track = track; + } + if (track->type == GES_TRACK_TYPE_VIDEO) { + app->video_tracks++; + if (!app->video_track) + app->video_track = track; + } + + + update_track_actions (app); +} + +static void +track_removed_cb (GESTimeline * timeline, GESTrack * track, App * app) +{ + if (track->type == GES_TRACK_TYPE_AUDIO) + app->audio_tracks--; + if (track->type == GES_TRACK_TYPE_VIDEO) + app->video_tracks--; + + update_track_actions (app); +} + +static gboolean +create_ui (App * app) +{ + GtkBuilder *builder; + GtkTreeView *timeline; + GtkTreeViewColumn *duration_col; + GtkCellRenderer *duration_renderer; + GtkCellRenderer *background_type_renderer; + GtkListStore *backgrounds; + GstBus *bus; + + /* construct widget tree */ + + builder = gtk_builder_new (); + gtk_builder_add_from_file (builder, "ges-ui.glade", NULL); + + /* get a bunch of widgets from the XML tree */ + + GET_WIDGET (timeline, "timeline_treeview", GTK_TREE_VIEW); + GET_WIDGET (app->properties, "properties", GTK_WIDGET); + GET_WIDGET (app->filesource_properties, "filesource_properties", GTK_WIDGET); + GET_WIDGET (app->text_properties, "text_properties", GTK_WIDGET); + GET_WIDGET (app->main_window, "window", GTK_WIDGET); + GET_WIDGET (app->add_effect_dlg, "add_effect_dlg", GTK_WIDGET); + GET_WIDGET (app->audio_effect_entry, "entry1", GTK_WIDGET); + GET_WIDGET (app->video_effect_entry, "entry2", GTK_WIDGET); + GET_WIDGET (app->duration, "duration_scale", GTK_HSCALE); + GET_WIDGET (app->in_point, "in_point_scale", GTK_HSCALE); + GET_WIDGET (app->halign, "halign", GTK_COMBO_BOX); + GET_WIDGET (app->valign, "valign", GTK_COMBO_BOX); + GET_WIDGET (app->text, "text", GTK_ENTRY); + GET_WIDGET (duration_col, "duration_column", GTK_TREE_VIEW_COLUMN); + GET_WIDGET (duration_renderer, "duration_renderer", GTK_CELL_RENDERER); + GET_WIDGET (app->add_file, "add_file", GTK_ACTION); + GET_WIDGET (app->add_effect, "add_effect", GTK_ACTION); + GET_WIDGET (app->add_title, "add_text", GTK_ACTION); + GET_WIDGET (app->add_test, "add_test", GTK_ACTION); + GET_WIDGET (app->add_transition, "add_transition", GTK_ACTION); + GET_WIDGET (app->delete, "delete", GTK_ACTION); + GET_WIDGET (app->play, "play", GTK_ACTION); + GET_WIDGET (app->stop, "stop", GTK_ACTION); + GET_WIDGET (app->move_up, "move_up", GTK_ACTION); + GET_WIDGET (app->move_down, "move_down", GTK_ACTION); + GET_WIDGET (app->seconds, "seconds", GTK_ENTRY); + GET_WIDGET (app->generic_duration, "generic_duration", GTK_WIDGET); + GET_WIDGET (app->background_type, "background_type", GTK_COMBO_BOX); + GET_WIDGET (app->background_properties, "background_properties", GTK_WIDGET); + GET_WIDGET (app->frequency, "frequency", GTK_SPIN_BUTTON); + GET_WIDGET (app->volume, "volume", GTK_HSCALE); + GET_WIDGET (app->audio_track_action, "audio_track", GTK_TOGGLE_ACTION); + GET_WIDGET (app->video_track_action, "video_track", GTK_TOGGLE_ACTION); + + /* get text notifications */ + + g_signal_connect (app->text, "notify::text", + G_CALLBACK (text_notify_text_changed_cb), app); + + g_signal_connect (app->seconds, "notify::text", + G_CALLBACK (seconds_notify_text_changed_cb), app); + + /* we care when the tree selection changes */ + + if (!(app->selection = gtk_tree_view_get_selection (timeline))) + goto fail; + + gtk_tree_selection_set_mode (app->selection, GTK_SELECTION_MULTIPLE); + + g_signal_connect (app->selection, "changed", + G_CALLBACK (app_selection_changed_cb), app); + + /* create the model for the treeview */ + + if (!(app->model = + gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_OBJECT))) + goto fail; + + gtk_tree_view_set_model (timeline, GTK_TREE_MODEL (app->model)); + + /* register custom cell data function */ + + gtk_tree_view_column_set_cell_data_func (duration_col, duration_renderer, + duration_cell_func, NULL, NULL); + + /* initialize combo boxes */ + + if (!(backgrounds = get_video_patterns ())) + goto fail; + + if (!(background_type_renderer = gtk_cell_renderer_text_new ())) + goto fail; + + gtk_combo_box_set_model (app->background_type, (GtkTreeModel *) + backgrounds); + + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (app->background_type), + background_type_renderer, FALSE); + + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (app->background_type), + background_type_renderer, "text", 0); + + g_signal_connect (app->timeline, "layer-added", G_CALLBACK + (layer_added_cb), app); + g_signal_connect (app->timeline, "track-added", G_CALLBACK + (track_added_cb), app); + g_signal_connect (app->timeline, "track-removed", G_CALLBACK + (track_removed_cb), app); + + /* register callbacks on GES objects */ + bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline)); + gst_bus_add_signal_watch (bus); + g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), app); + + /* success */ + gtk_builder_connect_signals (builder, app); + gst_object_unref (G_OBJECT (builder)); + return TRUE; + +fail: + gst_object_unref (G_OBJECT (builder)); + return FALSE; +} + +#undef GET_WIDGET + +/* application methods ******************************************************/ + +static void selection_foreach (GtkTreeModel * model, GtkTreePath * path, + GtkTreeIter * iter, gpointer user); + +static void +app_toggle_playpause (App * app) +{ + if (app->state != GST_STATE_PLAYING) { + gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PLAYING); + } else { + gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PAUSED); + } +} + +static void +app_stop_playback (App * app) +{ + if ((app->state != GST_STATE_NULL) && (app->state != GST_STATE_READY)) { + gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_READY); + } +} + +typedef struct +{ + GList *objects; + guint n; +} select_info; + +static void +app_update_selection (App * app) +{ + GList *cur; + GType type; + select_info info = { NULL, 0 }; + + /* clear old selection */ + for (cur = app->selected_objects; cur; cur = cur->next) { + disconnect_from_object (cur->data, app); + gst_object_unref (cur->data); + cur->data = NULL; + } + g_list_free (app->selected_objects); + app->selected_objects = NULL; + app->n_selected = 0; + + /* get new selection */ + gtk_tree_selection_selected_foreach (GTK_TREE_SELECTION (app->selection), + selection_foreach, &info); + app->selected_objects = info.objects; + app->n_selected = info.n; + + type = G_TYPE_NONE; + if (app->selected_objects) { + type = G_TYPE_FROM_INSTANCE (app->selected_objects->data); + for (cur = app->selected_objects; cur; cur = cur->next) { + if (type != G_TYPE_FROM_INSTANCE (cur->data)) { + type = G_TYPE_NONE; + break; + } + } + } + + if (type != G_TYPE_NONE) { + for (cur = app->selected_objects; cur; cur = cur->next) { + connect_to_object (cur->data, app); + } + } + + app->selected_type = type; + app_update_first_last_selected (app); +} + +static void +selection_foreach (GtkTreeModel * model, GtkTreePath * path, GtkTreeIter + * iter, gpointer user) +{ + select_info *info = (select_info *) user; + GESClip *clip; + + gtk_tree_model_get (model, iter, 2, &clip, -1); + info->objects = g_list_append (info->objects, clip); + + info->n++; + return; +} + +static GList * +app_get_selected_objects (App * app) +{ + return g_list_copy (app->selected_objects); +} + +static void +app_delete_objects (App * app, GList * objects) +{ + GList *cur; + + for (cur = objects; cur; cur = cur->next) { + ges_layer_remove_clip (app->layer, GES_CLIP (cur->data)); + cur->data = NULL; + } + + g_list_free (objects); +} + +/* the following two methods assume exactly one clip is selected and that the + * requested action is valid */ + +static void +app_move_selected_up (App * app) +{ + GST_FIXME ("This function is not implement, please implement it :)"); +} + +static void +app_add_effect_on_selected_clips (App * app, const gchar * bin_desc) +{ + GList *objects, *tmp; + GESTrackElement *effect = NULL; + + /* No crash if the video is playing */ + gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PAUSED); + objects = ges_layer_get_clips (app->layer); + + for (tmp = objects; tmp; tmp = tmp->next) { + effect = GES_TRACK_ELEMENT (ges_effect_new (bin_desc)); + ges_container_add (GES_CONTAINER (tmp->data), + GES_TIMELINE_ELEMENT (effect)); + gst_object_unref (tmp->data); + } +} + +gboolean +add_effect_dlg_delete_event_cb (GtkWidget * widget, GdkEvent * event, + gpointer * app) +{ + gtk_widget_hide (((App *) app)->add_effect_dlg); + return TRUE; +} + +void +on_cancel_add_effect_cb (GtkButton * button, App * app) +{ + gtk_widget_hide (app->add_effect_dlg); +} + +void +on_apply_effect_cb (GtkButton * button, App * app) +{ + const gchar *effect; + + effect = gtk_entry_get_text (GTK_ENTRY (app->video_effect_entry)); + if (g_strcmp0 (effect, "")) + app_add_effect_on_selected_clips (app, effect); + + gtk_entry_set_text (GTK_ENTRY (app->video_effect_entry), ""); + + effect = gtk_entry_get_text (GTK_ENTRY (app->audio_effect_entry)); + if (g_strcmp0 (effect, "")) + app_add_effect_on_selected_clips (app, effect); + + gtk_entry_set_text (GTK_ENTRY (app->audio_effect_entry), ""); + + gtk_widget_hide (app->add_effect_dlg); +} + +static void +app_move_selected_down (App * app) +{ + GST_FIXME ("This function is not implement, please implement it :)"); +} + +static void +app_add_file (App * app, gchar * uri) +{ + GESClip *clip; + + GST_DEBUG ("adding file %s", uri); + + clip = GES_CLIP (ges_uri_clip_new (uri)); + ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip), + ges_layer_get_duration (app->layer)); + + ges_layer_add_clip (app->layer, clip); +} + +static void +app_launch_project (App * app, gchar * uri) +{ + GESTimeline *timeline; + GMainLoop *mainloop; + GESPipeline *pipeline; + GstBus *bus; + GESProject *project; + + uri = g_strsplit (uri, "//", 2)[1]; + printf ("we will launch this uri : %s\n", uri); + project = ges_project_new (uri); + timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), NULL)); + pipeline = ges_pipeline_new (); + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + mainloop = g_main_loop_new (NULL, FALSE); + + ges_pipeline_set_timeline (pipeline, timeline); + ges_pipeline_set_mode (pipeline, GES_PIPELINE_MODE_PREVIEW_VIDEO); + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); + gst_bus_add_signal_watch (bus); + g_signal_connect (bus, "message", G_CALLBACK (project_bus_message_cb), + mainloop); + g_main_loop_run (mainloop); + g_object_unref (project); +} + +static void +app_add_title (App * app) +{ + GESClip *clip; + + GST_DEBUG ("adding title"); + + clip = GES_CLIP (ges_title_clip_new ()); + g_object_set (G_OBJECT (clip), "duration", GST_SECOND, + "start", ges_layer_get_duration (app->layer), NULL); + ges_layer_add_clip (app->layer, clip); +} + +static void +app_add_test (App * app) +{ + GESClip *clip; + + GST_DEBUG ("adding test"); + + clip = GES_CLIP (ges_test_clip_new ()); + g_object_set (G_OBJECT (clip), "duration", GST_SECOND, + "start", ges_layer_get_duration (app->layer), NULL); + + ges_layer_add_clip (app->layer, clip); +} + +static void +app_add_transition (App * app) +{ + GST_FIXME ("This function is not implement, please implement it :)"); +} + +static void +app_save_to_uri (App * app, gchar * uri) +{ + ges_timeline_save_to_uri (app->timeline, uri, NULL, FALSE, NULL); +} + +static void +app_add_audio_track (App * app) +{ + if (app->audio_tracks) + return; + + app->audio_track = GES_TRACK (ges_audio_track_new ()); + ges_timeline_add_track (app->timeline, app->audio_track); +} + +static void +app_remove_audio_track (App * app) +{ + if (!app->audio_tracks) + return; + + ges_timeline_remove_track (app->timeline, app->audio_track); + app->audio_track = NULL; +} + +static void +app_add_video_track (App * app) +{ + if (app->video_tracks) + return; + + app->video_track = GES_TRACK (ges_video_track_new ()); + ges_timeline_add_track (app->timeline, app->video_track); +} + +static void +app_remove_video_track (App * app) +{ + if (!app->video_tracks) + return; + + ges_timeline_remove_track (app->timeline, app->video_track); + app->video_track = NULL; +} + +static void +app_dispose (App * app) +{ + if (app) { + if (app->pipeline) { + gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_NULL); + gst_object_unref (app->pipeline); + } + + g_free (app); + } + + n_instances--; + + if (n_instances == 0) { + gtk_main_quit (); + } +} + +static App * +app_init (void) +{ + App *ret; + ret = g_new0 (App, 1); + n_instances++; + + ret->selected_type = G_TYPE_NONE; + + if (!(ret->timeline = ges_timeline_new ())) + goto fail; + + if (!(ret->pipeline = ges_pipeline_new ())) + goto fail; + + if (!ges_pipeline_set_timeline (ret->pipeline, ret->timeline)) + goto fail; + + if (!(create_ui (ret))) + goto fail; + + return ret; + +fail: + app_dispose (ret); + return NULL; +} + +static App * +app_new (void) +{ + App *ret; + GESTrack *a = NULL, *v = NULL; + + ret = app_init (); + + /* add base audio and video track */ + + if (!(a = GES_TRACK (ges_audio_track_new ()))) + goto fail; + + if (!(ges_timeline_add_track (ret->timeline, a))) + goto fail; + + if (!(v = GES_TRACK (ges_video_track_new ()))) + goto fail; + + if (!(ges_timeline_add_track (ret->timeline, v))) + goto fail; + + if (!(ret->layer = ges_layer_new ())) + goto fail; + + if (!(ges_timeline_add_layer (ret->timeline, ret->layer))) + goto fail; + + ret->audio_track = a; + ret->video_track = v; + return ret; + +fail: + + if (a) + gst_object_unref (a); + if (v) + gst_object_unref (v); + app_dispose (ret); + return NULL; +} + +static gboolean +load_file_async (App * app) +{ + ges_timeline_load_from_uri (app->timeline, app->pending_uri, NULL); + + g_free (app->pending_uri); + app->pending_uri = NULL; + + return FALSE; +} + +static gboolean +app_new_from_uri (gchar * uri) +{ + App *ret; + + ret = app_init (); + ret->pending_uri = g_strdup (uri); + g_idle_add ((GSourceFunc) load_file_async, ret); + + return FALSE; +} + +/* UI callbacks ************************************************************/ + +gboolean +window_delete_event_cb (GtkWidget * window, GdkEvent * event, App * app) +{ + app_dispose (app); + return FALSE; +} + +void +new_activate_cb (GtkMenuItem * item, App * app) +{ + app_new (); +} + +void +launch_project_activate_cb (GtkMenuItem * item, App * app) +{ + GtkFileChooserDialog *dlg; + GtkFileFilter *filter; + + GST_DEBUG ("add file signal handler"); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, "pitivi projects"); + gtk_file_filter_add_pattern (filter, "*.xptv"); + dlg = (GtkFileChooserDialog *) + gtk_file_chooser_dialog_new ("Preview Project...", + GTK_WINDOW (app->main_window), + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), filter); + + g_object_set (G_OBJECT (dlg), "select-multiple", FALSE, NULL); + + if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) { + gchar *uri; + uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg)); + gtk_widget_destroy ((GtkWidget *) dlg); + app_launch_project (app, uri); + } +} + +void +open_activate_cb (GtkMenuItem * item, App * app) +{ + GtkFileChooserDialog *dlg; + + GST_DEBUG ("add file signal handler"); + + dlg = (GtkFileChooserDialog *) gtk_file_chooser_dialog_new ("Open Project...", + GTK_WINDOW (app->main_window), + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); + + g_object_set (G_OBJECT (dlg), "select-multiple", FALSE, NULL); + + if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) { + gchar *uri; + uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg)); + app_new_from_uri (uri); + g_free (uri); + } + gtk_widget_destroy ((GtkWidget *) dlg); +} + +void +save_as_activate_cb (GtkMenuItem * item, App * app) +{ + GtkFileChooserDialog *dlg; + + GST_DEBUG ("save as signal handler"); + + dlg = (GtkFileChooserDialog *) + gtk_file_chooser_dialog_new ("Save project as...", + GTK_WINDOW (app->main_window), GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_OK, + NULL); + + g_object_set (G_OBJECT (dlg), "select-multiple", FALSE, NULL); + + if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) { + gchar *uri; + uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg)); + app_save_to_uri (app, uri); + g_free (uri); + } + gtk_widget_destroy ((GtkWidget *) dlg); +} + +void +quit_item_activate_cb (GtkMenuItem * item, App * app) +{ + gtk_main_quit (); +} + +void +delete_activate_cb (GtkAction * item, App * app) +{ + /* get a gslist of selected track elements */ + GList *objects = NULL; + + objects = app_get_selected_objects (app); + app_delete_objects (app, objects); +} + +void +add_effect_activate_cb (GtkAction * item, App * app) +{ + gtk_widget_show_all (app->add_effect_dlg); +} + +void +add_file_activate_cb (GtkAction * item, App * app) +{ + GtkFileChooserDialog *dlg; + + GST_DEBUG ("add file signal handler"); + + dlg = (GtkFileChooserDialog *) gtk_file_chooser_dialog_new ("Add File...", + GTK_WINDOW (app->main_window), + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); + + g_object_set (G_OBJECT (dlg), "select-multiple", TRUE, NULL); + + if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) { + GSList *uris; + GSList *cur; + uris = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (dlg)); + for (cur = uris; cur; cur = cur->next) + app_add_file (app, cur->data); + g_slist_free (uris); + } + gtk_widget_destroy ((GtkWidget *) dlg); +} + +void +add_text_activate_cb (GtkAction * item, App * app) +{ + app_add_title (app); +} + +void +add_test_activate_cb (GtkAction * item, App * app) +{ + app_add_test (app); +} + +void +add_transition_activate_cb (GtkAction * item, App * app) +{ + app_add_transition (app); +} + +void +play_activate_cb (GtkAction * item, App * app) +{ + app_toggle_playpause (app); +} + +void +stop_activate_cb (GtkAction * item, App * app) +{ + app_stop_playback (app); +} + +void +move_up_activate_cb (GtkAction * item, App * app) +{ + app_move_selected_up (app); +} + +void +move_down_activate_cb (GtkAction * item, App * app) +{ + app_move_selected_down (app); +} + +void +audio_track_activate_cb (GtkToggleAction * item, App * app) +{ + if (gtk_toggle_action_get_active (item)) { + app_add_audio_track (app); + } else { + app_remove_audio_track (app); + } +} + +void +video_track_activate_cb (GtkToggleAction * item, App * app) +{ + if (gtk_toggle_action_get_active (item)) { + app_add_video_track (app); + } else { + app_remove_video_track (app); + } +} + +void +app_selection_changed_cb (GtkTreeSelection * selection, App * app) +{ + app_update_selection (app); + + update_delete_sensitivity (app); + update_effect_sensitivity (app); + update_add_transition_sensitivity (app); + update_move_up_down_sensitivity (app); + + gtk_widget_set_visible (app->properties, app->n_selected > 0); + + gtk_widget_set_visible (app->filesource_properties, + app->selected_type == GES_TYPE_URI_CLIP); + + gtk_widget_set_visible (app->text_properties, + app->selected_type == GES_TYPE_TITLE_CLIP); + + gtk_widget_set_visible (app->generic_duration, + app->selected_type != G_TYPE_NONE && + app->selected_type != G_TYPE_INVALID); + + gtk_widget_set_visible (app->background_properties, + app->selected_type == GES_TYPE_TEST_CLIP); +} + +gboolean +duration_scale_change_value_cb (GtkRange * range, GtkScrollType unused, + gdouble value, App * app) +{ + GList *i; + + for (i = app->selected_objects; i; i = i->next) { + guint64 duration, maxduration; + maxduration = GES_TIMELINE_ELEMENT_MAX_DURATION (i->data); + duration = (value < maxduration ? (value > 0 ? value : 0) : maxduration); + g_object_set (G_OBJECT (i->data), "duration", (guint64) duration, NULL); + } + return TRUE; +} + +gboolean +in_point_scale_change_value_cb (GtkRange * range, GtkScrollType unused, + gdouble value, App * app) +{ + GList *i; + + for (i = app->selected_objects; i; i = i->next) { + guint64 in_point, maxduration; + maxduration = GES_TIMELINE_ELEMENT_MAX_DURATION (i->data) - + GES_TIMELINE_ELEMENT_DURATION (i->data); + in_point = (value < maxduration ? (value > 0 ? value : 0) : maxduration); + g_object_set (G_OBJECT (i->data), "in-point", (guint64) in_point, NULL); + } + return TRUE; +} + +void +halign_changed_cb (GtkComboBox * widget, App * app) +{ + GList *tmp; + int active; + + if (app->ignore_input) + return; + + active = gtk_combo_box_get_active (app->halign); + + for (tmp = app->selected_objects; tmp; tmp = tmp->next) { + g_object_set (G_OBJECT (tmp->data), "halignment", active, NULL); + } +} + +void +valign_changed_cb (GtkComboBox * widget, App * app) +{ + GList *tmp; + int active; + + if (app->ignore_input) + return; + + active = gtk_combo_box_get_active (app->valign); + + for (tmp = app->selected_objects; tmp; tmp = tmp->next) { + g_object_set (G_OBJECT (tmp->data), "valignment", active, NULL); + } +} + +void +background_type_changed_cb (GtkComboBox * widget, App * app) +{ + GList *tmp; + gint p; + + if (app->ignore_input) + return; + + p = gtk_combo_box_get_active (widget); + + for (tmp = app->selected_objects; tmp; tmp = tmp->next) { + g_object_set (G_OBJECT (tmp->data), "vpattern", (gint) p, NULL); + } +} + +void +frequency_value_changed_cb (GtkSpinButton * widget, App * app) +{ + GList *tmp; + gdouble value; + + if (app->ignore_input) + return; + + value = gtk_spin_button_get_value (widget); + + for (tmp = app->selected_objects; tmp; tmp = tmp->next) { + g_object_set (G_OBJECT (tmp->data), "freq", (gdouble) value, NULL); + } +} + +gboolean +volume_change_value_cb (GtkRange * widget, GtkScrollType unused, gdouble + value, App * app) +{ + GList *tmp; + + value = value >= 0 ? (value <= 2.0 ? value : 2.0) : 0; + + for (tmp = app->selected_objects; tmp; tmp = tmp->next) { + g_object_set (G_OBJECT (tmp->data), "volume", (gdouble) value, NULL); + } + return TRUE; +} + +/* main *********************************************************************/ + +int +main (int argc, char *argv[]) +{ + App *app; + + /* intialize GStreamer and GES */ + gst_init (&argc, &argv); + ges_init (); + + /* initialize UI */ + gtk_init (&argc, &argv); + + if ((app = app_new ())) { + gtk_main (); + } + + return 0; +} diff --git a/examples/c/ges-ui.glade b/examples/c/ges-ui.glade new file mode 100644 index 0000000000..4ce11df648 --- /dev/null +++ b/examples/c/ges-ui.glade @@ -0,0 +1,1143 @@ + + + + + + + + + + + + Left + + + Center + + + Right + + + + + + + + + + + Baseline + + + Bottom + + + Top + + + + + True + GES Demo + 540 + 450 + + + + True + + + True + + + True + _File + True + + + True + + + gtk-new + True + True + True + + + + + + gtk-open + True + True + True + + + + + + gtk-save + True + True + + + + + gtk-save-as + True + True + True + + + + + + Preview Pitivi Project + True + Launches a .xptv project + image1 + False + + + + + + + + + + True + _Edit + True + + + True + + + gtk-cut + True + True + + + + + gtk-copy + True + True + + + + + gtk-paste + True + True + + + + + True + delete + True + True + True + + + + + + + + + _View + True + + + + + True + _Timeline + True + + + True + + + True + add_file + True + + + + + True + add_text + True + + + + + True + add_test + True + True + True + + + + + True + add_transition + True + True + True + + + + + True + + + + + True + move_up + True + True + True + + + + + True + move_down + True + True + True + + + + + True + True + + + + + True + play + True + True + True + + + + + True + stop + True + True + True + + + + + True + add_effect + True + + + + + + + + + _Help + True + + + True + + + gtk-about + True + True + True + + + + + + + + + False + 0 + + + + + True + + + True + True + play + toolbutton1 + True + + + False + True + + + + + True + True + stop + toolbutton6 + True + + + False + True + + + + + True + + + False + True + + + + + True + True + delete + Delete + True + + + False + True + + + + + True + + + False + True + + + + + True + True + add_file + toolbutton1 + True + + + False + True + + + + + True + True + add_text + toolbutton2 + True + + + False + True + + + + + True + True + add_test + toolbutton4 + True + + + False + True + + + + + True + True + add_transition + toolbutton5 + True + + + False + True + + + + + True + add_effect + True + File + True + + + False + True + + + + + True + + + False + True + + + + + True + True + move_up + toolbutton8 + True + + + False + True + + + + + True + True + move_down + toolbutton9 + True + + + False + True + + + + + True + + + False + True + + + + + True + True + audio_track + toolbutton11 + True + + + False + True + + + + + True + True + video_track + toolbutton12 + True + + + False + True + + + + + False + 1 + + + + + True + True + 12 + 450 + True + + + True + True + in + + + True + True + 0 + False + + + True + autosize + Description + True + + + + 0 + + + + + + + autosize + Duration + + + + 1 + + + + + + + + + False + True + + + + + True + 0 + none + + + True + 6 + + + True + 6 + + + True + 1 + 12 + Duration: + + + False + False + 0 + + + + + True + True + + + + 1 + + + + + False + False + 0 + + + + + True + 6 + + + True + 6 + + + True + 1 + 0 + 12 + 3 + Duration: + + + False + False + 0 + + + + + True + True + False + False + bottom + + + + 1 + + + + + False + False + 0 + + + + + True + 6 + + + True + 1 + 0 + 12 + 3 + In Point: + + + False + False + 0 + + + + + True + True + True + False + False + bottom + + + + 1 + + + + + False + False + 1 + + + + + False + False + 1 + + + + + True + 6 + + + True + 6 + + + True + 1 + 12 + Background: + + + False + False + 0 + + + + + True + + + + 1 + + + + + False + False + 0 + + + + + True + 6 + + + True + 1 + 12 + Sound: + + + False + False + 0 + + + + + True + Frequency: + + + False + False + 1 + + + + + True + True + + adjustment2 + + + + False + False + 2 + + + + + True + Volume: + + + False + False + 3 + + + + + True + True + off + False + False + + + + 4 + + + + + False + False + 1 + + + + + False + False + 2 + + + + + 6 + + + True + 6 + + + True + 1 + 12 + Text: + + + False + False + 0 + + + + + True + True + + + + 1 + + + + + 0 + + + + + True + 6 + + + True + 1 + 12 + Alignment: + + + False + False + 0 + + + + + True + liststore1 + + + + + 0 + + + + + 1 + + + + + True + liststore2 + + + + + 0 + + + + + 2 + + + + + 1 + + + + + False + False + 3 + + + + + + + True + 3 + <b>Edit Object</b> + True + + + + + True + False + + + + + 2 + + + + + + + + 5 + Add effects + center-always + dialog + + + + True + vertical + 2 + + + True + vertical + + + True + + + True + Audio effects bin description + + + 0 + + + + + True + True + + + + + 1 + + + + + 0 + + + + + True + + + True + Video effects bin desciption: + + + 0 + + + + + True + True + + + + + 1 + + + + + 1 + + + + + 1 + + + + + True + end + + + gtk-cancel + True + True + True + True + + + + False + False + 0 + + + + + gtk-apply + True + True + True + True + + + + False + False + 1 + + + + + False + end + 0 + + + + + + button2 + button1 + + + + True + gtk-floppy + + + + + + + + + + + + + + + + + Delete + gtk-delete + True + True + + + + Add File... + File + applications-multimedia + True + + + + Play + gtk-media-play + True + False + True + + + + Add Text + Title + insert-text + True + + + + + 2 + 1 + 0.10000000000000001 + 0.10000000000000001 + + + 440 + 20000 + 1 + 10 + + + Add Test Source + Test Source + utilities-system-monitor + True + + + + Add Transition + Transition + media-playlist-shuffle + True + + + + Stop + Stop + gtk-media-stop + + + + Move Selected Up + Move Up + gtk-go-up + + + + Move Selected Down + Move Down + gtk-go-down + + + + Audio + Audio + Play Audio Track + audio-x-generic + True + True + + + + Video + Video + Render Video Track + video-x-generic + True + True + + + + Add effect... + Effect + face-smile + True + + + diff --git a/examples/c/gessrc.c b/examples/c/gessrc.c new file mode 100644 index 0000000000..40917f14a3 --- /dev/null +++ b/examples/c/gessrc.c @@ -0,0 +1,124 @@ +/* GStreamer GES plugin + * + * Copyright (C) 2019 Igalia S.L + * Author: 2019 Thibault Saunier + * + * gesdemux.c + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +static void +bus_message_cb (GstBus * bus, GstMessage * message, GMainLoop * mainloop) +{ + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + gst_printerr ("Got error message on the bus\n"); + g_main_loop_quit (mainloop); + break; + case GST_MESSAGE_EOS: + gst_print ("Done\n"); + g_main_loop_quit (mainloop); + break; + default: + break; + } +} + +static void +source_setup_cb (GstElement * playbin, GstElement * source, + GESTimeline * timeline) +{ + g_object_set (source, "timeline", timeline, NULL); +} + +int +main (int argc, char **argv) +{ + GMainLoop *mainloop = NULL; + GstElement *pipeline = NULL; + GESTimeline *timeline; + GESLayer *layer = NULL; + GstBus *bus = NULL; + guint i, ret = 0; + gchar *uri = NULL; + GstClockTime start = 0; + + if (argc < 2) { + gst_print ("Usage: %s \n", argv[0]); + return -1; + } + + gst_init (&argc, &argv); + ges_init (); + + timeline = ges_timeline_new_audio_video (); + + layer = (GESLayer *) ges_layer_new (); + if (!ges_timeline_add_layer (timeline, layer)) + return -1; + + /* Build the timeline */ + for (i = 1; i < argc; i++) { + GESClip *clip; + + uri = g_strdup (argv[i]); + if (!gst_uri_is_valid (uri)) { + g_free (uri); + uri = gst_filename_to_uri (argv[i], NULL); + } + clip = GES_CLIP (ges_uri_clip_new (uri)); + + if (!clip) { + gst_printerr ("Could not create clip for file: %s\n", argv[i]); + g_free (uri); + goto err; + } + g_object_set (clip, "start", start, NULL); + ges_layer_add_clip (layer, clip); + + start += ges_timeline_element_get_duration (GES_TIMELINE_ELEMENT (clip)); + + g_free (uri); + } + + /* Use a usual playbin pipeline */ + pipeline = gst_element_factory_make ("playbin", NULL); + g_object_set (pipeline, "uri", "ges://", NULL); + g_signal_connect (pipeline, "source-setup", G_CALLBACK (source_setup_cb), + timeline); + + mainloop = g_main_loop_new (NULL, FALSE); + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + gst_bus_add_signal_watch (bus); + g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), mainloop); + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + g_main_loop_run (mainloop); + gst_element_set_state (pipeline, GST_STATE_NULL); + +done: + gst_clear_object (&pipeline); + if (mainloop) + g_main_loop_unref (mainloop); + + return ret; +err: + goto done; +} diff --git a/examples/c/meson.build b/examples/c/meson.build new file mode 100644 index 0000000000..ac751b05b0 --- /dev/null +++ b/examples/c/meson.build @@ -0,0 +1,30 @@ +examples = [ + 'concatenate', + 'gessrc', + 'simple1', + 'test1', + 'test2', + 'test3', + 'test4', + 'transition', + 'thumbnails', + 'overlays', + 'text_properties', + 'assets', + 'multifilesrc', + 'play_timeline_with_one_clip' +] + +# TODO Properly port to Gtk 3 +# +# if gtk_dep.found() +# examples = examples + ['ges-ui'] +# endif + + +foreach example_name : examples + exe = executable(example_name, '@0@.c'.format(example_name), + c_args : ges_c_args, + dependencies : libges_deps + [ges_dep], + ) +endforeach diff --git a/examples/c/multifilesrc.c b/examples/c/multifilesrc.c new file mode 100644 index 0000000000..ac57ff9666 --- /dev/null +++ b/examples/c/multifilesrc.c @@ -0,0 +1,94 @@ +/* GStreamer Editing Services + * Copyright (C) 2013 Lubosz Sarnecki + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +/* A image sequence test */ +int +main (int argc, gchar ** argv) +{ + GError *err = NULL; + GOptionContext *ctx; + GESPipeline *pipeline; + GESTimeline *timeline; + GESAsset *asset; + GESLayer *layer; + GMainLoop *mainloop; + GESTrack *track; + + gint duration = 10; + gchar *filepattern = NULL; + + GOptionEntry options[] = { + {"duration", 'd', 0, G_OPTION_ARG_INT, &duration, + "duration to use from the file (in seconds, default:10s)", "seconds"}, + {"pattern-url", 'u', 0, G_OPTION_ARG_FILENAME, &filepattern, + "Pattern of the files. i.e. multifile:///foo/%04d.jpg", + "pattern-url"}, + {NULL} + }; + + ctx = g_option_context_new ("- Plays an image sequence"); + g_option_context_add_main_entries (ctx, options, NULL); + g_option_context_add_group (ctx, gst_init_get_option_group ()); + + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + gst_print ("Error initializing %s\n", err->message); + g_option_context_free (ctx); + g_clear_error (&err); + exit (1); + } + + if (filepattern == NULL) { + gst_print ("%s", g_option_context_get_help (ctx, TRUE, NULL)); + exit (0); + } + g_option_context_free (ctx); + + gst_init (&argc, &argv); + ges_init (); + + timeline = ges_timeline_new (); + track = GES_TRACK (ges_video_track_new ()); + ges_timeline_add_track (timeline, track); + + layer = ges_layer_new (); + if (!ges_timeline_add_layer (timeline, layer)) + return -1; + + asset = GES_ASSET (ges_uri_clip_asset_request_sync (filepattern, &err)); + + ges_layer_add_asset (layer, asset, 0, 0, 5 * GST_SECOND, + GES_TRACK_TYPE_VIDEO); + + pipeline = ges_pipeline_new (); + + if (!ges_pipeline_set_timeline (pipeline, timeline)) + return -1; + + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); + + mainloop = g_main_loop_new (NULL, FALSE); + + g_timeout_add_seconds (4, (GSourceFunc) g_main_loop_quit, mainloop); + g_main_loop_run (mainloop); + + return 0; +} diff --git a/examples/c/overlays.c b/examples/c/overlays.c new file mode 100644 index 0000000000..331504f36c --- /dev/null +++ b/examples/c/overlays.c @@ -0,0 +1,178 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +typedef struct +{ + int type; + char *name; +} transition_type; + +GESClip *make_source (char *path, guint64 start, guint64 duration, + gint priority); + +GESClip *make_overlay (char *text, guint64 start, guint64 duration, + gint priority, guint32 color, gdouble xpos, gdouble ypos); + +GESPipeline *make_timeline (char *path, float duration, char *text, + guint32 color, gdouble xpos, gdouble ypos); + +#define DEFAULT_DURATION 5 +#define DEFAULT_POS 0.5 + +GESClip * +make_source (char *path, guint64 start, guint64 duration, gint priority) +{ + gchar *uri = gst_filename_to_uri (path, NULL); + + GESClip *ret = GES_CLIP (ges_uri_clip_new (uri)); + + g_object_set (ret, + "start", (guint64) start, + "duration", (guint64) duration, + "priority", (guint32) priority, "in-point", (guint64) 0, NULL); + + g_free (uri); + + return ret; +} + +GESClip * +make_overlay (char *text, guint64 start, guint64 duration, gint priority, + guint32 color, gdouble xpos, gdouble ypos) +{ + GESClip *ret = GES_CLIP (ges_text_overlay_clip_new ()); + + g_object_set (ret, + "text", (gchar *) text, + "start", (guint64) start, + "duration", (guint64) duration, + "priority", (guint32) priority, + "in-point", (guint64) 0, + "color", (guint32) color, + "valignment", (gint) GES_TEXT_VALIGN_POSITION, + "halignment", (gint) GES_TEXT_HALIGN_POSITION, + "xpos", (gdouble) xpos, "ypos", (gdouble) ypos, NULL); + + return ret; +} + +GESPipeline * +make_timeline (char *path, float duration, char *text, guint32 color, + gdouble xpos, gdouble ypos) +{ + GESTimeline *timeline; + GESTrack *trackv, *tracka; + GESLayer *layer1; + GESClip *srca; + GESClip *overlay; + GESPipeline *pipeline; + guint64 aduration; + + pipeline = ges_pipeline_new (); + + ges_pipeline_set_mode (pipeline, GES_PIPELINE_MODE_PREVIEW_VIDEO); + + timeline = ges_timeline_new (); + ges_pipeline_set_timeline (pipeline, timeline); + + trackv = GES_TRACK (ges_video_track_new ()); + ges_timeline_add_track (timeline, trackv); + + tracka = GES_TRACK (ges_audio_track_new ()); + ges_timeline_add_track (timeline, tracka); + + layer1 = GES_LAYER (ges_layer_new ()); + g_object_set (layer1, "priority", (gint32) 0, NULL); + + if (!ges_timeline_add_layer (timeline, layer1)) + exit (-1); + + aduration = (guint64) (duration * GST_SECOND); + srca = make_source (path, 0, aduration, 1); + overlay = make_overlay (text, 0, aduration, 0, color, xpos, ypos); + ges_layer_add_clip (layer1, srca); + ges_layer_add_clip (layer1, overlay); + + return pipeline; +} + +int +main (int argc, char **argv) +{ + GError *err = NULL; + GOptionContext *ctx; + GESPipeline *pipeline; + GMainLoop *mainloop; + gdouble duration = DEFAULT_DURATION; + char *path = NULL, *text; + guint64 color; + gdouble xpos = DEFAULT_POS, ypos = DEFAULT_POS; + + GOptionEntry options[] = { + {"duration", 'd', 0, G_OPTION_ARG_DOUBLE, &duration, + "duration of segment", "seconds"}, + {"path", 'p', 0, G_OPTION_ARG_STRING, &path, + "path to file", "path"}, + {"text", 't', 0, G_OPTION_ARG_STRING, &text, + "text to render", "text"}, + {"color", 'c', 0, G_OPTION_ARG_INT64, &color, + "color of the text", "color"}, + {"xpos", 'x', 0, G_OPTION_ARG_DOUBLE, &xpos, + "horizontal position of the text", "color"}, + {"ypos", 'y', 0, G_OPTION_ARG_DOUBLE, &ypos, + "vertical position of the text", "color"}, + {NULL} + }; + + ctx = g_option_context_new ("- file segment playback with text overlay"); + g_option_context_add_main_entries (ctx, options, NULL); + g_option_context_add_group (ctx, gst_init_get_option_group ()); + + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + gst_print ("Error initializing %s\n", err->message); + g_option_context_free (ctx); + g_clear_error (&err); + exit (1); + } + + if (argc > 1) { + gst_print ("%s", g_option_context_get_help (ctx, TRUE, NULL)); + exit (0); + } + + g_option_context_free (ctx); + + ges_init (); + + if (path == NULL) + g_error ("Must specify --path=/path/to/media/file option\n"); + + pipeline = make_timeline (path, duration, text, color, xpos, ypos); + + mainloop = g_main_loop_new (NULL, FALSE); + g_timeout_add_seconds ((duration) + 1, (GSourceFunc) g_main_loop_quit, + mainloop); + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); + g_main_loop_run (mainloop); + + return 0; +} diff --git a/examples/c/play_timeline_with_one_clip.c b/examples/c/play_timeline_with_one_clip.c new file mode 100644 index 0000000000..53ae235cc9 --- /dev/null +++ b/examples/c/play_timeline_with_one_clip.c @@ -0,0 +1,63 @@ +/* This example can be found in the GStreamer Editing Services git repository in: + * examples/c/play_timeline_with_one_clip.c + */ +#include + +int +main (int argc, char **argv) +{ + GESLayer *layer; + GESTimeline *timeline; + + if (argc == 1) { + gst_printerr ("Usage: play_timeline_with_one_clip file:///clip/uri\n"); + + return 1; + } + + gst_init (NULL, NULL); + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_timeline_append_layer (timeline); + + { + /* Add a clip with a duration of 5 seconds */ + GESClip *clip = GES_CLIP (ges_uri_clip_new (argv[1])); + + if (clip == NULL) { + gst_printerr + ("%s can not be used, make sure it is a supported media file", + argv[1]); + + return 1; + } + + g_object_set (clip, "duration", 5 * GST_SECOND, "start", 0, NULL); + ges_layer_add_clip (layer, clip); + } + + /* Commiting the timeline is always necessary for changes + * inside it to be taken into account by the Non Linear Engine */ + ges_timeline_commit (timeline); + + { + /* Play the timeline */ + GESPipeline *pipeline = ges_pipeline_new (); + GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + + ges_pipeline_set_timeline (pipeline, timeline); + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); + + /* Simple way to just play the pipeline until EOS or an error pops on the bus */ + gst_bus_timed_pop_filtered (bus, 10 * GST_SECOND, + GST_MESSAGE_EOS | GST_MESSAGE_ERROR); + + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + gst_object_unref (bus); + gst_object_unref (pipeline); + } + + + return 0; +} diff --git a/examples/c/simple1.c b/examples/c/simple1.c new file mode 100644 index 0000000000..0423968499 --- /dev/null +++ b/examples/c/simple1.c @@ -0,0 +1,99 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Edward Hervey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +int +main (int argc, gchar ** argv) +{ + GError *err = NULL; + GOptionContext *ctx; + GESPipeline *pipeline; + GESTimeline *timeline; + GESLayer *layer1; + GESUriClip *src; + gchar *uri; + GMainLoop *mainloop; + + gint inpoint = 0, duration = 10; + gboolean mute = FALSE; + gchar *audiofile = NULL; + GOptionEntry options[] = { + {"inpoint", 'i', 0, G_OPTION_ARG_INT, &inpoint, + "in-point in the file (in seconds, default:0s)", "seconds"}, + {"duration", 'd', 0, G_OPTION_ARG_INT, &duration, + "duration to use from the file (in seconds, default:10s)", "seconds"}, + {"mute", 'm', 0, G_OPTION_ARG_NONE, &mute, + "Whether to mute the audio from the file",}, + {"audiofile", 'a', 0, G_OPTION_ARG_FILENAME, &audiofile, + "Use this audiofile instead of the original audio from the file", + "audiofile"}, + {NULL} + }; + + ctx = + g_option_context_new + ("- Plays an video file with sound (origin/muted/replaced)"); + g_option_context_add_main_entries (ctx, options, NULL); + g_option_context_add_group (ctx, gst_init_get_option_group ()); + + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + gst_print ("Error initializing %s\n", err->message); + g_option_context_free (ctx); + g_clear_error (&err); + exit (1); + } + + if (argc == 1) { + gst_print ("%s", g_option_context_get_help (ctx, TRUE, NULL)); + exit (0); + } + g_option_context_free (ctx); + + ges_init (); + + /* Create an Audio/Video pipeline with two layers */ + pipeline = ges_pipeline_new (); + timeline = ges_timeline_new_audio_video (); + layer1 = ges_timeline_append_layer (timeline); + + if (!ges_pipeline_set_timeline (pipeline, timeline)) { + g_error ("Could not set timeline to pipeline"); + return -1; + } + + uri = gst_filename_to_uri (argv[1], NULL); + /* Add the main audio/video file */ + src = ges_uri_clip_new (uri); + ges_layer_add_clip (layer1, GES_CLIP (src)); + g_free (uri); + g_object_set (src, "start", 0, "in-point", inpoint * GST_SECOND, + "duration", duration * GST_SECOND, "mute", mute, NULL); + + /* Play the pipeline */ + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); + mainloop = g_main_loop_new (NULL, FALSE); + g_timeout_add_seconds (duration + 1, (GSourceFunc) g_main_loop_quit, + mainloop); + g_main_loop_run (mainloop); + + return 0; +} diff --git a/examples/c/test1.c b/examples/c/test1.c new file mode 100644 index 0000000000..72d382a917 --- /dev/null +++ b/examples/c/test1.c @@ -0,0 +1,88 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +/* A simple timeline with 3 audio/video sources */ +int +main (int argc, gchar ** argv) +{ + GESAsset *src_asset; + GESPipeline *pipeline; + GESTimeline *timeline; + GESClip *source; + GESLayer *layer; + GMainLoop *mainloop; + + /* Initialize GStreamer (this will parse environment variables and commandline + * arguments. */ + gst_init (&argc, &argv); + + /* Initialize the GStreamer Editing Services */ + ges_init (); + + /* Setup of a A/V timeline */ + + /* This is our main GESTimeline */ + timeline = ges_timeline_new_audio_video (); + + /* We are only going to be doing one layer of clips */ + layer = ges_layer_new (); + + /* Add the tracks and the layer to the timeline */ + if (!ges_timeline_add_layer (timeline, layer)) + return -1; + + /* We create a simple asset able to extract GESTestClip */ + src_asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + /* Add sources to our layer */ + ges_layer_add_asset (layer, src_asset, 0, 0, GST_SECOND, + GES_TRACK_TYPE_UNKNOWN); + source = ges_layer_add_asset (layer, src_asset, GST_SECOND, 0, + GST_SECOND, GES_TRACK_TYPE_UNKNOWN); + g_object_set (source, "freq", 480.0, "vpattern", 2, NULL); + ges_layer_add_asset (layer, src_asset, 2 * GST_SECOND, 0, + GST_SECOND, GES_TRACK_TYPE_UNKNOWN); + + + /* In order to view our timeline, let's grab a convenience pipeline to put + * our timeline in. */ + pipeline = ges_pipeline_new (); + + /* Add the timeline to that pipeline */ + if (!ges_pipeline_set_timeline (pipeline, timeline)) + return -1; + + /* The following is standard usage of a GStreamer pipeline (note how you haven't + * had to care about GStreamer so far ?). + * + * We set the pipeline to playing ... */ + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); + + /* .. and we start a GMainLoop. GES **REQUIRES** a GMainLoop to be running in + * order to function properly ! */ + mainloop = g_main_loop_new (NULL, FALSE); + + /* Simple code to have the mainloop shutdown after 4s */ + g_timeout_add_seconds (4, (GSourceFunc) g_main_loop_quit, mainloop); + g_main_loop_run (mainloop); + + return 0; +} diff --git a/examples/c/test2.c b/examples/c/test2.c new file mode 100644 index 0000000000..86229aa9d0 --- /dev/null +++ b/examples/c/test2.c @@ -0,0 +1,99 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +int +main (int argc, gchar ** argv) +{ + GESPipeline *pipeline; + GESTimeline *timeline; + GESTrack *tracka; + GESLayer *layer; + GMainLoop *mainloop; + GstClockTime offset = 0; + guint i; + + if (argc < 2) { + gst_print ("Usage: %s \n", argv[0]); + return -1; + } + + /* Initialize GStreamer (this will parse environment variables and commandline + * arguments. */ + gst_init (&argc, &argv); + + /* Initialize the GStreamer Editing Services */ + ges_init (); + + /* Setup of an audio timeline */ + + /* This is our main GESTimeline */ + timeline = ges_timeline_new (); + + tracka = GES_TRACK (ges_audio_track_new ()); + + /* We are only going to be doing one layer of clips */ + layer = ges_layer_new (); + + /* Add the tracks and the layer to the timeline */ + if (!ges_timeline_add_layer (timeline, layer)) + return -1; + if (!ges_timeline_add_track (timeline, tracka)) + return -1; + + /* Here we've finished initializing our timeline, we're + * ready to start using it... by solely working with the layer ! */ + + for (i = 1; i < argc; i++, offset += GST_SECOND) { + gchar *uri = gst_filename_to_uri (argv[i], NULL); + GESUriClip *src = ges_uri_clip_new (uri); + + g_assert (src); + g_free (uri); + + g_object_set (src, "start", offset, "duration", GST_SECOND, NULL); + + ges_layer_add_clip (layer, (GESClip *) src); + } + + /* In order to listen our timeline, let's grab a convenience pipeline to put + * our timeline in. */ + pipeline = ges_pipeline_new (); + + /* Add the timeline to that pipeline */ + if (!ges_pipeline_set_timeline (pipeline, timeline)) + return -1; + + /* The following is standard usage of a GStreamer pipeline (note how you + * haven't had to care about GStreamer so far ?). + * + * We set the pipeline to playing ... */ + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); + + /* ... and we start a GMainLoop. GES **REQUIRES** a GMainLoop to be running in + * order to function properly ! */ + mainloop = g_main_loop_new (NULL, FALSE); + + /* Simple code to have the mainloop shutdown after 4s */ + g_timeout_add_seconds (argc - 1, (GSourceFunc) g_main_loop_quit, mainloop); + g_main_loop_run (mainloop); + + return 0; +} diff --git a/examples/c/test3.c b/examples/c/test3.c new file mode 100644 index 0000000000..51a6e31b07 --- /dev/null +++ b/examples/c/test3.c @@ -0,0 +1,98 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +int +main (int argc, gchar ** argv) +{ + GESPipeline *pipeline; + GESTimeline *timeline; + GESTrack *tracka; + GESLayer *layer; + GMainLoop *mainloop; + guint i; + + if (argc < 2) { + gst_print ("Usage: %s \n", argv[0]); + return -1; + } + + /* Initialize GStreamer (this will parse environment variables and commandline + * arguments. */ + gst_init (&argc, &argv); + + /* Initialize the GStreamer Editing Services */ + ges_init (); + + /* Setup of an audio timeline */ + + /* This is our main GESTimeline */ + timeline = ges_timeline_new (); + + tracka = GES_TRACK (ges_audio_track_new ()); + + /* We are only going to be doing one layer of clips */ + layer = ges_layer_new (); + + /* Add the tracks and the layer to the timeline */ + if (!ges_timeline_add_layer (timeline, layer)) + return -1; + if (!ges_timeline_add_track (timeline, tracka)) + return -1; + + /* Here we've finished initializing our timeline, we're + * ready to start using it... by solely working with the layer ! */ + + for (i = 1; i < argc; i++) { + gchar *uri = gst_filename_to_uri (argv[i], NULL); + GESUriClip *src = ges_uri_clip_new (uri); + + g_assert (src); + g_free (uri); + + g_object_set (src, "start", ges_layer_get_duration (layer), + "duration", GST_SECOND, NULL); + ges_layer_add_clip (layer, (GESClip *) src); + } + + /* In order to view our timeline, let's grab a convenience pipeline to put + * our timeline in. */ + pipeline = ges_pipeline_new (); + + /* Add the timeline to that pipeline */ + if (!ges_pipeline_set_timeline (pipeline, timeline)) + return -1; + + /* The following is standard usage of a GStreamer pipeline (note how you haven't + * had to care about GStreamer so far ?). + * + * We set the pipeline to playing ... */ + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); + + /* .. and we start a GMainLoop. GES **REQUIRES** a GMainLoop to be running in + * order to function properly ! */ + mainloop = g_main_loop_new (NULL, FALSE); + + /* Simple code to have the mainloop shutdown after 4s */ + g_timeout_add_seconds (argc - 1, (GSourceFunc) g_main_loop_quit, mainloop); + g_main_loop_run (mainloop); + + return 0; +} diff --git a/examples/c/test4.c b/examples/c/test4.c new file mode 100644 index 0000000000..1cec66b6f0 --- /dev/null +++ b/examples/c/test4.c @@ -0,0 +1,179 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +GstEncodingProfile *make_encoding_profile (gchar * audio, gchar * container); + +/* This example will take a series of files and create a audio-only timeline + * containing the first second of each file and render it to the output uri + * using ogg/vorbis */ + +/* make_encoding_profile + * simple method creating an encoding profile. This is here in + * order not to clutter the main function. */ +GstEncodingProfile * +make_encoding_profile (gchar * audio, gchar * container) +{ + GstEncodingContainerProfile *profile; + GstEncodingProfile *stream; + GstCaps *caps; + + caps = gst_caps_from_string (container); + profile = + gst_encoding_container_profile_new ((gchar *) "ges-test4", NULL, caps, + NULL); + gst_caps_unref (caps); + + caps = gst_caps_from_string (audio); + stream = (GstEncodingProfile *) + gst_encoding_audio_profile_new (caps, NULL, NULL, 0); + gst_encoding_container_profile_add_profile (profile, stream); + gst_caps_unref (caps); + + return (GstEncodingProfile *) profile; +} + +int +main (int argc, gchar ** argv) +{ + GESPipeline *pipeline; + GESTimeline *timeline; + GESTrack *tracka; + GESLayer *layer; + GMainLoop *mainloop; + GstEncodingProfile *profile; + gchar *container = (gchar *) "application/ogg"; + gchar *audio = (gchar *) "audio/x-vorbis"; + gchar *output_uri; + guint i; + GError *err = NULL; + GOptionEntry options[] = { + {"format", 'f', 0, G_OPTION_ARG_STRING, &container, + "Container format", ""}, + {"aformat", 'a', 0, G_OPTION_ARG_STRING, &audio, + "Audio format", ""}, + {NULL} + }; + GOptionContext *ctx; + + ctx = g_option_context_new ("- renders a sequence of audio files."); + g_option_context_add_main_entries (ctx, options, NULL); + g_option_context_add_group (ctx, gst_init_get_option_group ()); + + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + gst_printerr ("Error initializing: %s\n", err->message); + g_option_context_free (ctx); + g_clear_error (&err); + return -1; + } + g_option_context_free (ctx); + + if (argc < 3) { + gst_print ("Usage: %s \n", argv[0]); + return -1; + } + + /* Initialize GStreamer (this will parse environment variables and commandline + * arguments. */ + gst_init (&argc, &argv); + + /* Initialize the GStreamer Editing Services */ + ges_init (); + + /* Setup of an audio timeline */ + + /* This is our main GESTimeline */ + timeline = ges_timeline_new (); + + tracka = GES_TRACK (ges_audio_track_new ()); + + /* We are only going to be doing one layer of clips */ + layer = ges_layer_new (); + + /* Add the tracks and the layer to the timeline */ + if (!ges_timeline_add_layer (timeline, layer)) + return -1; + if (!ges_timeline_add_track (timeline, tracka)) + return -1; + + /* Here we've finished initializing our timeline, we're + * ready to start using it... by solely working with the layer ! */ + + for (i = 2; i < argc; i++) { + gchar *uri = gst_filename_to_uri (argv[i], NULL); + GESUriClip *src = ges_uri_clip_new (uri); + + g_assert (src); + g_free (uri); + + g_object_set (src, "start", ges_layer_get_duration (layer), + "duration", GST_SECOND, NULL); + /* Since we're using a GESSimpleLayer, objects will be automatically + * appended to the end of the layer */ + ges_layer_add_clip (layer, (GESClip *) src); + } + + /* In order to view our timeline, let's grab a convenience pipeline to put + * our timeline in. */ + pipeline = ges_pipeline_new (); + + /* Add the timeline to that pipeline */ + if (!ges_pipeline_set_timeline (pipeline, timeline)) + return -1; + + + /* RENDER SETTINGS ! */ + /* We set our output URI and rendering setting on the pipeline */ + if (gst_uri_is_valid (argv[1])) { + output_uri = g_strdup (argv[1]); + } else if (g_file_test (argv[1], G_FILE_TEST_EXISTS)) { + output_uri = gst_filename_to_uri (argv[1], NULL); + } else { + gst_printerr ("Unrecognised command line argument '%s'.\n" + "Please pass an URI or file as argument!\n", argv[1]); + return -1; + } + profile = make_encoding_profile (audio, container); + if (!ges_pipeline_set_render_settings (pipeline, output_uri, profile)) + return -1; + + /* We want the pipeline to render (without any preview) */ + if (!ges_pipeline_set_mode (pipeline, GES_PIPELINE_MODE_SMART_RENDER)) + return -1; + + + /* The following is standard usage of a GStreamer pipeline (note how you haven't + * had to care about GStreamer so far ?). + * + * We set the pipeline to playing ... */ + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); + + /* ... and we start a GMainLoop. GES **REQUIRES** a GMainLoop to be running in + * order to function properly ! */ + mainloop = g_main_loop_new (NULL, FALSE); + + /* Simple code to have the mainloop shutdown after 4s */ + /* FIXME : We should wait for EOS ! */ + g_timeout_add_seconds (argc - 1, (GSourceFunc) g_main_loop_quit, mainloop); + g_main_loop_run (mainloop); + + return 0; +} diff --git a/examples/c/text_properties.c b/examples/c/text_properties.c new file mode 100644 index 0000000000..2252587529 --- /dev/null +++ b/examples/c/text_properties.c @@ -0,0 +1,138 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +typedef struct +{ + int type; + char *name; +} transition_type; + +GESClip *make_source (char *path, guint64 start, guint64 duration, + gint priority, gchar * text); + +GESPipeline *make_timeline (char *path, float duration, char *text); + +GESClip * +make_source (char *path, guint64 start, guint64 duration, gint priority, + gchar * text) +{ + gchar *uri = gst_filename_to_uri (path, NULL); + + GESClip *ret = GES_CLIP (ges_uri_clip_new (uri)); + + g_object_set (ret, + "start", (guint64) start, + "duration", (guint64) duration, + "priority", (guint32) priority, "in-point", (guint64) 0, + "text", text, NULL); + + g_free (uri); + + return ret; +} + +GESPipeline * +make_timeline (char *path, float duration, char *text) +{ + GESTimeline *timeline; + GESTrack *trackv, *tracka; + GESLayer *layer1; + GESClip *srca; + GESPipeline *pipeline; + guint64 aduration; + + pipeline = ges_pipeline_new (); + + ges_pipeline_set_mode (pipeline, GES_PIPELINE_MODE_PREVIEW_VIDEO); + + timeline = ges_timeline_new (); + ges_pipeline_set_timeline (pipeline, timeline); + + trackv = GES_TRACK (ges_video_track_new ()); + ges_timeline_add_track (timeline, trackv); + + tracka = GES_TRACK (ges_audio_track_new ()); + ges_timeline_add_track (timeline, tracka); + + layer1 = GES_LAYER (ges_layer_new ()); + g_object_set (layer1, "priority", (gint32) 0, NULL); + + if (!ges_timeline_add_layer (timeline, layer1)) + exit (-1); + + aduration = (guint64) (duration * GST_SECOND); + srca = make_source (path, 0, aduration, 1, text); + ges_layer_add_clip (layer1, srca); + + return pipeline; +} + +int +main (int argc, char **argv) +{ + GError *err = NULL; + GOptionContext *ctx; + GESPipeline *pipeline; + GMainLoop *mainloop; + gdouble duration; + char *path, *text; + + GOptionEntry options[] = { + {"duration", 'd', 0, G_OPTION_ARG_DOUBLE, &duration, + "duration of transition", "seconds"}, + {"path", 'p', 0, G_OPTION_ARG_STRING, &path, + "path to file", "path"}, + {"text", 't', 0, G_OPTION_ARG_STRING, &text, + "text to render", "text"}, + {NULL} + }; + + ctx = g_option_context_new ("- transition between two media files"); + g_option_context_add_main_entries (ctx, options, NULL); + g_option_context_add_group (ctx, gst_init_get_option_group ()); + + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + gst_print ("Error initializing %s\n", err->message); + g_option_context_free (ctx); + g_clear_error (&err); + exit (1); + } + + if (argc > 1) { + gst_print ("%s", g_option_context_get_help (ctx, TRUE, NULL)); + exit (0); + } + + g_option_context_free (ctx); + + ges_init (); + + pipeline = make_timeline (path, duration, text); + + mainloop = g_main_loop_new (NULL, FALSE); + g_timeout_add_seconds ((duration) + 1, (GSourceFunc) g_main_loop_quit, + mainloop); + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); + g_main_loop_run (mainloop); + + return 0; +} diff --git a/examples/c/thumbnails.c b/examples/c/thumbnails.c new file mode 100644 index 0000000000..0047cb5efe --- /dev/null +++ b/examples/c/thumbnails.c @@ -0,0 +1,191 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Edward Hervey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +/* GLOBAL VARIABLE */ +static guint repeat = 0; +GESPipeline *pipeline = NULL; + +static gboolean thumbnail_cb (gpointer pipeline); + +#define TEST_PATH "test_thumbnail.jpg" + +static gboolean +thumbnail_cb (gpointer user) +{ + GstSample *b = NULL; + GstCaps *caps; + GESPipeline *p; + + p = GES_PIPELINE (user); + + caps = gst_caps_from_string ("image/jpeg"); + GST_INFO ("getting thumbnails"); + + /* check raw rgb use-case with scaling */ + b = ges_pipeline_get_thumbnail_rgb24 (p, 320, 240); + g_assert (b); + gst_sample_unref (b); + + /* check encoding use-case from caps */ + b = NULL; + b = ges_pipeline_get_thumbnail (p, caps); + g_assert (b); + gst_sample_unref (b); + + g_assert (ges_pipeline_save_thumbnail (p, -1, -1, (gchar *) + "image/jpeg", (gchar *) TEST_PATH, NULL)); + g_assert (g_file_test (TEST_PATH, G_FILE_TEST_EXISTS)); + g_unlink (TEST_PATH); + + gst_caps_unref (caps); + return FALSE; +} + +static GESPipeline * +create_timeline (void) +{ + GESPipeline *pipeline; + GESLayer *layer; + GESTrack *tracka, *trackv; + GESTimeline *timeline; + GESClip *src; + + timeline = ges_timeline_new (); + + tracka = GES_TRACK (ges_audio_track_new ()); + trackv = GES_TRACK (ges_video_track_new ()); + + layer = ges_layer_new (); + + /* Add the tracks and the layer to the timeline */ + if (!ges_timeline_add_layer (timeline, layer) || + !ges_timeline_add_track (timeline, tracka) || + !ges_timeline_add_track (timeline, trackv)) + return NULL; + + /* Add the main audio/video file */ + src = GES_CLIP (ges_test_clip_new ()); + g_object_set (src, + "vpattern", GES_VIDEO_TEST_PATTERN_SNOW, + "start", 0, "duration", 10 * GST_SECOND, NULL); + + ges_layer_add_clip (layer, GES_CLIP (src)); + + pipeline = ges_pipeline_new (); + + if (!ges_pipeline_set_timeline (pipeline, timeline)) + return NULL; + + return pipeline; +} + +static void +bus_message_cb (GstBus * bus, GstMessage * message, GMainLoop * mainloop) +{ + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + gst_print ("ERROR\n"); + g_main_loop_quit (mainloop); + break; + case GST_MESSAGE_EOS: + if (repeat > 0) { + gst_print ("Looping again\n"); + /* No need to change state before */ + gst_element_seek_simple (GST_ELEMENT (pipeline), GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, 0); + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); + repeat -= 1; + } else { + gst_print ("Done\n"); + g_main_loop_quit (mainloop); + } + break; + default: + break; + } +} + +int +main (int argc, gchar ** argv) +{ + GError *err = NULL; + GOptionEntry options[] = { + {NULL} + }; + GOptionContext *ctx; + GMainLoop *mainloop; + GstBus *bus; + + ctx = g_option_context_new ("tests thumbnail supoprt (produces no output)"); + g_option_context_set_summary (ctx, ""); + g_option_context_add_main_entries (ctx, options, NULL); + g_option_context_add_group (ctx, gst_init_get_option_group ()); + + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + gst_print ("Error initializing: %s\n", err->message); + g_option_context_free (ctx); + g_clear_error (&err); + exit (1); + } + + g_option_context_free (ctx); + /* Initialize the GStreamer Editing Services */ + ges_init (); + + /* Create the pipeline */ + pipeline = create_timeline (); + if (!pipeline) + exit (-1); + + ges_pipeline_set_mode (pipeline, GES_PIPELINE_MODE_PREVIEW); + + /* Play the pipeline */ + mainloop = g_main_loop_new (NULL, FALSE); + + gst_print ("thumbnailing every 1 seconds\n"); + g_timeout_add (1000, thumbnail_cb, pipeline); + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + gst_bus_add_signal_watch (bus); + g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), mainloop); + + if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { + gst_print ("Failed to start the encoding\n"); + return 1; + } + g_main_loop_run (mainloop); + + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + gst_object_unref (pipeline); + + return 0; +} diff --git a/examples/c/transition.c b/examples/c/transition.c new file mode 100644 index 0000000000..8a38549054 --- /dev/null +++ b/examples/c/transition.c @@ -0,0 +1,207 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +typedef struct +{ + int type; + char *name; +} transition_type; + +GESClip *make_source (gchar * path, guint64 start, guint64 inpoint, + guint64 duration, gint priority); + +gboolean print_transition_data (GESClip * tr); + +GESPipeline *make_timeline (gchar * nick, double tdur, gchar * patha, + gfloat adur, gdouble ainpoint, gchar * pathb, gfloat bdur, + gdouble binpoint); + +GESClip * +make_source (gchar * path, guint64 start, guint64 duration, guint64 inpoint, + gint priority) +{ + gchar *uri = gst_filename_to_uri (path, NULL); + + GESClip *ret = GES_CLIP (ges_uri_clip_new (uri)); + + g_object_set (ret, + "start", (guint64) start, + "duration", (guint64) duration, + "priority", (guint32) priority, "in-point", (guint64) inpoint, NULL); + + g_free (uri); + + return ret; +} + +gboolean +print_transition_data (GESClip * tr) +{ + GESTrackElement *trackelement; + GstElement *nleobj; + guint64 start, duration; + gint priority; + char *name; + GList *trackelements; + + if (!tr) + return FALSE; + + if (!(trackelements = GES_CONTAINER_CHILDREN (tr))) + return FALSE; + if (!(trackelement = GES_TRACK_ELEMENT (trackelements->data))) + return FALSE; + if (!(nleobj = ges_track_element_get_nleobject (trackelement))) + return FALSE; + + g_object_get (nleobj, "start", &start, "duration", &duration, + "priority", &priority, "name", &name, NULL); + gst_print ("nleobject for %s: %f %f %d\n", name, + ((gfloat) start) / GST_SECOND, + ((gfloat) duration) / GST_SECOND, priority); + + return FALSE; +} + +GESPipeline * +make_timeline (gchar * nick, gdouble tdur, gchar * patha, gfloat adur, + gdouble ainp, gchar * pathb, gfloat bdur, gdouble binp) +{ + GESTimeline *timeline; + GESTrack *trackv, *tracka; + GESLayer *layer1; + GESClip *srca, *srcb; + GESPipeline *pipeline; + guint64 aduration, bduration, tduration, tstart, ainpoint, binpoint; + GESTransitionClip *tr = NULL; + + pipeline = ges_pipeline_new (); + + ges_pipeline_set_mode (pipeline, GES_PIPELINE_MODE_PREVIEW_VIDEO); + + timeline = ges_timeline_new (); + ges_pipeline_set_timeline (pipeline, timeline); + + trackv = GES_TRACK (ges_video_track_new ()); + ges_timeline_add_track (timeline, trackv); + + tracka = GES_TRACK (ges_audio_track_new ()); + ges_timeline_add_track (timeline, tracka); + + layer1 = GES_LAYER (ges_layer_new ()); + g_object_set (layer1, "priority", (gint32) 0, NULL); + + if (!ges_timeline_add_layer (timeline, layer1)) + exit (-1); + + aduration = (guint64) (adur * GST_SECOND); + bduration = (guint64) (bdur * GST_SECOND); + tduration = (guint64) (tdur * GST_SECOND); + ainpoint = (guint64) (ainp * GST_SECOND); + binpoint = (guint64) (binp * GST_SECOND); + tstart = aduration - tduration; + srca = make_source (patha, 0, aduration, ainpoint, 1); + srcb = make_source (pathb, tstart, bduration, binpoint, 2); + ges_layer_add_clip (layer1, srca); + ges_layer_add_clip (layer1, srcb); + g_timeout_add_seconds (1, (GSourceFunc) print_transition_data, srca); + g_timeout_add_seconds (1, (GSourceFunc) print_transition_data, srcb); + + if (tduration != 0) { + gst_print ("creating transition at %" GST_TIME_FORMAT " of %f duration (%" + GST_TIME_FORMAT ")\n", GST_TIME_ARGS (tstart), tdur, + GST_TIME_ARGS (tduration)); + if (!(tr = ges_transition_clip_new_for_nick (nick))) + g_error ("invalid transition type %s\n", nick); + + g_object_set (tr, + "start", (guint64) tstart, + "duration", (guint64) tduration, "in-point", (guint64) 0, NULL); + ges_layer_add_clip (layer1, GES_CLIP (tr)); + g_timeout_add_seconds (1, (GSourceFunc) print_transition_data, tr); + } + + return pipeline; +} + +int +main (int argc, char **argv) +{ + GError *err = NULL; + GOptionContext *ctx; + GESPipeline *pipeline; + GMainLoop *mainloop; + gchar *type = (gchar *) "crossfade"; + gchar *patha, *pathb; + gdouble adur, bdur, tdur, ainpoint, binpoint; + + GOptionEntry options[] = { + {"type", 't', 0, G_OPTION_ARG_STRING, &type, + "type of transition to create", ""}, + {"duration", 'd', 0, G_OPTION_ARG_DOUBLE, &tdur, + "duration of transition", "seconds"}, + {NULL} + }; + + ctx = g_option_context_new ("- transition between two media files"); + g_option_context_set_summary (ctx, + "Select two files, and optionally a transition duration and type.\n" + "A file is a triplet of filename, inpoint (in seconds) and duration (in seconds).\n" + "Example:\n" "transition file1.avi 0 5 file2.avi 25 5 -d 2 -t crossfade"); + g_option_context_add_main_entries (ctx, options, NULL); + g_option_context_add_group (ctx, gst_init_get_option_group ()); + + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + gst_print ("Error initializing %s\n", err->message); + g_option_context_free (ctx); + g_clear_error (&err); + exit (1); + } + + if (argc < 4) { + gst_print ("%s", g_option_context_get_help (ctx, TRUE, NULL)); + exit (0); + } + + g_option_context_free (ctx); + + ges_init (); + + patha = argv[1]; + ainpoint = (gdouble) atof (argv[2]); + adur = (gdouble) atof (argv[3]); + pathb = argv[4]; + binpoint = (gdouble) atof (argv[5]); + bdur = (gdouble) atof (argv[6]); + + pipeline = + make_timeline (type, tdur, patha, adur, ainpoint, pathb, bdur, binpoint); + + mainloop = g_main_loop_new (NULL, FALSE); + g_timeout_add_seconds ((adur + bdur) + 1, (GSourceFunc) g_main_loop_quit, + mainloop); + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); + g_main_loop_run (mainloop); + + return 0; +} diff --git a/examples/meson.build b/examples/meson.build new file mode 100644 index 0000000000..76d1974ff0 --- /dev/null +++ b/examples/meson.build @@ -0,0 +1 @@ +subdir('c') diff --git a/examples/python/gst-player.py b/examples/python/gst-player.py new file mode 100755 index 0000000000..51ba7391a7 --- /dev/null +++ b/examples/python/gst-player.py @@ -0,0 +1,50 @@ +#!/usr/bin/python3 +import sys +import gi + +gi.require_version('Gst', '1.0') +gi.require_version('GES', '1.0') +gi.require_version('GstPlayer', '1.0') +gi.require_version('GLib', '2.0') + +from gi.repository import Gst, GES, GLib, GstPlayer + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("You must specify a file URI") + sys.exit(-1) + + Gst.init(None) + GES.init() + + timeline = GES.Timeline.new_audio_video() + layer = timeline.append_layer() + start = 0 + for uri in sys.argv[1:]: + if not Gst.uri_is_valid(uri): + uri = Gst.filename_to_uri(uri) + + clip = GES.UriClip.new(uri) + clip.props.start = start + layer.add_clip(clip) + + start += clip.props.duration + + player = GstPlayer + player = GstPlayer.Player.new(None, GstPlayer.PlayerGMainContextSignalDispatcher.new(None)) + player.set_uri("ges://") + player.get_pipeline().connect("source-setup", + lambda playbin, source: source.set_property("timeline", timeline)) + + loop = GLib.MainLoop() + player.connect("end-of-stream", lambda x: loop.quit()) + + def error(player, err): + loop.quit() + print("Got error: %s" % err) + sys.exit(1) + + player.connect("error", error) + player.play() + loop.run() diff --git a/examples/python/keyframes.py b/examples/python/keyframes.py new file mode 100755 index 0000000000..05a4d035a9 --- /dev/null +++ b/examples/python/keyframes.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# +# GStreamer +# +# Copyright (C) 2019 Thibault Saunier +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library 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 +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Suite 500, +# Boston, MA 02110-1335, USA. + +import gi +import sys + +gi.require_version('Gst', '1.0') +gi.require_version('GES', '1.0') +gi.require_version('GstController', '1.0') + +from gi.repository import Gst, GES, GLib, GstController # noqa + +Gst.init(None) +GES.init() + + +def play_timeline(timeline): + pipeline = GES.Pipeline() + pipeline.set_timeline(timeline) + bus = pipeline.get_bus() + bus.add_signal_watch() + loop = GLib.MainLoop() + bus.connect("message", bus_message_cb, loop, pipeline) + pipeline.set_state(Gst.State.PLAYING) + + loop.run() + +def bus_message_cb(unused_bus, message, loop, pipeline): + if message.type == Gst.MessageType.EOS: + print("eos") + pipeline.set_state(Gst.State.NULL) + loop.quit() + elif message.type == Gst.MessageType.ERROR: + error = message.parse_error() + pipeline.set_state(Gst.State.NULL) + print("error %s" % error[1]) + loop.quit() + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("You must specify a file path") + exit(-1) + + timeline = GES.Timeline.new_audio_video() + + layer = timeline.append_layer() + clip = GES.UriClip.new(Gst.filename_to_uri(sys.argv[1])) + + # Adding clip to the layer so the TrackElements are created + layer.add_clip(clip) + + # Create an InterpolationControlSource and make sure it interpolates linearly + control_source = GstController.InterpolationControlSource.new() + control_source.props.mode = GstController.InterpolationMode.LINEAR + + # Set the keyframes + control_source.set(0, 0.0) # Fully transparent at 0 second + control_source.set(Gst.SECOND, 1.0) # Fully opaque at 1 second + + # Get the video source + video_source = clip.find_track_element(None, GES.VideoSource) + assert(video_source) + + # And set the control source on the "alpha" property of the video source + # Using a "direct" binding but "direct-absolute" would work the exact + # same way as the alpha property range is [0.0 - 1.0] anyway. + video_source.set_control_source(control_source, "alpha", "direct") + + play_timeline(timeline) \ No newline at end of file diff --git a/examples/python/simple.py b/examples/python/simple.py new file mode 100755 index 0000000000..7874e973e1 --- /dev/null +++ b/examples/python/simple.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# +# GStreamer +# +# Copyright (C) 2013 Thibault Saunier + * Copyright (C) 2012 Volodymyr Rudyi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION: gesasset + * @title: GESAsset + * @short_description: Represents usable resources inside the GStreamer + * Editing Services + * + * A #GESAsset in the GStreamer Editing Services represents a resources + * that can be used. In particular, any class that implements the + * #GESExtractable interface may have some associated assets with a + * corresponding #GESAsset:extractable-type, from which its objects can be + * extracted using ges_asset_extract(). Some examples would be + * #GESClip, #GESFormatter and #GESTrackElement. + * + * All assets that are created within GES are stored in a cache; one per + * each #GESAsset:id and #GESAsset:extractable-type pair. These assets can + * be fetched, and initialized if they do not yet exist in the cache, + * using ges_asset_request(). + * + * ``` c + * GESAsset *effect_asset; + * GESEffect *effect; + * + * // You create an asset for an effect + * effect_asset = ges_asset_request (GES_TYPE_EFFECT, "agingtv", NULL); + * + * // And now you can extract an instance of GESEffect from that asset + * effect = GES_EFFECT (ges_asset_extract (effect_asset)); + * + * ``` + * + * The advantage of using assets, rather than simply creating the object + * directly, is that the currently loaded resources can be listed with + * ges_list_assets() and displayed to an end user. For example, to show + * which media files have been loaded, and a standard list of effects. In + * fact, the GES library already creates assets for #GESTransitionClip and + * #GESFormatter, which you can use to list all the available transition + * types and supported formats. + * + * The other advantage is that #GESAsset implements #GESMetaContainer, so + * metadata can be set on the asset, with some subclasses automatically + * creating this metadata on initiation. + * + * For example, to display information about the supported formats, you + * could do the following: + * |[ + * GList *formatter_assets, *tmp; + * + * // List all the transitions + * formatter_assets = ges_list_assets (GES_TYPE_FORMATTER); + * + * // Print some infos about the formatter GESAsset + * for (tmp = formatter_assets; tmp; tmp = tmp->next) { + * gst_print ("Name of the formatter: %s, file extension it produces: %s", + * ges_meta_container_get_string ( + * GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_NAME), + * ges_meta_container_get_string ( + * GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_EXTENSION)); + * } + * + * g_list_free (transition_assets); + * + * ]| + * + * ## ID + * + * Each asset is uniquely defined in the cache by its + * #GESAsset:extractable-type and #GESAsset:id. Depending on the + * #GESAsset:extractable-type, the #GESAsset:id can be used to parametrise + * the creation of the object upon extraction. By default, a class that + * implements #GESExtractable will only have a single associated asset, + * with an #GESAsset:id set to the type name of its objects. However, this + * is overwritten by some implementations, which allow a class to have + * multiple associated assets. For example, for #GESTransitionClip the + * #GESAsset:id will be a nickname of the #GESTransitionClip:vtype. You + * should check the documentation for each extractable type to see if they + * differ from the default. + * + * Moreover, each #GESAsset:extractable-type may also associate itself + * with a specific asset subclass. In such cases, when their asset is + * requested, an asset of this subclass will be returned instead. + * + * ## Managing + * + * You can use a #GESProject to easily manage the assets of a + * #GESTimeline. + * + * ## Proxies + * + * Some assets can (temporarily) act as the #GESAsset:proxy of another + * asset. When the original asset is requested from the cache, the proxy + * will be returned in its place. This can be useful if, say, you want + * to substitute a #GESUriClipAsset corresponding to a high resolution + * media file with the asset of a lower resolution stand in. + * + * An asset may even have several proxies, the first of which will act as + * its default and be returned on requests, but the others will be ordered + * to take its place once it is removed. You can add a proxy to an asset, + * or set its default, using ges_asset_set_proxy(), and you can remove + * them with ges_asset_unproxy(). + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges.h" +#include "ges-internal.h" + +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include + +GST_DEBUG_CATEGORY_STATIC (ges_asset_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT ges_asset_debug + +enum +{ + PROP_0, + PROP_TYPE, + PROP_ID, + PROP_PROXY, + PROP_PROXY_TARGET, + PROP_LAST +}; + +typedef enum +{ + ASSET_NOT_INITIALIZED, + ASSET_INITIALIZING, ASSET_INITIALIZED_WITH_ERROR, + ASSET_PROXIED, + ASSET_NEEDS_RELOAD, + ASSET_INITIALIZED +} GESAssetState; + +static GParamSpec *_properties[PROP_LAST]; + +struct _GESAssetPrivate +{ + gchar *id; + GESAssetState state; + GType extractable_type; + + /* used internally by try_proxy to pre-set a proxy whilst an asset is + * still loading. It can be used later to set the proxy for the asset + * once it has finished loading */ + char *proxied_asset_id; + + /* actual list of proxies */ + GList *proxies; + /* the asset whose proxies list we belong to */ + GESAsset *proxy_target; + + /* The error that occurred when an asset has been initialized with error */ + GError *error; +}; + +/* Internal structure to help avoid full loading + * of one asset several times + */ +typedef struct +{ + GList *results; + GESAsset *asset; +} GESAssetCacheEntry; + +/* We are mapping entries by types and ID, such as: + * + * { + * first_extractable_type_name1 : + * { + * "some ID": GESAssetCacheEntry, + * "some other ID": GESAssetCacheEntry 2 + * }, + * second_extractable_type_name : + * { + * "some ID": GESAssetCacheEntry, + * "some other ID": GESAssetCacheEntry 2 + * } + * } + * + * (The first extractable type is the type of the class that implemented + * the GESExtractable interface ie: GESClip, GESTimeline, + * GESFomatter, etc... but not subclasses) + * + * This is in order to be able to have 2 Asset with the same ID but + * different extractable types. + **/ +static GHashTable *type_entries_table = NULL; +/* Protect all the entries in the cache */ +static GRecMutex asset_cache_lock; +#define LOCK_CACHE (g_rec_mutex_lock (&asset_cache_lock)) +#define UNLOCK_CACHE (g_rec_mutex_unlock (&asset_cache_lock)) + +static gchar * +_check_and_update_parameters (GType * extractable_type, const gchar * id, + GError ** error) +{ + gchar *real_id; + GType old_type = *extractable_type; + + *extractable_type = + ges_extractable_get_real_extractable_type_for_id (*extractable_type, id); + + if (*extractable_type == G_TYPE_NONE) { + GST_WARNING ("No way to create a Asset for ID: %s, type: %s", id, + g_type_name (old_type)); + + if (error && *error == NULL) + g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID, + "Wrong ID, can not find any extractable_type"); + return NULL; + } + + real_id = ges_extractable_type_check_id (*extractable_type, id, error); + if (real_id == NULL) { + GST_WARNING ("Wrong ID %s, can not create asset", id); + + g_free (real_id); + if (error && *error == NULL) + g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID, "Wrong ID"); + + return NULL; + } + + return real_id; +} + +/* FIXME: why are we not accepting a GError ** error argument, which we + * could pass to ges_asset_cache_set_loaded ()? Which would allow the + * error to be set for the GInitable init method below */ +static gboolean +start_loading (GESAsset * asset) +{ + GInitableIface *iface; + + iface = g_type_interface_peek (GES_ASSET_GET_CLASS (asset), G_TYPE_INITABLE); + + if (!iface->init) { + GST_INFO_OBJECT (asset, "Can not start loading sync, as no ->init vmethod"); + return FALSE; + } + + ges_asset_cache_put (gst_object_ref (asset), NULL); + return ges_asset_cache_set_loaded (asset->priv->extractable_type, + asset->priv->id, NULL); +} + +static gboolean +initable_init (GInitable * initable, GCancellable * cancellable, + GError ** error) +{ + /* FIXME: Is there actually a reason to be freeing the GError that + * error points to? */ + g_clear_error (error); + + return start_loading (GES_ASSET (initable)); +} + +static void +async_initable_init_async (GAsyncInitable * initable, gint io_priority, + GCancellable * cancellable, GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + GError *error = NULL; + GESAsset *asset = GES_ASSET (initable); + + task = g_task_new (asset, cancellable, callback, user_data); + + ges_asset_cache_put (gst_object_ref (asset), task); + switch (GES_ASSET_GET_CLASS (asset)->start_loading (asset, &error)) { + case GES_ASSET_LOADING_ERROR: + { + if (error == NULL) + g_set_error (&error, GES_ERROR, GES_ERROR_ASSET_LOADING, + "Could not start loading asset"); + + /* FIXME Define error code */ + ges_asset_cache_set_loaded (asset->priv->extractable_type, + asset->priv->id, error); + g_error_free (error); + return; + } + case GES_ASSET_LOADING_OK: + { + ges_asset_cache_set_loaded (asset->priv->extractable_type, + asset->priv->id, error); + return; + } + case GES_ASSET_LOADING_ASYNC: + /* If Async.... let it go */ + /* FIXME: how are user subclasses that implement ->start_loading + * to return GES_ASSET_LOADING_ASYNC meant to invoke the private + * method ges_asset_cache_set_loaded once they finish initializing? + */ + break; + } +} + +static void +async_initable_iface_init (GAsyncInitableIface * async_initable_iface) +{ + async_initable_iface->init_async = async_initable_init_async; +} + +static void +initable_iface_init (GInitableIface * initable_iface) +{ + initable_iface->init = initable_init; +} + +G_DEFINE_TYPE_WITH_CODE (GESAsset, ges_asset, G_TYPE_OBJECT, + G_ADD_PRIVATE (GESAsset) + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init) + G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, NULL)); + +/* GESAsset virtual methods default implementation */ +static GESAssetLoadingReturn +ges_asset_start_loading_default (GESAsset * asset, GError ** error) +{ + return GES_ASSET_LOADING_OK; +} + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS; +static GESExtractable * +ges_asset_extract_default (GESAsset * asset, GError ** error) +{ + guint n_params; + GParameter *params; + GESAssetPrivate *priv = asset->priv; + GESExtractable *n_extractable; + gint i; + GValue *values; + const gchar **names; + + params = ges_extractable_type_get_parameters_from_id (priv->extractable_type, + priv->id, &n_params); + + + values = g_malloc0 (sizeof (GValue) * n_params); + names = g_malloc0 (sizeof (gchar *) * n_params); + + for (i = 0; i < n_params; i++) { + values[i] = params[i].value; + names[i] = params[i].name; + } + + n_extractable = + GES_EXTRACTABLE (g_object_new_with_properties (priv->extractable_type, + n_params, names, values)); + g_free (names); + g_free (values); + + while (n_params--) + g_value_unset (¶ms[n_params].value); + + g_free (params); + + return n_extractable; +} + +G_GNUC_END_IGNORE_DEPRECATIONS; + +static gboolean +ges_asset_request_id_update_default (GESAsset * self, gchar ** proposed_new_id, + GError * error) +{ + return FALSE; +} + +/* GObject virtual methods implementation */ +static void +ges_asset_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESAsset *asset = GES_ASSET (object); + + switch (property_id) { + case PROP_TYPE: + g_value_set_gtype (value, asset->priv->extractable_type); + break; + case PROP_ID: + g_value_set_string (value, asset->priv->id); + break; + case PROP_PROXY: + g_value_set_object (value, ges_asset_get_proxy (asset)); + break; + case PROP_PROXY_TARGET: + g_value_set_object (value, ges_asset_get_proxy_target (asset)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_asset_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESAsset *asset = GES_ASSET (object); + + switch (property_id) { + case PROP_TYPE: + asset->priv->extractable_type = g_value_get_gtype (value); + /* NOTE: we calling this in the setter so metadata is set on the + * asset upon initiation, but before it has been loaded. */ + ges_extractable_register_metas (asset->priv->extractable_type, asset); + break; + case PROP_ID: + asset->priv->id = g_value_dup_string (value); + break; + case PROP_PROXY: + ges_asset_set_proxy (asset, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_asset_finalize (GObject * object) +{ + GESAssetPrivate *priv = GES_ASSET (object)->priv; + + GST_LOG_OBJECT (object, "finalizing"); + + if (priv->id) + g_free (priv->id); + + if (priv->proxied_asset_id) + g_free (priv->proxied_asset_id); + + if (priv->error) + g_error_free (priv->error); + + if (priv->proxies) + g_list_free (priv->proxies); + + G_OBJECT_CLASS (ges_asset_parent_class)->finalize (object); +} + +void +ges_asset_class_init (GESAssetClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = ges_asset_get_property; + object_class->set_property = ges_asset_set_property; + object_class->finalize = ges_asset_finalize; + + /** + * GESAsset:extractable-type: + * + * The #GESExtractable object type that can be extracted from the asset. + */ + _properties[PROP_TYPE] = + g_param_spec_gtype ("extractable-type", "Extractable type", + "The type of the Object that can be extracted out of the asset", + G_TYPE_OBJECT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + /** + * GESAsset:id: + * + * The ID of the asset. This should be unique amongst all assets with + * the same #GESAsset:extractable-type. Depending on the associated + * #GESExtractable implementation, this id may convey some information + * about the #GObject that should be extracted. Note that, as such, the + * ID will have an expected format, and you can not choose this value + * arbitrarily. By default, this will be set to the type name of the + * #GESAsset:extractable-type, but you should check the documentation + * of the extractable type to see whether they differ from the + * default behaviour. + */ + _properties[PROP_ID] = + g_param_spec_string ("id", "Identifier", + "The unique identifier of the asset", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + /** + * GESAsset:proxy: + * + * The default proxy for this asset, or %NULL if it has no proxy. A + * proxy will act as a substitute for the original asset when the + * original is requested (see ges_asset_request()). + * + * Setting this property will not usually remove the existing proxy, but + * will replace it as the default (see ges_asset_set_proxy()). + */ + _properties[PROP_PROXY] = + g_param_spec_object ("proxy", "Proxy", + "The asset default proxy.", GES_TYPE_ASSET, G_PARAM_READWRITE); + + /** + * GESAsset:proxy-target: + * + * The asset that this asset is a proxy for, or %NULL if it is not a + * proxy for another asset. + * + * Note that even if this asset is acting as a proxy for another asset, + * but this asset is not the default #GESAsset:proxy, then @proxy-target + * will *still* point to this other asset. So you should check the + * #GESAsset:proxy property of @target-proxy before assuming it is the + * current default proxy for the target. + * + * Note that the #GObject::notify for this property is emitted after + * the #GESAsset:proxy #GObject::notify for the corresponding (if any) + * asset it is now the proxy of/no longer the proxy of. + */ + _properties[PROP_PROXY_TARGET] = + g_param_spec_object ("proxy-target", "Proxy target", + "The target of a proxy asset.", GES_TYPE_ASSET, G_PARAM_READABLE); + + g_object_class_install_properties (object_class, PROP_LAST, _properties); + + klass->start_loading = ges_asset_start_loading_default; + klass->extract = ges_asset_extract_default; + klass->request_id_update = ges_asset_request_id_update_default; + klass->inform_proxy = NULL; + + GST_DEBUG_CATEGORY_INIT (ges_asset_debug, "ges-asset", + GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GES Asset"); +} + +void +ges_asset_init (GESAsset * self) +{ + self->priv = ges_asset_get_instance_private (self); + + self->priv->state = ASSET_INITIALIZING; + self->priv->proxied_asset_id = NULL; +} + +/* Internal methods */ + +static inline const gchar * +_extractable_type_name (GType type) +{ + /* We can use `ges_asset_request (GES_TYPE_FORMATTER);` */ + if (g_type_is_a (type, GES_TYPE_FORMATTER)) + return g_type_name (GES_TYPE_FORMATTER); + + return g_type_name (type); +} + +static void +ges_asset_cache_init_unlocked (void) +{ + if (type_entries_table) + return; + + type_entries_table = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_hash_table_unref); + + _init_formatter_assets (); + _init_standard_transition_assets (); +} + + +/* WITH LOCK_CACHE */ +static GHashTable * +_get_type_entries (void) +{ + if (type_entries_table) + return type_entries_table; + + ges_asset_cache_init_unlocked (); + + return type_entries_table; +} + +/* WITH LOCK_CACHE */ +static inline GESAssetCacheEntry * +_lookup_entry (GType extractable_type, const gchar * id) +{ + GHashTable *entries_table; + + entries_table = g_hash_table_lookup (_get_type_entries (), + _extractable_type_name (extractable_type)); + if (entries_table) + return g_hash_table_lookup (entries_table, id); + + return NULL; +} + +static void +_free_entries (gpointer entry) +{ + GESAssetCacheEntry *data = (GESAssetCacheEntry *) entry; + if (data->asset) + gst_object_unref (data->asset); + g_slice_free (GESAssetCacheEntry, entry); +} + +static void +_gtask_return_error (GTask * task, GError * error) +{ + g_task_return_error (task, g_error_copy (error)); +} + +static void +_gtask_return_true (GTask * task, gpointer udata) +{ + g_task_return_boolean (task, TRUE); +} + +/** + * ges_asset_cache_lookup: + * + * @id String identifier of asset + * + * Looks for asset with specified id in cache and it's completely loaded. + * + * Returns: (transfer none) (nullable): The #GESAsset found or %NULL + */ +GESAsset * +ges_asset_cache_lookup (GType extractable_type, const gchar * id) +{ + GESAsset *asset = NULL; + GESAssetCacheEntry *entry = NULL; + + g_return_val_if_fail (id, NULL); + + LOCK_CACHE; + entry = _lookup_entry (extractable_type, id); + if (entry) + asset = entry->asset; + UNLOCK_CACHE; + + return asset; +} + +static void +ges_asset_cache_append_task (GType extractable_type, + const gchar * id, GTask * task) +{ + GESAssetCacheEntry *entry = NULL; + + LOCK_CACHE; + if ((entry = _lookup_entry (extractable_type, id))) + entry->results = g_list_append (entry->results, task); + UNLOCK_CACHE; +} + +gboolean +ges_asset_cache_set_loaded (GType extractable_type, const gchar * id, + GError * error) +{ + GESAsset *asset; + GESAssetCacheEntry *entry = NULL; + GList *results = NULL; + GFunc user_func = NULL; + gpointer user_data = NULL; + + LOCK_CACHE; + if ((entry = _lookup_entry (extractable_type, id)) == NULL) { + UNLOCK_CACHE; + GST_ERROR ("Calling but type %s ID: %s not in cached, " + "something massively screwed", g_type_name (extractable_type), id); + + return FALSE; + } + + asset = entry->asset; + GST_DEBUG_OBJECT (entry->asset, ": (extractable type: %s) loaded, calling %i " + "callback (Error: %s)", g_type_name (asset->priv->extractable_type), + g_list_length (entry->results), error ? error->message : ""); + + results = entry->results; + entry->results = NULL; + + if (error) { + asset->priv->state = ASSET_INITIALIZED_WITH_ERROR; + if (asset->priv->error) + g_error_free (asset->priv->error); + asset->priv->error = g_error_copy (error); + + /* In case of error we do not want to emit in idle as we need to recover + * if possible */ + user_func = (GFunc) _gtask_return_error; + user_data = error; + GST_DEBUG_OBJECT (asset, "initialized with error"); + } else { + asset->priv->state = ASSET_INITIALIZED; + user_func = (GFunc) _gtask_return_true; + GST_DEBUG_OBJECT (asset, "initialized"); + } + UNLOCK_CACHE; + + g_list_foreach (results, user_func, user_data); + g_list_free_full (results, g_object_unref); + + return TRUE; +} + +/* transfer full for both @asset and @task */ +void +ges_asset_cache_put (GESAsset * asset, GTask * task) +{ + GType extractable_type; + const gchar *asset_id; + GESAssetCacheEntry *entry; + + /* Needing to work with the cache, taking the lock */ + asset_id = ges_asset_get_id (asset); + extractable_type = asset->priv->extractable_type; + + LOCK_CACHE; + if (!(entry = _lookup_entry (extractable_type, asset_id))) { + GHashTable *entries_table; + + entries_table = g_hash_table_lookup (_get_type_entries (), + _extractable_type_name (extractable_type)); + if (entries_table == NULL) { + entries_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + _free_entries); + + g_hash_table_insert (_get_type_entries (), + g_strdup (_extractable_type_name (extractable_type)), entries_table); + } + + entry = g_slice_new0 (GESAssetCacheEntry); + + /* transfer asset to entry */ + entry->asset = asset; + if (task) + entry->results = g_list_prepend (entry->results, task); + g_hash_table_insert (entries_table, (gpointer) g_strdup (asset_id), + (gpointer) entry); + } else { + /* give up the reference we were given */ + gst_object_unref (asset); + if (task) { + GST_DEBUG ("%s already in cache, adding result %p", asset_id, task); + entry->results = g_list_prepend (entry->results, task); + } + } + UNLOCK_CACHE; +} + +void +ges_asset_cache_init (void) +{ + LOCK_CACHE; + ges_asset_cache_init_unlocked (); + UNLOCK_CACHE; +} + +void +ges_asset_cache_deinit (void) +{ + _deinit_formatter_assets (); + + LOCK_CACHE; + g_hash_table_destroy (type_entries_table); + type_entries_table = NULL; + UNLOCK_CACHE; +} + +gboolean +ges_asset_request_id_update (GESAsset * asset, gchar ** proposed_id, + GError * error) +{ + g_return_val_if_fail (GES_IS_ASSET (asset), FALSE); + + return GES_ASSET_GET_CLASS (asset)->request_id_update (asset, proposed_id, + error); +} + +/* pre-set a proxy id whilst the asset is still loading. Once the proxy + * is loaded, call ges_asset_finish_proxy (proxy) */ +gboolean +ges_asset_try_proxy (GESAsset * asset, const gchar * new_id) +{ + GESAssetClass *class; + + g_return_val_if_fail (GES_IS_ASSET (asset), FALSE); + + if (g_strcmp0 (asset->priv->id, new_id) == 0) { + GST_WARNING_OBJECT (asset, "Trying to proxy to itself (%s)," + " NOT possible", new_id); + + return FALSE; + } else if (g_strcmp0 (asset->priv->proxied_asset_id, new_id) == 0) { + GST_WARNING_OBJECT (asset, + "Trying to proxy to same currently set proxy: %s -- %s", + asset->priv->proxied_asset_id, new_id); + + return FALSE; + } + + g_free (asset->priv->proxied_asset_id); + asset->priv->state = ASSET_PROXIED; + asset->priv->proxied_asset_id = g_strdup (new_id); + + /* FIXME: inform_proxy is not used consistently. For example, it is + * not called in set_proxy. However, it is still used by GESUriAsset. + * We should find some other method */ + class = GES_ASSET_GET_CLASS (asset); + if (class->inform_proxy) + GES_ASSET_GET_CLASS (asset)->inform_proxy (asset, new_id); + + GST_DEBUG_OBJECT (asset, "Trying to proxy to %s", new_id); + + return TRUE; +} + +static gboolean +_lookup_proxied_asset (const gchar * id, GESAssetCacheEntry * entry, + const gchar * asset_id) +{ + return !g_strcmp0 (asset_id, entry->asset->priv->proxied_asset_id); +} + +/* find the assets that called try_proxy for the asset id of @proxy + * and set @proxy as their proxy */ +gboolean +ges_asset_finish_proxy (GESAsset * proxy) +{ + GESAsset *proxied_asset; + GHashTable *entries_table; + GESAssetCacheEntry *entry; + + LOCK_CACHE; + entries_table = g_hash_table_lookup (_get_type_entries (), + _extractable_type_name (proxy->priv->extractable_type)); + entry = g_hash_table_find (entries_table, (GHRFunc) _lookup_proxied_asset, + (gpointer) ges_asset_get_id (proxy)); + + if (!entry) { + UNLOCK_CACHE; + GST_DEBUG_OBJECT (proxy, "Not proxying any asset %s", proxy->priv->id); + return FALSE; + } + + proxied_asset = entry->asset; + UNLOCK_CACHE; + + /* If the asset with the matching ->proxied_asset_id is already proxied + * by another asset, we actually want @proxy to proxy this instead */ + while (proxied_asset->priv->proxies) + proxied_asset = proxied_asset->priv->proxies->data; + + /* unless it is ourselves. I.e. it is already proxied by us */ + if (proxied_asset == proxy) + return FALSE; + + GST_INFO_OBJECT (proxied_asset, + "%s Making sure the proxy chain is fully set.", + ges_asset_get_id (entry->asset)); + if (g_strcmp0 (proxied_asset->priv->proxied_asset_id, proxy->priv->id) || + g_strcmp0 (proxied_asset->priv->id, proxy->priv->proxied_asset_id)) + ges_asset_finish_proxy (proxied_asset); + return ges_asset_set_proxy (proxied_asset, proxy); +} + +static gboolean +_contained_in_proxy_tree (GESAsset * node, GESAsset * search) +{ + GList *tmp; + if (node == search) + return TRUE; + for (tmp = node->priv->proxies; tmp; tmp = tmp->next) { + if (_contained_in_proxy_tree (tmp->data, search)) + return TRUE; + } + return FALSE; +} + +/** + * ges_asset_set_proxy: + * @asset: The #GESAsset to proxy + * @proxy: (allow-none): A new default proxy for @asset + * + * Sets the #GESAsset:proxy for the asset. + * + * If @proxy is among the existing proxies of the asset (see + * ges_asset_list_proxies()) it will be moved to become the default + * proxy. Otherwise, if @proxy is not %NULL, it will be added to the list + * of proxies, as the new default. The previous default proxy will become + * 'next in line' for if the new one is removed, and so on. As such, this + * will **not** actually remove the previous default proxy (use + * ges_asset_unproxy() for that). + * + * Note that an asset can only act as a proxy for one other asset. + * + * As a special case, if @proxy is %NULL, then this method will actually + * remove **all** proxies from the asset. + * + * Returns: %TRUE if @proxy was successfully set as the default for + * @asset. + */ +gboolean +ges_asset_set_proxy (GESAsset * asset, GESAsset * proxy) +{ + GESAsset *current_target; + g_return_val_if_fail (GES_IS_ASSET (asset), FALSE); + g_return_val_if_fail (proxy == NULL || GES_IS_ASSET (proxy), FALSE); + g_return_val_if_fail (asset != proxy, FALSE); + + if (!proxy) { + GList *tmp, *proxies; + if (asset->priv->error) { + GST_ERROR_OBJECT (asset, + "Asset was loaded with error (%s), it should not be 'unproxied'", + asset->priv->error->message); + + return FALSE; + } + + GST_DEBUG_OBJECT (asset, "Removing all proxies"); + proxies = asset->priv->proxies; + asset->priv->proxies = NULL; + + for (tmp = proxies; tmp; tmp = tmp->next) { + GESAsset *proxy = tmp->data; + proxy->priv->proxy_target = NULL; + } + asset->priv->state = ASSET_INITIALIZED; + + g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]); + for (tmp = proxies; tmp; tmp = tmp->next) + g_object_notify_by_pspec (G_OBJECT (tmp->data), + _properties[PROP_PROXY_TARGET]); + + g_list_free (proxies); + return TRUE; + } + current_target = proxy->priv->proxy_target; + + if (current_target && current_target != asset) { + GST_ERROR_OBJECT (asset, + "Trying to use '%s' as a proxy, but it is already proxying '%s'", + proxy->priv->id, current_target->priv->id); + + return FALSE; + } + + if (_contained_in_proxy_tree (proxy, asset)) { + GST_ERROR_OBJECT (asset, "Trying to setup a circular proxying dependency!"); + + return FALSE; + } + + if (g_list_find (asset->priv->proxies, proxy)) { + GST_INFO_OBJECT (asset, + "%" GST_PTR_FORMAT " already marked as proxy, moving to first", proxy); + asset->priv->proxies = g_list_remove (asset->priv->proxies, proxy); + } + + GST_INFO ("%s is now proxied by %s", asset->priv->id, proxy->priv->id); + asset->priv->proxies = g_list_prepend (asset->priv->proxies, proxy); + + proxy->priv->proxy_target = asset; + asset->priv->state = ASSET_PROXIED; + + g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]); + if (current_target != asset) + g_object_notify_by_pspec (G_OBJECT (proxy), _properties[PROP_PROXY_TARGET]); + + /* FIXME: ->inform_proxy is not called. We should figure out what the + * purpose of ->inform_proxy should be generically. Currently, it is + * only called in ges_asset_try_proxy! */ + + return TRUE; +} + +/** + * ges_asset_unproxy: + * @asset: The #GESAsset to no longer proxy with @proxy + * @proxy: An existing proxy of @asset + * + * Removes the proxy from the available list of proxies for the asset. If + * the given proxy is the default proxy of the list, then the next proxy + * in the available list (see ges_asset_list_proxies()) will become the + * default. If there are no other proxies, then the asset will no longer + * have a default #GESAsset:proxy. + * + * Returns: %TRUE if @proxy was successfully removed from @asset's proxy + * list. + */ +gboolean +ges_asset_unproxy (GESAsset * asset, GESAsset * proxy) +{ + gboolean removing_default; + gboolean last_proxy; + g_return_val_if_fail (GES_IS_ASSET (asset), FALSE); + g_return_val_if_fail (GES_IS_ASSET (proxy), FALSE); + g_return_val_if_fail (asset != proxy, FALSE); + + /* also tests if the list is NULL */ + if (!g_list_find (asset->priv->proxies, proxy)) { + GST_INFO_OBJECT (asset, "'%s' is not a proxy.", proxy->priv->id); + + return FALSE; + } + + last_proxy = (asset->priv->proxies->next == NULL); + if (last_proxy && asset->priv->error) { + GST_ERROR_OBJECT (asset, + "Asset was loaded with error (%s), its last proxy '%s' should " + "not be removed", asset->priv->error->message, proxy->priv->id); + return FALSE; + } + + removing_default = (asset->priv->proxies->data == proxy); + + asset->priv->proxies = g_list_remove (asset->priv->proxies, proxy); + + if (last_proxy) + asset->priv->state = ASSET_INITIALIZED; + proxy->priv->proxy_target = NULL; + + if (removing_default) + g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]); + g_object_notify_by_pspec (G_OBJECT (proxy), _properties[PROP_PROXY_TARGET]); + + return TRUE; +} + +/** + * ges_asset_list_proxies: + * @asset: A #GESAsset + * + * Get all the proxies that the asset has. The first item of the list will + * be the default #GESAsset:proxy. The second will be the proxy that is + * 'next in line' to be default, and so on. + * + * Returns: (element-type GESAsset) (transfer none): The list of proxies + * that @asset has. + */ +GList * +ges_asset_list_proxies (GESAsset * asset) +{ + g_return_val_if_fail (GES_IS_ASSET (asset), NULL); + + return asset->priv->proxies; +} + +/** + * ges_asset_get_proxy: + * @asset: A #GESAsset + * + * Gets the default #GESAsset:proxy of the asset. + * + * Returns: (transfer none) (nullable): The default proxy of @asset. + */ +GESAsset * +ges_asset_get_proxy (GESAsset * asset) +{ + g_return_val_if_fail (GES_IS_ASSET (asset), NULL); + + if (asset->priv->state == ASSET_PROXIED && asset->priv->proxies) { + return asset->priv->proxies->data; + } + + return NULL; +} + +/** + * ges_asset_get_proxy_target: + * @proxy: A #GESAsset + * + * Gets the #GESAsset:proxy-target of the asset. + * + * Note that the proxy target may have loaded with an error, so you should + * call ges_asset_get_error() on the returned target. + * + * Returns: (transfer none) (nullable): The asset that @proxy is a proxy + * of. + */ +GESAsset * +ges_asset_get_proxy_target (GESAsset * proxy) +{ + g_return_val_if_fail (GES_IS_ASSET (proxy), NULL); + + return proxy->priv->proxy_target; +} + +/* Caution, this method should be used in rare cases (ie: for the project + * as we can change its ID from a useless one to a proper URI). In most + * cases you want to update the ID creating a proxy + */ +void +ges_asset_set_id (GESAsset * asset, const gchar * id) +{ + GHashTable *entries; + + gpointer orig_id = NULL; + GESAssetCacheEntry *entry = NULL; + GESAssetPrivate *priv = NULL; + + g_return_if_fail (GES_IS_ASSET (asset)); + + priv = asset->priv; + + if (priv->state != ASSET_INITIALIZED) { + GST_WARNING_OBJECT (asset, "Trying to rest ID on an object that is" + " not properly loaded"); + return; + } + + if (g_strcmp0 (id, priv->id) == 0) { + GST_DEBUG_OBJECT (asset, "ID is already %s", id); + + return; + } + + LOCK_CACHE; + entries = g_hash_table_lookup (_get_type_entries (), + _extractable_type_name (asset->priv->extractable_type)); + + g_return_if_fail (g_hash_table_lookup_extended (entries, priv->id, &orig_id, + (gpointer *) & entry)); + + g_hash_table_steal (entries, priv->id); + g_hash_table_insert (entries, g_strdup (id), entry); + + GST_DEBUG_OBJECT (asset, "Changing id from %s to %s", priv->id, id); + g_free (priv->id); + g_free (orig_id); + priv->id = g_strdup (id); + UNLOCK_CACHE; +} + +static GESAsset * +_ensure_asset_for_wrong_id (const gchar * wrong_id, GType extractable_type, + GError * error) +{ + GESAsset *asset; + + if ((asset = ges_asset_cache_lookup (extractable_type, wrong_id))) + return asset; + + /* It is a dummy GESAsset, we just bruteforce its creation */ + asset = g_object_new (GES_TYPE_ASSET, "id", wrong_id, + "extractable-type", extractable_type, NULL); + + /* transfer ownership to the cache */ + ges_asset_cache_put (asset, NULL); + ges_asset_cache_set_loaded (extractable_type, wrong_id, error); + + return asset; +} + +/********************************** + * * + * API implementation * + * * + **********************************/ + +/** + * ges_asset_get_extractable_type: + * @self: The #GESAsset + * + * Gets the #GESAsset:extractable-type of the asset. + * + * Returns: The extractable type of @self. + */ +GType +ges_asset_get_extractable_type (GESAsset * self) +{ + g_return_val_if_fail (GES_IS_ASSET (self), G_TYPE_INVALID); + + return self->priv->extractable_type; +} + +/** + * ges_asset_request: + * @extractable_type: The #GESAsset:extractable-type of the asset + * @id: (allow-none): The #GESAsset:id of the asset + * @error: (allow-none): An error to be set if the requested asset has + * loaded with an error, or %NULL to ignore + * + * Returns an asset with the given properties. If such an asset already + * exists in the cache (it has been previously created in GES), then a + * reference to the existing asset is returned. Otherwise, a newly created + * asset is returned, and also added to the cache. + * + * If the requested asset has been loaded with an error, then @error is + * set, if given, and %NULL will be returned instead. + * + * Note that the given @id may not be exactly the #GESAsset:id that is + * set on the returned asset. For instance, it may be adjusted into a + * standard format. Or, if a #GESExtractable type does not have its + * extraction parametrised, as is the case by default, then the given @id + * may be ignored entirely and the #GESAsset:id set to some standard, in + * which case a %NULL @id can be given. + * + * Similarly, the given @extractable_type may not be exactly the + * #GESAsset:extractable-type that is set on the returned asset. Instead, + * the actual extractable type may correspond to a subclass of the given + * @extractable_type, depending on the given @id. + * + * Moreover, depending on the given @extractable_type, the returned asset + * may belong to a subclass of #GESAsset. + * + * Finally, if the requested asset has a #GESAsset:proxy, then the proxy + * that is found at the end of the chain of proxies is returned (a proxy's + * proxy will take its place, and so on, unless it has no proxy). + * + * Some asset subclasses only support asynchronous construction of its + * assets, such as #GESUriClip. For such assets this method will fail, and + * you should use ges_asset_request_async() instead. In the case of + * #GESUriClip, you can use ges_uri_clip_asset_request_sync() if you only + * want to wait for the request to finish. + * + * Returns: (transfer full) (allow-none): A reference to the requested + * asset, or %NULL if an error occurred. + */ +GESAsset * +ges_asset_request (GType extractable_type, const gchar * id, GError ** error) +{ + gchar *real_id; + + GError *lerr = NULL; + GESAsset *asset = NULL, *proxy; + gboolean proxied = TRUE; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + g_return_val_if_fail (g_type_is_a (extractable_type, G_TYPE_OBJECT), NULL); + g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE), + NULL); + + real_id = _check_and_update_parameters (&extractable_type, id, &lerr); + if (real_id == NULL) { + /* We create an asset for that wrong ID so we have a reference that the + * user requested it */ + _ensure_asset_for_wrong_id (id, extractable_type, lerr); + real_id = g_strdup (id); + } + if (lerr) + g_error_free (lerr); + + /* asset owned by cache */ + asset = ges_asset_cache_lookup (extractable_type, real_id); + if (asset) { + while (proxied) { + proxied = FALSE; + switch (asset->priv->state) { + case ASSET_INITIALIZED: + break; + case ASSET_INITIALIZING: + asset = NULL; + break; + case ASSET_PROXIED: + proxy = ges_asset_get_proxy (asset); + if (proxy == NULL) { + GST_ERROR ("Proxied against an asset we do not" + " have in cache, something massively screwed"); + asset = NULL; + } else { + proxied = TRUE; + do { + asset = proxy; + } while ((proxy = ges_asset_get_proxy (asset))); + } + break; + case ASSET_NEEDS_RELOAD: + GST_DEBUG_OBJECT (asset, "Asset in cache and needs reload"); + if (!start_loading (asset)) { + GST_ERROR ("Failed to reload the asset for id %s", id); + asset = NULL; + } + break; + case ASSET_INITIALIZED_WITH_ERROR: + GST_WARNING_OBJECT (asset, "Initialized with error, not returning"); + if (asset->priv->error && error) + *error = g_error_copy (asset->priv->error); + asset = NULL; + break; + default: + GST_WARNING ("Case %i not handle, returning", asset->priv->state); + asset = NULL; + break; + } + } + if (asset) + gst_object_ref (asset); + } else { + GObjectClass *klass; + GInitableIface *iface; + GType asset_type = ges_extractable_type_get_asset_type (extractable_type); + + klass = g_type_class_ref (asset_type); + iface = g_type_interface_peek (klass, G_TYPE_INITABLE); + + if (iface->init) { + /* FIXME: allow the error to be set, which GInitable is designed + * for! */ + asset = g_initable_new (asset_type, + NULL, NULL, "id", real_id, "extractable-type", + extractable_type, NULL); + } else { + GST_WARNING ("Tried to create an Asset for type %s but no ->init method", + g_type_name (extractable_type)); + } + g_type_class_unref (klass); + } + + if (real_id) + g_free (real_id); + + GST_DEBUG ("New asset created synchronously: %p", asset); + return asset; +} + +/** + * ges_asset_request_async: + * @extractable_type: The #GESAsset:extractable-type of the asset + * @id: (allow-none): The #GESAsset:id of the asset + * @cancellable: (allow-none): An object to allow cancellation of the + * asset request, or %NULL to ignore + * @callback: A function to call when the initialization is finished + * @user_data: Data to be passed to @callback + * + * Requests an asset with the given properties asynchronously (see + * ges_asset_request()). When the asset has been initialized or fetched + * from the cache, the given callback function will be called. The + * asset can then be retrieved in the callback using the + * ges_asset_request_finish() method on the given #GAsyncResult. + * + * Note that the source object passed to the callback will be the + * #GESAsset corresponding to the request, but it may not have loaded + * correctly and therefore can not be used as is. Instead, + * ges_asset_request_finish() should be used to fetch a usable asset, or + * indicate that an error occurred in the asset's creation. + * + * Note that the callback will be called in the #GMainLoop running under + * the same #GMainContext that ges_init() was called in. So, if you wish + * the callback to be invoked outside the default #GMainContext, you can + * call g_main_context_push_thread_default() in a new thread before + * calling ges_init(). + * + * Example of an asynchronous asset request: + * ``` c + * // The request callback + * static void + * asset_loaded_cb (GESAsset * source, GAsyncResult * res, gpointer user_data) + * { + * GESAsset *asset; + * GError *error = NULL; + * + * asset = ges_asset_request_finish (res, &error); + * if (asset) { + * gst_print ("The file: %s is usable as a GESUriClip", + * ges_asset_get_id (asset)); + * } else { + * gst_print ("The file: %s is *not* usable as a GESUriClip because: %s", + * ges_asset_get_id (source), error->message); + * } + * + * gst_object_unref (asset); + * } + * + * // The request: + * ges_asset_request_async (GES_TYPE_URI_CLIP, some_uri, NULL, + * (GAsyncReadyCallback) asset_loaded_cb, user_data); + * ``` + */ +void +ges_asset_request_async (GType extractable_type, + const gchar * id, GCancellable * cancellable, GAsyncReadyCallback callback, + gpointer user_data) +{ + gchar *real_id; + GESAsset *asset; + GError *error = NULL; + GTask *task = NULL; + + g_return_if_fail (g_type_is_a (extractable_type, G_TYPE_OBJECT)); + g_return_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE)); + g_return_if_fail (callback); + + GST_DEBUG ("Creating asset with extractable type %s and ID=%s", + g_type_name (extractable_type), id); + + real_id = _check_and_update_parameters (&extractable_type, id, &error); + if (error) { + _ensure_asset_for_wrong_id (id, extractable_type, error); + real_id = g_strdup (id); + } + + /* Check if we already have an asset for this ID */ + asset = ges_asset_cache_lookup (extractable_type, real_id); + if (asset) { + task = g_task_new (asset, NULL, callback, user_data); + + /* In the case of proxied asset, we will loop until we find the + * last asset of the chain of proxied asset */ + while (TRUE) { + switch (asset->priv->state) { + case ASSET_INITIALIZED: + GST_DEBUG_OBJECT (asset, "Asset in cache and initialized, " + "using it"); + + /* Takes its own references to @asset */ + g_task_return_boolean (task, TRUE); + + goto done; + case ASSET_INITIALIZING: + GST_DEBUG_OBJECT (asset, "Asset in cache and but not " + "initialized, setting a new callback"); + ges_asset_cache_append_task (extractable_type, real_id, task); + task = NULL; + + goto done; + case ASSET_PROXIED:{ + GESAsset *target = ges_asset_get_proxy (asset); + + if (target == NULL) { + GST_ERROR ("Asset %s proxied against an asset (%s) we do not" + " have in cache, something massively screwed", + asset->priv->id, asset->priv->proxied_asset_id); + + goto done; + } + asset = target; + break; + } + case ASSET_NEEDS_RELOAD: + GST_DEBUG_OBJECT (asset, "Asset in cache and needs reload"); + ges_asset_cache_append_task (extractable_type, real_id, task); + task = NULL; + GES_ASSET_GET_CLASS (asset)->start_loading (asset, &error); + + goto done; + case ASSET_INITIALIZED_WITH_ERROR: + g_task_return_error (task, + error ? g_error_copy (error) : g_error_copy (asset->priv->error)); + + g_clear_error (&error); + + goto done; + default: + GST_WARNING ("Case %i not handle, returning", asset->priv->state); + return; + } + } + } + + g_async_initable_new_async (ges_extractable_type_get_asset_type + (extractable_type), G_PRIORITY_DEFAULT, cancellable, callback, user_data, + "id", real_id, "extractable-type", extractable_type, NULL); +done: + if (task) + gst_object_unref (task); + if (real_id) + g_free (real_id); +} + +/** + * ges_asset_needs_reload + * @extractable_type: The #GESAsset:extractable-type of the asset that + * needs reloading + * @id: (allow-none): The #GESAsset:id of the asset asset that needs + * reloading + * + * Indicate that an existing #GESAsset in the cache should be reloaded + * upon the next request. This can be used when some condition has + * changed, which may require that an existing asset should be updated. + * For example, if an external resource has changed or now become + * available. + * + * Note, the asset is not immediately changed, but will only actually + * reload on the next call to ges_asset_request() or + * ges_asset_request_async(). + * + * Returns: %TRUE if the specified asset exists in the cache and could be + * marked for reloading. + */ +gboolean +ges_asset_needs_reload (GType extractable_type, const gchar * id) +{ + gchar *real_id; + GESAsset *asset; + GError *error = NULL; + + g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE), + FALSE); + + real_id = _check_and_update_parameters (&extractable_type, id, &error); + if (error) { + _ensure_asset_for_wrong_id (id, extractable_type, error); + real_id = g_strdup (id); + } + + asset = ges_asset_cache_lookup (extractable_type, real_id); + + if (real_id) { + g_free (real_id); + } + + if (asset) { + GST_DEBUG_OBJECT (asset, + "Asset with id %s switch state to ASSET_NEEDS_RELOAD", + ges_asset_get_id (asset)); + asset->priv->state = ASSET_NEEDS_RELOAD; + g_clear_error (&asset->priv->error); + return TRUE; + } + + GST_DEBUG ("Asset with id %s not found in cache", id); + return FALSE; +} + +/** + * ges_asset_get_id: + * @self: A #GESAsset + * + * Gets the #GESAsset:id of the asset. + * + * Returns: The ID of @self. + */ +const gchar * +ges_asset_get_id (GESAsset * self) +{ + g_return_val_if_fail (GES_IS_ASSET (self), NULL); + + return self->priv->id; +} + +/** + * ges_asset_extract: + * @self: The #GESAsset to extract an object from + * @error: (allow-none): An error to be set in case something goes wrong, + * or %NULL to ignore + * + * Extracts a new #GESAsset:extractable-type object from the asset. The + * #GESAsset:id of the asset may determine the properties and state of the + * newly created object. + * + * Returns: (transfer floating): A newly created object, or %NULL if an + * error occurred. + */ +GESExtractable * +ges_asset_extract (GESAsset * self, GError ** error) +{ + GESExtractable *extractable; + + g_return_val_if_fail (GES_IS_ASSET (self), NULL); + g_return_val_if_fail (GES_ASSET_GET_CLASS (self)->extract, NULL); + + GST_DEBUG_OBJECT (self, "Extracting asset of type %s", + g_type_name (self->priv->extractable_type)); + extractable = GES_ASSET_GET_CLASS (self)->extract (self, error); + + if (extractable == NULL) + return NULL; + + if (ges_extractable_get_asset (extractable) == NULL) + ges_extractable_set_asset (extractable, self); + + return extractable; +} + +/** + * ges_asset_request_finish: + * @res: The task result to fetch the asset from + * @error: (out) (allow-none) (transfer full): An error to be set in case + * something goes wrong, or %NULL to ignore + * + * Fetches an asset requested by ges_asset_request_async(), which + * finalises the request. + * + * Returns: (transfer full): The requested asset, or %NULL if an error + * occurred. + */ +GESAsset * +ges_asset_request_finish (GAsyncResult * res, GError ** error) +{ + GObject *object; + GObject *source_object; + + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + + source_object = g_async_result_get_source_object (res); + g_assert (source_object != NULL); + + object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), + res, error); + + gst_object_unref (source_object); + + return GES_ASSET (object); +} + +/** + * ges_list_assets: + * @filter: The type of object that can be extracted from the asset + * + * List all the assets in the current cache whose + * #GESAsset:extractable-type are of the given type (including + * subclasses). + * + * Note that, since only a #GESExtractable can be extracted from an asset, + * using `GES_TYPE_EXTRACTABLE` as @filter will return all the assets in + * the current cache. + * + * Returns: (transfer container) (element-type GESAsset): A list of all + * #GESAsset-s currently in the cache whose #GESAsset:extractable-type is + * of the @filter type. + */ +GList * +ges_list_assets (GType filter) +{ + GList *ret = NULL; + GESAsset *asset; + GHashTableIter iter, types_iter; + gpointer key, value, typename, assets; + + g_return_val_if_fail (g_type_is_a (filter, GES_TYPE_EXTRACTABLE), NULL); + + LOCK_CACHE; + g_hash_table_iter_init (&types_iter, _get_type_entries ()); + while (g_hash_table_iter_next (&types_iter, &typename, &assets)) { + if (g_type_is_a (filter, g_type_from_name ((gchar *) typename)) == FALSE) + continue; + + g_hash_table_iter_init (&iter, (GHashTable *) assets); + while (g_hash_table_iter_next (&iter, &key, &value)) { + asset = ((GESAssetCacheEntry *) value)->asset; + + if (g_type_is_a (asset->priv->extractable_type, filter)) + ret = g_list_append (ret, asset); + } + } + UNLOCK_CACHE; + + return ret; +} + +/** + * ges_asset_get_error: + * @self: A #GESAsset + * + * Retrieve the error that was set on the asset when it was loaded. + * + * Returns: (transfer none) (nullable): The error set on @asset, or + * %NULL if no error occurred when @asset was loaded. + * + * Since: 1.8 + */ +GError * +ges_asset_get_error (GESAsset * self) +{ + g_return_val_if_fail (GES_IS_ASSET (self), NULL); + + return self->priv->error; +} diff --git a/ges/ges-asset.h b/ges/ges-asset.h new file mode 100644 index 0000000000..b2a23c23db --- /dev/null +++ b/ges/ges-asset.h @@ -0,0 +1,146 @@ +/* GStreamer Editing Services + * + * Copyright (C) 2012 Thibault Saunier + * Copyright (C) 2012 Volodymyr Rudyi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +G_BEGIN_DECLS +#define GES_TYPE_ASSET ges_asset_get_type() +GES_DECLARE_TYPE(Asset, asset, ASSET); + +/** + * GESAssetLoadingReturn: + * @GES_ASSET_LOADING_ERROR: Indicates that an error occurred + * @GES_ASSET_LOADING_ASYNC: Indicates that the loading is being performed + * asynchronously + * @GES_ASSET_LOADING_OK: Indicates that the loading is complete, without + * error + */ +typedef enum +{ + GES_ASSET_LOADING_ERROR, + GES_ASSET_LOADING_ASYNC, + GES_ASSET_LOADING_OK +} GESAssetLoadingReturn; + +struct _GESAsset +{ + GObject parent; + + /* */ + GESAssetPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESAssetClass: + * @start_loading: A method to be called when an asset is being requested + * asynchronously. This will be after the properties of the asset have + * been set, so it is tasked with (re)loading the 'state' of the asset. + * The return value should indicated whether the loading is complete, is + * carrying on asynchronously, or an error occurred. The default + * implementation will simply return that loading is already complete (the + * asset is already in a usable state after the properties have been set). + * @extract: A method that returns a new object of the asset's + * #GESAsset:extractable-type, or %NULL if an error occurs. The default + * implementation will fetch the properties of the #GESExtractable from + * its get_parameters_from_id() class method and set them on a new + * #GESAsset:extractable-type #GObject, which is returned. + * @request_id_update: A method called by a #GESProject when an asset has + * failed to load. @error is the error given by + * ges_asset_request_finish (). Returns: %TRUE if a new id for @self was + * passed to @proposed_new_id. + * @proxied: Deprecated: 1.18: This vmethod is no longer called. + */ +/* FIXME: add documentation for inform_proxy when it is used properly */ +struct _GESAssetClass +{ + GObjectClass parent; + + GESAssetLoadingReturn (*start_loading) (GESAsset *self, + GError **error); + GESExtractable* (*extract) (GESAsset *self, + GError **error); + /* Let subclasses know that we proxied an asset */ + void (*inform_proxy) (GESAsset *self, + const gchar *proxy_id); + + void (*proxied) (GESAsset *self, + GESAsset *proxy); + + /* Ask subclasses for a new ID for @self when the asset failed loading + * This function returns %FALSE when the ID could be updated or %TRUE + * otherwize */ + gboolean (*request_id_update) (GESAsset *self, + gchar **proposed_new_id, + GError *error) ; + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API +GType ges_asset_get_extractable_type (GESAsset * self); +GES_API +void ges_asset_request_async (GType extractable_type, + const gchar * id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GES_API +GESAsset * ges_asset_request (GType extractable_type, + const gchar * id, + GError **error); +GES_API +const gchar * ges_asset_get_id (GESAsset* self); +GES_API +GESAsset * ges_asset_request_finish (GAsyncResult *res, + GError **error); +GES_API +GError * ges_asset_get_error (GESAsset * self); +GES_API +GESExtractable * ges_asset_extract (GESAsset * self, + GError **error); +GES_API +GList * ges_list_assets (GType filter); + + +GES_API +gboolean ges_asset_set_proxy (GESAsset *asset, GESAsset *proxy); +GES_API +gboolean ges_asset_unproxy (GESAsset *asset, GESAsset * proxy); +GES_API +GList * ges_asset_list_proxies (GESAsset *asset); +GES_API +GESAsset * ges_asset_get_proxy_target(GESAsset *proxy); +GES_API +GESAsset * ges_asset_get_proxy (GESAsset *asset); +GES_API +gboolean ges_asset_needs_reload (GType extractable_type, + const gchar * id); + +G_END_DECLS diff --git a/ges/ges-audio-source.c b/ges/ges-audio-source.c new file mode 100644 index 0000000000..1f1ce75090 --- /dev/null +++ b/ges/ges-audio-source.c @@ -0,0 +1,190 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesaudiosource + * @title: GESAudioSource + * @short_description: Base Class for audio sources + * + * ## Children Properties + * + * You can use the following children properties through the + * #ges_track_element_set_child_property and alike set of methods: + * + * - #gdouble `volume`: volume factor, 1.0=100%. + * - #gboolean `mute`: mute channel. + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges/ges-meta-container.h" +#include "ges-track-element.h" +#include "ges-audio-source.h" +#include "ges-layer.h" + +struct _GESAudioSourcePrivate +{ + GstElement *capsfilter; + GESTrack *current_track; +}; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESAudioSource, ges_audio_source, + GES_TYPE_SOURCE); + +static void +_sync_element_to_layer_property_float (GESTrackElement * trksrc, + GstElement * element, const gchar * meta, const gchar * propname) +{ + GESTimelineElement *parent; + GESLayer *layer; + gfloat value; + + parent = ges_timeline_element_get_parent (GES_TIMELINE_ELEMENT (trksrc)); + if (!parent) { + GST_DEBUG_OBJECT (trksrc, "Not in a clip... doing nothing"); + + return; + } + + layer = ges_clip_get_layer (GES_CLIP (parent)); + + gst_object_unref (parent); + + if (layer != NULL) { + + ges_meta_container_get_float (GES_META_CONTAINER (layer), meta, &value); + g_object_set (element, propname, value, NULL); + GST_DEBUG_OBJECT (trksrc, "Setting %s to %f", propname, value); + + + gst_object_unref (layer); + } else { + + GST_DEBUG_OBJECT (trksrc, "NOT setting the %s", propname); + } +} + +static void +restriction_caps_cb (GESTrack * track, + GParamSpec * arg G_GNUC_UNUSED, GESAudioSource * self) +{ + GstCaps *caps; + + g_object_get (track, "restriction-caps", &caps, NULL); + + GST_DEBUG_OBJECT (self, "Setting capsfilter caps to %" GST_PTR_FORMAT, caps); + g_object_set (self->priv->capsfilter, "caps", caps, NULL); + + if (caps) + gst_caps_unref (caps); +} + +static void +_track_changed_cb (GESAudioSource * self, GParamSpec * arg G_GNUC_UNUSED, + gpointer udata) +{ + GESTrack *track = ges_track_element_get_track (GES_TRACK_ELEMENT (self)); + + if (self->priv->current_track) { + g_signal_handlers_disconnect_by_func (self->priv->current_track, + (GCallback) restriction_caps_cb, self); + } + + self->priv->current_track = track; + if (track) { + restriction_caps_cb (track, NULL, self); + + g_signal_connect (track, "notify::restriction-caps", + G_CALLBACK (restriction_caps_cb), self); + } +} + +static GstElement * +ges_audio_source_create_element (GESTrackElement * trksrc) +{ + GstElement *volume, *vbin; + GstElement *topbin; + GstElement *sub_element; + GPtrArray *elements; + GESSourceClass *source_class = GES_SOURCE_GET_CLASS (trksrc); + const gchar *props[] = { "volume", "mute", NULL }; + GESAudioSource *self = GES_AUDIO_SOURCE (trksrc); + + g_assert (source_class->create_source); + + sub_element = source_class->create_source (GES_SOURCE (trksrc)); + + GST_DEBUG_OBJECT (trksrc, "Creating a bin sub_element ! volume"); + vbin = + gst_parse_bin_from_description + ("audioconvert ! audioresample ! volume name=v ! capsfilter name=audio-track-caps-filter", + TRUE, NULL); + elements = g_ptr_array_new (); + g_ptr_array_add (elements, vbin); + topbin = ges_source_create_topbin (GES_SOURCE (trksrc), "audiosrcbin", + sub_element, elements); + volume = gst_bin_get_by_name (GST_BIN (vbin), "v"); + self->priv->capsfilter = gst_bin_get_by_name (GST_BIN (vbin), + "audio-track-caps-filter"); + + g_signal_connect (self, "notify::track", (GCallback) _track_changed_cb, NULL); + _track_changed_cb (self, NULL, NULL); + + _sync_element_to_layer_property_float (trksrc, volume, GES_META_VOLUME, + "volume"); + ges_track_element_add_children_props (trksrc, volume, NULL, NULL, props); + gst_object_unref (volume); + + return topbin; +} + +static void +ges_audio_source_dispose (GObject * object) +{ + GESAudioSource *self = GES_AUDIO_SOURCE (object); + + if (self->priv->capsfilter) { + gst_object_unref (self->priv->capsfilter); + self->priv->capsfilter = NULL; + } + + G_OBJECT_CLASS (ges_audio_source_parent_class)->dispose (object); +} + +static void +ges_audio_source_class_init (GESAudioSourceClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GESTrackElementClass *track_class = GES_TRACK_ELEMENT_CLASS (klass); + + gobject_class->dispose = ges_audio_source_dispose; + track_class->nleobject_factorytype = "nlesource"; + track_class->create_element = ges_audio_source_create_element; + track_class->ABI.abi.default_track_type = GES_TRACK_TYPE_AUDIO; +} + +static void +ges_audio_source_init (GESAudioSource * self) +{ + self->priv = ges_audio_source_get_instance_private (self); +} diff --git a/ges/ges-audio-source.h b/ges/ges-audio-source.h new file mode 100644 index 0000000000..b8a23aca8e --- /dev/null +++ b/ges/ges-audio-source.h @@ -0,0 +1,74 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GES_TYPE_AUDIO_SOURCE ges_audio_source_get_type() +GES_DECLARE_TYPE(AudioSource, audio_source, AUDIO_SOURCE); + +/** + * GESAudioSource: + * + * Base class for audio sources + */ + +struct _GESAudioSource { + /*< private >*/ + GESSource parent; + + GESAudioSourcePrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESAudioSourceClass: + */ +struct _GESAudioSourceClass { + /*< private >*/ + GESSourceClass parent_class; + + /*< public >*/ + /** + * GESAudioSource::create_element: + * @object: The #GESTrackElement + * + * Returns: (transfer floating): the #GstElement that the underlying nleobject + * controls. + * + * Deprecated: 1.20: Use #GESSourceClass::create_element instead. + */ + GstElement* (*create_source) (GESTrackElement * object); + + /*< private >*/ + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +G_END_DECLS diff --git a/ges/ges-audio-test-source.c b/ges/ges-audio-test-source.c new file mode 100644 index 0000000000..69d2f365bf --- /dev/null +++ b/ges/ges-audio-test-source.c @@ -0,0 +1,217 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesaudiotestsource + * @title: GESAudioTestSource + * @short_description: produce a simple test waveform or silence + * + * Outputs a test audio stream using audiotestsrc. The default property values + * output silence. Useful for testing pipelines, or to fill gaps in an audio + * track. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-track-element.h" +#include "ges-audio-test-source.h" + +#define DEFAULT_VOLUME 1.0 + +struct _GESAudioTestSourcePrivate +{ + gdouble freq; + gdouble volume; +}; + +enum +{ + PROP_0, +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GESAudioTestSource, ges_audio_test_source, + GES_TYPE_AUDIO_SOURCE); + +static void ges_audio_test_source_get_property (GObject * object, guint + property_id, GValue * value, GParamSpec * pspec); + +static void ges_audio_test_source_set_property (GObject * object, guint + property_id, const GValue * value, GParamSpec * pspec); + +static GstElement *ges_audio_test_source_create_source (GESSource * source); + +static void +ges_audio_test_source_class_init (GESAudioTestSourceClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESSourceClass *source_class = GES_SOURCE_CLASS (klass); + + object_class->get_property = ges_audio_test_source_get_property; + object_class->set_property = ges_audio_test_source_set_property; + + source_class->create_source = ges_audio_test_source_create_source; +} + +static void +ges_audio_test_source_init (GESAudioTestSource * self) +{ + self->priv = ges_audio_test_source_get_instance_private (self); + self->priv->freq = 440; + self->priv->volume = DEFAULT_VOLUME; +} + +static void +ges_audio_test_source_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_audio_test_source_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static GstElement * +ges_audio_test_source_create_source (GESSource * source) +{ + GESAudioTestSource *self; + GstElement *ret; + const gchar *props[] = { "volume", "freq", NULL }; + + self = (GESAudioTestSource *) source; + ret = gst_element_factory_make ("audiotestsrc", NULL); + g_object_set (ret, "volume", (gdouble) self->priv->volume, "freq", (gdouble) + self->priv->freq, NULL); + + ges_track_element_add_children_props (GES_TRACK_ELEMENT (self), ret, NULL, + NULL, props); + + return ret; +} + +/** + * ges_audio_test_source_set_freq: + * @self: a #GESAudioTestSource + * @freq: The frequency you want to apply on @self + * + * Lets you set the frequency applied on the track element + */ +void +ges_audio_test_source_set_freq (GESAudioTestSource * self, gdouble freq) +{ + GstElement *element = + ges_track_element_get_element (GES_TRACK_ELEMENT (self)); + + self->priv->freq = freq; + if (element) { + GValue val = { 0 }; + + g_value_init (&val, G_TYPE_DOUBLE); + g_value_set_double (&val, freq); + ges_track_element_set_child_property (GES_TRACK_ELEMENT (self), "freq", + &val); + } +} + +/** + * ges_audio_test_source_set_volume: + * @self: a #GESAudioTestSource + * @volume: The volume you want to apply on @self + * + * Sets the volume of the test audio signal. + */ +void +ges_audio_test_source_set_volume (GESAudioTestSource * self, gdouble volume) +{ + GstElement *element = + ges_track_element_get_element (GES_TRACK_ELEMENT (self)); + + self->priv->volume = volume; + if (element) { + GValue val = { 0 }; + + g_value_init (&val, G_TYPE_DOUBLE); + g_value_set_double (&val, volume); + ges_track_element_set_child_property (GES_TRACK_ELEMENT (self), "volume", + &val); + } +} + +/** + * ges_audio_test_source_get_freq: + * @self: a #GESAudioTestSource + * + * Get the current frequency of @self. + * + * Returns: The current frequency of @self. + */ +double +ges_audio_test_source_get_freq (GESAudioTestSource * self) +{ + GValue val = { 0 }; + + ges_track_element_get_child_property (GES_TRACK_ELEMENT (self), "freq", &val); + return g_value_get_double (&val); +} + +/** + * ges_audio_test_source_get_volume: + * @self: a #GESAudioTestSource + * + * Get the current volume of @self. + * + * Returns: The current volume of @self + */ +double +ges_audio_test_source_get_volume (GESAudioTestSource * self) +{ + GValue val = { 0 }; + + ges_track_element_get_child_property (GES_TRACK_ELEMENT (self), "volume", + &val); + return g_value_get_double (&val); +} + +/* Creates a new #GESAudioTestSource. + * + * Returns: (transfer floating) (nullable): The newly created #GESAudioTestSource. + */ +GESAudioTestSource * +ges_audio_test_source_new (void) +{ + GESAudioTestSource *res; + GESAsset *asset = ges_asset_request (GES_TYPE_AUDIO_TEST_SOURCE, NULL, NULL); + + res = GES_AUDIO_TEST_SOURCE (ges_asset_extract (asset, NULL)); + gst_object_unref (asset); + + return res; +} diff --git a/ges/ges-audio-test-source.h b/ges/ges-audio-test-source.h new file mode 100644 index 0000000000..1105c9b0ba --- /dev/null +++ b/ges/ges-audio-test-source.h @@ -0,0 +1,70 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define GES_TYPE_AUDIO_TEST_SOURCE ges_audio_test_source_get_type() +GES_DECLARE_TYPE(AudioTestSource, audio_test_source, AUDIO_TEST_SOURCE); + +/** + * GESAudioTestSource: + * + * ### Children Properties + * + * {{ libs/GESAudioTestSource-children-props.md }} + */ + +struct _GESAudioTestSource { + GESAudioSource parent; + + /*< private >*/ + GESAudioTestSourcePrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESAudioTestSourceClass { + /*< private >*/ + GESAudioSourceClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API +void ges_audio_test_source_set_freq(GESAudioTestSource *self, + gdouble freq); + +GES_API +void ges_audio_test_source_set_volume(GESAudioTestSource *self, + gdouble volume); + +GES_API +double ges_audio_test_source_get_freq(GESAudioTestSource *self); +GES_API +double ges_audio_test_source_get_volume(GESAudioTestSource *self); +G_END_DECLS diff --git a/ges/ges-audio-track.c b/ges/ges-audio-track.c new file mode 100644 index 0000000000..48f2554052 --- /dev/null +++ b/ges/ges-audio-track.c @@ -0,0 +1,206 @@ +/* GStreamer Editing Services + * Copyright (C) <2013> Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION: gesaudiotrack + * @title: GESAudioTrack + * @short_description: A standard #GESTrack for raw audio + * + * A #GESAudioTrack is a default audio #GESTrack, with a + * #GES_TRACK_TYPE_AUDIO #GESTrack:track-type and "audio/x-raw(ANY)" + * #GESTrack:caps. + * + * By default, an audio track will have its #GESTrack:restriction-caps + * set to "audio/x-raw" with the following properties: + * + * - format: "S32LE" + * - channels: 2 + * - rate: 44100 + * - layout: "interleaved" + * + * These fields are needed for negotiation purposes, but you can change + * their values if you wish. It is advised that you do so using + * ges_track_update_restriction_caps() with new values for the fields you + * wish to change, and any additional fields you may want to add. Unlike + * using ges_track_set_restriction_caps(), this will ensure that these + * default fields will at least have some value set. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-smart-adder.h" +#include "ges-audio-track.h" + +#define DEFAULT_CAPS "audio/x-raw" + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN +#define DEFAULT_RESTRICTION_CAPS "audio/x-raw, format=S32LE, channels=2, "\ + "rate=44100, layout=interleaved" +#else +#define DEFAULT_RESTRICTION_CAPS "audio/x-raw, format=S32BE, channels=2, "\ + "rate=44100, layout=interleaved" +#endif + +struct _GESAudioTrackPrivate +{ + gpointer nothing; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GESAudioTrack, ges_audio_track, GES_TYPE_TRACK); + +/**************************************************** + * Private methods and utils * + ****************************************************/ +static void +_sync_capsfilter_with_track (GESTrack * track, GstElement * capsfilter) +{ + GstCaps *restriction, *caps; + gint rate; + GstStructure *structure; + + g_object_get (track, "restriction-caps", &restriction, NULL); + if (restriction == NULL) + return; + + if (gst_caps_get_size (restriction) == 0) + goto done; + + structure = gst_caps_get_structure (restriction, 0); + if (!gst_structure_get_int (structure, "rate", &rate)) + goto done; + + caps = gst_caps_new_simple ("audio/x-raw", "rate", G_TYPE_INT, rate, NULL); + + g_object_set (capsfilter, "caps", caps, NULL); + gst_caps_unref (caps); + +done: + gst_caps_unref (restriction); +} + +static void +_track_restriction_changed_cb (GESTrack * track, GParamSpec * arg G_GNUC_UNUSED, + GstElement * capsfilter) +{ + _sync_capsfilter_with_track (track, capsfilter); +} + +static void +_weak_notify_cb (GESTrack * track, GstElement * capsfilter) +{ + g_signal_handlers_disconnect_by_func (track, + (GCallback) _track_restriction_changed_cb, capsfilter); +} + +static GstElement * +create_element_for_raw_audio_gap (GESTrack * track) +{ + GstElement *bin; + GstElement *capsfilter; + + bin = gst_parse_bin_from_description + ("audiotestsrc wave=silence name=src ! audioconvert ! audioresample ! audioconvert ! capsfilter name=gapfilter caps=audio/x-raw", + TRUE, NULL); + + capsfilter = gst_bin_get_by_name (GST_BIN (bin), "gapfilter"); + g_object_weak_ref (G_OBJECT (capsfilter), (GWeakNotify) _weak_notify_cb, + track); + g_signal_connect (track, "notify::restriction-caps", + (GCallback) _track_restriction_changed_cb, capsfilter); + + _sync_capsfilter_with_track (track, capsfilter); + + gst_object_unref (capsfilter); + + return bin; +} + + +/**************************************************** + * GObject vmethods implementations * + ****************************************************/ + +static void +ges_audio_track_init (GESAudioTrack * self) +{ + self->priv = ges_audio_track_get_instance_private (self); +} + +static void +ges_audio_track_finalize (GObject * object) +{ + /* TODO: Add deinitalization code here */ + + G_OBJECT_CLASS (ges_audio_track_parent_class)->finalize (object); +} + +static void +ges_audio_track_class_init (GESAudioTrackClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); +/* GESTrackClass *parent_class = GES_TRACK_CLASS (klass); + */ + + object_class->finalize = ges_audio_track_finalize; + + GES_TRACK_CLASS (klass)->get_mixing_element = ges_smart_adder_new; +} + +/**************************************************** + * API implementation * + ****************************************************/ +/** + * ges_audio_track_new: + * + * Creates a new audio track, with a #GES_TRACK_TYPE_AUDIO + * #GESTrack:track-type, "audio/x-raw(ANY)" #GESTrack:caps, and + * "audio/x-raw" #GESTrack:restriction-caps with the properties: + * + * - format: "S32LE" + * - channels: 2 + * - rate: 44100 + * - layout: "interleaved" + * + * You should use ges_track_update_restriction_caps() if you wish to + * modify these fields, or add additional ones. + * + * Returns: (transfer floating): The newly created audio track. + */ +GESAudioTrack * +ges_audio_track_new (void) +{ + GESAudioTrack *ret; + GstCaps *caps = gst_caps_from_string (DEFAULT_CAPS); + GstCaps *restriction_caps = gst_caps_from_string (DEFAULT_RESTRICTION_CAPS); + + ret = g_object_new (GES_TYPE_AUDIO_TRACK, "caps", caps, + "track-type", GES_TRACK_TYPE_AUDIO, NULL); + + ges_track_set_create_element_for_gap_func (GES_TRACK (ret), + create_element_for_raw_audio_gap); + + ges_track_set_restriction_caps (GES_TRACK (ret), restriction_caps); + + gst_caps_unref (caps); + gst_caps_unref (restriction_caps); + + return ret; +} diff --git a/ges/ges-audio-track.h b/ges/ges-audio-track.h new file mode 100644 index 0000000000..45d8f5d2b5 --- /dev/null +++ b/ges/ges-audio-track.h @@ -0,0 +1,54 @@ +/* GStreamer Editing Services + * Copyright (C) <2013> Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include + +#include "ges-track.h" +#include "ges-types.h" + +G_BEGIN_DECLS + +#define GES_TYPE_AUDIO_TRACK (ges_audio_track_get_type ()) +GES_DECLARE_TYPE(AudioTrack, audio_track, AUDIO_TRACK); + + +struct _GESAudioTrackClass +{ + GESTrackClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESAudioTrack +{ + GESTrack parent_instance; + + /*< private >*/ + GESAudioTrackPrivate *priv; + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API +GESAudioTrack* ges_audio_track_new (void); + +G_END_DECLS \ No newline at end of file diff --git a/ges/ges-audio-transition.c b/ges/ges-audio-transition.c new file mode 100644 index 0000000000..b78eae62cb --- /dev/null +++ b/ges/ges-audio-transition.c @@ -0,0 +1,307 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesaudiotransition + * @title: GESAudioTransition + * @short_description: implements audio crossfade transition + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-track-element.h" +#include "ges-audio-transition.h" + +#include + +struct _GESAudioTransitionPrivate +{ + /* these enable volume interpolation. Unlike video, both inputs are adjusted + * simultaneously */ + GstControlSource *a_control_source; + + GstControlSource *b_control_source; + +}; + +enum +{ + PROP_0, +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GESAudioTransition, ges_audio_transition, + GES_TYPE_TRANSITION); + +#define fast_element_link(a,b) gst_element_link_pads_full((a),"src",(b),"sink",GST_PAD_LINK_CHECK_NOTHING) + +static void +ges_audio_transition_duration_changed (GESTrackElement * self, guint64); + +static GstElement *ges_audio_transition_create_element (GESTrackElement * self); + +static void ges_audio_transition_dispose (GObject * object); + +static void ges_audio_transition_finalize (GObject * object); + +static void ges_audio_transition_get_property (GObject * object, guint + property_id, GValue * value, GParamSpec * pspec); + +static void ges_audio_transition_set_property (GObject * object, guint + property_id, const GValue * value, GParamSpec * pspec); + +static void +duration_changed_cb (GESTrackElement * self, GParamSpec * arg G_GNUC_UNUSED) +{ + ges_audio_transition_duration_changed (self, + ges_timeline_element_get_duration (GES_TIMELINE_ELEMENT (self))); +} + +static void +ges_audio_transition_class_init (GESAudioTransitionClass * klass) +{ + GObjectClass *object_class; + GESTrackElementClass *toclass; + + object_class = G_OBJECT_CLASS (klass); + toclass = GES_TRACK_ELEMENT_CLASS (klass); + + object_class->get_property = ges_audio_transition_get_property; + object_class->set_property = ges_audio_transition_set_property; + object_class->dispose = ges_audio_transition_dispose; + object_class->finalize = ges_audio_transition_finalize; + + toclass->create_element = ges_audio_transition_create_element; + toclass->ABI.abi.default_track_type = GES_TRACK_TYPE_AUDIO; + +} + +static void +ges_audio_transition_init (GESAudioTransition * self) +{ + + self->priv = ges_audio_transition_get_instance_private (self); +} + +static void +ges_audio_transition_dispose (GObject * object) +{ + GESAudioTransition *self; + + self = GES_AUDIO_TRANSITION (object); + + if (self->priv->a_control_source) { + if (self->priv->a_control_source) + gst_object_unref (self->priv->a_control_source); + self->priv->a_control_source = NULL; + } + + if (self->priv->b_control_source) { + if (self->priv->b_control_source) + gst_object_unref (self->priv->b_control_source); + self->priv->b_control_source = NULL; + } + + g_signal_handlers_disconnect_by_func (GES_TRACK_ELEMENT (self), + duration_changed_cb, NULL); + + G_OBJECT_CLASS (ges_audio_transition_parent_class)->dispose (object); +} + +static void +ges_audio_transition_finalize (GObject * object) +{ + G_OBJECT_CLASS (ges_audio_transition_parent_class)->finalize (object); +} + +static void +ges_audio_transition_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_audio_transition_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static GObject * +link_element_to_mixer_with_volume (GstBin * bin, GstElement * element, + GstElement * mixer) +{ + GstElement *volume = gst_element_factory_make ("volume", NULL); + GstElement *resample = gst_element_factory_make ("audioresample", NULL); + + gst_bin_add (bin, volume); + gst_bin_add (bin, resample); + + if (!fast_element_link (element, volume) || + !fast_element_link (volume, resample) || + !gst_element_link_pads_full (resample, "src", mixer, "sink_%u", + GST_PAD_LINK_CHECK_NOTHING)) + GST_ERROR_OBJECT (bin, "Error linking volume to mixer"); + + return G_OBJECT (volume); +} + +static GstElement * +ges_audio_transition_create_element (GESTrackElement * track_element) +{ + GESAudioTransition *self; + GstElement *topbin, *iconva, *iconvb, *oconv; + GObject *atarget, *btarget = NULL; + const gchar *propname = "volume"; + GstElement *mixer = NULL; + GstPad *sinka_target, *sinkb_target, *src_target, *sinka, *sinkb, *src; + guint64 duration; + GstControlSource *acontrol_source, *bcontrol_source; + + self = GES_AUDIO_TRANSITION (track_element); + + GST_LOG ("creating an audio bin"); + + topbin = gst_bin_new ("transition-bin"); + iconva = gst_element_factory_make ("audioconvert", "tr-aconv-a"); + iconvb = gst_element_factory_make ("audioconvert", "tr-aconv-b"); + oconv = gst_element_factory_make ("audioconvert", "tr-aconv-output"); + + gst_bin_add_many (GST_BIN (topbin), iconva, iconvb, oconv, NULL); + + mixer = gst_element_factory_make ("audiomixer", NULL); + gst_bin_add (GST_BIN (topbin), mixer); + + atarget = link_element_to_mixer_with_volume (GST_BIN (topbin), iconva, mixer); + btarget = link_element_to_mixer_with_volume (GST_BIN (topbin), iconvb, mixer); + + g_assert (atarget && btarget); + + fast_element_link (mixer, oconv); + + sinka_target = gst_element_get_static_pad (iconva, "sink"); + sinkb_target = gst_element_get_static_pad (iconvb, "sink"); + src_target = gst_element_get_static_pad (oconv, "src"); + + sinka = gst_ghost_pad_new ("sinka", sinka_target); + sinkb = gst_ghost_pad_new ("sinkb", sinkb_target); + src = gst_ghost_pad_new ("src", src_target); + + gst_element_add_pad (topbin, src); + gst_element_add_pad (topbin, sinka); + gst_element_add_pad (topbin, sinkb); + + /* set up interpolation */ + + gst_object_unref (sinka_target); + gst_object_unref (sinkb_target); + gst_object_unref (src_target); + + acontrol_source = gst_interpolation_control_source_new (); + g_object_set (acontrol_source, "mode", GST_INTERPOLATION_MODE_LINEAR, NULL); + + bcontrol_source = gst_interpolation_control_source_new (); + g_object_set (bcontrol_source, "mode", GST_INTERPOLATION_MODE_LINEAR, NULL); + + self->priv->a_control_source = acontrol_source; + self->priv->b_control_source = bcontrol_source; + + duration = + ges_timeline_element_get_duration (GES_TIMELINE_ELEMENT (track_element)); + ges_audio_transition_duration_changed (track_element, duration); + + g_signal_connect (track_element, "notify::duration", + G_CALLBACK (duration_changed_cb), NULL); + + gst_object_add_control_binding (GST_OBJECT (atarget), + gst_direct_control_binding_new (GST_OBJECT (atarget), propname, + acontrol_source)); + gst_object_add_control_binding (GST_OBJECT (btarget), + gst_direct_control_binding_new (GST_OBJECT (btarget), propname, + bcontrol_source)); + + self->priv->a_control_source = acontrol_source; + self->priv->b_control_source = bcontrol_source; + + return topbin; +} + +static void +ges_audio_transition_duration_changed (GESTrackElement * track_element, + guint64 duration) +{ + GESAudioTransition *self; + GstElement *nleobj = ges_track_element_get_nleobject (track_element); + GstTimedValueControlSource *ta, *tb; + + self = GES_AUDIO_TRANSITION (track_element); + + GST_INFO ("updating controller: nleobj (%p)", nleobj); + + if (G_UNLIKELY ((!self->priv->a_control_source || + !self->priv->b_control_source))) + return; + + GST_INFO ("setting values on controller"); + ta = GST_TIMED_VALUE_CONTROL_SOURCE (self->priv->a_control_source); + tb = GST_TIMED_VALUE_CONTROL_SOURCE (self->priv->b_control_source); + + gst_timed_value_control_source_unset_all (ta); + gst_timed_value_control_source_unset_all (tb); + /* The volume property goes from 0 to 10, so we want to interpolate between + * 0 and 0.1 */ + gst_timed_value_control_source_set (ta, 0, 0.1); + gst_timed_value_control_source_set (ta, duration, 0.0); + + gst_timed_value_control_source_set (tb, 0, 0.0); + gst_timed_value_control_source_set (tb, duration, 0.1); + + GST_INFO ("done updating controller"); +} + +/** + * ges_audio_transition_new: + * + * Creates a new #GESAudioTransition. + * + * Returns: (transfer floating): The newly created #GESAudioTransition. + * + * Deprecated: 1.18: This should never be called by applications as this will + * be created by clips. + */ +GESAudioTransition * +ges_audio_transition_new (void) +{ + GESAudioTransition *res; + GESAsset *asset = ges_asset_request (GES_TYPE_AUDIO_TRANSITION, NULL, NULL); + + res = GES_AUDIO_TRANSITION (ges_asset_extract (asset, NULL)); + gst_object_unref (asset); + + return res; +} diff --git a/ges/ges-audio-transition.h b/ges/ges-audio-transition.h new file mode 100644 index 0000000000..2be0d8b014 --- /dev/null +++ b/ges/ges-audio-transition.h @@ -0,0 +1,57 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define GES_TYPE_AUDIO_TRANSITION ges_audio_transition_get_type() +GES_DECLARE_TYPE(AudioTransition, audio_transition, AUDIO_TRANSITION); + +/** + * GESAudioTransition: + * + */ + +struct _GESAudioTransition { + GESTransition parent; + + /*< private >*/ + GESAudioTransitionPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESAudioTransitionClass { + GESTransitionClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_DEPRECATED +GESAudioTransition* ges_audio_transition_new (void); + +G_END_DECLS diff --git a/ges/ges-audio-uri-source.c b/ges/ges-audio-uri-source.c new file mode 100644 index 0000000000..2d56160d83 --- /dev/null +++ b/ges/ges-audio-uri-source.c @@ -0,0 +1,180 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesaudiourisource + * @title: GESAudioUriSource + * @short_description: outputs a single audio stream from a given file + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-utils.h" +#include "ges-internal.h" +#include "ges-track-element.h" +#include "ges-uri-source.h" +#include "ges-audio-uri-source.h" +#include "ges-uri-asset.h" +#include "ges-extractable.h" + +struct _GESAudioUriSourcePrivate +{ + GESUriSource parent; +}; + +enum +{ + PROP_0, + PROP_URI +}; + +/* GESSource VMethod */ +static GstElement * +ges_audio_uri_source_create_source (GESSource * element) +{ + return ges_uri_source_create_source (GES_AUDIO_URI_SOURCE (element)->priv); +} + +/* Extractable interface implementation */ + +static gchar * +ges_extractable_check_id (GType type, const gchar * id, GError ** error) +{ + return g_strdup (id); +} + +static void +ges_extractable_interface_init (GESExtractableInterface * iface) +{ + iface->asset_type = GES_TYPE_URI_SOURCE_ASSET; + iface->check_id = ges_extractable_check_id; +} + +G_DEFINE_TYPE_WITH_CODE (GESAudioUriSource, ges_audio_uri_source, + GES_TYPE_AUDIO_SOURCE, G_ADD_PRIVATE (GESAudioUriSource) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, + ges_extractable_interface_init)); + + +/* GObject VMethods */ + +static gboolean +_get_natural_framerate (GESTimelineElement * self, gint * framerate_n, + gint * framerate_d) +{ + if (self->parent) + return ges_timeline_element_get_natural_framerate (self->parent, + framerate_n, framerate_d); + + return FALSE; +} + +static void +ges_audio_uri_source_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESAudioUriSource *uriclip = GES_AUDIO_URI_SOURCE (object); + + switch (property_id) { + case PROP_URI: + g_value_set_string (value, uriclip->uri); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_audio_uri_source_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESAudioUriSource *uriclip = GES_AUDIO_URI_SOURCE (object); + + switch (property_id) { + case PROP_URI: + if (uriclip->uri) { + GST_WARNING_OBJECT (object, "Uri already set to %s", uriclip->uri); + return; + } + uriclip->priv->uri = uriclip->uri = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_audio_uri_source_finalize (GObject * object) +{ + GESAudioUriSource *uriclip = GES_AUDIO_URI_SOURCE (object); + + g_free (uriclip->uri); + + G_OBJECT_CLASS (ges_audio_uri_source_parent_class)->finalize (object); +} + +static void +ges_audio_uri_source_class_init (GESAudioUriSourceClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass); + GESSourceClass *src_class = GES_SOURCE_CLASS (klass); + + object_class->get_property = ges_audio_uri_source_get_property; + object_class->set_property = ges_audio_uri_source_set_property; + object_class->finalize = ges_audio_uri_source_finalize; + + /** + * GESAudioUriSource:uri: + * + * The location of the file/resource to use. + */ + g_object_class_install_property (object_class, PROP_URI, + g_param_spec_string ("uri", "URI", "uri of the resource", + NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + element_class->get_natural_framerate = _get_natural_framerate; + + src_class->select_pad = ges_uri_source_select_pad; + src_class->create_source = ges_audio_uri_source_create_source; +} + +static void +ges_audio_uri_source_init (GESAudioUriSource * self) +{ + self->priv = ges_audio_uri_source_get_instance_private (self); + ges_uri_source_init (GES_TRACK_ELEMENT (self), self->priv); +} + +/** + * ges_audio_uri_source_new: + * @uri: the URI the source should control + * + * Creates a new #GESAudioUriSource for the provided @uri. + * + * Returns: (transfer floating) (nullable): The newly created + * #GESAudioUriSource, or %NULL if there was an error. + */ +GESAudioUriSource * +ges_audio_uri_source_new (gchar * uri) +{ + return g_object_new (GES_TYPE_AUDIO_URI_SOURCE, "uri", uri, NULL); +} diff --git a/ges/ges-audio-uri-source.h b/ges/ges-audio-uri-source.h new file mode 100644 index 0000000000..b62aed4474 --- /dev/null +++ b/ges/ges-audio-uri-source.h @@ -0,0 +1,60 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +typedef struct _GESUriSource GESUriSource; +#define GES_TYPE_AUDIO_URI_SOURCE ges_audio_uri_source_get_type() +GES_DECLARE_TYPE(AudioUriSource, audio_uri_source, AUDIO_URI_SOURCE); + +/** + * GESAudioUriSource: + * + * ### Children Properties + * + * {{ libs/GESVideoUriSource-children-props.md }} + */ +struct _GESAudioUriSource { + /*< private >*/ + GESAudioSource parent; + + gchar *uri; + + GESUriSource *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESAudioUriSourceClass { + /*< private >*/ + GESAudioSourceClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +G_END_DECLS diff --git a/ges/ges-auto-transition.c b/ges/ges-auto-transition.c new file mode 100644 index 0000000000..e2e083d758 --- /dev/null +++ b/ges/ges-auto-transition.c @@ -0,0 +1,222 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */ +/* + * gst-editing-services + * Copyright (C) 2013 Thibault Saunier + * + * gst-editing-services 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 3 of the License, or + * (at your option) any later version. + * + * gst-editing-services 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 program. If not, see ."; + */ + +/* This class warps a GESBaseTransitionClip, letting any implementation + * of a GESBaseTransitionClip to be used. + * + * NOTE: This is for internal use exclusively + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-auto-transition.h" +#include "ges-internal.h" +enum +{ + DESTROY_ME, + LAST_SIGNAL +}; + +static guint auto_transition_signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (GESAutoTransition, ges_auto_transition, G_TYPE_OBJECT); + +static void +neighbour_changed_cb (G_GNUC_UNUSED GObject * object, + G_GNUC_UNUSED GParamSpec * arg, GESAutoTransition * self) +{ + gint64 new_duration; + guint32 layer_prio; + GESLayer *layer; + GESTimeline *timeline; + + if (self->frozen) { + GST_LOG_OBJECT (self, "Not updating because frozen"); + return; + } + + if (self->positioning) { + /* this can happen when the transition is moved layers as the layer + * may resync its priorities */ + GST_LOG_OBJECT (self, "Not updating because positioning"); + return; + } + + layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self->next_source); + + if (layer_prio != GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self->previous_source)) { + GST_DEBUG_OBJECT (self, "Destroy changed layer"); + g_signal_emit (self, auto_transition_signals[DESTROY_ME], 0); + return; + } + + new_duration = + (_START (self->previous_source) + + _DURATION (self->previous_source)) - _START (self->next_source); + + if (new_duration <= 0 || new_duration >= _DURATION (self->previous_source) + || new_duration >= _DURATION (self->next_source)) { + + GST_DEBUG_OBJECT (self, "Destroy %" G_GINT64_FORMAT " not a valid duration", + new_duration); + g_signal_emit (self, auto_transition_signals[DESTROY_ME], 0); + return; + } + + timeline = GES_TIMELINE_ELEMENT_TIMELINE (self->transition_clip); + layer = timeline ? ges_timeline_get_layer (timeline, layer_prio) : NULL; + if (!layer) { + GST_DEBUG_OBJECT (self, "Destroy no layer"); + g_signal_emit (self, auto_transition_signals[DESTROY_ME], 0); + return; + } + + self->positioning = TRUE; + GES_TIMELINE_ELEMENT_SET_BEING_EDITED (self->transition_clip); + _set_start0 (GES_TIMELINE_ELEMENT (self->transition_clip), + _START (self->next_source)); + _set_duration0 (GES_TIMELINE_ELEMENT (self->transition_clip), new_duration); + ges_clip_move_to_layer (self->transition_clip, layer); + GES_TIMELINE_ELEMENT_UNSET_BEING_EDITED (self->transition_clip); + self->positioning = FALSE; + + gst_object_unref (layer); +} + +static void +_track_changed_cb (GESTrackElement * track_element, + GParamSpec * arg G_GNUC_UNUSED, GESAutoTransition * self) +{ + if (self->frozen) { + GST_LOG_OBJECT (self, "Not updating because frozen"); + return; + } + + if (ges_track_element_get_track (track_element) == NULL) { + GST_DEBUG_OBJECT (self, "Neighboor %" GST_PTR_FORMAT + " removed from track ... auto destructing", track_element); + + g_signal_emit (self, auto_transition_signals[DESTROY_ME], 0); + } +} + +static void +_connect_to_source (GESAutoTransition * self, GESTrackElement * source) +{ + g_signal_connect (source, "notify::start", + G_CALLBACK (neighbour_changed_cb), self); + g_signal_connect_after (source, "notify::priority", + G_CALLBACK (neighbour_changed_cb), self); + g_signal_connect (source, "notify::duration", + G_CALLBACK (neighbour_changed_cb), self); + + g_signal_connect (source, "notify::track", + G_CALLBACK (_track_changed_cb), self); +} + +static void +_disconnect_from_source (GESAutoTransition * self, GESTrackElement * source) +{ + g_signal_handlers_disconnect_by_func (source, neighbour_changed_cb, self); + g_signal_handlers_disconnect_by_func (source, _track_changed_cb, self); +} + +void +ges_auto_transition_set_source (GESAutoTransition * self, + GESTrackElement * source, GESEdge edge) +{ + _disconnect_from_source (self, self->previous_source); + _connect_to_source (self, source); + + if (edge == GES_EDGE_END) + self->next_source = source; + else + self->previous_source = source; +} + +static void +ges_auto_transition_init (GESAutoTransition * ges_auto_transition) +{ +} + +static void +ges_auto_transition_finalize (GObject * object) +{ + GESAutoTransition *self = GES_AUTO_TRANSITION (object); + + _disconnect_from_source (self, self->previous_source); + _disconnect_from_source (self, self->next_source); + + G_OBJECT_CLASS (ges_auto_transition_parent_class)->finalize (object); +} + +static void +ges_auto_transition_class_init (GESAutoTransitionClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + auto_transition_signals[DESTROY_ME] = + g_signal_new ("destroy-me", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + object_class->finalize = ges_auto_transition_finalize; +} + +GESAutoTransition * +ges_auto_transition_new (GESTrackElement * transition, + GESTrackElement * previous_source, GESTrackElement * next_source) +{ + GESAutoTransition *self = g_object_new (GES_TYPE_AUTO_TRANSITION, NULL); + + self->frozen = FALSE; + self->previous_source = previous_source; + self->next_source = next_source; + self->transition = transition; + + self->transition_clip = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (transition)); + + _connect_to_source (self, previous_source); + _connect_to_source (self, next_source); + + GST_DEBUG_OBJECT (self, "Created transition %" GST_PTR_FORMAT + " between %" GST_PTR_FORMAT "[%" GST_TIME_FORMAT + " - %" GST_TIME_FORMAT "] and: %" GST_PTR_FORMAT + "[%" GST_TIME_FORMAT " - %" GST_TIME_FORMAT "]" + " in layer nb %" G_GUINT32_FORMAT ", start: %" GST_TIME_FORMAT + " duration: %" GST_TIME_FORMAT, transition, previous_source, + GST_TIME_ARGS (_START (previous_source)), + GST_TIME_ARGS (_END (previous_source)), + next_source, + GST_TIME_ARGS (_START (next_source)), + GST_TIME_ARGS (_END (next_source)), + GES_TIMELINE_ELEMENT_LAYER_PRIORITY (next_source), + GST_TIME_ARGS (_START (transition)), + GST_TIME_ARGS (_DURATION (transition))); + + return self; +} + +void +ges_auto_transition_update (GESAutoTransition * self) +{ + GST_INFO ("Updating info %s", + GES_TIMELINE_ELEMENT_NAME (self->transition_clip)); + neighbour_changed_cb (NULL, NULL, self); +} diff --git a/ges/ges-auto-transition.h b/ges/ges-auto-transition.h new file mode 100644 index 0000000000..29f6238884 --- /dev/null +++ b/ges/ges-auto-transition.h @@ -0,0 +1,66 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */ +/* + * gst-editing-services + * Copyright (C) 2013 Thibault Saunier + * + * gst-editing-services 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 3 of the License, or + * (at your option) any later version. + * + * gst-editing-services 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 program. If not, see ."; + */ + +#pragma once + +#include +#include "ges-track-element.h" +#include "ges-clip.h" +#include "ges-layer.h" + +G_BEGIN_DECLS + +#define GES_TYPE_AUTO_TRANSITION (ges_auto_transition_get_type ()) +typedef struct _GESAutoTransitionClass GESAutoTransitionClass; +typedef struct _GESAutoTransition GESAutoTransition; + +GES_DECLARE_TYPE(AutoTransition, auto_transition, AUTO_TRANSITION); + +struct _GESAutoTransitionClass +{ + GObjectClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESAutoTransition +{ + GObject parent_instance; + + /* */ + GESTrackElement *previous_source; + GESTrackElement *next_source; + GESTrackElement *transition; + + GESClip *transition_clip; + gboolean positioning; + + gboolean frozen; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +G_GNUC_INTERNAL void ges_auto_transition_update (GESAutoTransition *self); +G_GNUC_INTERNAL GESAutoTransition * ges_auto_transition_new (GESTrackElement * transition, + GESTrackElement * previous_source, + GESTrackElement * next_source); + +G_END_DECLS diff --git a/ges/ges-base-effect-clip.c b/ges/ges-base-effect-clip.c new file mode 100644 index 0000000000..952abf5a19 --- /dev/null +++ b/ges/ges-base-effect-clip.c @@ -0,0 +1,85 @@ +/* GStreamer Editing Services + * Copyright (C) 2011 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION: gesbaseeffectclip + * @title: GESBaseEffectClip + * @short_description: An effect in a #GESLayer + * + * #GESBaseEffectClip-s are clips whose core elements are + * #GESBaseEffect-s. + * + * ## Effects + * + * #GESBaseEffectClip-s can have **additional** #GESBaseEffect-s added as + * non-core elements. These additional effects are applied to the output + * of the core effects of the clip that they share a #GESTrack with. See + * #GESClip for how to add and move these effects from the clip. + * + * Note that you cannot add time effects to #GESBaseEffectClip, neither + * as core children, nor as additional effects. + */ + +/* FIXME: properly handle the priority of the children. How should we sort + * the priority of effects when two #GESBaseEffectClip's overlap? */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "ges-internal.h" +#include "ges-types.h" + +struct _GESBaseEffectClipPrivate +{ + void *nothing; +}; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESBaseEffectClip, ges_base_effect_clip, + GES_TYPE_OPERATION_CLIP); + +static gboolean +ges_base_effect_clip_add_child (GESContainer * container, + GESTimelineElement * element) +{ + if (GES_IS_TIME_EFFECT (element)) { + GST_WARNING_OBJECT (container, "Cannot add %" GES_FORMAT " as a child " + "because it is a time effect", GES_ARGS (element)); + return FALSE; + } + + return + GES_CONTAINER_CLASS (ges_base_effect_clip_parent_class)->add_child + (container, element); +} + +static void +ges_base_effect_clip_class_init (GESBaseEffectClipClass * klass) +{ + GESContainerClass *container_class = GES_CONTAINER_CLASS (klass); + + GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) = TRUE; + container_class->add_child = ges_base_effect_clip_add_child; +} + +static void +ges_base_effect_clip_init (GESBaseEffectClip * self) +{ + self->priv = ges_base_effect_clip_get_instance_private (self); +} diff --git a/ges/ges-base-effect-clip.h b/ges/ges-base-effect-clip.h new file mode 100644 index 0000000000..867c6d1945 --- /dev/null +++ b/ges/ges-base-effect-clip.h @@ -0,0 +1,56 @@ +/* GStreamer Editing Services + * Copyright (C) 2011 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#define GES_TYPE_BASE_EFFECT_CLIP ges_base_effect_clip_get_type() +GES_DECLARE_TYPE(BaseEffectClip, base_effect_clip, BASE_EFFECT_CLIP); + +/** + * GESBaseEffectClip: + */ +struct _GESBaseEffectClip { + /*< private >*/ + GESOperationClip parent; + + GESBaseEffectClipPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESBaseEffectClipClass: + * + */ + +struct _GESBaseEffectClipClass { + /*< private >*/ + GESOperationClipClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +G_END_DECLS \ No newline at end of file diff --git a/ges/ges-base-effect.c b/ges/ges-base-effect.c new file mode 100644 index 0000000000..42f42e3c6b --- /dev/null +++ b/ges/ges-base-effect.c @@ -0,0 +1,429 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesbaseeffect + * @title: GESBaseEffect + * @short_description: adds an effect to a stream in a GESSourceClip or a + * GESLayer + * + * A #GESBaseEffect is some operation that applies an effect to the data + * it receives. + * + * ## Time Effects + * + * Some operations will change the timing of the stream data they receive + * in some way. In particular, the #GstElement that they wrap could alter + * the times of the segment they receive in a #GST_EVENT_SEGMENT event, + * or the times of a seek they receive in a #GST_EVENT_SEEK event. Such + * operations would be considered time effects since they translate the + * times they receive on their source to different times at their sink, + * and vis versa. This introduces two sets of time coordinates for the + * event: (internal) sink coordinates and (internal) source coordinates, + * where segment times are translated from the sink coordinates to the + * source coordinates, and seek times are translated from the source + * coordinates to the sink coordinates. + * + * If you use such an effect in GES, you will need to inform GES of the + * properties that control the timing with + * ges_base_effect_register_time_property(), and the effect's timing + * behaviour using ges_base_effect_set_time_translation_funcs(). + * + * Note that a time effect should not have its + * #GESTrackElement:has-internal-source set to %TRUE. + * + * In addition, note that GES only *fully* supports time effects whose + * mapping from the source to sink coordinates (those applied to seeks) + * obeys: + * + * + Maps the time `0` to `0`. So initial time-shifting effects are + * excluded. + * + Is monotonically increasing. So reversing effects, and effects that + * jump backwards in the stream are excluded. + * + Can handle a reasonable #GstClockTime, relative to the project. So + * this would exclude a time effect with an extremely large speed-up + * that would cause the converted #GstClockTime seeks to overflow. + * + Is 'continuously reversible'. This essentially means that for every + * time in the sink coordinates, we can, to 'good enough' accuracy, + * calculate the corresponding time in the source coordinates. Moreover, + * this should correspond to how segment times are translated from + * sink to source. + * + Only depends on the registered time properties, rather than the + * state of the #GstElement or the data it receives. This would exclude, + * say, an effect that would speedup if there is more red in the image + * it receives. + * + * Note that a constant-rate-change effect that is not extremely fast or + * slow would satisfy these conditions. For such effects, you may wish to + * use ges_effect_class_register_rate_property(). + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "ges-utils.h" +#include "ges-internal.h" +#include "ges-track-element.h" +#include "ges-base-effect.h" + +typedef struct _TimePropertyData +{ + gchar *property_name; + GObject *child; + GParamSpec *pspec; +} TimePropertyData; + +static void +_time_property_data_free (gpointer data_p) +{ + TimePropertyData *data = data_p; + g_free (data->property_name); + gst_object_unref (data->child); + g_param_spec_unref (data->pspec); + g_free (data); +} + +struct _GESBaseEffectPrivate +{ + GList *time_properties; + GESBaseEffectTimeTranslationFunc source_to_sink; + GESBaseEffectTimeTranslationFunc sink_to_source; + gpointer translation_data; + GDestroyNotify destroy_translation_data; +}; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESBaseEffect, ges_base_effect, + GES_TYPE_OPERATION); + +static gboolean +ges_base_effect_set_child_property_full (GESTimelineElement * element, + GObject * child, GParamSpec * pspec, const GValue * value, GError ** error) +{ + GESClip *parent = GES_IS_CLIP (element->parent) ? + GES_CLIP (element->parent) : NULL; + + if (parent && !ges_clip_can_set_time_property_of_child (parent, + GES_TRACK_ELEMENT (element), child, pspec, value, error)) { + GST_INFO_OBJECT (element, "Cannot set time property '%s::%s' " + "because the parent clip %" GES_FORMAT " would not allow it", + G_OBJECT_TYPE_NAME (child), pspec->name, GES_ARGS (parent)); + return FALSE; + } + + return + GES_TIMELINE_ELEMENT_CLASS + (ges_base_effect_parent_class)->set_child_property_full (element, child, + pspec, value, error); +} + +static void +ges_base_effect_dispose (GObject * object) +{ + GESBaseEffectPrivate *priv = GES_BASE_EFFECT (object)->priv; + + g_list_free_full (priv->time_properties, _time_property_data_free); + priv->time_properties = NULL; + if (priv->destroy_translation_data) + priv->destroy_translation_data (priv->translation_data); + priv->destroy_translation_data = NULL; + priv->source_to_sink = NULL; + priv->sink_to_source = NULL; + + G_OBJECT_CLASS (ges_base_effect_parent_class)->dispose (object); +} + +static void +ges_base_effect_class_init (GESBaseEffectClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass); + + object_class->dispose = ges_base_effect_dispose; + element_class->set_child_property_full = + ges_base_effect_set_child_property_full; +} + +static void +ges_base_effect_init (GESBaseEffect * self) +{ + self->priv = ges_base_effect_get_instance_private (self); +} + +static void +_child_property_removed (GESTimelineElement * element, GObject * child, + GParamSpec * pspec, gpointer user_data) +{ + GList *tmp; + GESBaseEffectPrivate *priv = GES_BASE_EFFECT (element)->priv; + + for (tmp = priv->time_properties; tmp; tmp = tmp->next) { + TimePropertyData *data = tmp->data; + if (data->child == child && data->pspec == pspec) { + priv->time_properties = g_list_remove (priv->time_properties, data); + _time_property_data_free (data); + return; + } + } +} + +/** + * ges_base_effect_register_time_property: + * @effect: A #GESBaseEffect + * @child_property_name: The name of the child property to register as + * a time property + * + * Register a child property of the effect as a property that, when set, + * can change the timing of its input data. The child property should be + * specified as in ges_timeline_element_lookup_child(). + * + * You should also set the corresponding time translation using + * ges_base_effect_set_time_translation_funcs(). + * + * Note that @effect must not be part of a clip, nor can it have + * #GESTrackElement:has-internal-source set to %TRUE. + * + * Returns: %TRUE if the child property was found and newly registered. + * Since: 1.18 + */ +gboolean +ges_base_effect_register_time_property (GESBaseEffect * effect, + const gchar * child_property_name) +{ + GESTimelineElement *element; + GESTrackElement *el; + GParamSpec *pspec; + GObject *child; + GList *tmp; + TimePropertyData *data; + + g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE); + el = GES_TRACK_ELEMENT (effect); + element = GES_TIMELINE_ELEMENT (el); + + g_return_val_if_fail (element->parent == NULL, FALSE); + g_return_val_if_fail (ges_track_element_has_internal_source (el) == FALSE, + FALSE); + + if (!ges_timeline_element_lookup_child (element, child_property_name, + &child, &pspec)) + return FALSE; + + for (tmp = effect->priv->time_properties; tmp; tmp = tmp->next) { + data = tmp->data; + if (data->child == child && data->pspec == pspec) { + GST_WARNING_OBJECT (effect, "Already registered the time effect for %s", + child_property_name); + g_object_unref (child); + g_param_spec_unref (pspec); + return FALSE; + } + } + + ges_track_element_set_has_internal_source_is_forbidden (el); + + data = g_new0 (TimePropertyData, 1); + data->child = child; + data->pspec = pspec; + data->property_name = g_strdup (child_property_name); + + effect->priv->time_properties = + g_list_prepend (effect->priv->time_properties, data); + + g_signal_handlers_disconnect_by_func (effect, _child_property_removed, NULL); + g_signal_connect (effect, "child-property-removed", + G_CALLBACK (_child_property_removed), NULL); + + return TRUE; +} + +/** + * ges_base_effect_set_time_translation_funcs: + * @effect: A #GESBaseEffect + * @source_to_sink_func: (nullable) (scope notified): The function to use + * for querying how a time is translated from the source coordinates to + * the sink coordinates of @effect + * @sink_to_source_func: (nullable) (scope notified): The function to use + * for querying how a time is translated from the sink coordinates to the + * source coordinates of @effect + * @user_data: (closure): Data to pass to both @source_to_sink_func and + * @sink_to_source_func + * @destroy: (destroy user_data) (nullable): Method to call to destroy + * @user_data, or %NULL + * + * Set the time translation query functions for the time effect. If an + * effect is a time effect, it will have two sets of coordinates: one + * at its sink and one at its source. The given functions should be able + * to translate between these two sets of coordinates. More specifically, + * @source_to_sink_func should *emulate* how the corresponding #GstElement + * would translate the #GstSegment @time field, and @sink_to_source_func + * should emulate how the corresponding #GstElement would translate the + * seek query @start and @stop values, as used in gst_element_seek(). As + * such, @sink_to_source_func should act as an approximate reverse of + * @source_to_sink_func. + * + * Note, these functions will be passed a table of time properties, as + * registered in ges_base_effect_register_time_property(), and their + * values. The functions should emulate what the translation *would* be + * *if* the time properties were set to the given values. They should not + * use the currently set values. + * + * Note that @effect must not be part of a clip, nor can it have + * #GESTrackElement:has-internal-source set to %TRUE. + * + * Returns: %TRUE if the translation functions were set. + * Since: 1.18 + */ +gboolean +ges_base_effect_set_time_translation_funcs (GESBaseEffect * effect, + GESBaseEffectTimeTranslationFunc source_to_sink_func, + GESBaseEffectTimeTranslationFunc sink_to_source_func, + gpointer user_data, GDestroyNotify destroy) +{ + GESTimelineElement *element; + GESTrackElement *el; + GESBaseEffectPrivate *priv; + + g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE); + + element = GES_TIMELINE_ELEMENT (effect); + el = GES_TRACK_ELEMENT (element); + + g_return_val_if_fail (element->parent == NULL, FALSE); + g_return_val_if_fail (ges_track_element_has_internal_source (el) == FALSE, + FALSE); + + ges_track_element_set_has_internal_source_is_forbidden (el); + + priv = effect->priv; + if (priv->destroy_translation_data) + priv->destroy_translation_data (priv->translation_data); + + priv->translation_data = user_data; + priv->destroy_translation_data = destroy; + priv->source_to_sink = source_to_sink_func; + priv->sink_to_source = sink_to_source_func; + + return TRUE; +} + +/** + * ges_base_effect_is_time_effect: + * @effect: A #GESBaseEffect + * + * Get whether the effect is considered a time effect or not. An effect + * with registered time properties or set translation functions is + * considered a time effect. + * + * Returns: %TRUE if @effect is considered a time effect. + * Since: 1.18 + */ +gboolean +ges_base_effect_is_time_effect (GESBaseEffect * effect) +{ + GESBaseEffectPrivate *priv; + g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE); + + priv = effect->priv; + if (priv->time_properties || priv->source_to_sink || priv->sink_to_source) + return TRUE; + return FALSE; +} + +gchar * +ges_base_effect_get_time_property_name (GESBaseEffect * effect, + GObject * child, GParamSpec * pspec) +{ + GList *tmp; + for (tmp = effect->priv->time_properties; tmp; tmp = tmp->next) { + TimePropertyData *data = tmp->data; + if (data->pspec == pspec && data->child == child) + return g_strdup (data->property_name); + } + return NULL; +} + +static void +_gvalue_free (gpointer data) +{ + GValue *val = data; + g_value_unset (val); + g_free (val); +} + +GHashTable * +ges_base_effect_get_time_property_values (GESBaseEffect * effect) +{ + GList *tmp; + GHashTable *ret = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, _gvalue_free); + + for (tmp = effect->priv->time_properties; tmp; tmp = tmp->next) { + TimePropertyData *data = tmp->data; + GValue *value = g_new0 (GValue, 1); + + /* FIXME: once we move to GLib 2.60, g_object_get_property() will + * automatically initialize the type */ + g_value_init (value, data->pspec->value_type); + g_object_get_property (data->child, data->pspec->name, value); + + g_hash_table_insert (ret, g_strdup (data->property_name), value); + } + + return ret; +} + +GstClockTime +ges_base_effect_translate_source_to_sink_time (GESBaseEffect * effect, + GstClockTime time, GHashTable * time_property_values) +{ + GESBaseEffectPrivate *priv = effect->priv; + + if (!GST_CLOCK_TIME_IS_VALID (time)) + return GST_CLOCK_TIME_NONE; + + if (priv->source_to_sink) + return priv->source_to_sink (effect, time, time_property_values, + priv->translation_data); + + if (time_property_values && g_hash_table_size (time_property_values)) + GST_ERROR_OBJECT (effect, "The time effect is missing its source to " + "sink translation function"); + return time; +} + +GstClockTime +ges_base_effect_translate_sink_to_source_time (GESBaseEffect * effect, + GstClockTime time, GHashTable * time_property_values) +{ + GESBaseEffectPrivate *priv = effect->priv; + + if (!GST_CLOCK_TIME_IS_VALID (time)) + return GST_CLOCK_TIME_NONE; + + if (priv->sink_to_source) + return effect->priv->sink_to_source (effect, time, time_property_values, + priv->translation_data); + + if (time_property_values && g_hash_table_size (time_property_values)) + GST_ERROR_OBJECT (effect, "The time effect is missing its sink to " + "source translation function"); + return time; +} diff --git a/ges/ges-base-effect.h b/ges/ges-base-effect.h new file mode 100644 index 0000000000..abf3e55861 --- /dev/null +++ b/ges/ges-base-effect.h @@ -0,0 +1,94 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define GES_TYPE_BASE_EFFECT ges_base_effect_get_type() +GES_DECLARE_TYPE(BaseEffect, base_effect, BASE_EFFECT); + +/** + * GESBaseEffect: + */ +struct _GESBaseEffect +{ + /*< private > */ + GESOperation parent; + GESBaseEffectPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESBaseEffectClass: + * @parent_class: parent class + */ + +struct _GESBaseEffectClass +{ + /*< private > */ + GESOperationClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; + +}; + +/** + * GESBaseEffectTimeTranslationFunc: + * @effect: The #GESBaseEffect that is doing the time translation + * @time: The #GstClockTime to translation + * @time_property_values: (element-type gchar* GValue*): A table of child + * property name/value pairs + * @user_data: Data passed to ges_base_effect_set_time_translation_funcs() + * + * A function for querying how an effect would translate a time if it had + * the given child property values set. The keys for @time_properties will + * be the same string that was passed to + * ges_base_effect_register_time_property(), the values will be #GValue* + * values of the corresponding child properties. You should always use the + * values given in @time_properties before using the currently set values. + * + * Returns: The translated time. + * Since: 1.18 + */ +typedef GstClockTime (*GESBaseEffectTimeTranslationFunc) (GESBaseEffect * effect, + GstClockTime time, + GHashTable * time_property_values, + gpointer user_data); + +GES_API gboolean +ges_base_effect_register_time_property (GESBaseEffect * effect, + const gchar * child_property_name); +GES_API gboolean +ges_base_effect_set_time_translation_funcs (GESBaseEffect * effect, + GESBaseEffectTimeTranslationFunc source_to_sink_func, + GESBaseEffectTimeTranslationFunc sink_to_source_func, + gpointer user_data, + GDestroyNotify destroy); +GES_API gboolean +ges_base_effect_is_time_effect (GESBaseEffect * effect); + +G_END_DECLS diff --git a/ges/ges-base-transition-clip.c b/ges/ges-base-transition-clip.c new file mode 100644 index 0000000000..ac3a47cf9a --- /dev/null +++ b/ges/ges-base-transition-clip.c @@ -0,0 +1,51 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION: gesbasetransitionclip + * @title: GESBaseTransitionClip + * @short_description: Base classes for transitions + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "ges-internal.h" + +struct _GESBaseTransitionClipPrivate +{ + /* Dummy variable */ + void *nothing; +}; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESBaseTransitionClip, + ges_base_transition_clip, GES_TYPE_OPERATION_CLIP); + +static void +ges_base_transition_clip_class_init (GESBaseTransitionClipClass * klass) +{ +} + +static void +ges_base_transition_clip_init (GESBaseTransitionClip * self) +{ + self->priv = ges_base_transition_clip_get_instance_private (self); +} diff --git a/ges/ges-base-transition-clip.h b/ges/ges-base-transition-clip.h new file mode 100644 index 0000000000..e4f23adcc2 --- /dev/null +++ b/ges/ges-base-transition-clip.h @@ -0,0 +1,60 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "ges-operation-clip.h" + +#include +#include + +G_BEGIN_DECLS + +#define GES_TYPE_BASE_TRANSITION_CLIP ges_base_transition_clip_get_type() +GES_DECLARE_TYPE(BaseTransitionClip, base_transition_clip, BASE_TRANSITION_CLIP); + +/** + * GESBaseTransitionClip: + */ +struct _GESBaseTransitionClip { + /*< private >*/ + GESOperationClip parent; + + /*< private >*/ + GESBaseTransitionClipPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESBaseTransitionClipClass: + * + */ + +struct _GESBaseTransitionClipClass { + /*< private >*/ + GESOperationClipClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +G_END_DECLS diff --git a/ges/ges-base-xml-formatter.c b/ges/ges-base-xml-formatter.c new file mode 100644 index 0000000000..1a1f4e3dbb --- /dev/null +++ b/ges/ges-base-xml-formatter.c @@ -0,0 +1,1353 @@ +/* Gstreamer Editing Services + * + * Copyright (C) <2012> Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges.h" +#include "ges-internal.h" + +GST_DEBUG_CATEGORY_STATIC (base_xml_formatter); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT base_xml_formatter + +#define parent_class ges_base_xml_formatter_parent_class + +#define _GET_PRIV(o) (((GESBaseXmlFormatter*) o)->priv) + + +static gboolean _loading_done_cb (GESFormatter * self); + +typedef struct PendingGroup +{ + GESGroup *group; + + GList *pending_children; +} PendingGroup; + +typedef struct LayerEntry +{ + GESLayer *layer; + gboolean auto_trans; +} LayerEntry; + +typedef struct PendingAsset +{ + GESFormatter *formatter; + gchar *metadatas; + GstStructure *properties; + gchar *proxy_id; + GType extractable_type; + gchar *id; +} PendingAsset; + +/* @STATE_CHECK_LOADABLE: Quickly check if XML is valid + * @STATE_ASSETS: start loading all assets asynchronously + * and setup all elements that are synchronously loadable (tracks, and layers basically). + * @STATE_LOADING_CLIPS: adding clips and groups to the timeline + */ +typedef enum +{ + STATE_CHECK_LOADABLE, + STATE_LOADING_ASSETS_AND_SYNC, + STATE_LOADING_CLIPS, +} LoadingState; + +struct _GESBaseXmlFormatterPrivate +{ + GMarkupParseContext *parsecontext; + gsize xmlsize; + LoadingState state; + + /* Clip.ID -> Clip */ + GHashTable *containers; + + /* ID -> track */ + GHashTable *tracks; + + /* layer.prio -> LayerEntry */ + GHashTable *layers; + + /* List of asset waited to be created */ + GList *pending_assets; + + GError *asset_error; + + /* current track element */ + GESTrackElement *current_track_element; + + GESClip *current_clip; + GstClockTime current_clip_duration; + + gboolean timeline_auto_transition; + + GList *groups; +}; + +static void new_asset_cb (GESAsset * source, GAsyncResult * res, + PendingAsset * passet); + +static const gchar * +loading_state_name (LoadingState state) +{ + switch (state) { + case STATE_CHECK_LOADABLE: + return "check-loadable"; + case STATE_LOADING_ASSETS_AND_SYNC: + return "loading-assets-and-sync"; + case STATE_LOADING_CLIPS: + return "loading-clips"; + } + + return "??"; +} + + +static void +_free_layer_entry (LayerEntry * entry) +{ + gst_object_unref (entry->layer); + g_slice_free (LayerEntry, entry); +} + +static void +_free_pending_group (PendingGroup * pgroup) +{ + if (pgroup->group) + g_object_unref (pgroup->group); + g_list_free_full (pgroup->pending_children, g_free); + g_slice_free (PendingGroup, pgroup); +} + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESBaseXmlFormatter, + ges_base_xml_formatter, GES_TYPE_FORMATTER); +static gint +compare_assets_for_loading (PendingAsset * a, PendingAsset * b) +{ + if (a->extractable_type == GES_TYPE_TIMELINE) + return -1; + + if (b->extractable_type == GES_TYPE_TIMELINE) + return 1; + + if (a->proxy_id) + return -1; + + if (b->proxy_id) + return 1; + + return 0; +} + +static GMarkupParseContext * +_parse (GESBaseXmlFormatter * self, GError ** error, LoadingState state) +{ + GError *err = NULL; + GMarkupParseContext *parsecontext = NULL; + GESBaseXmlFormatterClass *self_class = + GES_BASE_XML_FORMATTER_GET_CLASS (self); + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + + if (!self->xmlcontent || g_strcmp0 (self->xmlcontent, "") == 0) { + err = g_error_new (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED, + "Nothing contained in the project file."); + + goto failed; + } + + parsecontext = g_markup_parse_context_new (&self_class->content_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT, self, NULL); + + priv->state = state; + GST_DEBUG_OBJECT (self, "Running %s pass", loading_state_name (state)); + if (!g_markup_parse_context_parse (parsecontext, self->xmlcontent, + priv->xmlsize, &err)) + goto failed; + + if (!g_markup_parse_context_end_parse (parsecontext, &err)) + goto failed; + + if (priv->pending_assets) { + GList *tmp; + priv->pending_assets = g_list_sort (priv->pending_assets, + (GCompareFunc) compare_assets_for_loading); + + for (tmp = priv->pending_assets; tmp; tmp = tmp->next) { + PendingAsset *passet = tmp->data; + + ges_asset_request_async (passet->extractable_type, passet->id, NULL, + (GAsyncReadyCallback) new_asset_cb, passet); + ges_project_add_loading_asset (GES_FORMATTER (self)->project, + passet->extractable_type, passet->id); + } + } + +done: + return parsecontext; + +failed: + GST_WARNING ("failed to load contents: %s", err->message); + g_propagate_error (error, err); + + if (parsecontext) { + g_markup_parse_context_free (parsecontext); + parsecontext = NULL; + } + + goto done; +} + +static GMarkupParseContext * +_load_and_parse (GESBaseXmlFormatter * self, const gchar * uri, GError ** error, + LoadingState state) +{ + GFile *file = NULL; + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + + GError *err = NULL; + + GST_DEBUG_OBJECT (self, "loading xml from %s, %s", uri, + loading_state_name (state)); + + file = g_file_new_for_uri (uri); + /* TODO Handle GCancellable */ + if (!g_file_query_exists (file, NULL)) { + err = g_error_new (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED, + "Invalid URI: \"%s\"", uri); + goto failed; + } + + g_clear_pointer (&self->xmlcontent, g_free); + if (!g_file_load_contents (file, NULL, &self->xmlcontent, &priv->xmlsize, + NULL, &err)) + goto failed; + g_object_unref (file); + + return _parse (self, error, state); + +failed: + g_object_unref (file); + GST_INFO_OBJECT (self, "failed to load contents from \"%s\"", uri); + g_propagate_error (error, err); + return NULL; +} + +/*********************************************** + * * + * GESFormatter virtual methods implementation * + * * + ***********************************************/ + +static gboolean +_can_load_uri (GESFormatter * dummy_formatter, const gchar * uri, + GError ** error) +{ + GMarkupParseContext *ctx; + GESBaseXmlFormatter *self = GES_BASE_XML_FORMATTER (dummy_formatter); + + ctx = _load_and_parse (self, uri, error, STATE_CHECK_LOADABLE); + if (!ctx) + return FALSE; + + g_markup_parse_context_free (ctx); + return TRUE; +} + +static gboolean +_load_from_uri (GESFormatter * self, GESTimeline * timeline, const gchar * uri, + GError ** error) +{ + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + + GST_INFO_OBJECT (self, "Loading %s in %" GST_PTR_FORMAT, uri, timeline); + ges_timeline_set_auto_transition (timeline, FALSE); + + priv->parsecontext = + _load_and_parse (GES_BASE_XML_FORMATTER (self), uri, error, + STATE_LOADING_ASSETS_AND_SYNC); + + if (!priv->parsecontext) + return FALSE; + + if (priv->pending_assets == NULL) + ges_idle_add ((GSourceFunc) _loading_done_cb, g_object_ref (self), NULL); + + return TRUE; +} + +static gboolean +_save_to_uri (GESFormatter * formatter, GESTimeline * timeline, + const gchar * uri, gboolean overwrite, GError ** error) +{ + GFile *file; + gboolean ret; + GString *str; + GOutputStream *stream; + GError *lerror = NULL; + + g_return_val_if_fail (formatter->project, FALSE); + + file = g_file_new_for_uri (uri); + stream = G_OUTPUT_STREAM (g_file_create (file, G_FILE_CREATE_NONE, NULL, + &lerror)); + if (stream == NULL) { + if (overwrite && lerror->code == G_IO_ERROR_EXISTS) { + g_clear_error (&lerror); + stream = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE, + G_FILE_CREATE_NONE, NULL, &lerror)); + } + + if (stream == NULL) + goto failed_opening_file; + } + + str = GES_BASE_XML_FORMATTER_GET_CLASS (formatter)->save (formatter, + timeline, error); + + if (str == NULL) + goto serialization_failed; + + ret = g_output_stream_write_all (stream, str->str, str->len, NULL, + NULL, &lerror); + ret = g_output_stream_close (stream, NULL, &lerror); + + if (ret == FALSE) + GST_WARNING_OBJECT (formatter, "Could not save %s because: %s", uri, + lerror->message); + + g_string_free (str, TRUE); + gst_object_unref (file); + gst_object_unref (stream); + + if (lerror) + g_propagate_error (error, lerror); + + return ret; + +serialization_failed: + gst_object_unref (file); + + g_output_stream_close (stream, NULL, NULL); + gst_object_unref (stream); + if (lerror) + g_propagate_error (error, lerror); + + return FALSE; + +failed_opening_file: + gst_object_unref (file); + + GST_WARNING_OBJECT (formatter, "Could not open %s because: %s", uri, + lerror->message); + + if (lerror) + g_propagate_error (error, lerror); + + return FALSE; +} + +/*********************************************** + * * + * GOBject virtual methods implementation * + * * + ***********************************************/ + +static void +_dispose (GObject * object) +{ + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (object); + + g_clear_pointer (&priv->containers, g_hash_table_unref); + g_clear_pointer (&priv->tracks, g_hash_table_unref); + g_clear_pointer (&priv->layers, g_hash_table_unref); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +_finalize (GObject * object) +{ + GESBaseXmlFormatter *self = GES_BASE_XML_FORMATTER (object); + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (object); + + if (priv->parsecontext != NULL) + g_markup_parse_context_free (priv->parsecontext); + g_clear_pointer (&self->xmlcontent, g_free); + + g_list_free_full (priv->groups, (GDestroyNotify) _free_pending_group); + priv->groups = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +ges_base_xml_formatter_init (GESBaseXmlFormatter * self) +{ + GESBaseXmlFormatterPrivate *priv; + + self->priv = ges_base_xml_formatter_get_instance_private (self); + + priv = self->priv; + + priv->parsecontext = NULL; + priv->pending_assets = NULL; + + priv->containers = g_hash_table_new_full (g_str_hash, + g_str_equal, g_free, gst_object_unref); + priv->tracks = g_hash_table_new_full (g_str_hash, + g_str_equal, g_free, gst_object_unref); + priv->layers = g_hash_table_new_full (g_direct_hash, + g_direct_equal, NULL, (GDestroyNotify) _free_layer_entry); + priv->current_track_element = NULL; + priv->current_clip = NULL; + priv->current_clip_duration = GST_CLOCK_TIME_NONE; + priv->timeline_auto_transition = FALSE; +} + +static void +ges_base_xml_formatter_class_init (GESBaseXmlFormatterClass * self_class) +{ + GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (self_class); + GObjectClass *object_class = G_OBJECT_CLASS (self_class); + + object_class->dispose = _dispose; + object_class->finalize = _finalize; + + formatter_klass->can_load_uri = _can_load_uri; + formatter_klass->load_from_uri = _load_from_uri; + formatter_klass->save_to_uri = _save_to_uri; + + self_class->save = NULL; + + GST_DEBUG_CATEGORY_INIT (base_xml_formatter, "gesbasexmlformatter", + GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "Base XML Formatter"); +} + +/*********************************************** + * * + * Private methods * + * * + ***********************************************/ + + +static GESTrackElement * +_get_element_by_track_id (GESBaseXmlFormatterPrivate * priv, + const gchar * track_id, GESClip * clip) +{ + GESTrack *track = g_hash_table_lookup (priv->tracks, track_id); + + return ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE); +} + +static void +_set_auto_transition (gpointer prio, LayerEntry * entry, gpointer udata) +{ + ges_layer_set_auto_transition (entry->layer, entry->auto_trans); +} + +static void +_add_all_groups (GESFormatter * self) +{ + GList *tmp; + GESTimelineElement *child; + GESBaseXmlFormatterPrivate *priv = GES_BASE_XML_FORMATTER (self)->priv; + + for (tmp = priv->groups; tmp; tmp = tmp->next) { + GList *lchild; + PendingGroup *pgroup = tmp->data; + + timeline_add_group (self->timeline, pgroup->group); + + for (lchild = ((PendingGroup *) tmp->data)->pending_children; lchild; + lchild = lchild->next) { + child = g_hash_table_lookup (priv->containers, lchild->data); + + GST_DEBUG_OBJECT (tmp->data, "Adding %s child %" GST_PTR_FORMAT " %s", + (const gchar *) lchild->data, child, + GES_TIMELINE_ELEMENT_NAME (child)); + if (!ges_container_add (GES_CONTAINER (pgroup->group), child)) { + GST_ERROR ("%" GES_FORMAT " could not add child %p while" + " reloading, this should never happen", GES_ARGS (pgroup->group), + child); + } + } + pgroup->group = NULL; + } + + g_list_free_full (priv->groups, (GDestroyNotify) _free_pending_group); + priv->groups = NULL; +} + +static void +_loading_done (GESFormatter * self) +{ + GList *assets, *tmp; + GError *error = NULL; + GESBaseXmlFormatterPrivate *priv = GES_BASE_XML_FORMATTER (self)->priv; + + if (priv->parsecontext) + g_markup_parse_context_free (priv->parsecontext); + priv->parsecontext = NULL; + /* Go over all assets and make sure that all proxies we were 'trying' to set are finally + * properly set */ + assets = ges_project_list_assets (self->project, GES_TYPE_EXTRACTABLE); + for (tmp = assets; tmp; tmp = tmp->next) { + ges_asset_finish_proxy (tmp->data); + } + g_list_free_full (assets, g_object_unref); + + if (priv->asset_error) { + error = priv->asset_error; + priv->asset_error = NULL; + } else if (priv->state == STATE_LOADING_ASSETS_AND_SYNC) { + GMarkupParseContext *context = + _parse (GES_BASE_XML_FORMATTER (self), &error, STATE_LOADING_CLIPS); + GST_INFO_OBJECT (self, "Assets cached... now loading the timeline."); + + if (context) + g_markup_parse_context_free (context); + g_assert (priv->pending_assets == NULL); + } + + _add_all_groups (self); + ges_timeline_set_auto_transition (self->timeline, + priv->timeline_auto_transition); + + g_hash_table_foreach (priv->layers, (GHFunc) _set_auto_transition, NULL); + ges_project_set_loaded (self->project, self, error); + g_clear_error (&error); +} + +static gboolean +_loading_done_cb (GESFormatter * self) +{ + _loading_done (self); + gst_object_unref (self); + + return FALSE; +} + +static gboolean +_set_child_property (GQuark field_id, const GValue * value, + GESTimelineElement * tlelement) +{ + GParamSpec *pspec; + GObject *object; + + /* FIXME: error handling? */ + if (!ges_timeline_element_lookup_child (tlelement, + g_quark_to_string (field_id), &object, &pspec)) { +#ifndef GST_DISABLE_GST_DEBUG + gchar *tmp = gst_value_serialize (value); + GST_ERROR_OBJECT (tlelement, "Could not set %s=%s", + g_quark_to_string (field_id), tmp); + g_free (tmp); +#endif + return TRUE; + } + + g_object_set_property (G_OBJECT (object), pspec->name, value); + g_param_spec_unref (pspec); + gst_object_unref (object); + return TRUE; +} + +gboolean +set_property_foreach (GQuark field_id, const GValue * value, GObject * object) +{ + g_object_set_property (object, g_quark_to_string (field_id), value); + return TRUE; +} + +static inline GESClip * +_add_object_to_layer (GESBaseXmlFormatterPrivate * priv, const gchar * id, + GESLayer * layer, GESAsset * asset, GstClockTime start, + GstClockTime inpoint, GstClockTime duration, + GESTrackType track_types, const gchar * metadatas, + GstStructure * properties, GstStructure * children_properties, + GError ** error) +{ + GESClip *clip = ges_layer_add_asset (layer, + asset, start, inpoint, duration, track_types); + + if (clip == NULL) { + g_set_error (error, GES_ERROR, GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE, + "Could not add clip %s [ %" GST_TIME_FORMAT ", ( %" GST_TIME_FORMAT + ") - %" GST_TIME_FORMAT "]", id, GST_TIME_ARGS (start), + GST_TIME_ARGS (inpoint), GST_TIME_ARGS (duration)); + + return NULL; + } + + if (metadatas) + ges_meta_container_add_metas_from_string (GES_META_CONTAINER (clip), + metadatas); + + if (properties) + gst_structure_foreach (properties, + (GstStructureForeachFunc) set_property_foreach, clip); + + if (children_properties) + gst_structure_foreach (children_properties, + (GstStructureForeachFunc) _set_child_property, clip); + + g_hash_table_insert (priv->containers, g_strdup (id), gst_object_ref (clip)); + return clip; +} + +static void +_add_track_element (GESFormatter * self, GESClip * clip, + GESTrackElement * trackelement, const gchar * track_id, + GstStructure * children_properties, GstStructure * properties) +{ + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + GESTrack *track = g_hash_table_lookup (priv->tracks, track_id); + + if (track == NULL) { + GST_WARNING_OBJECT (self, "No track with id %s, can not add trackelement", + track_id); + gst_object_unref (trackelement); + return; + } + + GST_DEBUG_OBJECT (self, "Adding track_element: %" GST_PTR_FORMAT + " To : %" GST_PTR_FORMAT, trackelement, clip); + + if (!ges_container_add (GES_CONTAINER (clip), + GES_TIMELINE_ELEMENT (trackelement))) + GST_ERROR ("%" GES_FORMAT " could not add child %p while" + " reloading, this should never happen", GES_ARGS (clip), trackelement); + gst_structure_foreach (children_properties, + (GstStructureForeachFunc) _set_child_property, trackelement); + + if (properties) { + gboolean has_internal_source; + /* We do not serialize the priority anymore, and we should never have. */ + gst_structure_remove_field (properties, "priority"); + + /* Ensure that has-internal-source is set before inpoint as otherwise + * the inpoint will be ignored */ + if (gst_structure_get_boolean (properties, "has-internal-source", + &has_internal_source) && has_internal_source) + g_object_set (trackelement, "has-internal-source", has_internal_source, + NULL); + gst_structure_foreach (properties, + (GstStructureForeachFunc) set_property_foreach, trackelement); + } +} + +static void +_free_pending_asset (GESBaseXmlFormatterPrivate * priv, PendingAsset * passet) +{ + g_free (passet->metadatas); + g_free (passet->id); + g_free (passet->proxy_id); + if (passet->properties) + gst_structure_free (passet->properties); + + priv->pending_assets = g_list_remove (priv->pending_assets, passet); + g_slice_free (PendingAsset, passet); +} + +static void +new_asset_cb (GESAsset * source, GAsyncResult * res, PendingAsset * passet) +{ + GError *error = NULL; + gchar *possible_id = NULL; + GESFormatter *self = passet->formatter; + const gchar *id = ges_asset_get_id (source); + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + GESAsset *asset = ges_asset_request_finish (res, &error); + + if (error) { + GST_INFO_OBJECT (self, "Error %s creating asset id: %s", error->message, + id); + + /* We set the metas on the Asset to give hints to the user */ + if (passet->metadatas) + ges_meta_container_add_metas_from_string (GES_META_CONTAINER (source), + passet->metadatas); + if (passet->properties) + gst_structure_foreach (passet->properties, + (GstStructureForeachFunc) set_property_foreach, source); + + possible_id = ges_project_try_updating_id (GES_FORMATTER (self)->project, + source, error); + + if (possible_id == NULL) { + GST_WARNING_OBJECT (self, "Abandoning creation of asset %s with ID %s" + "- Error: %s", g_type_name (G_OBJECT_TYPE (source)), id, + error->message); + + _free_pending_asset (priv, passet); + if (!priv->asset_error) + priv->asset_error = g_error_copy (error); + goto done; + } + + /* We got a possible ID replacement for that asset, create it */ + ges_asset_request_async (ges_asset_get_extractable_type (source), + possible_id, NULL, (GAsyncReadyCallback) new_asset_cb, passet); + ges_project_add_loading_asset (GES_FORMATTER (self)->project, + ges_asset_get_extractable_type (source), possible_id); + + goto done; + } + + if (passet->proxy_id) { + /* We set the URI to be used as a proxy, + * this will finally be set as the proxy when we + * are done loading all assets */ + ges_asset_try_proxy (asset, passet->proxy_id); + } + + if (passet->metadatas) + ges_meta_container_add_metas_from_string (GES_META_CONTAINER (asset), + passet->metadatas); + + /* And now add to the project */ + ges_project_add_asset (self->project, asset); + gst_object_unref (self); + + _free_pending_asset (priv, passet); + +done: + if (asset) + gst_object_unref (asset); + if (possible_id) + g_free (possible_id); + + g_clear_error (&error); + + if (priv->pending_assets == NULL) + _loading_done (self); +} + +GstElement * +get_element_for_encoding_profile (GstEncodingProfile * prof, + GstElementFactoryListType type) +{ + GstEncodingProfile *prof_copy; + GstElement *encodebin; + GList *tmp; + GstElement *element = NULL; + + prof_copy = gst_encoding_profile_copy (prof); + + gst_encoding_profile_set_presence (prof_copy, 1); + gst_encoding_profile_set_preset (prof_copy, NULL); + + encodebin = gst_element_factory_make ("encodebin", NULL); + g_object_set (encodebin, "profile", prof_copy, NULL); + + GST_OBJECT_LOCK (encodebin); + for (tmp = GST_BIN (encodebin)->children; tmp; tmp = tmp->next) { + GstElementFactory *factory; + factory = gst_element_get_factory (GST_ELEMENT (tmp->data)); + + if (factory && gst_element_factory_list_is_type (factory, type)) { + element = GST_ELEMENT (tmp->data); + gst_object_ref (element); + break; + } + } + GST_OBJECT_UNLOCK (encodebin); + gst_object_unref (encodebin); + + gst_encoding_profile_unref (prof_copy); + + return element; +} + +static GstEncodingProfile * +_create_profile (GESBaseXmlFormatter * self, + const gchar * type, const gchar * parent, const gchar * name, + const gchar * description, GstCaps * format, const gchar * preset, + GstStructure * preset_properties, const gchar * preset_name, gint id, + guint presence, GstCaps * restriction, guint pass, + gboolean variableframerate, gboolean enabled) +{ + GstEncodingProfile *profile = NULL; + + if (!g_strcmp0 (type, "container")) { + profile = GST_ENCODING_PROFILE (gst_encoding_container_profile_new (name, + description, format, preset)); + gst_encoding_profile_set_preset_name (profile, preset_name); + } else if (!g_strcmp0 (type, "video")) { + GstEncodingVideoProfile *sprof = gst_encoding_video_profile_new (format, + preset, restriction, presence); + + gst_encoding_video_profile_set_variableframerate (sprof, variableframerate); + gst_encoding_video_profile_set_pass (sprof, pass); + + profile = GST_ENCODING_PROFILE (sprof); + } else if (!g_strcmp0 (type, "audio")) { + profile = GST_ENCODING_PROFILE (gst_encoding_audio_profile_new (format, + preset, restriction, presence)); + } else { + GST_ERROR_OBJECT (self, "Unknown profile format '%s'", type); + + return NULL; + } + + if (!g_strcmp0 (type, "video") || !g_strcmp0 (type, "audio")) { + gst_encoding_profile_set_name (profile, name); + gst_encoding_profile_set_enabled (profile, enabled); + gst_encoding_profile_set_description (profile, description); + gst_encoding_profile_set_preset_name (profile, preset_name); + } + if (preset_properties) { + gst_encoding_profile_set_element_properties (profile, + gst_structure_copy (preset_properties)); + } + + return profile; +} + +/*********************************************** + * * + * Public methods * + * * + ***********************************************/ + +void +ges_base_xml_formatter_add_asset (GESBaseXmlFormatter * self, + const gchar * id, GType extractable_type, GstStructure * properties, + const gchar * metadatas, const gchar * proxy_id, GError ** error) +{ + PendingAsset *passet; + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + + if (priv->state != STATE_LOADING_ASSETS_AND_SYNC) { + GST_DEBUG_OBJECT (self, "Not parsing assets in %s state", + loading_state_name (priv->state)); + + return; + } + + passet = g_slice_new0 (PendingAsset); + passet->metadatas = g_strdup (metadatas); + passet->id = g_strdup (id); + passet->extractable_type = extractable_type; + passet->proxy_id = g_strdup (proxy_id); + passet->formatter = gst_object_ref (self); + if (properties) + passet->properties = gst_structure_copy (properties); + priv->pending_assets = g_list_prepend (priv->pending_assets, passet); +} + +void +ges_base_xml_formatter_add_clip (GESBaseXmlFormatter * self, + const gchar * id, const char *asset_id, GType type, GstClockTime start, + GstClockTime inpoint, GstClockTime duration, + guint layer_prio, GESTrackType track_types, GstStructure * properties, + GstStructure * children_properties, + const gchar * metadatas, GError ** error) +{ + GESAsset *asset; + GESClip *nclip; + LayerEntry *entry; + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + + if (priv->state != STATE_LOADING_CLIPS) { + GST_DEBUG_OBJECT (self, "Not adding clip in %s state.", + loading_state_name (priv->state)); + return; + } + + entry = g_hash_table_lookup (priv->layers, GINT_TO_POINTER (layer_prio)); + if (entry == NULL) { + g_set_error (error, GES_ERROR, GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE, + "We got a Clip in a layer" + " that does not exist, something is wrong either in the project file or" + " in %s", g_type_name (G_OBJECT_TYPE (self))); + return; + } + + /* We do not want the properties that are passed to layer-add_asset to be reset */ + if (properties) + gst_structure_remove_fields (properties, "supported-formats", + "inpoint", "start", "duration", NULL); + + asset = ges_asset_request (type, asset_id, NULL); + if (!asset) { + g_set_error (error, GES_ERROR, GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE, + "Clip references asset %s of type %s which was not present in the list of ressource," + " the file seems to be malformed.", asset_id, g_type_name (type)); + return; + } + + nclip = _add_object_to_layer (priv, id, entry->layer, + asset, start, inpoint, duration, track_types, metadatas, properties, + children_properties, error); + + gst_object_unref (asset); + if (!nclip) + return; + + priv->current_clip_duration = duration; + priv->current_clip = nclip; +} + +void +ges_base_xml_formatter_set_timeline_properties (GESBaseXmlFormatter * self, + GESTimeline * timeline, const gchar * properties, const gchar * metadatas) +{ + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + gboolean auto_transition = FALSE; + + if (properties) { + GstStructure *props = gst_structure_from_string (properties, NULL); + + if (props) { + if (gst_structure_get_boolean (props, "auto-transition", + &auto_transition)) + gst_structure_remove_field (props, "auto-transition"); + + gst_structure_foreach (props, + (GstStructureForeachFunc) set_property_foreach, timeline); + gst_structure_free (props); + } + } + + if (metadatas) { + ges_meta_container_add_metas_from_string (GES_META_CONTAINER (timeline), + metadatas); + }; + + priv->timeline_auto_transition = auto_transition; +} + +void +ges_base_xml_formatter_add_layer (GESBaseXmlFormatter * self, + GType extractable_type, guint priority, GstStructure * properties, + const gchar * metadatas, gchar ** deactivated_tracks, GError ** error) +{ + LayerEntry *entry; + GESAsset *asset; + GESLayer *layer; + gboolean auto_transition = FALSE; + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + + if (priv->state != STATE_LOADING_ASSETS_AND_SYNC) { + GST_INFO_OBJECT (self, "Not loading layer in %s state.", + loading_state_name (priv->state)); + return; + } + + if (extractable_type == G_TYPE_NONE) + layer = ges_layer_new (); + else { + asset = ges_asset_request (extractable_type, NULL, error); + if (asset == NULL) { + if (error && *error == NULL) { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Layer type %s could not be created'", + g_type_name (extractable_type)); + } + return; + } + layer = GES_LAYER (ges_asset_extract (asset, error)); + gst_object_unref (asset); + } + + ges_layer_set_priority (layer, priority); + ges_timeline_add_layer (GES_FORMATTER (self)->timeline, layer); + if (properties) { + if (gst_structure_get_boolean (properties, "auto-transition", + &auto_transition)) + gst_structure_remove_field (properties, "auto-transition"); + + gst_structure_foreach (properties, + (GstStructureForeachFunc) set_property_foreach, layer); + } + + if (metadatas) + ges_meta_container_add_metas_from_string (GES_META_CONTAINER (layer), + metadatas); + + if (deactivated_tracks) { + gint i; + GList *tracks = NULL; + + for (i = 0; deactivated_tracks[i] && deactivated_tracks[i][0] != '\0'; i++) { + GESTrack *track = + g_hash_table_lookup (priv->tracks, deactivated_tracks[i]); + + if (!track) { + GST_ERROR_OBJECT (self, + "Unknown deactivated track: %s", deactivated_tracks[i]); + continue; + } + + tracks = g_list_append (tracks, track); + } + + ges_layer_set_active_for_tracks (layer, FALSE, tracks); + g_list_free (tracks); + } + + entry = g_slice_new0 (LayerEntry); + entry->layer = gst_object_ref (layer); + entry->auto_trans = auto_transition; + + g_hash_table_insert (priv->layers, GINT_TO_POINTER (priority), entry); +} + +void +ges_base_xml_formatter_add_track (GESBaseXmlFormatter * self, + GESTrackType track_type, GstCaps * caps, const gchar * id, + GstStructure * properties, const gchar * metadatas, GError ** error) +{ + GESTrack *track; + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + + if (priv->state != STATE_LOADING_ASSETS_AND_SYNC) { + GST_INFO_OBJECT (self, "Not loading track in %s state.", + loading_state_name (priv->state)); + return; + } + + track = ges_track_new (track_type, caps); + ges_timeline_add_track (GES_FORMATTER (self)->timeline, track); + + if (properties) { + gchar *restriction = NULL; + GstCaps *restriction_caps; + + if (gst_structure_get (properties, "restriction-caps", G_TYPE_STRING, + &restriction, NULL) && g_strcmp0 (restriction, "NULL")) { + restriction_caps = gst_caps_from_string (restriction); + if (restriction_caps) { + ges_track_set_restriction_caps (track, restriction_caps); + gst_caps_unref (restriction_caps); + } else { + GST_ERROR_OBJECT (self, "No caps read from the given track property: " + "restriction-caps=\"%s\"", restriction); + } + } + gst_structure_remove_fields (properties, "restriction-caps", "caps", + "message-forward", NULL); + gst_structure_foreach (properties, + (GstStructureForeachFunc) set_property_foreach, track); + g_free (restriction); + } + + g_hash_table_insert (priv->tracks, g_strdup (id), gst_object_ref (track)); + if (metadatas) + ges_meta_container_add_metas_from_string (GES_META_CONTAINER (track), + metadatas); +} + +void +ges_base_xml_formatter_add_control_binding (GESBaseXmlFormatter * self, + const gchar * binding_type, const gchar * source_type, + const gchar * property_name, gint mode, const gchar * track_id, + GSList * timed_values) +{ + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + GESTrackElement *element = NULL; + + if (priv->state != STATE_LOADING_CLIPS) { + GST_DEBUG_OBJECT (self, "Not loading control bindings in %s state.", + loading_state_name (priv->state)); + goto done; + } + + if (track_id[0] != '-' && priv->current_clip) + element = _get_element_by_track_id (priv, track_id, priv->current_clip); + else + element = priv->current_track_element; + + if (element == NULL) { + GST_WARNING ("No current track element to which we can append a binding"); + goto done; + } + + if (!g_strcmp0 (source_type, "interpolation")) { + GstControlSource *source; + + source = gst_interpolation_control_source_new (); + + /* add first before setting values to avoid clamping */ + ges_track_element_set_control_source (element, source, + property_name, binding_type); + + g_object_set (source, "mode", mode, NULL); + if (!gst_timed_value_control_source_set_from_list + (GST_TIMED_VALUE_CONTROL_SOURCE (source), timed_values)) { + GST_ERROR_OBJECT (self, "Could not set timed values on %" GES_FORMAT, + GES_ARGS (source)); + } + + gst_object_unref (source); + } else + GST_WARNING ("This interpolation type is not supported\n"); + +done: + g_slist_free_full (timed_values, g_free); +} + +void +ges_base_xml_formatter_add_source (GESBaseXmlFormatter * self, + const gchar * track_id, GstStructure * children_properties, + GstStructure * properties, const gchar * metadatas) +{ + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + GESTrackElement *element = NULL; + + if (priv->state != STATE_LOADING_CLIPS) { + GST_DEBUG_OBJECT (self, "Not loading source elements in %s state.", + loading_state_name (priv->state)); + return; + } + + if (track_id[0] != '-' && priv->current_clip) + element = _get_element_by_track_id (priv, track_id, priv->current_clip); + else + element = priv->current_track_element; + + if (element == NULL) { + GST_WARNING + ("No current track element to which we can append children properties"); + return; + } + + if (properties) + gst_structure_foreach (properties, + (GstStructureForeachFunc) set_property_foreach, element); + + if (children_properties) + gst_structure_foreach (children_properties, + (GstStructureForeachFunc) _set_child_property, element); + + if (metadatas) + ges_meta_container_add_metas_from_string (GES_META_CONTAINER + (element), metadatas); +} + +void +ges_base_xml_formatter_add_track_element (GESBaseXmlFormatter * self, + GType track_element_type, const gchar * asset_id, const gchar * track_id, + const gchar * timeline_obj_id, GstStructure * children_properties, + GstStructure * properties, const gchar * metadatas, GError ** error) +{ + GESTrackElement *trackelement; + + GError *err = NULL; + GESAsset *asset = NULL; + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + + if (priv->state != STATE_LOADING_CLIPS) { + GST_DEBUG_OBJECT (self, "Not loading track elements in %s state.", + loading_state_name (priv->state)); + return; + } + + if (g_type_is_a (track_element_type, GES_TYPE_TRACK_ELEMENT) == FALSE) { + GST_DEBUG_OBJECT (self, "%s is not a TrackElement, can not create it", + g_type_name (track_element_type)); + goto out; + } + + if (g_type_is_a (track_element_type, GES_TYPE_BASE_EFFECT) == FALSE) { + GST_FIXME_OBJECT (self, "%s currently not supported", + g_type_name (track_element_type)); + goto out; + } + + asset = ges_asset_request (track_element_type, asset_id, &err); + if (asset == NULL) { + GST_DEBUG_OBJECT (self, "Can not create trackelement %s", asset_id); + GST_FIXME_OBJECT (self, "Check if missing plugins etc %s", + err ? err->message : ""); + + goto out; + } + + trackelement = GES_TRACK_ELEMENT (ges_asset_extract (asset, NULL)); + if (trackelement) { + GESClip *clip; + if (metadatas) + ges_meta_container_add_metas_from_string (GES_META_CONTAINER + (trackelement), metadatas); + + clip = g_hash_table_lookup (priv->containers, timeline_obj_id); + _add_track_element (GES_FORMATTER (self), clip, trackelement, track_id, + children_properties, properties); + priv->current_track_element = trackelement; + } + + ges_project_add_asset (GES_FORMATTER (self)->project, asset); + +out: + if (asset) + gst_object_unref (asset); + if (err) + g_error_free (err); + + return; +} + +void +ges_base_xml_formatter_add_encoding_profile (GESBaseXmlFormatter * self, + const gchar * type, const gchar * parent, const gchar * name, + const gchar * description, GstCaps * format, const gchar * preset, + GstStructure * preset_properties, const gchar * preset_name, guint id, + guint presence, GstCaps * restriction, guint pass, + gboolean variableframerate, GstStructure * properties, gboolean enabled, + GError ** error) +{ + const GList *tmp; + GstEncodingProfile *profile; + GstEncodingContainerProfile *parent_profile = NULL; + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + + if (priv->state != STATE_LOADING_ASSETS_AND_SYNC) { + GST_DEBUG_OBJECT (self, "Not loading encoding profiles in %s state.", + loading_state_name (priv->state)); + goto done; + } + + if (parent == NULL) { + profile = + _create_profile (self, type, parent, name, description, format, preset, + preset_properties, preset_name, id, presence, restriction, pass, + variableframerate, enabled); + ges_project_add_encoding_profile (GES_FORMATTER (self)->project, profile); + gst_object_unref (profile); + + goto done; + } + + for (tmp = ges_project_list_encoding_profiles (GES_FORMATTER (self)->project); + tmp; tmp = tmp->next) { + GstEncodingProfile *tmpprofile = GST_ENCODING_PROFILE (tmp->data); + + if (g_strcmp0 (gst_encoding_profile_get_name (tmpprofile), + gst_encoding_profile_get_name (tmpprofile)) == 0) { + + if (!GST_IS_ENCODING_CONTAINER_PROFILE (tmpprofile)) { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Profile '%s' parent %s is not a container...'", name, parent); + goto done; + } + + parent_profile = GST_ENCODING_CONTAINER_PROFILE (tmpprofile); + break; + } + } + + if (parent_profile == NULL) { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Profile '%s' parent %s does not exist'", name, parent); + goto done; + } + + profile = + _create_profile (self, type, parent, name, description, format, preset, + preset_properties, preset_name, id, presence, restriction, pass, + variableframerate, enabled); + + if (profile == NULL) + goto done; + + gst_encoding_container_profile_add_profile (parent_profile, profile); + +done: + if (format) + gst_caps_unref (format); + if (restriction) + gst_caps_unref (restriction); +} + +void +ges_base_xml_formatter_add_group (GESBaseXmlFormatter * self, + const gchar * id, const gchar * properties, const gchar * metadatas) +{ + PendingGroup *pgroup; + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + + if (priv->state != STATE_LOADING_ASSETS_AND_SYNC) { + GST_DEBUG_OBJECT (self, "Not loading groups in %s state.", + loading_state_name (priv->state)); + return; + } + + pgroup = g_slice_new0 (PendingGroup); + pgroup->group = ges_group_new (); + + if (metadatas) + ges_meta_container_add_metas_from_string (GES_META_CONTAINER + (pgroup->group), metadatas); + + g_hash_table_insert (priv->containers, g_strdup (id), + gst_object_ref (pgroup->group)); + priv->groups = g_list_prepend (priv->groups, pgroup); + + return; +} + +void +ges_base_xml_formatter_last_group_add_child (GESBaseXmlFormatter * self, + const gchar * child_id, const gchar * name) +{ + PendingGroup *pgroup; + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + + if (priv->state != STATE_LOADING_CLIPS) { + GST_DEBUG_OBJECT (self, "Not adding children to groups in %s state.", + loading_state_name (priv->state)); + + return; + } + + g_return_if_fail (priv->groups); + + pgroup = priv->groups->data; + + pgroup->pending_children = + g_list_prepend (pgroup->pending_children, g_strdup (child_id)); + + GST_DEBUG_OBJECT (self, "Adding %s to %s", child_id, + GES_TIMELINE_ELEMENT_NAME (((PendingGroup *) priv->groups->data)->group)); +} + +void +ges_base_xml_formatter_end_current_clip (GESBaseXmlFormatter * self) +{ + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + + if (priv->state != STATE_LOADING_CLIPS) { + GST_DEBUG_OBJECT (self, "Not ending clip in %s state.", + loading_state_name (priv->state)); + return; + } + + g_return_if_fail (priv->current_clip); + + if (_DURATION (priv->current_clip) != priv->current_clip_duration) + _set_duration0 (GES_TIMELINE_ELEMENT (priv->current_clip), + priv->current_clip_duration); + + priv->current_clip = NULL; + priv->current_clip_duration = GST_CLOCK_TIME_NONE; +} diff --git a/ges/ges-base-xml-formatter.h b/ges/ges-base-xml-formatter.h new file mode 100644 index 0000000000..fa543e4310 --- /dev/null +++ b/ges/ges-base-xml-formatter.h @@ -0,0 +1,62 @@ +/* Gstreamer Editing Services + * + * Copyright (C) <2012> Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "ges-formatter.h" + +#pragma once + +G_BEGIN_DECLS +typedef struct _GESBaseXmlFormatter GESBaseXmlFormatter; +typedef struct _GESBaseXmlFormatterClass GESBaseXmlFormatterClass; + +#define GES_TYPE_BASE_XML_FORMATTER (ges_base_xml_formatter_get_type ()) +GES_DECLARE_TYPE(BaseXmlFormatter, base_xml_formatter, BASE_XML_FORMATTER); + +/** + * GESBaseXmlFormatter: + */ +struct _GESBaseXmlFormatter +{ + GESFormatter parent; + + /*< public > */ + /* */ + GESBaseXmlFormatterPrivate *priv; + gchar *xmlcontent; + + gpointer _ges_reserved[GES_PADDING - 1]; +}; + +/** + * GESBaseXmlFormatterClass: + */ +struct _GESBaseXmlFormatterClass +{ + GESFormatterClass parent; + + /* Should be overriden by subclasses */ + GMarkupParser content_parser; + + GString * (*save) (GESFormatter *formatter, GESTimeline *timeline, GError **error); + + gpointer _ges_reserved[GES_PADDING]; +}; + +G_END_DECLS diff --git a/ges/ges-clip-asset.c b/ges/ges-clip-asset.c new file mode 100644 index 0000000000..38b8855a1b --- /dev/null +++ b/ges/ges-clip-asset.c @@ -0,0 +1,238 @@ +/* Gstreamer Editing Services + * + * Copyright (C) <2011> Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION: gesclipasset + * @title: GESClipAsset + * @short_description: A GESAsset subclass specialized in GESClip extraction + * + * The #GESUriClipAsset is a special #GESAsset specilized in #GESClip. + * it is mostly used to get information about the #GESTrackType-s the objects extracted + * from it can potentialy create #GESTrackElement for. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-clip-asset.h" +#include "ges-source-clip.h" +#include "ges-internal.h" + +#define GES_CLIP_ASSET_GET_PRIVATE(o)\ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), GES_TYPE_CLIP_ASSET, \ + GESClipAssetPrivate)) + +#define parent_class ges_clip_asset_parent_class + +struct _GESClipAssetPrivate +{ + GESTrackType supportedformats; +}; + + +enum +{ + PROP_0, + PROP_SUPPORTED_FORMATS, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +G_DEFINE_TYPE_WITH_PRIVATE (GESClipAsset, ges_clip_asset, GES_TYPE_ASSET); + +/*********************************************** + * * + * GObject vmetods implemenation * + * * + ***********************************************/ + +static void +_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESClipAssetPrivate *priv = GES_CLIP_ASSET (object)->priv; + switch (property_id) { + case PROP_SUPPORTED_FORMATS: + g_value_set_flags (value, priv->supportedformats); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESClipAssetPrivate *priv = GES_CLIP_ASSET (object)->priv; + + switch (property_id) { + case PROP_SUPPORTED_FORMATS: + priv->supportedformats = g_value_get_flags (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_clip_asset_init (GESClipAsset * self) +{ + self->priv = ges_clip_asset_get_instance_private (self); +} + +static void +_constructed (GObject * object) +{ + GType extractable_type = ges_asset_get_extractable_type (GES_ASSET (object)); + GObjectClass *class = g_type_class_ref (extractable_type); + GParamSpecFlags *pspec; + + pspec = G_PARAM_SPEC_FLAGS (g_object_class_find_property (class, + "supported-formats")); + + GES_CLIP_ASSET (object)->priv->supportedformats = pspec->default_value; + g_type_class_unref (class); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +ges_clip_asset_class_init (GESClipAssetClass * self_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (self_class); + + object_class->constructed = _constructed; + object_class->get_property = _get_property; + object_class->set_property = _set_property; + + /** + * GESClipAsset:supported-formats: + * + * The formats supported by the asset. + */ + properties[PROP_SUPPORTED_FORMATS] = g_param_spec_flags ("supported-formats", + "Supported formats", "Formats supported by the file", + GES_TYPE_TRACK_TYPE, GES_TRACK_TYPE_AUDIO | GES_TRACK_TYPE_VIDEO, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + g_object_class_install_property (object_class, PROP_SUPPORTED_FORMATS, + properties[PROP_SUPPORTED_FORMATS]); +} + +/*********************************************** + * * + * Public methods * + * * + ***********************************************/ +/** + * ges_clip_asset_set_supported_formats: + * @self: a #GESClipAsset + * @supportedformats: The track types supported by the GESClipAsset + * + * Sets track types for which objects extracted from @self can create #GESTrackElement + */ +void +ges_clip_asset_set_supported_formats (GESClipAsset * self, + GESTrackType supportedformats) +{ + g_return_if_fail (GES_IS_CLIP_ASSET (self)); + + self->priv->supportedformats = supportedformats; +} + +/** + * ges_clip_asset_get_supported_formats: + * @self: a #GESClipAsset + * + * Gets track types for which objects extracted from @self can create #GESTrackElement + * + * Returns: The track types on which @self will create TrackElement when added to + * a layer + */ +GESTrackType +ges_clip_asset_get_supported_formats (GESClipAsset * self) +{ + g_return_val_if_fail (GES_IS_CLIP_ASSET (self), GES_TRACK_TYPE_UNKNOWN); + + return self->priv->supportedformats; +} + +/** + * ges_clip_asset_get_natural_framerate: + * @self: The object from which to retrieve the natural framerate + * @framerate_n: The framerate numerator + * @framerate_d: The framerate denominator + * + * Result: %TRUE if @self has a natural framerate %FALSE otherwise + * + * Since: 1.18 + */ +gboolean +ges_clip_asset_get_natural_framerate (GESClipAsset * self, + gint * framerate_n, gint * framerate_d) +{ + GESClipAssetClass *klass; + g_return_val_if_fail (GES_IS_CLIP_ASSET (self), FALSE); + g_return_val_if_fail (framerate_n && framerate_d, FALSE); + + klass = GES_CLIP_ASSET_GET_CLASS (self); + + *framerate_n = 0; + *framerate_d = -1; + + if (klass->get_natural_framerate) + return klass->get_natural_framerate (self, framerate_n, framerate_d); + + return FALSE; +} + +/** + * ges_clip_asset_get_frame_time: + * @self: The object for which to compute timestamp for specifed frame + * @frame_number: The frame number we want the internal time coordinate timestamp of + * + * Converts the given frame number into a timestamp, using the "natural" frame + * rate of the asset. + * + * You can use this to reference a specific frame in a media file and use this + * as, for example, the `in-point` or `max-duration` of a #GESClip. + * + * Returns: The timestamp corresponding to @frame_number in the element source, given + * in internal time coordinates, or #GST_CLOCK_TIME_NONE if the clip asset does not have a + * natural frame rate. + * + * Since: 1.18 + */ +GstClockTime +ges_clip_asset_get_frame_time (GESClipAsset * self, GESFrameNumber frame_number) +{ + gint fps_n, fps_d; + + g_return_val_if_fail (GES_IS_CLIP_ASSET (self), GST_CLOCK_TIME_NONE); + g_return_val_if_fail (GES_FRAME_NUMBER_IS_VALID (frame_number), + GST_CLOCK_TIME_NONE); + + + if (!ges_clip_asset_get_natural_framerate (self, &fps_n, &fps_d)) + return GST_CLOCK_TIME_NONE; + + return gst_util_uint64_scale_ceil (frame_number, fps_d * GST_SECOND, fps_n); +} diff --git a/ges/ges-clip-asset.h b/ges/ges-clip-asset.h new file mode 100644 index 0000000000..bcfce5b133 --- /dev/null +++ b/ges/ges-clip-asset.h @@ -0,0 +1,73 @@ +/* GStreamer Editing Services + * + * Copyright (C) 2012 Thibault Saunier + * Copyright (C) 2012 Volodymyr Rudyi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define GES_TYPE_CLIP_ASSET (ges_clip_asset_get_type ()) +GES_DECLARE_TYPE(ClipAsset, clip_asset, CLIP_ASSET); + +struct _GESClipAsset +{ + GESAsset parent; + + /* */ + GESClipAssetPrivate *priv; + + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESClipAssetClass +{ + GESAssetClass parent; + + /** + * GESClipAssetClass::get_natural_framerate: + * @self: A #GESClipAsset + * @framerate_n: The framerate numerator to retrieve + * @framerate_d: The framerate denominator to retrieve + * + * Returns: %TRUE if @self has a natural framerate @FALSE otherwise. + * + * Since: 1.18 + */ + gboolean (*get_natural_framerate) (GESClipAsset *self, gint *framerate_n, gint *framerate_d); + + gpointer _ges_reserved[GES_PADDING - 1]; +}; + +GES_API +void ges_clip_asset_set_supported_formats (GESClipAsset *self, + GESTrackType supportedformats); +GES_API +GESTrackType ges_clip_asset_get_supported_formats (GESClipAsset *self); +GES_API +gboolean ges_clip_asset_get_natural_framerate (GESClipAsset* self, gint* framerate_n, gint* framerate_d); +GES_API +GstClockTime ges_clip_asset_get_frame_time (GESClipAsset* self, GESFrameNumber frame_number); + +G_END_DECLS diff --git a/ges/ges-clip.c b/ges/ges-clip.c new file mode 100644 index 0000000000..1b297b37a5 --- /dev/null +++ b/ges/ges-clip.c @@ -0,0 +1,4519 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey + * 2009 Nokia Corporation + * 2012 Collabora Ltd. + * Author: Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesclip + * @title: GESClip + * @short_description: Base class for elements that occupy a single + * #GESLayer and maintain equal timings of their children + * + * #GESClip-s are the core objects of a #GESLayer. Each clip may exist in + * a single layer but may control several #GESTrackElement-s that span + * several #GESTrack-s. A clip will ensure that all its children share the + * same #GESTimelineElement:start and #GESTimelineElement:duration in + * their tracks, which will match the #GESTimelineElement:start and + * #GESTimelineElement:duration of the clip itself. Therefore, changing + * the timing of the clip will change the timing of the children, and a + * change in the timing of a child will change the timing of the clip and + * subsequently all its siblings. As such, a clip can be treated as a + * singular object in its layer. + * + * For most uses of a #GESTimeline, it is often sufficient to only + * interact with #GESClip-s directly, which will take care of creating and + * organising the elements of the timeline's tracks. + * + * ## Core Children + * + * In more detail, clips will usually have some *core* #GESTrackElement + * children, which are created by the clip when it is added to a layer in + * a timeline. The type and form of these core children will depend on the + * clip's subclass. You can use ges_track_element_is_core() to determine + * whether a track element is considered such a core track element. Note, + * if a core track element is part of a clip, it will always be treated as + * a core *child* of the clip. You can connect to the + * #GESContainer::child-added signal to be notified of their creation. + * + * When a child is added to a clip, the timeline will select its tracks + * using #GESTimeline::select-tracks-for-object. Note that it may be the + * case that the child will still have no set #GESTrackElement:track + * after this process. For example, if the timeline does not have a track + * of the corresponding #GESTrack:track-type. A clip can safely contain + * such children, which may have their track set later, although they will + * play no functioning role in the timeline in the meantime. + * + * If a clip may create track elements with various + * #GESTrackElement:track-type(s), such as a #GESUriClip, but you only + * want it to create a subset of these types, you should set the + * #GESClip:supported-formats of the clip to the subset of types. This + * should be done *before* adding the clip to a layer. + * + * If a clip will produce several core elements of the same + * #GESTrackElement:track-type, you should connect to the timeline's + * #GESTimeline::select-tracks-for-object signal to coordinate which + * tracks each element should land in. Note, no two core children within a + * clip can share the same #GESTrack, so you should not select the same + * track for two separate core children. Provided you stick to this rule, + * it is still safe to select several tracks for the same core child, the + * core child will be copied into the additional tracks. You can manually + * add the child to more tracks later using ges_clip_add_child_to_track(). + * If you do not wish to use a core child, you can always select no track. + * + * The #GESTimelineElement:in-point of the clip will control the + * #GESTimelineElement:in-point of its core children to be the same + * value if their #GESTrackElement:has-internal-source is set to %TRUE. + * + * The #GESTimelineElement:max-duration of the clip is the minimum + * #GESTimelineElement:max-duration of its core children. If you set its + * value to anything other than its current value, this will also set the + * #GESTimelineElement:max-duration of all its core children to the same + * value if their #GESTrackElement:has-internal-source is set to %TRUE. + * As a special case, whilst a clip does not yet have any core children, + * its #GESTimelineElement:max-duration may be set to indicate what its + * value will be once they are created. + * + * ## Effects + * + * Some subclasses (#GESSourceClip and #GESBaseEffectClip) may also allow + * their objects to have additional non-core #GESBaseEffect-s elements as + * children. These are additional effects that are applied to the output + * data of the core elements. They can be added to the clip using + * ges_clip_add_top_effect(), which will take care of adding the effect to + * the timeline's tracks. The new effect will be placed between the clip's + * core track elements and its other effects. As such, the newly added + * effect will be applied to any source data **before** the other existing + * effects. You can change the ordering of effects using + * ges_clip_set_top_effect_index(). + * + * Tracks are selected for top effects in the same way as core children. + * If you add a top effect to a clip before it is part of a timeline, and + * later add the clip to a timeline, the track selection for the top + * effects will occur just after the track selection for the core + * children. If you add a top effect to a clip that is already part of a + * timeline, the track selection will occur immediately. Since a top + * effect must be applied on top of a core child, if you use + * #GESTimeline::select-tracks-for-object, you should ensure that the + * added effects are destined for a #GESTrack that already contains a core + * child. + * + * In addition, if the core child in the track is not + * #GESTrackElement:active, then neither can any of its effects be + * #GESTrackElement:active. Therefore, if a core child is made in-active, + * all of the additional effects in the same track will also become + * in-active. Similarly, if an effect is set to be active, then the core + * child will also become active, but other effects will be left alone. + * Finally, if an active effect is added to the track of an in-active core + * child, it will become in-active as well. Note, in contrast, setting a + * core child to be active, or an effect to be in-active will *not* change + * the other children in the same track. + * + * ### Time Effects + * + * Some effects also change the timing of their data (see #GESBaseEffect + * for what counts as a time effect). Note that a #GESBaseEffectClip will + * refuse time effects, but a #GESSource will allow them. + * + * When added to a clip, time effects may adjust the timing of other + * children in the same track. Similarly, when changing the order of + * effects, making them (in)-active, setting their time property values + * or removing time effects. These can cause the #GESClip:duration-limit + * to change in value. However, if such an operation would ever cause the + * #GESTimelineElement:duration to shrink such that a clip's #GESSource is + * totally overlapped in the timeline, the operation would be prevented. + * Note that the same can happen when adding non-time effects with a + * finite #GESTimelineElement:max-duration. + * + * Therefore, when working with time effects, you should -- more so than + * usual -- not assume that setting the properties of the clip's children + * will succeed. In particular, you should use + * ges_timeline_element_set_child_property_full() when setting the time + * properties. + * + * If you wish to preserve the *internal* duration of a source in a clip + * during these time effect operations, you can do something like the + * following. + * + * ```c + * void + * do_time_effect_change (GESClip * clip) + * { + * GList *tmp, *children; + * GESTrackElement *source; + * GstClockTime source_outpoint; + * GstClockTime new_end; + * GError *error = NULL; + * + * // choose some active source in a track to preserve the internal + * // duration of + * source = ges_clip_get_track_element (clip, NULL, GES_TYPE_SOURCE); + * + * // note its current internal end time + * source_outpoint = ges_clip_get_internal_time_from_timeline_time ( + * clip, source, GES_TIMELINE_ELEMENT_END (clip), NULL); + * + * // handle invalid out-point + * + * // stop the children's control sources from clamping when their + * // out-point changes with a change in the time effects + * children = ges_container_get_children (GES_CONTAINER (clip), FALSE); + * + * for (tmp = children; tmp; tmp = tmp->next) + * ges_track_element_set_auto_clamp_control_source (tmp->data, FALSE); + * + * // add time effect, or set their children properties, or move them around + * ... + * // user can make sure that if a time effect changes one source, we should + * // also change the time effect for another source. E.g. if + * // "GstVideorate::rate" is set to 2.0, we also set "GstPitch::rate" to + * // 2.0 + * + * // Note the duration of the clip may have already changed if the + * // duration-limit of the clip dropped below its current value + * + * new_end = ges_clip_get_timeline_time_from_internal_time ( + * clip, source, source_outpoint, &error); + * // handle error + * + * if (!ges_timeline_elemnet_edit_full (GES_TIMELINE_ELEMENT (clip), + * -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, new_end, &error)) + * // handle error + * + * for (tmp = children; tmp; tmp = tmp->next) + * ges_track_element_set_auto_clamp_control_source (tmp->data, TRUE); + * + * g_list_free_full (children, gst_object_unref); + * gst_object_unref (source); + * } + * ``` + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-clip.h" +#include "ges.h" +#include "ges-internal.h" + +#include + +static GList *ges_clip_create_track_elements_func (GESClip * clip, + GESTrackType type); +static void _compute_height (GESContainer * container); +static GstClockTime _convert_core_time (GESClip * clip, GstClockTime time, + gboolean to_timeline, gboolean * no_core, GError ** error); + +struct _GESClipPrivate +{ + /*< public > */ + GESLayer *layer; + + /*< private > */ + guint nb_effects; + + GList *copied_track_elements; + GESLayer *copied_layer; + GESTimeline *copied_timeline; + gboolean prevent_resort; + + gboolean updating_max_duration; + gboolean setting_max_duration; + gboolean setting_inpoint; + gboolean setting_priority; + gboolean setting_active; + + gboolean allow_any_track; + + /* The formats supported by this Clip */ + GESTrackType supportedformats; + + GstClockTime duration_limit; + gboolean prevent_duration_limit_update; + gboolean prevent_children_outpoint_update; + + gboolean allow_any_remove; + + gboolean use_effect_priority; + guint32 effect_priority; + GError *add_error; + GError *remove_error; +}; + +enum +{ + PROP_0, + PROP_LAYER, + PROP_SUPPORTED_FORMATS, + PROP_DURATION_LIMIT, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +static void +ges_extractable_interface_init (GESExtractableInterface * iface) +{ + iface->asset_type = GES_TYPE_CLIP_ASSET; +} + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESClip, ges_clip, + GES_TYPE_CONTAINER, G_ADD_PRIVATE (GESClip) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, + ges_extractable_interface_init)); + +/**************************************************** + * * + * Listen to our children * + * and restrict them * + * * + ****************************************************/ + +#define _IS_CORE_CHILD(child) \ + ges_track_element_is_core (GES_TRACK_ELEMENT (child)) + +#define _IS_TOP_EFFECT(child) \ + (!_IS_CORE_CHILD (child) && GES_IS_BASE_EFFECT (child)) + +#define _IS_CORE_INTERNAL_SOURCE_CHILD(child) \ + (_IS_CORE_CHILD (child) \ + && ges_track_element_has_internal_source (GES_TRACK_ELEMENT (child))) + +#define _MIN_CLOCK_TIME(a, b) \ + (GST_CLOCK_TIME_IS_VALID (a) ? \ + (GST_CLOCK_TIME_IS_VALID (b) ? MIN (a, b) : a) : b) \ + +/**************************************************** + * duration-limit * + ****************************************************/ + +typedef struct _DurationLimitData +{ + GESTrackElement *child; + GESTrack *track; + guint32 priority; + GstClockTime max_duration; + GstClockTime inpoint; + gboolean active; + GHashTable *time_property_values; +} DurationLimitData; + +static DurationLimitData * +_duration_limit_data_new (GESTrackElement * child) +{ + GESTrack *track = ges_track_element_get_track (child); + DurationLimitData *data = g_new0 (DurationLimitData, 1); + + data->child = gst_object_ref (child); + data->track = track ? gst_object_ref (track) : NULL; + data->inpoint = _INPOINT (child); + data->max_duration = _MAXDURATION (child); + data->priority = _PRIORITY (child); + data->active = ges_track_element_is_active (child); + + if (GES_IS_TIME_EFFECT (child)) + data->time_property_values = + ges_base_effect_get_time_property_values (GES_BASE_EFFECT (child)); + + return data; +} + +static void +_duration_limit_data_free (gpointer data_p) +{ + DurationLimitData *data = data_p; + gst_clear_object (&data->track); + gst_clear_object (&data->child); + if (data->time_property_values) + g_hash_table_unref (data->time_property_values); + g_free (data); +} + +static GList * +_duration_limit_data_list (GESClip * clip) +{ + GList *tmp, *list = NULL; + + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) + list = g_list_prepend (list, _duration_limit_data_new (tmp->data)); + + return list; +} + +/* transfer-full of data */ +static GList * +_duration_limit_data_list_with_data (GESClip * clip, DurationLimitData * data) +{ + GList *tmp, *list = g_list_append (NULL, data); + + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTrackElement *child = tmp->data; + if (data->child == child) + continue; + list = g_list_prepend (list, _duration_limit_data_new (child)); + } + + return list; +} + +static gint +_cmp_duration_limit_data_by_track_then_priority (gconstpointer a_p, + gconstpointer b_p) +{ + const DurationLimitData *a = a_p, *b = b_p; + if (a->track < b->track) + return -1; + else if (a->track > b->track) + return 1; + /* if higher priority (numerically lower) place later */ + if (a->priority < b->priority) + return 1; + else if (a->priority > b->priority) + return -1; + return 0; +} + +#define _INTERNAL_LIMIT(data) \ + ((data->active && GST_CLOCK_TIME_IS_VALID (data->max_duration)) ? \ + data->max_duration - data->inpoint : GST_CLOCK_TIME_NONE) + +static GstClockTime +_calculate_track_duration_limit (GESClip * self, GList * start, GList * end) +{ + GList *tmp; + DurationLimitData *data = start->data; + GstClockTime track_limit; + + /* convert source-duration to timeline-duration + * E.g. consider the following stack + * + * *=============================* + * | source | + * | in-point = 5 | + * | max-duration = 20 | + * *=============================* + * 5 10 15 20 (internal coordinates) + * + * duration-limit = 15 because max-duration - in-point = 15 + * + * 0 5 10 15 + * *=============================* + * | time-effect | | sink_to_source + * | rate = 0.5 | v / 0.5 + * *=============================* + * 0 10 20 30 + * + * duration-limit = 30 because rate effect can make it last longer + * + * 13 23 33 (internal coordinates) + * *===================* + * |effect-with-source | + * | in-point = 13 | + * | max-duration = 33 | + * *===================* + * 13 23 33 (internal coordinates) + * + * duration-limit = 20 because effect-with-source cannot cover 30 + * + * 0 10 20 + * *===================* + * | time-effect | | sink_to_source + * | rate = 2.0 | v / 2.0 + * *===================* + * 0 5 10 + * + * duration-limit = 10 because rate effect uses up twice as much + * + * -----------------------------------------------timeline + */ + + while (!_IS_CORE_CHILD (data->child)) { + GST_WARNING_OBJECT (self, "Child %" GES_FORMAT " has a lower " + "priority than the core child in the same track. Ignoring.", + GES_ARGS (data->child)); + + start = start->next; + if (start == end) { + GST_ERROR_OBJECT (self, "Track %" GST_PTR_FORMAT " is missing a " + "core child", data->track); + return GST_CLOCK_TIME_NONE; + } + data = start->data; + } + + track_limit = _INTERNAL_LIMIT (data); + + for (tmp = start->next; tmp != end; tmp = tmp->next) { + data = tmp->data; + + if (GES_IS_TIME_EFFECT (data->child)) { + GESBaseEffect *effect = GES_BASE_EFFECT (data->child); + if (data->inpoint) + GST_ERROR_OBJECT (self, "Did not expect an in-point to be set " + "for the time effect %" GES_FORMAT, GES_ARGS (effect)); + if (GST_CLOCK_TIME_IS_VALID (data->max_duration)) + GST_ERROR_OBJECT (self, "Did not expect a max-duration to be set " + "for the time effect %" GES_FORMAT, GES_ARGS (effect)); + + if (data->active) { + /* for the time effect, the minimum time it will receive is 0 + * (it should map 0 -> 0), and the maximum time will be track_limit */ + track_limit = ges_base_effect_translate_sink_to_source_time (effect, + track_limit, data->time_property_values); + } + } else { + GstClockTime el_limit = _INTERNAL_LIMIT (data); + track_limit = _MIN_CLOCK_TIME (track_limit, el_limit); + } + } + + GST_LOG_OBJECT (self, "Track duration-limit for track %" GST_PTR_FORMAT + " is %" GST_TIME_FORMAT, data->track, GST_TIME_ARGS (track_limit)); + + return track_limit; +} + +/* transfer-full of child_data */ +static GstClockTime +_calculate_duration_limit (GESClip * self, GList * child_data) +{ + GstClockTime limit = GST_CLOCK_TIME_NONE; + GList *start, *end; + + child_data = g_list_sort (child_data, + _cmp_duration_limit_data_by_track_then_priority); + + start = child_data; + + while (start) { + /* we have the first element in the track, of the lowest priority, and + * work our way up from here */ + GESTrack *track = ((DurationLimitData *) (start->data))->track; + + end = start; + do { + end = end->next; + } while (end && ((DurationLimitData *) (end->data))->track == track); + + if (track) { + GstClockTime track_limit = + _calculate_track_duration_limit (self, start, end); + limit = _MIN_CLOCK_TIME (limit, track_limit); + } + start = end; + } + GST_LOG_OBJECT (self, "calculated duration-limit for the clip is %" + GST_TIME_FORMAT, GST_TIME_ARGS (limit)); + + g_list_free_full (child_data, _duration_limit_data_free); + + return limit; +} + +static void +_update_children_outpoints (GESClip * self) +{ + GList *tmp; + + if (self->priv->prevent_children_outpoint_update) + return; + + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + ges_track_element_update_outpoint (tmp->data); + } +} + +static void +_update_duration_limit (GESClip * self) +{ + GstClockTime duration_limit; + + if (self->priv->prevent_duration_limit_update) + return; + + duration_limit = _calculate_duration_limit (self, + _duration_limit_data_list (self)); + + if (duration_limit != self->priv->duration_limit) { + GESTimelineElement *element = GES_TIMELINE_ELEMENT (self); + + self->priv->duration_limit = duration_limit; + GST_INFO_OBJECT (self, "duration-limit for the clip is %" + GST_TIME_FORMAT, GST_TIME_ARGS (duration_limit)); + + if (GES_CLOCK_TIME_IS_LESS (duration_limit, element->duration) + && !GES_TIMELINE_ELEMENT_BEING_EDITED (self)) { + gboolean res; + + GST_INFO_OBJECT (self, "Automatically reducing duration to %" + GST_TIME_FORMAT " to match the new duration-limit because " + "the current duration %" GST_TIME_FORMAT " exceeds it", + GST_TIME_ARGS (duration_limit), GST_TIME_ARGS (element->duration)); + + /* trim end with no snapping */ + if (element->timeline) + res = timeline_tree_trim (timeline_get_tree (element->timeline), + element, 0, GST_CLOCK_DIFF (duration_limit, element->duration), + GES_EDGE_END, 0, NULL); + else + res = ges_timeline_element_set_duration (element, duration_limit); + + if (!res) + GST_ERROR_OBJECT (self, "Could not reduce the duration of the " + "clip to below its duration-limit of %" GST_TIME_FORMAT, + GST_TIME_ARGS (duration_limit)); + } + /* notify after the auto-change in duration to allow the user to set + * the duration in response to the change in their callbacks */ + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION_LIMIT]); + } +} + +/* transfer full of child_data */ +static gboolean +_can_update_duration_limit (GESClip * self, GList * child_data, GError ** error) +{ + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (self); + GstClockTime duration = _calculate_duration_limit (self, child_data); + GESTimelineElement *element = GES_TIMELINE_ELEMENT (self); + + if (GES_CLOCK_TIME_IS_LESS (duration, element->duration)) { + /* NOTE: timeline would normally not be NULL at this point */ + if (timeline + && !timeline_tree_can_move_element (timeline_get_tree (timeline), + element, ges_timeline_element_get_layer_priority (element), + element->start, duration, error)) { + return FALSE; + } + } + return TRUE; +} + +/**************************************************** + * priority * + ****************************************************/ + +/* @min_priority: The absolute minimum priority a child of @container should have + * @max_priority: The absolute maximum priority a child of @container should have + */ +static void +_get_priority_range_full (GESContainer * container, guint32 * min_priority, + guint32 * max_priority, guint32 priority_base) +{ + GESLayer *layer = GES_CLIP (container)->priv->layer; + + if (layer) { + *min_priority = priority_base + layer->min_nle_priority; + *max_priority = layer->max_nle_priority; + } else { + *min_priority = priority_base + MIN_NLE_PRIO; + *max_priority = G_MAXUINT32; + } +} + +static void +_get_priority_range (GESContainer * container, guint32 * min_priority, + guint32 * max_priority) +{ + _get_priority_range_full (container, min_priority, max_priority, + _PRIORITY (container)); +} + +gboolean +ges_clip_can_set_priority_of_child (GESClip * clip, GESTrackElement * child, + guint32 priority, GError ** error) +{ + GList *child_data; + DurationLimitData *data; + + if (clip->priv->setting_priority) + return TRUE; + + data = _duration_limit_data_new (child); + data->priority = priority; + + child_data = _duration_limit_data_list_with_data (clip, data); + + if (!_can_update_duration_limit (clip, child_data, error)) { + GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT " from " + "priority %" G_GUINT32_FORMAT " to %" G_GUINT32_FORMAT " because " + "the duration-limit cannot be adjusted", GES_ARGS (child), + _PRIORITY (child), priority); + return FALSE; + } + + return TRUE; +} + +static void +_child_priority_changed (GESContainer * container, GESTimelineElement * child) +{ + /* we do not change the rest of the clip in response to a change in + * the child priority */ + GST_DEBUG_OBJECT (container, "TimelineElement %" GES_FORMAT + " priority changed to %u", GES_ARGS (child), _PRIORITY (child)); + + if (!(GES_CLIP (container))->priv->prevent_resort) { + _ges_container_sort_children (container); + _compute_height (container); + } +} + +/**************************************************** + * in-point * + ****************************************************/ + +GstClockTime +ges_clip_duration_limit_with_new_children_inpoints (GESClip * clip, + GHashTable * child_inpoints) +{ + GHashTableIter iter; + gpointer key, value; + GList *child_data = NULL; + + g_hash_table_iter_init (&iter, child_inpoints); + while (g_hash_table_iter_next (&iter, &key, &value)) { + GESTrackElement *child = key; + GstClockTime *inpoint_p = value; + DurationLimitData *data = _duration_limit_data_new (child); + data->inpoint = *inpoint_p; + child_data = g_list_prepend (child_data, data); + } + + return _calculate_duration_limit (clip, child_data); +} + +static gboolean +_can_set_inpoint_of_core_children (GESClip * clip, GstClockTime inpoint, + GError ** error) +{ + GList *tmp; + GList *child_data = NULL; + + if (GES_TIMELINE_ELEMENT_BEING_EDITED (clip)) + return TRUE; + + /* setting the in-point of a core child will shift the in-point of all + * core children with an internal source */ + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + DurationLimitData *data = + _duration_limit_data_new (GES_TRACK_ELEMENT (child)); + + if (_IS_CORE_INTERNAL_SOURCE_CHILD (child)) { + if (GES_CLOCK_TIME_IS_LESS (child->maxduration, inpoint)) { + GST_INFO_OBJECT (clip, "Cannot set the in-point from %" + GST_TIME_FORMAT " to %" GST_TIME_FORMAT " because it would " + "cause the in-point of its core child %" GES_FORMAT + " to exceed its max-duration", GST_TIME_ARGS (_INPOINT (clip)), + GST_TIME_ARGS (inpoint), GES_ARGS (child)); + g_set_error (error, GES_ERROR, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT, + "Cannot set the in-point of \"%s\" to %" GST_TIME_FORMAT + " because it would exceed the max-duration of %" GST_TIME_FORMAT + " for the child \"%s\"", GES_TIMELINE_ELEMENT_NAME (clip), + GST_TIME_ARGS (inpoint), GST_TIME_ARGS (child->maxduration), + child->name); + + _duration_limit_data_free (data); + g_list_free_full (child_data, _duration_limit_data_free); + return FALSE; + } + + data->inpoint = inpoint; + } + + child_data = g_list_prepend (child_data, data); + } + + if (!_can_update_duration_limit (clip, child_data, error)) { + GST_INFO_OBJECT (clip, "Cannot set the in-point from %" GST_TIME_FORMAT + " to %" GST_TIME_FORMAT " because the duration-limit cannot be " + "adjusted", GST_TIME_ARGS (_INPOINT (clip)), GST_TIME_ARGS (inpoint)); + return FALSE; + } + + return TRUE; +} + +/* Whether @clip can have its in-point set to @inpoint because none of + * its children have a max-duration below it */ +gboolean +ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTrackElement * child, + GstClockTime inpoint, GError ** error) +{ + /* don't bother checking if we are setting the value */ + if (clip->priv->setting_inpoint) + return TRUE; + + if (GES_TIMELINE_ELEMENT_BEING_EDITED (child)) + return TRUE; + + if (!_IS_CORE_CHILD (child)) { + /* no other sibling will move */ + GList *child_data; + DurationLimitData *data = _duration_limit_data_new (child); + data->inpoint = inpoint; + + child_data = _duration_limit_data_list_with_data (clip, data); + + if (!_can_update_duration_limit (clip, child_data, error)) { + GST_INFO_OBJECT (clip, "Cannot set the in-point of non-core child %" + GES_FORMAT " from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT + " because the duration-limit cannot be adjusted", GES_ARGS (child), + GST_TIME_ARGS (_INPOINT (child)), GST_TIME_ARGS (inpoint)); + return FALSE; + } + + return TRUE; + } + + /* setting the in-point of a core child will shift the in-point of all + * core children with an internal source */ + return _can_set_inpoint_of_core_children (clip, inpoint, error); +} + +/* returns TRUE if duration-limit needs to be updated */ +static gboolean +_child_inpoint_changed (GESClip * self, GESTimelineElement * child) +{ + if (self->priv->setting_inpoint) + return FALSE; + + /* if we have a non-core child, then we do not need the in-point of the + * clip to change. Similarly, if the track element is core but has no + * internal content, then this means its in-point has been set (back) to + * 0, which means we do not need to update the in-point of the clip. */ + if (!_IS_CORE_INTERNAL_SOURCE_CHILD (child)) + return TRUE; + + /* if setting the in-point of the clip, this will handle the change in + * the duration-limit */ + + /* If the child->inpoint is the same as our own, set_inpoint will do + * nothing. For example, when we set them in add_child (the notifies for + * this are released after child_added is called because + * ges_container_add freezes them) */ + _set_inpoint0 (GES_TIMELINE_ELEMENT (self), child->inpoint); + return FALSE; +} + +/**************************************************** + * max-duration * + ****************************************************/ + +static void +_update_max_duration (GESContainer * container) +{ + GList *tmp; + GstClockTime min = GST_CLOCK_TIME_NONE; + GESClipPrivate *priv = GES_CLIP (container)->priv; + + if (priv->setting_max_duration) + return; + + for (tmp = container->children; tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + if (_IS_CORE_CHILD (child)) + min = _MIN_CLOCK_TIME (min, child->maxduration); + } + priv->updating_max_duration = TRUE; + ges_timeline_element_set_max_duration (GES_TIMELINE_ELEMENT (container), min); + priv->updating_max_duration = FALSE; +} + +gboolean +ges_clip_can_set_max_duration_of_child (GESClip * clip, GESTrackElement * child, + GstClockTime max_duration, GError ** error) +{ + GList *child_data; + DurationLimitData *data; + + if (clip->priv->setting_max_duration) + return TRUE; + + data = _duration_limit_data_new (child); + data->max_duration = max_duration; + + child_data = _duration_limit_data_list_with_data (clip, data); + + if (!_can_update_duration_limit (clip, child_data, error)) { + GST_INFO_OBJECT (clip, "Cannot set the max-duration of child %" + GES_FORMAT " from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT + " because the duration-limit cannot be adjusted", GES_ARGS (child), + GST_TIME_ARGS (_MAXDURATION (child)), GST_TIME_ARGS (max_duration)); + return FALSE; + } + + return TRUE; +} + +gboolean +ges_clip_can_set_max_duration_of_all_core (GESClip * clip, + GstClockTime max_duration, GError ** error) +{ + GList *tmp; + GList *child_data = NULL; + + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + DurationLimitData *data = + _duration_limit_data_new (GES_TRACK_ELEMENT (child)); + + if (_IS_CORE_CHILD (child)) { + /* don't check that it has an internal-source, since we are assuming + * we will have one if the max-duration is valid */ + if (GES_CLOCK_TIME_IS_LESS (max_duration, child->inpoint)) { + GST_INFO_OBJECT (clip, "Cannot set the max-duration from %" + GST_TIME_FORMAT " to %" GST_TIME_FORMAT " because it would " + "cause the in-point of its core child %" GES_FORMAT + " to exceed its max-duration", + GST_TIME_ARGS (child->maxduration), + GST_TIME_ARGS (max_duration), GES_ARGS (child)); + g_set_error (error, GES_ERROR, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT, + "Cannot set the max-duration of the child \"%s\" under the " + "clip \"%s\" to %" GST_TIME_FORMAT " because it would be " + "below the in-point of %" GST_TIME_FORMAT " of the child", + child->name, GES_TIMELINE_ELEMENT_NAME (clip), + GST_TIME_ARGS (max_duration), GST_TIME_ARGS (child->inpoint)); + + _duration_limit_data_free (data); + g_list_free_full (child_data, _duration_limit_data_free); + return FALSE; + } + data->max_duration = max_duration; + } + + child_data = g_list_prepend (child_data, data); + } + + if (!_can_update_duration_limit (clip, child_data, error)) { + GST_INFO_OBJECT (clip, "Cannot set the max-duration of the core " + "children to %" GST_TIME_FORMAT " because the duration-limit " + "cannot be adjusted", GST_TIME_ARGS (max_duration)); + return FALSE; + } + + return TRUE; +} + +static void +_child_max_duration_changed (GESContainer * container, + GESTimelineElement * child) +{ + /* ignore non-core */ + if (!_IS_CORE_CHILD (child)) + return; + + _update_max_duration (container); +} + +/**************************************************** + * has-internal-source * + ****************************************************/ + +static void +_child_has_internal_source_changed (GESClip * self, GESTimelineElement * child) +{ + /* ignore non-core */ + /* if the track element is now registered to have no internal content, + * we don't have to do anything + * Note that the change in max-duration and in-point will already trigger + * a change in the duration-limit, which can only increase since the + * max-duration is now GST_CLOCK_TIME_NONE */ + if (!_IS_CORE_INTERNAL_SOURCE_CHILD (child)) + return; + + /* otherwise, we need to make its in-point match ours + * Note that the duration-limit will be GST_CLOCK_TIME_NONE, so this + * should not change the duration-limit */ + _set_inpoint0 (child, _INPOINT (self)); +} + +/**************************************************** + * active * + ****************************************************/ + +gboolean +ges_clip_can_set_active_of_child (GESClip * clip, GESTrackElement * child, + gboolean active, GError ** error) +{ + GESTrack *track = ges_track_element_get_track (child); + gboolean is_core = _IS_CORE_CHILD (child); + GList *child_data = NULL; + DurationLimitData *data; + + if (clip->priv->setting_active) + return TRUE; + + /* We want to ensure that each active non-core element has a + * corresponding active core element in the same track */ + if (!track || is_core == active) { + /* only the one child will change */ + data = _duration_limit_data_new (child); + data->active = active; + child_data = _duration_limit_data_list_with_data (clip, data); + } else { + GList *tmp; + /* If we are core, make all the non-core elements in-active + * If we are non-core, make the core element active */ + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTrackElement *sibling = tmp->data; + data = _duration_limit_data_new (sibling); + + if (sibling == child) + data->active = active; + + if (ges_track_element_get_track (sibling) == track + && _IS_CORE_CHILD (sibling) != is_core + && ges_track_element_is_active (sibling) != active) + data->active = active; + + child_data = g_list_prepend (child_data, data); + } + } + + if (!_can_update_duration_limit (clip, child_data, error)) { + GST_INFO_OBJECT (clip, "Cannot set the active of child %" GES_FORMAT + " from %i to %i because the duration-limit cannot be adjusted", + GES_ARGS (child), ges_track_element_is_active (child), active); + return FALSE; + } + + return TRUE; +} + +static void +_child_active_changed (GESClip * self, GESTrackElement * child) +{ + GList *tmp; + GESTrack *track = ges_track_element_get_track (child); + gboolean active = ges_track_element_is_active (child); + gboolean is_core = _IS_CORE_CHILD (child); + gboolean prev_prevent = self->priv->prevent_duration_limit_update; + gboolean prev_prevent_outpoint = self->priv->prevent_children_outpoint_update; + + /* We want to ensure that each active non-core element has a + * corresponding active core element in the same track */ + if (self->priv->setting_active || !track || is_core == active) + return; + + self->priv->setting_active = TRUE; + self->priv->prevent_duration_limit_update = TRUE; + self->priv->prevent_children_outpoint_update = TRUE; + + /* If we are core, make all the non-core elements in-active + * If we are non-core, make the core element active (should only be one) */ + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + GESTrackElement *sibling = tmp->data; + + if (ges_track_element_get_track (sibling) == track + && _IS_CORE_CHILD (sibling) != is_core + && ges_track_element_is_active (sibling) != active) { + + GST_INFO_OBJECT (self, "Setting active to %i for child %" GES_FORMAT + " since the sibling %" GES_FORMAT " in the same track %" + GST_PTR_FORMAT " has been set to %i", active, GES_ARGS (sibling), + GES_ARGS (child), track, active); + + if (!ges_track_element_set_active (sibling, active)) + GST_ERROR_OBJECT (self, "Failed to set active for child %" + GES_FORMAT, GES_ARGS (sibling)); + } + } + + self->priv->setting_active = FALSE; + self->priv->prevent_duration_limit_update = prev_prevent; + self->priv->prevent_children_outpoint_update = prev_prevent_outpoint; +} + +/**************************************************** + * track * + ****************************************************/ + +static GESTrackElement * +_find_core_in_track (GESClip * clip, GESTrack * track) +{ + GList *tmp; + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTrackElement *child = tmp->data; + if (_IS_CORE_CHILD (child) && ges_track_element_get_track (child) == track) + return child; + } + return NULL; +} + +static gboolean +_track_contains_non_core (GESClip * clip, GESTrack * track) +{ + GList *tmp; + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTrackElement *child = tmp->data; + if (!_IS_CORE_CHILD (child) && ges_track_element_get_track (child) == track) + return TRUE; + } + return FALSE; +} + +gboolean +ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, + GESTrack * track, GError ** error) +{ + GList *child_data; + DurationLimitData *data; + GESTrack *current_track = ges_track_element_get_track (child); + GESTrackElement *core = NULL; + + if (clip->priv->allow_any_track) + return TRUE; + + if (current_track == track) + return TRUE; + + /* NOTE: we consider the following error cases programming errors by + * the user */ + if (current_track) { + /* can not remove a core element from a track if a non-core one sits + * above it */ + if (_IS_CORE_CHILD (child) + && _track_contains_non_core (clip, current_track)) { + GST_WARNING_OBJECT (clip, "Cannot move the core child %" GES_FORMAT + " to the track %" GST_PTR_FORMAT " because it has non-core " + "siblings above it in its current track %" GST_PTR_FORMAT, + GES_ARGS (child), track, current_track); + return FALSE; + } + /* otherwise can remove */ + } + if (track) { + GESTimeline *clip_timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip); + const GESTimeline *track_timeline = ges_track_get_timeline (track); + if (track_timeline == NULL) { + GST_WARNING_OBJECT (clip, "Cannot move the child %" GES_FORMAT + " to the track %" GST_PTR_FORMAT " because it is not part " + "of a timeline", GES_ARGS (child), track); + return FALSE; + } + if (track_timeline != clip_timeline) { + GST_WARNING_OBJECT (clip, "Cannot move the child %" GES_FORMAT + " to the track %" GST_PTR_FORMAT " because its timeline %" + GST_PTR_FORMAT " does not match the clip's timeline %" + GST_PTR_FORMAT, GES_ARGS (child), track, track_timeline, + clip_timeline); + return FALSE; + } + + core = _find_core_in_track (clip, track); + /* one core child per track, and other children (effects) can only be + * placed in a track that already has a core child */ + if (_IS_CORE_CHILD (child)) { + if (core) { + GST_WARNING_OBJECT (clip, "Cannot move the core child %" GES_FORMAT + " to the track %" GST_PTR_FORMAT " because it contains a " + "core sibling %" GES_FORMAT, GES_ARGS (child), track, + GES_ARGS (core)); + return FALSE; + } + } else { + if (!core) { + GST_WARNING_OBJECT (clip, "Cannot move the non-core child %" + GES_FORMAT " to the track %" GST_PTR_FORMAT " because it " + " does not contain a core sibling", GES_ARGS (child), track); + return FALSE; + } + } + } + + data = _duration_limit_data_new (child); + gst_clear_object (&data->track); + data->track = track ? gst_object_ref (track) : NULL; + if (core && !ges_track_element_is_active (core)) { + /* if core is set, then we are adding a non-core to a track containing + * a core track element. If this happens, but the core is in-active + * then we will make the non-core element also inactive upon setting + * its track */ + data->active = FALSE; + } + + child_data = _duration_limit_data_list_with_data (clip, data); + + if (!_can_update_duration_limit (clip, child_data, error)) { + GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT " from " + "track %" GST_PTR_FORMAT " to track %" GST_PTR_FORMAT " because " + "the duration-limit cannot be adjusted", GES_ARGS (child), + current_track, track); + return FALSE; + } + + return TRUE; +} + +static void +_update_active_for_track (GESClip * self, GESTrackElement * child) +{ + GESTrack *track = ges_track_element_get_track (child); + GESTrackElement *core; + gboolean active; + gboolean prev_prevent = self->priv->prevent_duration_limit_update; + gboolean prev_prevent_outpoint = self->priv->prevent_children_outpoint_update; + + if (self->priv->allow_any_track || _IS_CORE_CHILD (child) || !track) + return; + + /* if we add a non-core to a track, but the core child is inactive, we + * also need the non-core to be inactive */ + core = _find_core_in_track (self, track); + + if (!core) { + GST_ERROR_OBJECT (self, "The non-core child %" GES_FORMAT " is in " + "the track %" GST_PTR_FORMAT " with no core sibling", + GES_ARGS (child), track); + active = FALSE; + } else { + active = ges_track_element_is_active (core); + } + + if (!active && ges_track_element_is_active (child)) { + + GST_INFO_OBJECT (self, "De-activating non-core child %" GES_FORMAT + " since the core child in the same track %" GST_PTR_FORMAT " is " + "not active", GES_ARGS (child), track); + + self->priv->setting_active = TRUE; + self->priv->prevent_duration_limit_update = TRUE; + self->priv->prevent_children_outpoint_update = TRUE; + + if (!ges_track_element_set_active (child, FALSE)) + GST_ERROR_OBJECT (self, "Failed to de-activate child %" GES_FORMAT, + GES_ARGS (child)); + + self->priv->setting_active = FALSE; + self->priv->prevent_duration_limit_update = prev_prevent; + self->priv->prevent_children_outpoint_update = prev_prevent_outpoint; + } +} + +#define _IS_PROP(prop) (g_strcmp0 (name, prop) == 0) + +static void +_child_property_changed_cb (GESTimelineElement * child, GParamSpec * pspec, + GESClip * self) +{ + gboolean update_limit = FALSE; + gboolean update_outpoint = FALSE; + const gchar *name = pspec->name; + + if (_IS_PROP ("track")) { + update_limit = TRUE; + update_outpoint = TRUE; + _update_active_for_track (self, GES_TRACK_ELEMENT (child)); + } else if (_IS_PROP ("active")) { + update_limit = TRUE; + update_outpoint = TRUE; + _child_active_changed (self, GES_TRACK_ELEMENT (child)); + } else if (_IS_PROP ("priority")) { + update_limit = TRUE; + update_outpoint = TRUE; + _child_priority_changed (GES_CONTAINER (self), child); + } else if (_IS_PROP ("in-point")) { + /* update outpoint already handled by the track element */ + update_limit = _child_inpoint_changed (self, child); + } else if (_IS_PROP ("max-duration")) { + update_limit = TRUE; + _child_max_duration_changed (GES_CONTAINER (self), child); + } else if (_IS_PROP ("has-internal-source")) { + _child_has_internal_source_changed (self, child); + } + + if (update_limit) + _update_duration_limit (self); + if (update_outpoint) + _update_children_outpoints (self); +} + +/**************************************************** + * time properties * + ****************************************************/ + +gboolean +ges_clip_can_set_time_property_of_child (GESClip * clip, + GESTrackElement * child, GObject * child_prop_object, GParamSpec * pspec, + const GValue * value, GError ** error) +{ + if (_IS_TOP_EFFECT (child)) { + gchar *prop_name = + ges_base_effect_get_time_property_name (GES_BASE_EFFECT (child), + child_prop_object, pspec); + + if (prop_name) { + GList *child_data; + DurationLimitData *data = _duration_limit_data_new (child); + GValue *copy = g_new0 (GValue, 1); + + g_value_init (copy, pspec->value_type); + g_value_copy (value, copy); + + g_hash_table_insert (data->time_property_values, prop_name, copy); + + child_data = _duration_limit_data_list_with_data (clip, data); + + if (!_can_update_duration_limit (clip, child_data, error)) { + gchar *val_str = gst_value_serialize (value); + GST_INFO_OBJECT (clip, "Cannot set the child-property %s of " + "child %" GES_FORMAT " to %s because the duration-limit " + "cannot be adjusted", prop_name, GES_ARGS (child), val_str); + g_free (val_str); + return FALSE; + } + } + } + return TRUE; +} + +static void +_child_time_property_changed_cb (GESTimelineElement * child, + GObject * prop_object, GParamSpec * pspec, GESClip * self) +{ + gchar *time_prop = + ges_base_effect_get_time_property_name (GES_BASE_EFFECT (child), + prop_object, pspec); + if (time_prop) { + g_free (time_prop); + _update_duration_limit (self); + _update_children_outpoints (self); + } +} + +/***************************************************** + * * + * GESTimelineElement virtual methods implementation * + * * + *****************************************************/ + +static gboolean +_set_start (GESTimelineElement * element, GstClockTime start) +{ + GList *tmp, *children; + GESContainer *container = GES_CONTAINER (element); + + GST_DEBUG_OBJECT (element, "Setting children start, (initiated_move: %" + GST_PTR_FORMAT ")", container->initiated_move); + + /* get copy of children, since GESContainer may resort the clip */ + children = ges_container_get_children (container, FALSE); + container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; + for (tmp = children; tmp; tmp = g_list_next (tmp)) { + GESTimelineElement *child = (GESTimelineElement *) tmp->data; + + if (child != container->initiated_move) + _set_start0 (GES_TIMELINE_ELEMENT (child), start); + } + container->children_control_mode = GES_CHILDREN_UPDATE; + g_list_free_full (children, gst_object_unref); + + return TRUE; +} + +/* returns TRUE if we did not break early */ +static gboolean +_set_childrens_inpoint (GESTimelineElement * element, GstClockTime inpoint, + gboolean break_on_failure) +{ + GESClip *self = GES_CLIP (element); + GList *tmp; + GESClipPrivate *priv = self->priv; + gboolean prev_prevent = priv->prevent_duration_limit_update; + + priv->setting_inpoint = TRUE; + priv->prevent_duration_limit_update = TRUE; + for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + + if (_IS_CORE_INTERNAL_SOURCE_CHILD (child)) { + if (!_set_inpoint0 (child, inpoint)) { + GST_ERROR_OBJECT ("Could not set the in-point of child %" + GES_FORMAT " to %" GST_TIME_FORMAT, GES_ARGS (child), + GST_TIME_ARGS (inpoint)); + if (break_on_failure) { + priv->setting_inpoint = FALSE; + priv->prevent_duration_limit_update = prev_prevent; + return FALSE; + } + } + } + } + priv->setting_inpoint = FALSE; + priv->prevent_duration_limit_update = prev_prevent; + + _update_duration_limit (self); + + return TRUE; +} + +static gboolean +_set_inpoint (GESTimelineElement * element, GstClockTime inpoint) +{ + if (!_can_set_inpoint_of_core_children (GES_CLIP (element), inpoint, NULL)) { + GST_WARNING_OBJECT (element, "Cannot set the in-point to %" + GST_TIME_FORMAT, GST_TIME_ARGS (inpoint)); + return FALSE; + } + + if (!_set_childrens_inpoint (element, inpoint, TRUE)) { + _set_childrens_inpoint (element, element->inpoint, FALSE); + return FALSE; + } + return TRUE; +} + +static gboolean +_set_duration (GESTimelineElement * element, GstClockTime duration) +{ + GList *tmp, *children; + GESContainer *container = GES_CONTAINER (element); + + /* get copy of children, since GESContainer may resort the clip */ + children = ges_container_get_children (container, FALSE); + container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; + for (tmp = container->children; tmp; tmp = g_list_next (tmp)) { + GESTimelineElement *child = (GESTimelineElement *) tmp->data; + + if (child != container->initiated_move) + _set_duration0 (GES_TIMELINE_ELEMENT (child), duration); + } + container->children_control_mode = GES_CHILDREN_UPDATE; + g_list_free_full (children, gst_object_unref); + + return TRUE; +} + +static gboolean +_set_max_duration (GESTimelineElement * element, GstClockTime maxduration) +{ + GList *tmp; + GList *child_data = NULL; + GESClip *self = GES_CLIP (element); + GESClipPrivate *priv = self->priv; + GstClockTime new_min = GST_CLOCK_TIME_NONE; + gboolean has_core = FALSE; + gboolean res = FALSE; + gboolean prev_prevent = priv->prevent_duration_limit_update; + + /* if we are setting based on a change in the minimum */ + if (priv->updating_max_duration) + return TRUE; + + /* else, we set every core child to have the same max duration */ + + /* check that the duration-limit can be changed */ + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + GESTrackElement *child = tmp->data; + DurationLimitData *data = _duration_limit_data_new (child); + + if (_IS_CORE_INTERNAL_SOURCE_CHILD (child)) + data->max_duration = maxduration; + + child_data = g_list_prepend (child_data, data); + } + + if (!_can_update_duration_limit (self, child_data, NULL)) { + GST_WARNING_OBJECT (self, "Cannot set the max-duration from %" + GST_TIME_FORMAT " to %" GST_TIME_FORMAT " because the " + "duration-limit cannot be adjusted", + GST_TIME_ARGS (element->maxduration), GST_TIME_ARGS (maxduration)); + return FALSE; + } + + priv->prevent_duration_limit_update = TRUE; + priv->setting_max_duration = TRUE; + for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + + if (_IS_CORE_CHILD (child)) { + has_core = TRUE; + if (ges_track_element_has_internal_source (GES_TRACK_ELEMENT (child))) { + if (!ges_timeline_element_set_max_duration (child, maxduration)) + GST_ERROR_OBJECT ("Could not set the max-duration of child %" + GES_FORMAT " to %" GST_TIME_FORMAT, GES_ARGS (child), + GST_TIME_ARGS (maxduration)); + new_min = _MIN_CLOCK_TIME (new_min, child->maxduration); + } + } + } + priv->setting_max_duration = FALSE; + priv->prevent_duration_limit_update = prev_prevent; + + if (!has_core) { + /* allow max-duration to be set arbitrarily when we have no + * core children, even though there is no actual minimum max-duration + * when it has no core children */ + if (GST_CLOCK_TIME_IS_VALID (maxduration)) + GST_INFO_OBJECT (element, + "Allowing max-duration of the clip to be set to %" GST_TIME_FORMAT + " because it has no core children", GST_TIME_ARGS (maxduration)); + res = TRUE; + goto done; + } + + if (new_min != maxduration) { + if (GST_CLOCK_TIME_IS_VALID (new_min)) + GST_WARNING_OBJECT (element, "Failed to set the max-duration of the " + "clip to %" GST_TIME_FORMAT " because it was not possible to " + "match this with the actual minimum of %" GST_TIME_FORMAT, + GST_TIME_ARGS (maxduration), GST_TIME_ARGS (new_min)); + else + GST_WARNING_OBJECT (element, "Failed to set the max-duration of the " + "clip to %" GST_TIME_FORMAT " because it has no core children " + "whose max-duration could be set to anything other than " + "GST_CLOCK_TIME_NONE", GST_TIME_ARGS (maxduration)); + priv->updating_max_duration = TRUE; + ges_timeline_element_set_max_duration (element, new_min); + priv->updating_max_duration = FALSE; + goto done; + } + + res = TRUE; + +done: + _update_duration_limit (self); + + return res; +} + +static gboolean +_set_priority (GESTimelineElement * element, guint32 priority) +{ + GESClipPrivate *priv = GES_CLIP (element)->priv; + GList *tmp; + guint32 min_prio, max_prio, min_child_prio = G_MAXUINT32; + gboolean prev_prevent = priv->prevent_duration_limit_update; + gboolean prev_prevent_outpoint = priv->prevent_children_outpoint_update; + GESContainer *container = GES_CONTAINER (element); + + for (tmp = container->children; tmp; tmp = g_list_next (tmp)) + min_child_prio = MIN (min_child_prio, _PRIORITY (tmp->data)); + + /* send the new 'priority' to determine what the new 'min_prio' should + * be for the clip */ + _get_priority_range_full (container, &min_prio, &max_prio, priority); + + /* offsets will remain constant for the children */ + priv->prevent_resort = TRUE; + priv->prevent_duration_limit_update = TRUE; + priv->prevent_children_outpoint_update = TRUE; + priv->setting_priority = TRUE; + for (tmp = container->children; tmp; tmp = g_list_next (tmp)) { + GESTimelineElement *child = tmp->data; + guint32 track_element_prio = min_prio + (child->priority - min_child_prio); + + if (track_element_prio > max_prio) { + GST_WARNING_OBJECT (container, "%s priority of %i, is outside of its " + "containing layer space. (%d/%d) setting it to the maximum it can be", + child->name, priority, min_prio, max_prio); + + track_element_prio = max_prio; + } + _set_priority0 (child, track_element_prio); + } + /* no need to re-sort the container since we maintained the relative + * offsets. As such, the height and duration-limit remains the same as + * well. */ + priv->prevent_resort = FALSE; + priv->setting_priority = FALSE; + priv->prevent_duration_limit_update = prev_prevent; + priv->prevent_children_outpoint_update = prev_prevent_outpoint; + + return TRUE; +} + +static guint32 +_get_layer_priority (GESTimelineElement * element) +{ + GESClip *clip = GES_CLIP (element); + + if (clip->priv->layer == NULL) + return GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY; + + return ges_layer_get_priority (clip->priv->layer); +} + +static gboolean +_get_natural_framerate (GESTimelineElement * self, gint * framerate_n, + gint * framerate_d) +{ + GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (self)); + + if (!asset) { + GST_WARNING_OBJECT (self, "No asset set?"); + + return FALSE; + } + + return ges_clip_asset_get_natural_framerate (GES_CLIP_ASSET (asset), + framerate_n, framerate_d); +} + +/**************************************************** + * * + * GESContainer virtual methods implementation * + * * + ****************************************************/ + +static void +_compute_height (GESContainer * container) +{ + GList *tmp; + guint32 min_prio = G_MAXUINT32, max_prio = 0; + + if (container->children == NULL) { + /* FIXME Why not 0! */ + _ges_container_set_height (container, 1); + return; + } + + /* Go over all childs and check if height has changed */ + for (tmp = container->children; tmp; tmp = tmp->next) { + guint tck_priority = _PRIORITY (tmp->data); + + if (tck_priority < min_prio) + min_prio = tck_priority; + if (tck_priority > max_prio) + max_prio = tck_priority; + } + + _ges_container_set_height (container, max_prio - min_prio + 1); +} + +void +ges_clip_take_add_error (GESClip * clip, GError ** error) +{ + GESClipPrivate *priv = clip->priv; + + g_clear_error (error); + if (error) { + if (*error) { + GST_ERROR_OBJECT (clip, "Error not handled: %s", (*error)->message); + g_error_free (*error); + } + *error = priv->add_error; + } else { + g_clear_error (&priv->add_error); + } + priv->add_error = NULL; +} + +void +ges_clip_set_add_error (GESClip * clip, GError * error) +{ + GESClipPrivate *priv = clip->priv; + + g_clear_error (&priv->add_error); + priv->add_error = error; +} + +static gboolean +_add_child (GESContainer * container, GESTimelineElement * element) +{ + gboolean ret = FALSE; + GESClip *self = GES_CLIP (container); + GESTrackElement *track_el = GES_TRACK_ELEMENT (element); + GESClipClass *klass = GES_CLIP_GET_CLASS (self); + guint32 min_prio, max_prio, new_prio; + GESTrack *track; + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (container); + GESClipPrivate *priv = self->priv; + GESAsset *asset, *creator_asset; + gboolean prev_prevent = priv->prevent_duration_limit_update; + gboolean prev_prevent_outpoint = priv->prevent_children_outpoint_update; + GList *tmp; + GError *error = NULL; + + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (element), FALSE); + + if (element->timeline && element->timeline != timeline) { + GST_WARNING_OBJECT (self, "Cannot add %" GES_FORMAT " as a child " + "because its timeline is %" GST_PTR_FORMAT " rather than the " + "clip's timeline %" GST_PTR_FORMAT, GES_ARGS (element), + element->timeline, timeline); + goto done; + } + + asset = ges_extractable_get_asset (GES_EXTRACTABLE (self)); + creator_asset = ges_track_element_get_creator_asset (track_el); + if (creator_asset && asset != creator_asset) { + GST_WARNING_OBJECT (self, + "Cannot add the track element %" GES_FORMAT " as a child " + "because it is a core element created by another clip with a " + "different asset to the current clip's asset", GES_ARGS (element)); + goto done; + } + + track = ges_track_element_get_track (track_el); + + if (track && ges_track_get_timeline (track) != timeline) { + /* really, an element in a track should have the same timeline as + * the track, so we would have checked this with the + * element->timeline check. But technically a user could get around + * this, so we double check here. */ + GST_WARNING_OBJECT (self, "Cannot add %" GES_FORMAT " as a child " + "because its track %" GST_PTR_FORMAT " is part of the timeline %" + GST_PTR_FORMAT " rather than the clip's timeline %" GST_PTR_FORMAT, + GES_ARGS (element), track, ges_track_get_timeline (track), timeline); + goto done; + } + + /* NOTE: notifies are currently frozen by ges_container_add */ + + _get_priority_range (container, &min_prio, &max_prio); + + if (creator_asset) { + /* NOTE: Core track elements that are base effects are added like any + * other core elements. In particular, they are *not* added to the + * list of added effects, so we do not increase nb_effects. */ + + /* Set the core element to have the same in-point, which we don't + * apply to effects */ + GstClockTime new_inpoint; + if (ges_track_element_has_internal_source (track_el)) + new_inpoint = _INPOINT (self); + else + new_inpoint = 0; + + /* new priority is that of the lowest priority core child. Usually + * each core child has the same priority. + * Also must be lower than all effects */ + + new_prio = min_prio; + for (tmp = container->children; tmp; tmp = tmp->next) { + if (_IS_CORE_CHILD (tmp->data)) + new_prio = MAX (new_prio, _PRIORITY (tmp->data)); + else if (_IS_TOP_EFFECT (tmp->data)) + new_prio = MAX (new_prio, _PRIORITY (tmp->data) + 1); + } + + if (track && !priv->allow_any_track) { + GList *child_data; + DurationLimitData *data; + GESTrackElement *core = _find_core_in_track (self, track); + + if (core) { + GST_WARNING_OBJECT (self, "Cannot add the core child %" GES_FORMAT + " because it is in the same track %" GST_PTR_FORMAT " as an " + "existing core child %" GES_FORMAT, GES_ARGS (element), track, + GES_ARGS (core)); + goto done; + } + + data = _duration_limit_data_new (track_el); + data->inpoint = new_inpoint; + data->priority = new_prio; + + child_data = _duration_limit_data_list_with_data (self, data); + + if (!_can_update_duration_limit (self, child_data, &error)) { + GST_INFO_OBJECT (self, "Cannot add core %" GES_FORMAT " as a " + "child because the duration-limit cannot be adjusted", + GES_ARGS (element)); + goto done; + } + } + + if (GES_CLOCK_TIME_IS_LESS (element->maxduration, new_inpoint)) { + GST_INFO_OBJECT (self, "Can not set the in-point of the " + "element %" GES_FORMAT " to %" GST_TIME_FORMAT " because its " + "max-duration is %" GST_TIME_FORMAT, GES_ARGS (element), + GST_TIME_ARGS (new_inpoint), GST_TIME_ARGS (element->maxduration)); + + g_set_error (&error, GES_ERROR, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT, + "Cannot add the child \"%s\" to clip \"%s\" because its max-" + "duration is %" GST_TIME_FORMAT ", which is less than the in-" + "point of the clip %" GST_TIME_FORMAT, element->name, + GES_TIMELINE_ELEMENT_NAME (self), + GST_TIME_ARGS (element->maxduration), GST_TIME_ARGS (new_inpoint)); + + goto done; + } + + /* adding can fail if the max-duration of the element is smaller + * than the current in-point of the clip */ + if (!_set_inpoint0 (element, new_inpoint)) { + GST_WARNING_OBJECT (self, "Could not set the in-point of the " + "element %" GES_FORMAT " to %" GST_TIME_FORMAT ". Not adding " + "as a child", GES_ARGS (element), GST_TIME_ARGS (new_inpoint)); + goto done; + } + + _set_priority0 (element, new_prio); + + } else if (GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) && _IS_TOP_EFFECT (element)) { + /* Add the effect at the lowest priority among effects (just after + * the core elements). Need to shift the core elements up by 1 + * to make room. */ + + /* new priority is the lowest priority effect */ + if (priv->use_effect_priority) { + new_prio = priv->effect_priority; + } else { + new_prio = min_prio; + for (tmp = container->children; tmp; tmp = tmp->next) { + if (_IS_TOP_EFFECT (tmp->data)) + new_prio = MAX (new_prio, _PRIORITY (tmp->data) + 1); + } + } + /* make sure higher than core */ + for (tmp = container->children; tmp; tmp = tmp->next) { + if (_IS_CORE_CHILD (tmp->data)) + new_prio = MIN (new_prio, _PRIORITY (tmp->data)); + } + + if (track && !priv->allow_any_track) { + GList *child_data, *tmp; + DurationLimitData *data; + GESTrackElement *core = _find_core_in_track (self, track); + + if (!core) { + GST_WARNING_OBJECT (self, "Cannot add the effect %" GES_FORMAT + " because its track %" GST_PTR_FORMAT " does not contain one " + "of the clip's core children", GES_ARGS (element), track); + goto done; + } + + data = _duration_limit_data_new (track_el); + data->priority = new_prio; + if (!ges_track_element_is_active (core)) + data->active = FALSE; + + child_data = _duration_limit_data_list_with_data (self, data); + + for (tmp = child_data; tmp; tmp = tmp->next) { + data = tmp->data; + if (data->priority >= new_prio) + data->priority++; + } + + if (!_can_update_duration_limit (self, child_data, &error)) { + GST_INFO_OBJECT (self, "Cannot add effect %" GES_FORMAT " as " + "a child because the duration-limit cannot be adjusted", + GES_ARGS (element)); + goto done; + } + } + + _update_active_for_track (self, track_el); + + priv->nb_effects++; + GST_DEBUG_OBJECT (self, "Adding %ith effect: %" GES_FORMAT + " Priority %i", priv->nb_effects, GES_ARGS (element), new_prio); + + /* changing priorities, and updating their offset */ + priv->prevent_resort = TRUE; + priv->setting_priority = TRUE; + priv->prevent_duration_limit_update = TRUE; + priv->prevent_children_outpoint_update = TRUE; + + /* increase the priority of anything with a lower priority */ + for (tmp = container->children; tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + if (child->priority >= new_prio) + ges_timeline_element_set_priority (child, child->priority + 1); + } + _set_priority0 (element, new_prio); + + priv->prevent_resort = FALSE; + priv->setting_priority = FALSE; + priv->prevent_duration_limit_update = prev_prevent; + priv->prevent_children_outpoint_update = prev_prevent_outpoint; + /* no need to call _ges_container_sort_children (container) since + * there is no change to the ordering yet (this happens after the + * child is actually added) */ + /* The height has already changed (increased by 1) */ + _compute_height (container); + /* update duration limit in _child_added */ + } else { + if (_IS_TOP_EFFECT (element)) + GST_WARNING_OBJECT (self, "Cannot add the effect %" GES_FORMAT + " because it is not a core element created by the clip itself " + "and the %s class does not allow for adding extra effects", + GES_ARGS (element), G_OBJECT_CLASS_NAME (klass)); + else if (GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass)) + GST_WARNING_OBJECT (self, "Cannot add the track element %" + GES_FORMAT " because it is neither a core element created by " + "the clip itself, nor a GESBaseEffect", GES_ARGS (element)); + else + GST_WARNING_OBJECT (self, "Cannot add the track element %" + GES_FORMAT " because it is not a core element created by the " + "clip itself", GES_ARGS (element)); + goto done; + } + + _set_start0 (element, GES_TIMELINE_ELEMENT_START (self)); + _set_duration0 (element, GES_TIMELINE_ELEMENT_DURATION (self)); + + ret = TRUE; + +done: + if (error) + ges_clip_set_add_error (self, error); + + return ret; +} + +void +ges_clip_take_remove_error (GESClip * clip, GError ** error) +{ + GESClipPrivate *priv = clip->priv; + + g_clear_error (error); + if (error) { + if (*error) { + GST_ERROR ("Error not handled: %s", (*error)->message); + g_error_free (*error); + } + *error = priv->remove_error; + } else { + g_clear_error (&priv->remove_error); + } + priv->remove_error = NULL; +} + +void +ges_clip_set_remove_error (GESClip * clip, GError * error) +{ + GESClipPrivate *priv = clip->priv; + + g_clear_error (&priv->remove_error); + priv->remove_error = error; +} + +static gboolean +_remove_child (GESContainer * container, GESTimelineElement * element) +{ + GESTrackElement *el = GES_TRACK_ELEMENT (element); + GESClip *self = GES_CLIP (container); + GESClipPrivate *priv = self->priv; + + /* check that the duration-limit can be changed */ + /* If we are removing a core child, then all other children in the + * same track will be removed from the track, which will make the + * duration-limit increase, which is safe + * Similarly, if it has no track, the duration-limit will not change */ + if (!priv->allow_any_remove && !_IS_CORE_CHILD (element) && + ges_track_element_get_track (el)) { + GList *child_data = NULL; + GList *tmp; + GError *error = NULL; + + for (tmp = container->children; tmp; tmp = tmp->next) { + GESTrackElement *child = tmp->data; + if (child == el) + continue; + child_data = + g_list_prepend (child_data, _duration_limit_data_new (child)); + } + + if (!_can_update_duration_limit (self, child_data, &error)) { + ges_clip_set_remove_error (self, error); + GST_INFO_OBJECT (self, "Cannot remove the child %" GES_FORMAT + " because the duration-limit cannot be adjusted", GES_ARGS (el)); + return FALSE; + } + } + + /* NOTE: notifies are currently frozen by ges_container_add */ + if (_IS_TOP_EFFECT (element)) { + GList *tmp; + gboolean prev_prevent = priv->prevent_duration_limit_update; + gboolean prev_prevent_outpoint = priv->prevent_children_outpoint_update; + GST_DEBUG_OBJECT (container, "Resyncing effects priority."); + + /* changing priorities, so preventing a re-sort */ + priv->prevent_resort = TRUE; + priv->setting_priority = TRUE; + priv->prevent_duration_limit_update = TRUE; + priv->prevent_children_outpoint_update = TRUE; + for (tmp = container->children; tmp; tmp = tmp->next) { + guint32 sibling_prio = GES_TIMELINE_ELEMENT_PRIORITY (tmp->data); + if (sibling_prio > element->priority) + ges_timeline_element_set_priority (GES_TIMELINE_ELEMENT (tmp->data), + sibling_prio - 1); + } + priv->nb_effects--; + priv->prevent_resort = FALSE; + priv->setting_priority = FALSE; + priv->prevent_duration_limit_update = prev_prevent; + priv->prevent_children_outpoint_update = prev_prevent_outpoint; + /* no need to re-sort the children since the rest keep the same + * relative priorities */ + /* height may have changed */ + _compute_height (container); + } + /* duration-limit updated in _child_removed */ + return TRUE; +} + +static void +_child_added (GESContainer * container, GESTimelineElement * element) +{ + GESClip *self = GES_CLIP (container); + + g_signal_connect (element, "notify", G_CALLBACK (_child_property_changed_cb), + self); + + if (GES_IS_TIME_EFFECT (element)) + g_signal_connect (element, "deep-notify", + G_CALLBACK (_child_time_property_changed_cb), self); + + if (_IS_CORE_CHILD (element)) + _update_max_duration (container); + + _update_duration_limit (self); + _update_children_outpoints (self); +} + +static void +_child_removed (GESContainer * container, GESTimelineElement * element) +{ + GESClip *self = GES_CLIP (container); + + g_signal_handlers_disconnect_by_func (element, _child_property_changed_cb, + self); + /* NOTE: we do not test if the effect is a time effect since technically + * it can stop being a time effect, although this would be rare */ + g_signal_handlers_disconnect_by_func (element, + _child_time_property_changed_cb, self); + + if (_IS_CORE_CHILD (element)) + _update_max_duration (container); + + _update_duration_limit (self); + _update_children_outpoints (self); + ges_track_element_update_outpoint (GES_TRACK_ELEMENT (element)); +} + +static void +add_clip_to_list (gpointer key, gpointer clip, GList ** list) +{ + *list = g_list_prepend (*list, gst_object_ref (clip)); +} + +/* NOTE: Since this does not change the track of @child, this should + * only be called if it is guaranteed that neither @from_clip nor @to_clip + * will not break the track rules: + * + no more than one core child per track + * + every non-core child must be in the same track as a core child + * NOTE: Since this does not change the creator asset of the child, this + * should only be called for transferring children between clips with the + * same asset. + * NOTE: This also prevents the update of the duration-limit, so you + * should ensure that you call _update_duration_limit on both clips when + * transferring has completed. + */ +static void +_transfer_child (GESClip * from_clip, GESClip * to_clip, + GESTrackElement * child) +{ + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (to_clip); + gboolean prev_prevent_from = from_clip->priv->prevent_duration_limit_update; + gboolean prev_prevent_to = to_clip->priv->prevent_duration_limit_update; + gboolean prev_prevent_outpoint_from = + from_clip->priv->prevent_children_outpoint_update; + gboolean prev_prevent_outpoint_to = + to_clip->priv->prevent_children_outpoint_update; + + /* We need to bump the refcount to avoid the object to be destroyed */ + gst_object_ref (child); + + /* don't want to change tracks */ + ges_timeline_set_moving_track_elements (timeline, TRUE); + + from_clip->priv->prevent_duration_limit_update = TRUE; + to_clip->priv->prevent_duration_limit_update = TRUE; + from_clip->priv->prevent_children_outpoint_update = TRUE; + to_clip->priv->prevent_children_outpoint_update = TRUE; + + from_clip->priv->allow_any_remove = TRUE; + ges_container_remove (GES_CONTAINER (from_clip), + GES_TIMELINE_ELEMENT (child)); + from_clip->priv->allow_any_remove = FALSE; + + to_clip->priv->allow_any_track = TRUE; + if (!ges_container_add (GES_CONTAINER (to_clip), + GES_TIMELINE_ELEMENT (child))) + GST_ERROR ("%" GES_FORMAT " could not add child %p while" + " transfering, this should never happen", GES_ARGS (to_clip), child); + to_clip->priv->allow_any_track = FALSE; + ges_timeline_set_moving_track_elements (timeline, FALSE); + + from_clip->priv->prevent_duration_limit_update = prev_prevent_from; + to_clip->priv->prevent_duration_limit_update = prev_prevent_to; + from_clip->priv->prevent_children_outpoint_update = + prev_prevent_outpoint_from; + to_clip->priv->prevent_children_outpoint_update = prev_prevent_outpoint_to; + + gst_object_unref (child); +} + +static GList * +_ungroup (GESContainer * container, gboolean recursive) +{ + GESClip *tmpclip; + GESTrackType track_type; + GESTrackElement *track_element; + + gboolean first_obj = TRUE; + GList *tmp, *children, *ret = NULL; + GESClip *clip = GES_CLIP (container); + GESTimelineElement *element = GES_TIMELINE_ELEMENT (container); + GESLayer *layer = clip->priv->layer; + GHashTable *_tracktype_clip = g_hash_table_new (g_int_hash, g_int_equal); + + /* If there is no TrackElement, just return @container in a list */ + if (GES_CONTAINER_CHILDREN (container) == NULL) { + GST_DEBUG ("No TrackElement, simply returning"); + return g_list_prepend (ret, container); + } + + children = ges_container_get_children (container, FALSE); + /* _add_child will add core elements at the lowest priority and new + * non-core effects at the lowest effect priority, so we need to add + * the highest priority children first to preserve the effect order. + * @children is already ordered by highest priority first */ + for (tmp = children; tmp; tmp = tmp->next) { + track_element = GES_TRACK_ELEMENT (tmp->data); + track_type = ges_track_element_get_track_type (track_element); + + tmpclip = g_hash_table_lookup (_tracktype_clip, &track_type); + if (tmpclip == NULL) { + if (G_UNLIKELY (first_obj == TRUE)) { + tmpclip = clip; + first_obj = FALSE; + } else { + tmpclip = GES_CLIP (ges_timeline_element_copy (element, FALSE)); + if (layer) { + /* Add new container to the same layer as @container */ + ges_clip_set_moving_from_layer (tmpclip, TRUE); + /* adding to the same layer should not fail when moving */ + ges_layer_add_clip (layer, tmpclip); + ges_clip_set_moving_from_layer (tmpclip, FALSE); + } + } + + g_hash_table_insert (_tracktype_clip, &track_type, tmpclip); + ges_clip_set_supported_formats (tmpclip, track_type); + } + + /* Move trackelement to the container it is supposed to land into */ + /* Note: it is safe to transfer the element whilst not changing tracks + * because all track elements in the same track will stay in the + * same clip */ + if (tmpclip != clip) + _transfer_child (clip, tmpclip, track_element); + } + g_list_free_full (children, gst_object_unref); + g_hash_table_foreach (_tracktype_clip, (GHFunc) add_clip_to_list, &ret); + g_hash_table_unref (_tracktype_clip); + + /* Need to update the duration limit. + * Since we have divided the clip by its tracks, the duration-limit, + * which is a minimum value calculated per track, can only increase in + * value, which means the duration of the clip should not change, which + * means updating should always be possible */ + for (tmp = ret; tmp; tmp = tmp->next) + _update_duration_limit (tmp->data); + + return ret; +} + +static gboolean +_group_test_share_track (GESClip * clip1, GESClip * clip2) +{ + GList *tmp1, *tmp2; + GESTrackElement *child1, *child2; + for (tmp1 = GES_CONTAINER_CHILDREN (clip1); tmp1; tmp1 = tmp1->next) { + child1 = tmp1->data; + for (tmp2 = GES_CONTAINER_CHILDREN (clip2); tmp2; tmp2 = tmp2->next) { + child2 = tmp2->data; + if (ges_track_element_get_track (child1) + == ges_track_element_get_track (child2)) { + GST_INFO_OBJECT (clip1, "Cannot group with clip %" GES_FORMAT + " because its child %" GES_FORMAT " shares the same " + "track with our child %" GES_FORMAT, GES_ARGS (clip2), + GES_ARGS (child2), GES_ARGS (child1)); + return TRUE; + } + } + } + return FALSE; +} + +#define _GROUP_TEST_EQUAL(val, expect, format) \ +if (val != expect) { \ + GST_INFO_OBJECT (clip, "Cannot group with other clip %" GES_FORMAT \ + " because the clip's " #expect " is %" format " rather than the " \ + #expect " of the other clip %" format, GES_ARGS (first_clip), val, \ + expect); \ + return NULL; \ +} + +static GESContainer * +_group (GList * containers) +{ + GESClip *first_clip = NULL; + GESTimeline *timeline; + GESTrackType supported_formats; + GESLayer *layer; + GList *tmp, *tmp2, *tmpclip; + GstClockTime start, inpoint, duration; + GESTimelineElement *element; + + GESAsset *asset; + GESContainer *ret = NULL; + + if (!containers) + return NULL; + + for (tmp = containers; tmp; tmp = tmp->next) { + if (GES_IS_CLIP (tmp->data) == FALSE) { + GST_DEBUG ("Can only work with clips"); + return NULL; + } + if (!first_clip) { + first_clip = tmp->data; + element = GES_TIMELINE_ELEMENT (first_clip); + start = element->start; + inpoint = element->inpoint; + duration = element->duration; + timeline = element->timeline; + layer = first_clip->priv->layer; + asset = ges_extractable_get_asset (GES_EXTRACTABLE (first_clip)); + } + } + + for (tmp = containers; tmp; tmp = tmp->next) { + GESClip *clip; + GESAsset *cmp_asset; + + element = GES_TIMELINE_ELEMENT (tmp->data); + clip = GES_CLIP (element); + + _GROUP_TEST_EQUAL (element->start, start, G_GUINT64_FORMAT); + _GROUP_TEST_EQUAL (element->duration, duration, G_GUINT64_FORMAT); + _GROUP_TEST_EQUAL (element->inpoint, inpoint, G_GUINT64_FORMAT); + _GROUP_TEST_EQUAL (element->timeline, timeline, GST_PTR_FORMAT); + _GROUP_TEST_EQUAL (clip->priv->layer, layer, GST_PTR_FORMAT); + cmp_asset = ges_extractable_get_asset (GES_EXTRACTABLE (clip)); + if (cmp_asset != asset) { + GST_INFO_OBJECT (clip, "Cannot group with other clip %" + GES_FORMAT " because the clip's asset is %s rather than " + " the asset of the other clip %s", GES_ARGS (first_clip), + cmp_asset ? ges_asset_get_id (cmp_asset) : NULL, + asset ? ges_asset_get_id (asset) : NULL); + return NULL; + } + /* make sure we don't share the same track */ + for (tmp2 = tmp->next; tmp2; tmp2 = tmp2->next) { + if (_group_test_share_track (clip, tmp2->data)) + return NULL; + } + } + + /* And now pass all TrackElements to the first clip, + * and remove others from the layer (updating the supported formats) */ + ret = containers->data; + supported_formats = GES_CLIP (ret)->priv->supportedformats; + for (tmpclip = containers->next; tmpclip; tmpclip = tmpclip->next) { + GESClip *cclip = tmpclip->data; + GList *children = ges_container_get_children (GES_CONTAINER (cclip), FALSE); + + /* _add_child will add core elements at the lowest priority and new + * non-core effects at the lowest effect priority, so we need to add + * the highest priority children first to preserve the effect order. + * @children is already ordered by highest priority first. + * Priorities between children in different tracks (as tmpclips are) + * is not important */ + for (tmp = children; tmp; tmp = tmp->next) { + GESTrackElement *celement = GES_TRACK_ELEMENT (tmp->data); + /* Note: it is safe to transfer the element whilst not changing + * tracks because the elements from different clips will have + * children in separate tracks. So it should not be possible for + * two core children to appear in the same track */ + _transfer_child (cclip, GES_CLIP (ret), celement); + supported_formats |= ges_track_element_get_track_type (celement); + } + g_list_free_full (children, gst_object_unref); + /* duration-limit should be GST_CLOCK_TIME_NONE now that we have no + * children */ + _update_duration_limit (cclip); + + if (layer) + ges_layer_remove_clip (layer, cclip); + } + + /* Need to update the duration limit. + * Each received clip C_i that has been grouped may have had a different + * duration-limit L_i. In each case the duration must be less than + * this limit, and since each clip shares the same duration, we have + * for each clip C_i: + * duration <= L_i + * Thus: + * duration <= min_i (L_i) + * + * Now, upon grouping each clip C_i into C, we have not changed the + * children properties that affect the duration-limit. And since the + * duration-limit is calculated as the minimum amongst the tracks of C, + * this means that the duration-limit for C should be + * L = min_i (L_i) >= duration + * Therefore, we can safely set the duration-limit of C to L without + * changing the duration of C. */ + _update_duration_limit (GES_CLIP (ret)); + + ges_clip_set_supported_formats (GES_CLIP (ret), supported_formats); + + return ret; +} + +void +ges_clip_empty_from_track (GESClip * clip, GESTrack * track) +{ + GList *tmp; + gboolean prev_prevent = clip->priv->prevent_duration_limit_update; + gboolean prev_prevent_outpoint = clip->priv->prevent_children_outpoint_update; + + if (track == NULL) + return; + /* allow us to remove in any order */ + clip->priv->allow_any_track = TRUE; + clip->priv->prevent_duration_limit_update = TRUE; + clip->priv->prevent_children_outpoint_update = TRUE; + + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTrackElement *child = tmp->data; + if (ges_track_element_get_track (child) == track) { + if (!ges_track_remove_element (track, child)) + GST_ERROR_OBJECT (clip, "Failed to remove child %" GES_FORMAT + " from the track %" GST_PTR_FORMAT, GES_ARGS (child), track); + } + } + clip->priv->allow_any_track = FALSE; + clip->priv->prevent_duration_limit_update = prev_prevent; + clip->priv->prevent_children_outpoint_update = prev_prevent_outpoint; + _update_duration_limit (clip); + _update_children_outpoints (clip); +} + +static GESTrackElement * +_copy_track_element_to (GESTrackElement * orig, GESClip * to_clip, + GstClockTime position) +{ + GESTrackElement *copy; + GESTimelineElement *el_copy, *el_orig; + + /* NOTE: we do not deep copy the track element, we instead call + * ges_track_element_copy_properties explicitly, which is the + * deep_copy for the GESTrackElementClass. */ + el_orig = GES_TIMELINE_ELEMENT (orig); + el_copy = ges_timeline_element_copy (el_orig, FALSE); + + if (el_copy == NULL) + return NULL; + + copy = GES_TRACK_ELEMENT (el_copy); + ges_track_element_copy_properties (el_orig, el_copy); + /* NOTE: control bindings that are not registered in GES are not + * handled */ + ges_track_element_copy_bindings (orig, copy, position); + + ges_track_element_set_creator_asset (copy, + ges_track_element_get_creator_asset (orig)); + + return copy; +} + +static GESTrackElement * +ges_clip_copy_track_element_into (GESClip * clip, GESTrackElement * orig, + GstClockTime position) +{ + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip); + GESTrackElement *copy; + + copy = _copy_track_element_to (orig, clip, position); + if (copy == NULL) { + GST_ERROR_OBJECT (clip, "Failed to create a copy of the " + "element %" GES_FORMAT " for the clip", GES_ARGS (orig)); + return NULL; + } + + gst_object_ref (copy); + ges_timeline_set_moving_track_elements (timeline, TRUE); + if (!ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (copy))) { + GST_ERROR_OBJECT (clip, "Failed to add the copied child track " + "element %" GES_FORMAT " to the clip", GES_ARGS (copy)); + ges_timeline_set_moving_track_elements (timeline, FALSE); + gst_object_unref (copy); + return NULL; + } + ges_timeline_set_moving_track_elements (timeline, FALSE); + /* now owned by the clip */ + gst_object_unref (copy); + + return copy; +} + +static void +_deep_copy (GESTimelineElement * element, GESTimelineElement * copy) +{ + GList *tmp; + GESClip *self = GES_CLIP (element), *ccopy = GES_CLIP (copy); + GESTrackElement *el, *el_copy; + + /* NOTE: this should only be called on a newly created @copy, so + * its copied_track_elements, and copied_layer, should be free to set + * without disposing of the previous values */ + for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { + el = GES_TRACK_ELEMENT (tmp->data); + + el_copy = _copy_track_element_to (el, ccopy, GST_CLOCK_TIME_NONE); + if (!el_copy) { + GST_ERROR_OBJECT (element, "Failed to copy the track element %" + GES_FORMAT " for pasting", GES_ARGS (el)); + continue; + } + /* owned by copied_track_elements */ + gst_object_ref_sink (el_copy); + + /* _add_child will add core elements at the lowest priority and new + * non-core effects at the lowest effect priority, so we need to add + * the highest priority children first to preserve the effect order. + * The clip's children are already ordered by highest priority first. + * So we order copied_track_elements in the same way */ + ccopy->priv->copied_track_elements = + g_list_append (ccopy->priv->copied_track_elements, el_copy); + } + + ccopy->priv->copied_layer = g_object_ref (self->priv->layer); + ccopy->priv->copied_timeline = self->priv->layer->timeline; +} + +static GESTimelineElement * +_paste (GESTimelineElement * element, GESTimelineElement * ref, + GstClockTime paste_position) +{ + GList *tmp; + GESClip *self = GES_CLIP (element); + GESLayer *layer = self->priv->copied_layer; + GESClip *nclip = GES_CLIP (ges_timeline_element_copy (element, FALSE)); + + ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (nclip), paste_position); + + /* paste in order of priority (highest first) */ + for (tmp = self->priv->copied_track_elements; tmp; tmp = tmp->next) + ges_clip_copy_track_element_into (nclip, tmp->data, GST_CLOCK_TIME_NONE); + + if (layer) { + if (layer->timeline != self->priv->copied_timeline) { + GST_WARNING_OBJECT (self, "Cannot be pasted into the layer %" + GST_PTR_FORMAT " because its timeline has changed", layer); + gst_object_ref_sink (nclip); + gst_object_unref (nclip); + return NULL; + } + + /* adding the clip to the layer will add it to the tracks, but not + * necessarily the same ones depending on select-tracks-for-object */ + if (!ges_layer_add_clip (layer, nclip)) { + GST_INFO ("%" GES_FORMAT " could not be pasted to %" GST_TIME_FORMAT, + GES_ARGS (element), GST_TIME_ARGS (paste_position)); + + return NULL; + } + } + + /* NOTE: self should not be used and be freed after this call, so we can + * leave the freeing of copied_layer and copied_track_elements to the + * dispose method */ + + return GES_TIMELINE_ELEMENT (nclip); +} + +static gboolean +_lookup_child (GESTimelineElement * self, const gchar * prop_name, + GObject ** child, GParamSpec ** pspec) +{ + GList *tmp; + + if (GES_TIMELINE_ELEMENT_CLASS (ges_clip_parent_class)->lookup_child (self, + prop_name, child, pspec)) + return TRUE; + + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + if (ges_timeline_element_lookup_child (tmp->data, prop_name, child, pspec)) + return TRUE; + } + + return FALSE; +} + +/**************************************************** + * * + * GObject virtual methods implementation * + * * + ****************************************************/ +static void +ges_clip_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESClip *clip = GES_CLIP (object); + + switch (property_id) { + case PROP_LAYER: + g_value_set_object (value, clip->priv->layer); + break; + case PROP_SUPPORTED_FORMATS: + g_value_set_flags (value, clip->priv->supportedformats); + break; + case PROP_DURATION_LIMIT: + g_value_set_uint64 (value, clip->priv->duration_limit); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_clip_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESClip *clip = GES_CLIP (object); + + switch (property_id) { + case PROP_SUPPORTED_FORMATS: + ges_clip_set_supported_formats (clip, g_value_get_flags (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_clip_dispose (GObject * object) +{ + GESClip *self = GES_CLIP (object); + + self->priv->allow_any_remove = TRUE; + + g_list_free_full (self->priv->copied_track_elements, gst_object_unref); + self->priv->copied_track_elements = NULL; + g_clear_object (&self->priv->copied_layer); + + g_clear_error (&self->priv->add_error); + self->priv->add_error = NULL; + g_clear_error (&self->priv->remove_error); + self->priv->remove_error = NULL; + + G_OBJECT_CLASS (ges_clip_parent_class)->dispose (object); +} + + +static void +ges_clip_class_init (GESClipClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESContainerClass *container_class = GES_CONTAINER_CLASS (klass); + GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass); + + object_class->get_property = ges_clip_get_property; + object_class->set_property = ges_clip_set_property; + object_class->dispose = ges_clip_dispose; + klass->create_track_elements = ges_clip_create_track_elements_func; + klass->create_track_element = NULL; + + /** + * GESClip:supported-formats: + * + * The #GESTrackType-s that the clip supports, which it can create + * #GESTrackElement-s for. Note that this can be a combination of + * #GESTrackType flags to indicate support for several + * #GESTrackElement:track-type elements. + */ + properties[PROP_SUPPORTED_FORMATS] = g_param_spec_flags ("supported-formats", + "Supported formats", "Formats supported by the clip", + GES_TYPE_TRACK_TYPE, GES_TRACK_TYPE_AUDIO | GES_TRACK_TYPE_VIDEO, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + g_object_class_install_property (object_class, PROP_SUPPORTED_FORMATS, + properties[PROP_SUPPORTED_FORMATS]); + + /** + * GESClip:layer: + * + * The layer this clip lies in. + * + * If you want to connect to this property's #GObject::notify signal, + * you should connect to it with g_signal_connect_after() since the + * signal emission may be stopped internally. + */ + properties[PROP_LAYER] = g_param_spec_object ("layer", "Layer", + "The GESLayer where this clip is being used.", + GES_TYPE_LAYER, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_property (object_class, PROP_LAYER, + properties[PROP_LAYER]); + + /** + * GESClip:duration-limit: + * + * The maximum #GESTimelineElement:duration that can be *currently* set + * for the clip, taking into account the #GESTimelineElement:in-point, + * #GESTimelineElement:max-duration, #GESTrackElement:active, and + * #GESTrackElement:track properties of its children, as well as any + * time effects. If there is no limit, this will be set to + * #GST_CLOCK_TIME_NONE. + * + * Note that whilst a clip has no children in any tracks, the limit will + * be unknown, and similarly set to #GST_CLOCK_TIME_NONE. + * + * If the duration-limit would ever go below the current + * #GESTimelineElement:duration of the clip due to a change in the above + * variables, its #GESTimelineElement:duration will be set to the new + * limit. + * + * Since: 1.18 + */ + properties[PROP_DURATION_LIMIT] = + g_param_spec_uint64 ("duration-limit", "Duration Limit", + "A limit on the duration of the clip", 0, G_MAXUINT64, + GST_CLOCK_TIME_NONE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_property (object_class, PROP_DURATION_LIMIT, + properties[PROP_DURATION_LIMIT]); + + element_class->set_start = _set_start; + element_class->set_duration = _set_duration; + element_class->set_inpoint = _set_inpoint; + element_class->set_priority = _set_priority; + element_class->set_max_duration = _set_max_duration; + element_class->paste = _paste; + element_class->deep_copy = _deep_copy; + element_class->lookup_child = _lookup_child; + element_class->get_layer_priority = _get_layer_priority; + element_class->get_natural_framerate = _get_natural_framerate; + + container_class->add_child = _add_child; + container_class->remove_child = _remove_child; + container_class->child_removed = _child_removed; + container_class->child_added = _child_added; + container_class->ungroup = _ungroup; + container_class->group = _group; + container_class->grouping_priority = G_MAXUINT; +} + +static void +ges_clip_init (GESClip * self) +{ + self->priv = ges_clip_get_instance_private (self); + self->priv->duration_limit = GST_CLOCK_TIME_NONE; +} + +/** + * ges_clip_create_track_element: + * @clip: A #GESClip + * @type: The track to create an element for + * + * Creates the core #GESTrackElement of the clip, of the given track type. + * + * Returns: (transfer floating) (nullable): The element created + * by @clip, or %NULL if @clip can not provide a track element for the + * given @type or an error occurred. + */ +GESTrackElement * +ges_clip_create_track_element (GESClip * clip, GESTrackType type) +{ + GESClipClass *class; + GESTrackElement *res; + + g_return_val_if_fail (GES_IS_CLIP (clip), NULL); + + GST_DEBUG_OBJECT (clip, "Creating track element for %s", + ges_track_type_name (type)); + if (!(type & clip->priv->supportedformats)) { + GST_DEBUG_OBJECT (clip, "We don't support this track type %i", type); + return NULL; + } + + class = GES_CLIP_GET_CLASS (clip); + + if (G_UNLIKELY (class->create_track_element == NULL)) { + GST_ERROR ("No 'create_track_element' implementation available fo type %s", + G_OBJECT_TYPE_NAME (clip)); + return NULL; + } + + res = class->create_track_element (clip, type); + return res; + +} + +/** + * ges_clip_create_track_elements: + * @clip: A #GESClip + * @type: The track-type to create elements for + * + * Creates the core #GESTrackElement-s of the clip, of the given track + * type. + * + * Returns: (transfer container) (element-type GESTrackElement): A list of + * the #GESTrackElement-s created by @clip for the given @type, or %NULL + * if no track elements are created or an error occurred. + */ + +GList * +ges_clip_create_track_elements (GESClip * clip, GESTrackType type) +{ + GList *tmp, *ret; + GESClipClass *klass; + gboolean already_created = FALSE; + GESAsset *asset; + + g_return_val_if_fail (GES_IS_CLIP (clip), NULL); + + if ((clip->priv->supportedformats & type) == 0) + return NULL; + + klass = GES_CLIP_GET_CLASS (clip); + + if (!(klass->create_track_elements)) { + GST_WARNING ("no GESClip::create_track_elements implentation"); + return NULL; + } + + GST_DEBUG_OBJECT (clip, "Creating TrackElements for type: %s", + ges_track_type_name (type)); + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTrackElement *child = GES_TRACK_ELEMENT (tmp->data); + + if (_IS_CORE_CHILD (child) + && ges_track_element_get_track_type (child) & type) { + /* assume the core track elements have all been created if we find + * at least one core child with the same type */ + already_created = TRUE; + break; + } + } + if (already_created) + return NULL; + + ret = klass->create_track_elements (clip, type); + asset = ges_extractable_get_asset (GES_EXTRACTABLE (clip)); + for (tmp = ret; tmp; tmp = tmp->next) + ges_track_element_set_creator_asset (tmp->data, asset); + return ret; +} + +/* + * default implementation of GESClipClass::create_track_elements + */ +GList * +ges_clip_create_track_elements_func (GESClip * clip, GESTrackType type) +{ + GESTrackElement *result; + + GST_DEBUG_OBJECT (clip, "Creating trackelement for track: %s", + ges_track_type_name (type)); + result = ges_clip_create_track_element (clip, type); + if (!result) { + GST_DEBUG ("Did not create track element"); + return NULL; + } + + return g_list_append (NULL, result); +} + +void +ges_clip_set_layer (GESClip * clip, GESLayer * layer) +{ + if (layer == clip->priv->layer) + return; + + clip->priv->layer = layer; + + GST_DEBUG ("clip:%p, layer:%p", clip, layer); + + /* We do not want to notify the setting of layer = NULL when + * it is actually the result of a move between layer (as we know + * that it will be added to another layer right after, and this + * is what imports here.) */ + if (!ELEMENT_FLAG_IS_SET (clip, GES_CLIP_IS_MOVING)) + g_object_notify_by_pspec (G_OBJECT (clip), properties[PROP_LAYER]); +} + +/** + * ges_clip_set_moving_from_layer: + * @clip: A #GESClip + * @is_moving: %TRUE if you want to start moving @clip to another layer + * %FALSE when you finished moving it + * + * Sets the clip in a moving to layer state. You might rather use the + * ges_clip_move_to_layer function to move #GESClip-s + * from a layer to another. + **/ +void +ges_clip_set_moving_from_layer (GESClip * clip, gboolean is_moving) +{ + g_return_if_fail (GES_IS_CLIP (clip)); + + if (is_moving) + ELEMENT_SET_FLAG (clip, GES_CLIP_IS_MOVING); + else + ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING); +} + +/** + * ges_clip_is_moving_from_layer: + * @clip: A #GESClip + * + * Tells you if the clip is currently moving from a layer to another. + * You might rather use the ges_clip_move_to_layer function to + * move #GESClip-s from a layer to another. + * + * Returns: %TRUE if @clip is currently moving from its current layer + * %FALSE otherwize. + **/ +gboolean +ges_clip_is_moving_from_layer (GESClip * clip) +{ + g_return_val_if_fail (GES_IS_CLIP (clip), FALSE); + + return ELEMENT_FLAG_IS_SET (clip, GES_CLIP_IS_MOVING); +} + +/** + * ges_clip_move_to_layer_full: + * @clip: A #GESClip + * @layer: The new layer + * @error: (nullable): Return location for an error + * + * Moves a clip to a new layer. If the clip already exists in a layer, it + * is first removed from its current layer before being added to the new + * layer. + * + * Returns: %TRUE if @clip was successfully moved to @layer. + * Since: 1.18 + */ +gboolean +ges_clip_move_to_layer_full (GESClip * clip, GESLayer * layer, GError ** error) +{ + gboolean ret = FALSE; + GESLayer *current_layer; + GESTimelineElement *element; + + g_return_val_if_fail (GES_IS_CLIP (clip), FALSE); + g_return_val_if_fail (GES_IS_LAYER (layer), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + element = GES_TIMELINE_ELEMENT (clip); + current_layer = clip->priv->layer; + + if (current_layer == layer) { + GST_INFO_OBJECT (clip, "Already in the layer %" GST_PTR_FORMAT, layer); + return TRUE; + } + + + if (current_layer == NULL) { + GST_DEBUG ("Not moving %p, only adding it to %p", clip, layer); + + return ges_layer_add_clip (layer, clip); + } + + if (element->timeline != layer->timeline) { + /* make sure we can perform the can_move_element_check in the timeline + * of the layer */ + GST_WARNING_OBJECT (layer, "Cannot move clip %" GES_FORMAT " into " + "the layer because its timeline %" GST_PTR_FORMAT " does not " + "match the timeline of the layer %" GST_PTR_FORMAT, + GES_ARGS (clip), element->timeline, layer->timeline); + return FALSE; + } + + if (layer->timeline && !GES_TIMELINE_ELEMENT_BEING_EDITED (clip)) { + /* move to new layer, also checks moving of toplevel */ + return timeline_tree_move (timeline_get_tree (layer->timeline), + element, (gint64) ges_layer_get_priority (current_layer) - + (gint64) ges_layer_get_priority (layer), 0, GES_EDGE_NONE, 0, error); + } + + gst_object_ref (clip); + ELEMENT_SET_FLAG (clip, GES_CLIP_IS_MOVING); + + GST_DEBUG_OBJECT (clip, "moving to layer %p, priority: %d", layer, + ges_layer_get_priority (layer)); + + ret = ges_layer_remove_clip (current_layer, clip); + + if (!ret) { + goto done; + } + + ret = ges_layer_add_clip (layer, clip); + + if (ret) { + g_object_notify_by_pspec (G_OBJECT (clip), properties[PROP_LAYER]); + } else { + /* try and move back into the original layer */ + ges_layer_add_clip (current_layer, clip); + } + +done: + ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING); + gst_object_unref (clip); + + return ret && (clip->priv->layer == layer); +} + +/** + * ges_clip_move_to_layer: + * @clip: A #GESClip + * @layer: The new layer + * + * See ges_clip_move_to_layer_full(), which also gives an error. + * + * Returns: %TRUE if @clip was successfully moved to @layer. + */ +gboolean +ges_clip_move_to_layer (GESClip * clip, GESLayer * layer) +{ + return ges_clip_move_to_layer_full (clip, layer, NULL); +} + +/** + * ges_clip_find_track_element: + * @clip: A #GESClip + * @track: (allow-none): The track to search in, or %NULL to search in + * all tracks + * @type: The type of track element to search for, or `G_TYPE_NONE` to + * match any type + * + * Finds an element controlled by the clip. If @track is given, + * then only the track elements in @track are searched for. If @type is + * given, then this function searches for a track element of the given + * @type. + * + * Note, if multiple track elements in the clip match the given criteria, + * this will return the element amongst them with the highest + * #GESTimelineElement:priority (numerically, the smallest). See + * ges_clip_find_track_elements() if you wish to find all such elements. + * + * Returns: (transfer full) (nullable): The element controlled by + * @clip, in @track, and of the given @type, or %NULL if no such element + * could be found. + */ + +GESTrackElement * +ges_clip_find_track_element (GESClip * clip, GESTrack * track, GType type) +{ + GList *tmp; + GESTrackElement *otmp; + + GESTrackElement *ret = NULL; + + g_return_val_if_fail (GES_IS_CLIP (clip), NULL); + g_return_val_if_fail (!(track == NULL && type == G_TYPE_NONE), NULL); + + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = g_list_next (tmp)) { + otmp = (GESTrackElement *) tmp->data; + + if ((type != G_TYPE_NONE) && !G_TYPE_CHECK_INSTANCE_TYPE (tmp->data, type)) + continue; + + if ((track == NULL) || (ges_track_element_get_track (otmp) == track)) { + ret = GES_TRACK_ELEMENT (tmp->data); + gst_object_ref (ret); + break; + } + } + + return ret; +} + +/** + * ges_clip_get_layer: + * @clip: A #GESClip + * + * Gets the #GESClip:layer of the clip. + * + * Returns: (transfer full) (nullable): The layer @clip is in, or %NULL if + * @clip is not in any layer. + */ +GESLayer * +ges_clip_get_layer (GESClip * clip) +{ + g_return_val_if_fail (GES_IS_CLIP (clip), NULL); + + if (clip->priv->layer != NULL) + gst_object_ref (G_OBJECT (clip->priv->layer)); + + return clip->priv->layer; +} + +/** + * ges_clip_get_duration_limit: + * @clip: A #GESClip + * + * Gets the #GESClip:duration-limit of the clip. + * + * Returns: The duration-limit of @clip. + * Since: 1.18 + */ +GstClockTime +ges_clip_get_duration_limit (GESClip * clip) +{ + g_return_val_if_fail (GES_IS_CLIP (clip), GST_CLOCK_TIME_NONE); + + return clip->priv->duration_limit; +} + +static gint +_cmp_children_by_priority (gconstpointer a_p, gconstpointer b_p) +{ + const GESTimelineElement *a = a_p, *b = b_p; + if (a->priority > b->priority) + return 1; + else if (a->priority < b->priority) + return -1; + return 0; +} + +/** + * ges_clip_add_top_effect: + * @clip: A #GESClip + * @effect: A top effect to add + * @index: The index to add @effect at, or -1 to add at the highest + * @error: (nullable): Return location for an error + * + * Add a top effect to a clip at the given index. + * + * Unlike using ges_container_add(), this allows you to set the index + * in advance. It will also check that no error occurred during the track + * selection for the effect. + * + * Note, only subclasses of #GESClipClass that have + * #GES_CLIP_CLASS_CAN_ADD_EFFECTS set to %TRUE (such as #GESSourceClip + * and #GESBaseEffectClip) can have additional top effects added. + * + * Note, if the effect is a time effect, this may be refused if the clip + * would not be able to adapt itself once the effect is added. + * + * Returns: %TRUE if @effect was successfully added to @clip at @index. + * Since: 1.18 + */ +gboolean +ges_clip_add_top_effect (GESClip * clip, GESBaseEffect * effect, gint index, + GError ** error) +{ + GESClipPrivate *priv; + GList *top_effects; + GESTimelineElement *replace; + GESTimeline *timeline; + gboolean res; + + g_return_val_if_fail (GES_IS_CLIP (clip), FALSE); + g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + priv = clip->priv; + + if (index >= 0) { + top_effects = ges_clip_get_top_effects (clip); + replace = g_list_nth_data (top_effects, index); + if (replace) { + priv->use_effect_priority = TRUE; + priv->effect_priority = replace->priority; + } + g_list_free_full (top_effects, gst_object_unref); + } + /* otherwise the default _add_child will place it at the lowest + * priority / highest index */ + + + timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip); + if (timeline) + ges_timeline_set_track_selection_error (timeline, FALSE, NULL); + + /* note, if several tracks are selected, this may lead to several + * effects being added to the clip. + * The first effect we are adding will use the set effect_priority. + * The error on the timeline could be from any of the copies */ + ges_clip_set_add_error (clip, NULL); + res = ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (effect)); + + priv->use_effect_priority = FALSE; + + if (!res) { + /* if adding fails, there should have been no track selection, which + * means no other elements were added so the clip, so the adding error + * for the effect, if any, should still be available on the clip */ + ges_clip_take_add_error (clip, error); + return FALSE; + } + + if (timeline && ges_timeline_take_track_selection_error (timeline, error)) + goto remove; + + return TRUE; + +remove: + if (!ges_container_remove (GES_CONTAINER (clip), + GES_TIMELINE_ELEMENT (effect))) + GST_ERROR_OBJECT (clip, "Failed to remove effect %" GES_FORMAT, + GES_ARGS (effect)); + + return FALSE; +} + +static gboolean +_is_added_effect (GESClip * clip, GESBaseEffect * effect) +{ + if (GES_TIMELINE_ELEMENT_PARENT (effect) != GES_TIMELINE_ELEMENT (clip)) { + GST_WARNING_OBJECT (clip, "The effect %" GES_FORMAT + " does not belong to this clip", GES_ARGS (effect)); + return FALSE; + } + if (!_IS_TOP_EFFECT (effect)) { + GST_WARNING_OBJECT (clip, "The effect %" GES_FORMAT " is not a top " + "effect of this clip (it is a core element of the clip)", + GES_ARGS (effect)); + return FALSE; + } + return TRUE; +} + +/** + * ges_clip_remove_top_effect: + * @clip: A #GESClip + * @effect: The top effect to remove + * @error: (nullable): Return location for an error + * + * Remove a top effect from the clip. + * + * Note, if the effect is a time effect, this may be refused if the clip + * would not be able to adapt itself once the effect is removed. + * + * Returns: %TRUE if @effect was successfully added to @clip at @index. + * Since: 1.18 + */ +gboolean +ges_clip_remove_top_effect (GESClip * clip, GESBaseEffect * effect, + GError ** error) +{ + gboolean res; + + g_return_val_if_fail (GES_IS_CLIP (clip), FALSE); + g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + if (!_is_added_effect (clip, effect)) + return FALSE; + + ges_clip_set_remove_error (clip, NULL); + res = ges_container_remove (GES_CONTAINER (clip), + GES_TIMELINE_ELEMENT (effect)); + if (!res) + ges_clip_take_remove_error (clip, error); + + return res; +} + +/** + * ges_clip_get_top_effects: + * @clip: A #GESClip + * + * Gets the #GESBaseEffect-s that have been added to the clip. The + * returned list is ordered by their internal index in the clip. See + * ges_clip_get_top_effect_index(). + * + * Returns: (transfer full) (element-type GESTrackElement): A list of all + * #GESBaseEffect-s that have been added to @clip. + */ +GList * +ges_clip_get_top_effects (GESClip * clip) +{ + GList *tmp, *ret; + GESTimelineElement *child; + + g_return_val_if_fail (GES_IS_CLIP (clip), NULL); + + GST_DEBUG_OBJECT (clip, "Getting the %i top effects", clip->priv->nb_effects); + ret = NULL; + + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + child = tmp->data; + if (_IS_TOP_EFFECT (child)) + ret = g_list_append (ret, gst_object_ref (child)); + } + + return g_list_sort (ret, _cmp_children_by_priority); +} + +/** + * ges_clip_get_top_effect_index: + * @clip: A #GESClip + * @effect: The effect we want to get the index of + * + * Gets the internal index of an effect in the clip. The index of effects + * in a clip will run from 0 to n-1, where n is the total number of + * effects. If two effects share the same #GESTrackElement:track, the + * effect with the numerically lower index will be applied to the source + * data **after** the other effect, i.e. output data will always flow from + * a higher index effect to a lower index effect. + * + * Returns: The index of @effect in @clip, or -1 if something went wrong. + */ +gint +ges_clip_get_top_effect_index (GESClip * clip, GESBaseEffect * effect) +{ + GList *top_effects; + gint ret; + + g_return_val_if_fail (GES_IS_CLIP (clip), -1); + g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), -1); + if (!_is_added_effect (clip, effect)) + return -1; + + top_effects = ges_clip_get_top_effects (clip); + ret = g_list_index (top_effects, effect); + g_list_free_full (top_effects, gst_object_unref); + + return ret; +} + +/* TODO 2.0 remove as it is Deprecated */ +gint +ges_clip_get_top_effect_position (GESClip * clip, GESBaseEffect * effect) +{ + return ges_clip_get_top_effect_index (clip, effect); +} + +/* TODO 2.0 remove as it is Deprecated */ +gboolean +ges_clip_set_top_effect_priority (GESClip * clip, + GESBaseEffect * effect, guint newpriority) +{ + return ges_clip_set_top_effect_index (clip, effect, newpriority); +} + +/** + * ges_clip_set_top_effect_index_full: + * @clip: A #GESClip + * @effect: An effect within @clip to move + * @newindex: The index for @effect in @clip + * @error: (nullable): Return location for an error + * + * Set the index of an effect within the clip. See + * ges_clip_get_top_effect_index(). The new index must be an existing + * index of the clip. The effect is moved to the new index, and the other + * effects may be shifted in index accordingly to otherwise maintain the + * ordering. + * + * Returns: %TRUE if @effect was successfully moved to @newindex. + * Since: 1.18 + */ +gboolean +ges_clip_set_top_effect_index_full (GESClip * clip, GESBaseEffect * effect, + guint newindex, GError ** error) +{ + gint inc; + GList *top_effects, *tmp; + GList *child_data = NULL; + guint32 current_prio, new_prio; + GESTimelineElement *element, *replace; + + g_return_val_if_fail (GES_IS_CLIP (clip), FALSE); + g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + if (!_is_added_effect (clip, effect)) + return FALSE; + + element = GES_TIMELINE_ELEMENT (effect); + + top_effects = ges_clip_get_top_effects (clip); + replace = g_list_nth_data (top_effects, newindex); + g_list_free_full (top_effects, gst_object_unref); + + if (!replace) { + GST_WARNING_OBJECT (clip, "Does not contain %u effects", newindex + 1); + return FALSE; + } + + if (replace == element) + return TRUE; + + current_prio = element->priority; + new_prio = replace->priority; + + if (current_prio < new_prio) + inc = -1; + else + inc = +1; + + /* check that the duration-limit can be changed */ + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + guint32 priority = child->priority; + DurationLimitData *data = + _duration_limit_data_new (GES_TRACK_ELEMENT (child)); + + if (child == element) + data->priority = new_prio; + else if ((inc == +1 && priority >= new_prio && priority < current_prio) + || (inc == -1 && priority <= new_prio && priority > current_prio)) + data->priority = child->priority + inc; + + child_data = g_list_prepend (child_data, data); + } + + if (!_can_update_duration_limit (clip, child_data, error)) { + GST_INFO_OBJECT (clip, "Cannot move top effect %" GES_FORMAT + " to index %i because the duration-limit cannot adjust", + GES_ARGS (effect), newindex); + return FALSE; + } + + GST_DEBUG_OBJECT (clip, "Setting top effect %" GST_PTR_FORMAT "priority: %i", + effect, new_prio); + + /* prevent a re-sort of the list whilst we are traversing it! */ + clip->priv->prevent_resort = TRUE; + clip->priv->setting_priority = TRUE; + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + guint32 priority = child->priority; + + if (child == element) + continue; + + /* only need to change the priority for those between the new and old + * index */ + if ((inc == +1 && priority >= new_prio && priority < current_prio) + || (inc == -1 && priority <= new_prio && priority > current_prio)) + _set_priority0 (child, priority + inc); + } + _set_priority0 (element, new_prio); + + clip->priv->prevent_resort = FALSE; + clip->priv->setting_priority = FALSE; + _ges_container_sort_children (GES_CONTAINER (clip)); + /* height should have stayed the same */ + + return TRUE; +} + +/** + * ges_clip_set_top_effect_index: + * @clip: A #GESClip + * @effect: An effect within @clip to move + * @newindex: The index for @effect in @clip + * + * See ges_clip_set_top_effect_index_full(), which also gives an error. + * + * Returns: %TRUE if @effect was successfully moved to @newindex. + */ +gboolean +ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect, + guint newindex) +{ + return ges_clip_set_top_effect_index_full (clip, effect, newindex, NULL); +} + +/** + * ges_clip_split_full: + * @clip: The #GESClip to split + * @position: The timeline position at which to perform the split, between + * the start and end of the clip + * @error: (nullable): Return location for an error + * + * Splits a clip at the given timeline position into two clips. The clip + * must already have a #GESClip:layer. + * + * The original clip's #GESTimelineElement:duration is reduced such that + * its end point matches the split position. Then a new clip is created in + * the same layer, whose #GESTimelineElement:start matches the split + * position and #GESTimelineElement:duration will be set such that its end + * point matches the old end point of the original clip. Thus, the two + * clips together will occupy the same positions in the timeline as the + * original clip did. + * + * The children of the new clip will be new copies of the original clip's + * children, so it will share the same sources and use the same + * operations. + * + * The new clip will also have its #GESTimelineElement:in-point set so + * that any internal data will appear in the timeline at the same time. + * Thus, when the timeline is played, the playback of data should + * appear the same. This may be complicated by any additional + * #GESEffect-s that have been placed on the original clip that depend on + * the playback time or change the data consumption rate of sources. This + * method will attempt to translate these effects such that the playback + * appears the same. In such complex situations, you may get a better + * result if you place the clip in a separate sub #GESProject, which only + * contains this clip (and its effects), and in the original layer + * create two neighbouring #GESUriClip-s that reference this sub-project, + * but at a different #GESTimelineElement:in-point. + * + * Returns: (transfer none) (nullable): The newly created clip resulting + * from the splitting @clip, or %NULL if @clip can't be split. + * Since: 1.18 + */ +GESClip * +ges_clip_split_full (GESClip * clip, guint64 position, GError ** error) +{ + GList *tmp, *transitions = NULL; + GESClip *new_object; + gboolean no_core = FALSE; + GstClockTime start, duration, old_duration, new_duration, new_inpoint; + GESTimelineElement *element; + GESTimeline *timeline; + GHashTable *track_for_copy; + guint32 layer_prio; + + g_return_val_if_fail (GES_IS_CLIP (clip), NULL); + g_return_val_if_fail (clip->priv->layer, NULL); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), NULL); + g_return_val_if_fail (!error || !*error, NULL); + + element = GES_TIMELINE_ELEMENT (clip); + timeline = element->timeline; + + duration = element->duration; + start = element->start; + + if (position >= start + duration || position <= start) { + GST_WARNING_OBJECT (clip, "Can not split %" GST_TIME_FORMAT + " out of boundaries", GST_TIME_ARGS (position)); + return NULL; + } + + layer_prio = ges_timeline_element_get_layer_priority (element); + + old_duration = position - start; + new_duration = duration + start - position; + /* convert the split position into an internal core time */ + new_inpoint = _convert_core_time (clip, position, FALSE, &no_core, error); + + /* if the split clip does not contain any active core elements with + * an internal source, just set the in-point to 0 for the new_object */ + if (no_core) + new_inpoint = 0; + + if (!GST_CLOCK_TIME_IS_VALID (new_inpoint)) + return NULL; + + if (timeline + && !timeline_tree_can_move_element (timeline_get_tree (timeline), element, + layer_prio, start, old_duration, error)) { + GST_INFO_OBJECT (clip, + "Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT + " as timeline would be in an illegal state.", GES_ARGS (clip), + GST_TIME_ARGS (position)); + return NULL; + } + + if (timeline + && !timeline_tree_can_move_element (timeline_get_tree (timeline), element, + layer_prio, position, new_duration, error)) { + GST_INFO_OBJECT (clip, + "Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT + " as timeline would be in an illegal state.", GES_ARGS (clip), + GST_TIME_ARGS (position)); + return NULL; + } + + GST_DEBUG_OBJECT (clip, "Spliting at %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + + /* Create the new Clip */ + new_object = GES_CLIP (ges_timeline_element_copy (element, FALSE)); + new_object->priv->prevent_duration_limit_update = TRUE; + new_object->priv->prevent_children_outpoint_update = TRUE; + + GST_DEBUG_OBJECT (new_object, "New 'splitted' clip"); + /* Set new timing properties on the Clip */ + _set_start0 (GES_TIMELINE_ELEMENT (new_object), position); + _set_inpoint0 (GES_TIMELINE_ELEMENT (new_object), new_inpoint); + _set_duration0 (GES_TIMELINE_ELEMENT (new_object), new_duration); + + /* NOTE: it is technically possible that the new_object may shrink + * later on in this method if the clip contains any non-linear time + * effects, which cause the duration-limit to drop. However, this + * should be safe since we have already checked with timeline-tree + * that the split position is not in the middle of an overlap. This + * means that the new_object should only be overlapping another + * element on its end, which makes shrinking safe. + * + * The original clip, however, should not shrink if the time effects + * obey the property that they do not depend on how much data they + * receive, which should be true for the time effects supported by GES. + */ + + /* split binding before duration changes since shrinking can destroy + * binding values */ + track_for_copy = g_hash_table_new_full (NULL, NULL, + gst_object_unref, gst_object_unref); + + /* _add_child will add core elements at the lowest priority and new + * non-core effects at the lowest effect priority, so we need to add the + * highest priority children first to preserve the effect order. The + * clip's children are already ordered by highest priority first. */ + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTrackElement *copy, *orig = tmp->data; + GESTrack *track = ges_track_element_get_track (orig); + GESAutoTransition *trans; + gchar *meta; + + copy = ges_clip_copy_track_element_into (new_object, orig, new_inpoint); + + if (!copy) + continue; + + if (track) + g_hash_table_insert (track_for_copy, gst_object_ref (copy), + gst_object_ref (track)); + + meta = ges_meta_container_metas_to_string (GES_META_CONTAINER (orig)); + ges_meta_container_add_metas_from_string (GES_META_CONTAINER (copy), meta); + g_free (meta); + + trans = timeline ? + ges_timeline_get_auto_transition_at_edge (timeline, orig, + GES_EDGE_END) : NULL; + + if (trans) { + trans->frozen = TRUE; + ges_auto_transition_set_source (trans, copy, GES_EDGE_START); + transitions = g_list_append (transitions, trans); + } + } + + GES_TIMELINE_ELEMENT_SET_BEING_EDITED (clip); + _set_duration0 (GES_TIMELINE_ELEMENT (clip), old_duration); + GES_TIMELINE_ELEMENT_UNSET_BEING_EDITED (clip); + + /* We do not want the timeline to create again TrackElement-s */ + ges_clip_set_moving_from_layer (new_object, TRUE); + /* adding to the same layer should not fail when moving */ + ges_layer_add_clip (clip->priv->layer, new_object); + ges_clip_set_moving_from_layer (new_object, FALSE); + + /* add to the track after the duration change so we don't overlap! */ + for (tmp = GES_CONTAINER_CHILDREN (new_object); tmp; tmp = tmp->next) { + GESTrackElement *copy = tmp->data; + GESTrack *track = g_hash_table_lookup (track_for_copy, copy); + if (track) { + new_object->priv->allow_any_track = TRUE; + ges_track_add_element (track, copy); + new_object->priv->allow_any_track = FALSE; + } + } + + for (tmp = transitions; tmp; tmp = tmp->next) { + GESAutoTransition *trans = tmp->data; + trans->frozen = FALSE; + ges_auto_transition_update (trans); + } + + g_hash_table_unref (track_for_copy); + g_list_free_full (transitions, gst_object_unref); + + new_object->priv->prevent_duration_limit_update = FALSE; + new_object->priv->prevent_children_outpoint_update = FALSE; + _update_duration_limit (new_object); + _update_children_outpoints (new_object); + + return new_object; +} + +/** + * ges_clip_split: + * @clip: The #GESClip to split + * @position: The timeline position at which to perform the split + * + * See ges_clip_split_full(), which also gives an error. + * + * Returns: (transfer none) (nullable): The newly created clip resulting + * from the splitting @clip, or %NULL if @clip can't be split. + */ +GESClip * +ges_clip_split (GESClip * clip, guint64 position) +{ + return ges_clip_split_full (clip, position, NULL); +} + +/** + * ges_clip_set_supported_formats: + * @clip: A #GESClip + * @supportedformats: The #GESTrackType-s supported by @clip + * + * Sets the #GESClip:supported-formats of the clip. This should normally + * only be called by subclasses, which should be responsible for updating + * its value, rather than the user. + */ +void +ges_clip_set_supported_formats (GESClip * clip, GESTrackType supportedformats) +{ + g_return_if_fail (GES_IS_CLIP (clip)); + + clip->priv->supportedformats = supportedformats; +} + +/** + * ges_clip_get_supported_formats: + * @clip: A #GESClip + * + * Gets the #GESClip:supported-formats of the clip. + * + * Returns: The #GESTrackType-s supported by @clip. + */ +GESTrackType +ges_clip_get_supported_formats (GESClip * clip) +{ + g_return_val_if_fail (GES_IS_CLIP (clip), GES_TRACK_TYPE_UNKNOWN); + + return clip->priv->supportedformats; +} + +/** + * ges_clip_add_asset: + * @clip: A #GESClip + * @asset: An asset with #GES_TYPE_TRACK_ELEMENT as its + * #GESAsset:extractable-type + * + * Extracts a #GESTrackElement from an asset and adds it to the clip. + * This can be used to add effects that derive from the asset to the + * clip, but this method is not intended to be used to create the core + * elements of the clip. + * + * Returns: (transfer none)(allow-none): The newly created element, or + * %NULL if an error occurred. + */ +/* FIXME: this is not used elsewhere in the GES library */ +GESTrackElement * +ges_clip_add_asset (GESClip * clip, GESAsset * asset) +{ + GESTrackElement *element; + + g_return_val_if_fail (GES_IS_CLIP (clip), NULL); + g_return_val_if_fail (GES_IS_ASSET (asset), NULL); + g_return_val_if_fail (g_type_is_a (ges_asset_get_extractable_type + (asset), GES_TYPE_TRACK_ELEMENT), NULL); + + element = GES_TRACK_ELEMENT (ges_asset_extract (asset, NULL)); + + if (!ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (element))) + return NULL; + + return element; + +} + +/** + * ges_clip_find_track_elements: + * @clip: A #GESClip + * @track: (allow-none): The track to search in, or %NULL to search in + * all tracks + * @track_type: The track-type of the track element to search for, or + * #GES_TRACK_TYPE_UNKNOWN to match any track type + * @type: The type of track element to search for, or %G_TYPE_NONE to + * match any type + * + * Finds the #GESTrackElement-s controlled by the clip that match the + * given criteria. If @track is given as %NULL and @track_type is given as + * #GES_TRACK_TYPE_UNKNOWN, then the search will match all elements in any + * track, including those with no track, and of any + * #GESTrackElement:track-type. Otherwise, if @track is not %NULL, but + * @track_type is #GES_TRACK_TYPE_UNKNOWN, then only the track elements in + * @track are searched for. Otherwise, if @track_type is not + * #GES_TRACK_TYPE_UNKNOWN, but @track is %NULL, then only the track + * elements whose #GESTrackElement:track-type matches @track_type are + * searched for. Otherwise, when both are given, the track elements that + * match **either** criteria are searched for. Therefore, if you wish to + * only find elements in a specific track, you should give the track as + * @track, but you should not give the track's #GESTrack:track-type as + * @track_type because this would also select elements from other tracks + * of the same type. + * + * You may also give @type to _further_ restrict the search to track + * elements of the given @type. + * + * Returns: (transfer full) (element-type GESTrackElement): A list of all + * the #GESTrackElement-s controlled by @clip, in @track or of the given + * @track_type, and of the given @type. + */ + +GList * +ges_clip_find_track_elements (GESClip * clip, GESTrack * track, + GESTrackType track_type, GType type) +{ + GList *tmp; + GESTrackElement *otmp; + + GList *ret = NULL; + + g_return_val_if_fail (GES_IS_CLIP (clip), NULL); + g_return_val_if_fail (!(track == NULL && type == G_TYPE_NONE && + track_type == GES_TRACK_TYPE_UNKNOWN), NULL); + + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = g_list_next (tmp)) { + otmp = (GESTrackElement *) tmp->data; + + if ((type != G_TYPE_NONE) && !G_TYPE_CHECK_INSTANCE_TYPE (tmp->data, type)) + continue; + + /* TODO 2.0: an AND condition, using a condition like the above type + * check would have made more sense here. Especially when both + * track != NULL and track_type != GES_TRACK_TYPE_UNKNOWN are given */ + if ((track == NULL && track_type == GES_TRACK_TYPE_UNKNOWN) || + (track != NULL && ges_track_element_get_track (otmp) == track) || + (track_type != GES_TRACK_TYPE_UNKNOWN + && ges_track_element_get_track_type (otmp) == track_type)) + ret = g_list_append (ret, gst_object_ref (otmp)); + } + + return ret; +} + +/* Convert from an internal time of a child within a clip to a + * =========================================================== + * timeline time + * ============= + * + * Given an internal time T for some child in a clip, we want to know + * what the corresponding time in the timeline is. + * + * If the time T is between the in-point and out-point of the child, + * then we can convert to the timeline coordinates by answering: + * + * a) "What is the timeline time at which the internal data from the child + * found at time T appears in the timeline output?" + * + * If the time T is after the out-point of the child, we instead want to + * answer: + * + * b) "If we extended the clip indefinetly in the timeline, what would be + * the timeline time at which the internal data from the child found at + * time T would appear in the timeline output?" + * + * However, if the time T is before the in-point of the child, we instead + * want to answer a more subtle question: + * + * c) "If we set the 'in-point' of the child to T, what would we need to + * set the 'start' of the clip to such that the internal data from the + * child currently found at the *beginning* of the clip would then appear + * at the same timeline time?" + * + * E.g. consider the following children of a clip, all in the same track, + * and all active: + * T + * : + * +=====================:======+ + * | _/ \_ | + * | source ~(o_o)~ | + * | / @ \ | + * +=====================:======+ + * i : + * : + * +=====================:======+ + * | time-effect0 : | | g0 + * +=====================:======+ v + * : + * +=====================:======+ + * | overlay : | + * +=====================:======+ + * i' : + * : + * +=====================:======+ + * | time-effect1 : | | g1 + * +=====================:======+ v + * : + * -------------------------------:-------------------timeline + * S X + * + * where i is the in-point of the source and i' is the in-point of the + * overlay. Also, g0 is the sink_to_source translation function for the + * first time effect, and g1 is the same for the second. S is the start of + * the clip. The ~(o_o)~ figure is the data that appears in the source at + * T. + * + * Essentially, question a) wants us to convert from the time T, where the + * data is, which is in the internal time coordinates of the source, to + * the timeline time X. First, we subtract i to convert from the internal + * source coordinates of the source to the external source coordinates of + * the source, then we apply the sink_to_source translation functions, + * which act on external source coordinates, then add 'start' to finally + * convert to the timeline coordinates. So overall we have + * + * X = S + g1(g0(T - i)) + * + * To answer b), T would be beyond the end of the clip. Since g1 and g0 + * can convert beyond the end time, we similarly compute + * + * X = S + g1(g0(T - i)) + * + * The user themselves should note that this could exceed the max-duration + * of any of the children. + * + * Now consider + * + * T + * : + * : +============================+ + * : \_ | + * : _o)~ source | + * : @ \ | + * : +============================+ + * : i + * : + * : +============================+ + * : | time-effect0 | | g0 + * : +============================+ v + * : + * : +============================+ + * : | overlay | + * : +============================+ + * : i' + * : + * : +============================+ + * : | time-effect1 | | g1 + * : +============================+ v + * : + * ---:-----------------------------------------------timeline + * X S + * + * To do the same as a), we would need to be able to convert from T to X, + * but this isn't defined since the children do not extend to here. More + * specifically, the functions g0 and g1 are not defined for negative + * times. Instead, we want to answer question c). That is, we want to know + * what we should set the start of the clip to to keep the figure at the + * same timeline position if we change the in-point of the source to T. + * + * First, if we set the in-point to T, then we would have + * + * T + * : + * +============================+ + * | _/ \_ | + * | ~(o_o)~ source | + * | / @ \ | + * +============================+ + * : i + * : : + * +=====:======================+ + * | : time-effect0 | | g0 + * +=====:======================+ v + * : : + * +=====:======================+ + * | : overlay | + * +=====:======================+ + * : : + * +=====:======================+ + * | : time-effect1 | | g1 + * +=====:======================+ v + * : : + * ---:-----:-----:-----------------------------------timeline + * X S Y + * + * In order to make the figure appear at 'start' again, we would need to + * reduce the start of the clip by the difference between S and Y, where + * Y is the conversion of the previous in-point i to the timeline time. + * + * Thus, + * + * X = S - (Y - S) + * = S - (S + g1(g0(i - T)) - S) + * = S - g1(g0(i - T)) + * + * If this would be negative, the conversion will not be possible. + * + * Note, we are relying on the *assumption* that the translation functions + * *do not* change when we change the in-point. GESBaseEffect only claims + * to support such time effects. + * + * Note that if g0 and g1 are simply identities, and we translate the + * internal time using a) and b), we calculate + * + * S + (T - i) + * + * and for c), we calculate + * + * S - (i - T) = S + (T - i) + * + * In summary, if we are converting from internal time T to a timeline + * time the return is + * + * G(T) = { S + g1(g0(T - i)) if T >= i, + * { S - g1(g0(i - T)) otherwise. + * + * Note that the overlay did not play a role since it overall translates + * all received times by the identity. Note that we could similarly want + * to convert from an internal time in the overlay to the timeline time. + * This would be given by + * + * S + g1(T - i') if T >= i', + * S - g1(i' - T) otherwise. + * + * + * Convert from a timeline time to an internal time of a child + * =========================================================== + * in a clip + * ========= + * + * We basically want to reverse the previous conversion. Specifically, + * when the timeline time X is between the start and end of the clip we + * want to answer: + * + * d) "What is the internal time at which the data from the child that + * appears in the timeline at time X is created in the child?" + * + * If the time X is after the end of the clip, we instead want to answer: + * + * e) "If we extended the clip indefinetly in the timeline, what would be + * the internal time at which the data from the child that appears in the + * timeline at time T would be created in the child?" + * + * However, if the time X is before the start of the child, we instead + * want to answer: + * + * f) "If we set the 'start' of the clip to X, what would we need to + * set the 'in-point' of the clip to such that the internal data from the + * child currently found at the *beginning* of the clip would then appear + * at the same timeline time?" + * + * Following the same arguments, these would all be answered by + * + * F(X) = { i + f0(f1(X - S)) if X >= S, + * { i - f0(f1(S - X)) otherwise. + * + * where f0 and f1 are the corresponding source_to_sink translation + * functions, which should be close reverses of g0 and g1, respectively. + * + * Note that this does indeed reverse the internal to timeline conversion: + * + * F(G(T)) = { i + f0(f1(G(T) - S)) if G(T) >= S, + * { i - f0(f1(S - G(T))) otherwise. + * + * but, since g1 and g0 map from [0,inf) to [0,inf), + * + * G(T) - S = { + g1(g0(T - i)) if T >= i, + * { - g1(g0(i - T)) otherwise. + * { >= 0 if T >= i, + * { = 0 if (T < i and g1(g0(i - T)) = 0) + * { < 0 otherwise. + * + * => ( G(T) >= S <==> T >= i or (T < i and g1(g0(i - T)) = 0) ) + * + * therefore + * F(G(T)) = { i + f0(f1(g1(g0(T - i)))) if T >= i, + * { i + f0(f1(0)) if T < i + * { and g1(g0(i - T)) = 0, + * { i - f0(f1(g1(g0(i - T)))) otherwise + * + * = { i + f0(f1(g1(g0(T - i)))) if T >= i, + * { i - f0(f1(g1(g0(i - T)))) otherwise + * + * = T + * + * because f1 reverses g1, and f0 reverses g0. + */ + +/* returns higher priority first */ +static GList * +_active_time_effects_in_track_after_priority (GESClip * clip, + GESTrack * track, guint32 priority) +{ + GList *tmp, *list = NULL; + + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTrackElement *child = tmp->data; + + if (GES_IS_TIME_EFFECT (child) + && ges_track_element_get_track (child) == track + && ges_track_element_is_active (child) + && _PRIORITY (child) < priority) + list = g_list_prepend (list, child); + } + + return g_list_sort (list, _cmp_children_by_priority); +} + +/** + * ges_clip_get_timeline_time_from_internal_time: + * @clip: A #GESClip + * @child: An #GESTrackElement:active child of @clip with a + * #GESTrackElement:track + * @internal_time: A time in the internal time coordinates of @child + * @error: (nullable): Return location for an error + * + * Convert the internal source time from the child to a timeline time. + * This will take any time effects placed on the clip into account (see + * #GESBaseEffect for what time effects are supported, and how to + * declare them in GES). + * + * When @internal_time is above the #GESTimelineElement:in-point of + * @child, this will return the timeline time at which the internal + * content found at @internal_time appears in the output of the timeline's + * track. For example, this would let you know where in the timeline a + * particular scene in a media file would appear. + * + * This will be done assuming the clip has an indefinite end, so the + * timeline time may be beyond the end of the clip, or even breaking its + * #GESClip:duration-limit. + * + * If, instead, @internal_time is below the current + * #GESTimelineElement:in-point of @child, this will return what you would + * need to set the #GESTimelineElement:start of @clip to if you set the + * #GESTimelineElement:in-point of @child to @internal_time and wanted to + * keep the content of @child currently found at the current + * #GESTimelineElement:start of @clip at the same timeline position. If + * this would be negative, the conversion fails. This is useful for + * determining what position to use in a #GES_EDIT_MODE_TRIM if you wish + * to trim to a specific point in the internal content, such as a + * particular scene in a media file. + * + * Note that whilst a clip has no time effects, this second return is + * equivalent to finding the timeline time at which the content of @child + * at @internal_time would be found in the timeline if it had indefinite + * extent in both directions. However, with non-linear time effects this + * second return will be more distinct. + * + * In either case, the returned time would be appropriate to use in + * ges_timeline_element_edit() for #GES_EDIT_MODE_TRIM, and similar, if + * you wish to use a particular internal point as a reference. For + * example, you could choose to end a clip at a certain internal + * 'out-point', similar to the #GESTimelineElement:in-point, by + * translating the desired end time into the timeline coordinates, and + * using this position to trim the end of a clip. + * + * See ges_clip_get_internal_time_from_timeline_time(), which performs the + * reverse, or ges_clip_get_timeline_time_from_source_frame() which does + * the same conversion, but using frame numbers. + * + * Returns: The time in the timeline coordinates corresponding to + * @internal_time, or #GST_CLOCK_TIME_NONE if the conversion could not be + * performed. + * + * Since: 1.18 + */ +GstClockTime +ges_clip_get_timeline_time_from_internal_time (GESClip * clip, + GESTrackElement * child, GstClockTime internal_time, GError ** error) +{ + GstClockTime inpoint, start, external_time; + gboolean decrease; + GESTrack *track; + GList *tmp, *time_effects; + + g_return_val_if_fail (GES_IS_CLIP (clip), GST_CLOCK_TIME_NONE); + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (child), GST_CLOCK_TIME_NONE); + g_return_val_if_fail (!error || !*error, GST_CLOCK_TIME_NONE); + + if (!g_list_find (GES_CONTAINER_CHILDREN (clip), child)) { + GST_WARNING_OBJECT (clip, "The track element %" GES_FORMAT " is not " + "a child of the clip", GES_ARGS (child)); + return GST_CLOCK_TIME_NONE; + } + + track = ges_track_element_get_track (child); + + if (!track) { + GST_WARNING_OBJECT (clip, "Cannot convert the internal time of the " + "child %" GES_FORMAT " to a timeline time because it is not part " + "of a track", GES_ARGS (child)); + return GST_CLOCK_TIME_NONE; + } + + if (!ges_track_element_is_active (child)) { + GST_WARNING_OBJECT (clip, "Cannot convert the internal time of the " + "child %" GES_FORMAT " to a timeline time because it is not " + "active in its track", GES_ARGS (child)); + return GST_CLOCK_TIME_NONE; + } + + if (internal_time == GST_CLOCK_TIME_NONE) + return GST_CLOCK_TIME_NONE; + + inpoint = _INPOINT (child); + if (inpoint <= internal_time) { + decrease = FALSE; + external_time = internal_time - inpoint; + } else { + decrease = TRUE; + external_time = inpoint - internal_time; + } + + time_effects = _active_time_effects_in_track_after_priority (clip, track, + _PRIORITY (child)); + + /* currently ordered with highest priority (closest to the timeline) + * first, with @child being at the *end* of the list. + * Want to reverse this so we can convert from the child towards the + * timeline */ + time_effects = g_list_reverse (time_effects); + + for (tmp = time_effects; tmp; tmp = tmp->next) { + GESBaseEffect *effect = tmp->data; + GHashTable *values = ges_base_effect_get_time_property_values (effect); + + external_time = ges_base_effect_translate_sink_to_source_time (effect, + external_time, values); + g_hash_table_unref (values); + } + + g_list_free (time_effects); + + if (!GST_CLOCK_TIME_IS_VALID (external_time)) + return GST_CLOCK_TIME_NONE; + + start = _START (clip); + + if (!decrease) + return start + external_time; + + if (external_time > start) { + GST_INFO_OBJECT (clip, "Cannot convert the internal time %" + GST_TIME_FORMAT " of the child %" GES_FORMAT " to a timeline " + "time because it would lie before the start of the timeline", + GST_TIME_ARGS (internal_time), GES_ARGS (child)); + + g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME, + "The internal time %" GST_TIME_FORMAT " of child \"%s\" " + "would correspond to a negative start of -%" GST_TIME_FORMAT + " for the clip \"%s\"", GST_TIME_ARGS (internal_time), + GES_TIMELINE_ELEMENT_NAME (child), + GST_TIME_ARGS (external_time - start), + GES_TIMELINE_ELEMENT_NAME (clip)); + + return GST_CLOCK_TIME_NONE; + } + + return start - external_time; +} + +/** + * ges_clip_get_internal_time_from_timeline_time: + * @clip: A #GESClip + * @child: An #GESTrackElement:active child of @clip with a + * #GESTrackElement:track + * @timeline_time: A time in the timeline time coordinates + * @error: (nullable): Return location for an error + * + * Convert the timeline time to an internal source time of the child. + * This will take any time effects placed on the clip into account (see + * #GESBaseEffect for what time effects are supported, and how to + * declare them in GES). + * + * When @timeline_time is above the #GESTimelineElement:start of @clip, + * this will return the internal time at which the content that appears at + * @timeline_time in the output of the timeline is created in @child. For + * example, if @timeline_time corresponds to the current seek position, + * this would let you know which part of a media file is being read. + * + * This will be done assuming the clip has an indefinite end, so the + * internal time may be beyond the current out-point of the child, or even + * its #GESTimelineElement:max-duration. + * + * If, instead, @timeline_time is below the current + * #GESTimelineElement:start of @clip, this will return what you would + * need to set the #GESTimelineElement:in-point of @child to if you set + * the #GESTimelineElement:start of @clip to @timeline_time and wanted + * to keep the content of @child currently found at the current + * #GESTimelineElement:start of @clip at the same timeline position. If + * this would be negative, the conversion fails. This is useful for + * determining what #GESTimelineElement:in-point would result from a + * #GES_EDIT_MODE_TRIM to @timeline_time. + * + * Note that whilst a clip has no time effects, this second return is + * equivalent to finding the internal time at which the content that + * appears at @timeline_time in the timeline can be found in @child if it + * had indefinite extent in both directions. However, with non-linear time + * effects this second return will be more distinct. + * + * In either case, the returned time would be appropriate to use for the + * #GESTimelineElement:in-point or #GESTimelineElement:max-duration of the + * child. + * + * See ges_clip_get_timeline_time_from_internal_time(), which performs the + * reverse. + * + * Returns: The time in the internal coordinates of @child corresponding + * to @timeline_time, or #GST_CLOCK_TIME_NONE if the conversion could not + * be performed. + * Since: 1.18 + */ +GstClockTime +ges_clip_get_internal_time_from_timeline_time (GESClip * clip, + GESTrackElement * child, GstClockTime timeline_time, GError ** error) +{ + GstClockTime inpoint, start, external_time; + gboolean decrease; + GESTrack *track; + GList *tmp, *time_effects; + + g_return_val_if_fail (GES_IS_CLIP (clip), GST_CLOCK_TIME_NONE); + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (child), GST_CLOCK_TIME_NONE); + g_return_val_if_fail (!error || !*error, GST_CLOCK_TIME_NONE); + + if (!g_list_find (GES_CONTAINER_CHILDREN (clip), child)) { + GST_WARNING_OBJECT (clip, "The track element %" GES_FORMAT " is not " + "a child of the clip", GES_ARGS (child)); + return GST_CLOCK_TIME_NONE; + } + + track = ges_track_element_get_track (child); + + if (!track) { + GST_WARNING_OBJECT (clip, "Cannot convert the timeline time to an " + "internal time of child %" GES_FORMAT " because it is not part " + "of a track", GES_ARGS (child)); + return GST_CLOCK_TIME_NONE; + } + + if (!ges_track_element_is_active (child)) { + GST_WARNING_OBJECT (clip, "Cannot convert the timeline time to an " + "internal time of child %" GES_FORMAT " because it is not active " + "in its track", GES_ARGS (child)); + return GST_CLOCK_TIME_NONE; + } + + if (timeline_time == GST_CLOCK_TIME_NONE) + return GST_CLOCK_TIME_NONE; + + start = _START (clip); + if (start <= timeline_time) { + decrease = FALSE; + external_time = timeline_time - start; + } else { + decrease = TRUE; + external_time = start - timeline_time; + } + + time_effects = _active_time_effects_in_track_after_priority (clip, track, + _PRIORITY (child)); + + /* currently ordered with highest priority (closest to the timeline) + * first, with @child being at the *end* of the list, which is what we + * want */ + + for (tmp = time_effects; tmp; tmp = tmp->next) { + GESBaseEffect *effect = tmp->data; + GHashTable *values = ges_base_effect_get_time_property_values (effect); + + external_time = ges_base_effect_translate_source_to_sink_time (effect, + external_time, values); + g_hash_table_unref (values); + } + + g_list_free (time_effects); + + if (!GST_CLOCK_TIME_IS_VALID (external_time)) + return GST_CLOCK_TIME_NONE; + + inpoint = _INPOINT (child); + + if (!decrease) + return inpoint + external_time; + + if (external_time > inpoint) { + GST_INFO_OBJECT (clip, "Cannot convert the timeline time %" + GST_TIME_FORMAT " to an internal time of the child %" + GES_FORMAT " because it would be before the element has any " + "internal content", GST_TIME_ARGS (timeline_time), GES_ARGS (child)); + + g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME, + "The timeline time %" GST_TIME_FORMAT " would correspond to " + "a negative in-point of -%" GST_TIME_FORMAT " for the child " + "\"%s\" under clip \"%s\"", GST_TIME_ARGS (timeline_time), + GST_TIME_ARGS (external_time - inpoint), + GES_TIMELINE_ELEMENT_NAME (child), GES_TIMELINE_ELEMENT_NAME (clip)); + + return GST_CLOCK_TIME_NONE; + } + + return inpoint - external_time; +} + +static GstClockTime +_convert_core_time (GESClip * clip, GstClockTime time, gboolean to_timeline, + gboolean * no_core, GError ** error) +{ + GList *tmp; + GstClockTime converted = GST_CLOCK_TIME_NONE; + GstClockTime half_frame; + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip); + GESClipAsset *asset = + GES_CLIP_ASSET (ges_extractable_get_asset (GES_EXTRACTABLE (clip))); + + if (no_core) + *no_core = TRUE; + + if (to_timeline) + half_frame = timeline ? ges_timeline_get_frame_time (timeline, 1) : 0; + else + half_frame = ges_clip_asset_get_frame_time (asset, 1); + half_frame = GST_CLOCK_TIME_IS_VALID (half_frame) ? half_frame / 2 : 0; + + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTrackElement *child = tmp->data; + GESTrack *track = ges_track_element_get_track (child); + + if (_IS_CORE_CHILD (child) && track && ges_track_element_is_active (child) + && ges_track_element_has_internal_source (child)) { + GstClockTime tmp_time; + GError *convert_error = NULL; + + if (no_core) + *no_core = FALSE; + + if (to_timeline) + tmp_time = + ges_clip_get_timeline_time_from_internal_time (clip, child, time, + &convert_error); + else + tmp_time = + ges_clip_get_internal_time_from_timeline_time (clip, child, time, + &convert_error); + + if (!GST_CLOCK_TIME_IS_VALID (converted)) { + converted = tmp_time; + } else if (!GST_CLOCK_TIME_IS_VALID (tmp_time)) { + GST_WARNING_OBJECT (clip, "The calculated %s time for %s time %" + GST_TIME_FORMAT " using core child %" GES_FORMAT " is not " + "defined, but it had a definite value of %" GST_TIME_FORMAT + " for another core child", to_timeline ? "timeline" : "internal", + to_timeline ? "internal" : "timeline", GST_TIME_ARGS (time), + GES_ARGS (child), GST_TIME_ARGS (converted)); + } else if (tmp_time != converted) { + GstClockTime diff = (tmp_time > converted) ? + tmp_time - converted : converted - tmp_time; + + if (diff > half_frame) { + GST_WARNING_OBJECT (clip, "The calculated %s time for %s time %" + GST_TIME_FORMAT " using core child %" GES_FORMAT " is %" + GST_TIME_FORMAT ", which is different from the value of %" + GST_TIME_FORMAT " calculated using a different core child", + to_timeline ? "timeline" : "internal", + to_timeline ? "internal" : "timeline", GST_TIME_ARGS (time), + GES_ARGS (child), GST_TIME_ARGS (tmp_time), + GST_TIME_ARGS (converted)); + } + + /* prefer result from video tracks */ + if (GES_IS_VIDEO_TRACK (track)) + converted = tmp_time; + } + if (convert_error) { + if (error) { + g_clear_error (error); + *error = convert_error; + } else { + g_error_free (convert_error); + } + } + } + } + + return converted; +} + +GstClockTime +ges_clip_get_core_internal_time_from_timeline_time (GESClip * clip, + GstClockTime timeline_time, gboolean * no_core, GError ** error) +{ + return _convert_core_time (clip, timeline_time, FALSE, no_core, error); +} + +/** + * ges_clip_get_timeline_time_from_source_frame: + * @clip: A #GESClip + * @frame_number: The frame number to get the corresponding timestamp of + * in the timeline coordinates + * @error: (nullable): Return location for an error + * + * Convert the source frame number to a timeline time. This acts the same + * as ges_clip_get_timeline_time_from_internal_time() using the core + * children of the clip and using the frame number to specify the internal + * position, rather than a timestamp. + * + * The returned timeline time can be used to seek or edit to a specific + * frame. + * + * Note that you can get the frame timestamp of a particular clip asset + * with ges_clip_asset_get_frame_time(). + * + * Returns: The timestamp corresponding to @frame_number in the core + * children of @clip, in the timeline coordinates, or #GST_CLOCK_TIME_NONE + * if the conversion could not be performed. + * Since: 1.18 + */ +GstClockTime +ges_clip_get_timeline_time_from_source_frame (GESClip * clip, + GESFrameNumber frame_number, GError ** error) +{ + GstClockTime timeline_time = GST_CLOCK_TIME_NONE; + GstClockTime frame_ts; + GESClipAsset *asset; + + g_return_val_if_fail (GES_IS_CLIP (clip), GST_CLOCK_TIME_NONE); + g_return_val_if_fail (!error || !*error, GST_CLOCK_TIME_NONE); + + if (!GES_FRAME_NUMBER_IS_VALID (frame_number)) + return GST_CLOCK_TIME_NONE; + + asset = GES_CLIP_ASSET (ges_extractable_get_asset (GES_EXTRACTABLE (clip))); + frame_ts = ges_clip_asset_get_frame_time (asset, frame_number); + if (!GST_CLOCK_TIME_IS_VALID (frame_ts)) + return GST_CLOCK_TIME_NONE; + + timeline_time = _convert_core_time (clip, frame_ts, TRUE, NULL, error); + + if (error && *error) { + g_clear_error (error); + g_set_error (error, GES_ERROR, GES_ERROR_INVALID_FRAME_NUMBER, + "Requested frame %" G_GINT64_FORMAT " would be outside the " + "timeline.", frame_number); + } + + return timeline_time; +} + +/** + * ges_clip_add_child_to_track: + * @clip: A #GESClip + * @child: A child of @clip + * @track: The track to add @child to + * @error: (nullable): Return location for an error + * + * Adds the track element child of the clip to a specific track. + * + * If the given child is already in another track, this will create a copy + * of the child, add it to the clip, and add this copy to the track. + * + * You should only call this whilst a clip is part of a #GESTimeline, and + * for tracks that are in the same timeline. + * + * This method is an alternative to using the + * #GESTimeline::select-tracks-for-object signal, but can be used to + * complement it when, say, you wish to copy a clip's children from one + * track into a new one. + * + * When the child is a core child, it must be added to a track that does + * not already contain another core child of the same clip. If it is not a + * core child (an additional effect), then it must be added to a track + * that already contains one of the core children of the same clip. + * + * This method can also fail if the adding the track element to the track + * would break a configuration rule of the corresponding #GESTimeline, + * such as causing three sources to overlap at a single time, or causing + * a source to completely overlap another in the same track. + * + * Returns: (transfer none): The element that was added to @track, either + * @child or a copy of child, or %NULL if the element could not be added. + * + * Since: 1.18 + */ +GESTrackElement * +ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child, + GESTrack * track, GError ** error) +{ + GESTimeline *timeline; + GESTrackElement *el; + GESTrack *current_track; + + g_return_val_if_fail (GES_IS_CLIP (clip), NULL); + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (child), NULL); + g_return_val_if_fail (GES_IS_TRACK (track), NULL); + g_return_val_if_fail (!error || !*error, NULL); + + timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip); + + if (!g_list_find (GES_CONTAINER_CHILDREN (clip), child)) { + GST_WARNING_OBJECT (clip, "The track element %" GES_FORMAT " is not " + "a child of the clip", GES_ARGS (child)); + return NULL; + } + + if (!timeline) { + GST_WARNING_OBJECT (clip, "Cannot add children to tracks unless " + "the clip is part of a timeline"); + return NULL; + } + + if (timeline != ges_track_get_timeline (track)) { + GST_WARNING_OBJECT (clip, "Cannot add the children to the track %" + GST_PTR_FORMAT " because its timeline is %" GST_PTR_FORMAT + " rather than that of the clip %" GST_PTR_FORMAT, + track, ges_track_get_timeline (track), timeline); + return NULL; + } + + current_track = ges_track_element_get_track (child); + + if (current_track == track) { + GST_WARNING_OBJECT (clip, "Child %s" GES_FORMAT " is already in the " + "track %" GST_PTR_FORMAT, GES_ARGS (child), track); + return NULL; + } + + /* copy if the element is already in a track */ + if (current_track) { + /* TODO: rather than add the effect at the next highest priority, we + * want to add copied effect into the same EffectCollection, which all + * share the same priority/index */ + if (_IS_TOP_EFFECT (child)) { + clip->priv->use_effect_priority = TRUE; + /* add at next lowest priority */ + clip->priv->effect_priority = GES_TIMELINE_ELEMENT_PRIORITY (child) + 1; + } + + el = ges_clip_copy_track_element_into (clip, child, GST_CLOCK_TIME_NONE); + + clip->priv->use_effect_priority = FALSE; + if (!el) { + GST_ERROR_OBJECT (clip, "Could not add a copy of the track element %" + GES_FORMAT " to the clip so cannot add it to the track %" + GST_PTR_FORMAT, GES_ARGS (child), track); + return NULL; + } + } else { + el = child; + } + + if (!ges_track_add_element_full (track, el, error)) { + GST_INFO_OBJECT (clip, "Could not add the track element %" + GES_FORMAT " to the track %" GST_PTR_FORMAT, GES_ARGS (el), track); + if (el != child) + ges_container_remove (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (el)); + return NULL; + } + + /* call _child_track_changed now so that the "active" status of the + * child can change. Note that this is needed because this method may + * be called during ges_container_add, in which case "notify" for el + * will be frozen. Thus, _update_active_for_track may not have been + * called yet. It is important for us to call this now because when + * the elements are un-frozen, we need to ensure the "active" status + * is already set before the duration-limit is calculated */ + _update_active_for_track (clip, el); + + return el; +} diff --git a/ges/ges-clip.h b/ges/ges-clip.h new file mode 100644 index 0000000000..5426cfa4e2 --- /dev/null +++ b/ges/ges-clip.h @@ -0,0 +1,246 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GES_TYPE_CLIP ges_clip_get_type() +GES_DECLARE_TYPE(Clip, clip, CLIP); + +/** + * GES_CLIP_CLASS_CAN_ADD_EFFECTS: + * @klass: A #GESClipClass + * + * Whether the class allows for the user to add additional non-core + * #GESBaseEffect-s to clips from this class. + */ +#define GES_CLIP_CLASS_CAN_ADD_EFFECTS(klass) ((GES_CLIP_CLASS (klass))->ABI.abi.can_add_effects) + +/** + * GESFillTrackElementFunc: + * @clip: The #GESClip controlling the track elements + * @track_element: The #GESTrackElement + * @nleobj: The nleobject that needs to be filled + * + * A function that will be called when the nleobject of a corresponding + * track element needs to be filled. + * + * The implementer of this function shall add the proper #GstElement to @nleobj + * using gst_bin_add(). + * + * Deprecated: 1.18: This method type is no longer used. + * + * Returns: %TRUE if the implementer successfully filled the @nleobj. + */ +typedef gboolean (*GESFillTrackElementFunc) (GESClip *clip, GESTrackElement *track_element, + GstElement *nleobj); + +/** + * GESCreateTrackElementFunc: + * @clip: A #GESClip + * @type: A #GESTrackType to create a #GESTrackElement for + * + * A method for creating the core #GESTrackElement of a clip, to be added + * to a #GESTrack of the given track type. + * + * If a clip may produce several track elements per track type, + * #GESCreateTrackElementsFunc is more appropriate. + * + * Returns: (transfer floating) (nullable): The #GESTrackElement created + * by @clip, or %NULL if @clip can not provide a track element for the + * given @type or an error occurred. + */ +typedef GESTrackElement *(*GESCreateTrackElementFunc) (GESClip * clip, GESTrackType type); + +/** + * GESCreateTrackElementsFunc: + * @clip: A #GESClip + * @type: A #GESTrackType to create #GESTrackElement-s for + * + * A method for creating the core #GESTrackElement-s of a clip, to be + * added to #GESTrack-s of the given track type. + * + * Returns: (transfer container) (element-type GESTrackElement): A list of + * the #GESTrackElement-s created by @clip for the given @type, or %NULL + * if no track elements are created or an error occurred. + */ +typedef GList * (*GESCreateTrackElementsFunc) (GESClip * clip, GESTrackType type); + +/** + * GESClip: + */ +struct _GESClip +{ + GESContainer parent; + + /*< private >*/ + GESClipPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING_LARGE]; +}; + +/** + * GESClipClass: + * @create_track_element: Method to create the core #GESTrackElement of a clip + * of this class. If a clip of this class may create several track elements per + * track type, this should be left as %NULL, and + * GESClipClass::create_track_elements should be used instead. Otherwise, you + * should implement this class method and leave + * GESClipClass::create_track_elements as the default implementation + * @create_track_elements: Method to create the (multiple) core + * #GESTrackElement-s of a clip of this class. If + * GESClipClass::create_track_element is implemented, this should be kept as the + * default implementation + * @can_add_effects: Whether the user can add additional non-core + * #GESBaseEffect-s to clips from this class, to be applied to the output data + * of the core elements. + */ +struct _GESClipClass +{ + /*< private > */ + GESContainerClass parent_class; + + /*< public > */ + GESCreateTrackElementFunc create_track_element; + GESCreateTrackElementsFunc create_track_elements; + + /*< private >*/ + /* Padding for API extension */ + union { + gpointer _ges_reserved[GES_PADDING_LARGE]; + struct { + gboolean can_add_effects; + } abi; + } ABI; +}; + +/**************************************************** + * TrackElement handling * + ****************************************************/ +GES_API +GESTrackType ges_clip_get_supported_formats (GESClip *clip); +GES_API +void ges_clip_set_supported_formats (GESClip *clip, + GESTrackType supportedformats); +GES_API +GESTrackElement* ges_clip_add_asset (GESClip *clip, + GESAsset *asset); +GES_API +GESTrackElement* ges_clip_find_track_element (GESClip *clip, + GESTrack *track, + GType type); +GES_API +GList * ges_clip_find_track_elements (GESClip * clip, + GESTrack * track, + GESTrackType track_type, + GType type); + +GES_API +GESTrackElement * ges_clip_add_child_to_track (GESClip * clip, + GESTrackElement * child, + GESTrack * track, + GError ** error); + +/**************************************************** + * Layer * + ****************************************************/ +GES_API +GESLayer* ges_clip_get_layer (GESClip * clip); +GES_API +gboolean ges_clip_move_to_layer (GESClip * clip, + GESLayer * layer); +GES_API +gboolean ges_clip_move_to_layer_full (GESClip * clip, + GESLayer * layer, + GError ** error); + +/**************************************************** + * Effects * + ****************************************************/ +GES_API +gboolean ges_clip_add_top_effect (GESClip * clip, + GESBaseEffect * effect, + gint index, + GError ** error); +GES_API +gboolean ges_clip_remove_top_effect (GESClip * clip, + GESBaseEffect * effect, + GError ** error); +GES_API +GList* ges_clip_get_top_effects (GESClip * clip); +GES_API +gint ges_clip_get_top_effect_position (GESClip * clip, + GESBaseEffect * effect); +GES_API +gint ges_clip_get_top_effect_index (GESClip * clip, + GESBaseEffect * effect); +GES_API +gboolean ges_clip_set_top_effect_priority (GESClip * clip, + GESBaseEffect * effect, + guint newpriority); +GES_API +gboolean ges_clip_set_top_effect_index (GESClip * clip, + GESBaseEffect * effect, + guint newindex); +GES_API +gboolean ges_clip_set_top_effect_index_full (GESClip * clip, + GESBaseEffect * effect, + guint newindex, + GError ** error); + +/**************************************************** + * Editing * + ****************************************************/ +GES_API +GESClip* ges_clip_split (GESClip *clip, + guint64 position); +GES_API +GESClip* ges_clip_split_full (GESClip *clip, + guint64 position, + GError ** error); + +GES_API +GstClockTime ges_clip_get_internal_time_from_timeline_time (GESClip * clip, + GESTrackElement * child, + GstClockTime timeline_time, + GError ** error); +GES_API +GstClockTime ges_clip_get_timeline_time_from_internal_time (GESClip * clip, + GESTrackElement * child, + GstClockTime internal_time, + GError ** error); +GES_API +GstClockTime ges_clip_get_timeline_time_from_source_frame (GESClip * clip, + GESFrameNumber frame_number, + GError ** error); + +GES_API +GstClockTime ges_clip_get_duration_limit (GESClip * clip); + +G_END_DECLS diff --git a/ges/ges-command-line-formatter.c b/ges/ges-command-line-formatter.c new file mode 100644 index 0000000000..823d9510ff --- /dev/null +++ b/ges/ges-command-line-formatter.c @@ -0,0 +1,1158 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2015> Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-command-line-formatter.h" + +#include "ges/ges-structured-interface.h" +#include "ges-structure-parser.h" +#include "ges-internal.h" +#define YY_NO_UNISTD_H +#include "ges-parse-lex.h" + +struct _GESCommandLineFormatterPrivate +{ + gpointer dummy; +}; + + +G_DEFINE_TYPE_WITH_PRIVATE (GESCommandLineFormatter, ges_command_line_formatter, + GES_TYPE_FORMATTER); + +static gboolean +_ges_command_line_formatter_add_clip (GESTimeline * timeline, + GstStructure * structure, GError ** error); +static gboolean +_ges_command_line_formatter_add_effect (GESTimeline * timeline, + GstStructure * structure, GError ** error); +static gboolean +_ges_command_line_formatter_add_test_clip (GESTimeline * timeline, + GstStructure * structure, GError ** error); +static gboolean +_ges_command_line_formatter_add_title_clip (GESTimeline * timeline, + GstStructure * structure, GError ** error); +static gboolean +_ges_command_line_formatter_add_track (GESTimeline * timeline, + GstStructure * structure, GError ** error); +static gboolean +_ges_command_line_formatter_add_keyframes (GESTimeline * timeline, + GstStructure * structure, GError ** error); + +typedef struct +{ + const gchar *long_name; + const gchar *short_name; + GType type; + const gchar *new_name; + const gchar *desc; +} Property; + +// Currently Clip has the most properties.. adapt as needed +#define MAX_PROPERTIES 8 +typedef struct +{ + const gchar *long_name; + gchar short_name; + ActionFromStructureFunc callback; + const gchar *synopsis; + const gchar *description; + const gchar *examples; + /* The first property must be the ID on the command line */ + Property properties[MAX_PROPERTIES]; +} GESCommandLineOption; + +/* *INDENT-OFF* */ +static GESCommandLineOption options[] = { + { + .long_name = "clip", + .short_name='c', + .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_clip, + .synopsis="", + .description="Adds a clip in the timeline. " + "See documentation for the --track-types option to ges-launch-1.0, as it " + " will affect the result of this command.", + .examples=" ges-launch-1.0 +clip /path/to/media\n\n" + "This will simply play the sample from its beginning to its end.\n\n" + " ges-launch-1.0 +clip /path/to/media inpoint=4.0\n\n" + "Assuming 'media' is a 10 second long media sample, this will play the sample\n" + "from the 4th second to the 10th, resulting in a 6-seconds long playback.\n\n" + " ges-launch-1.0 +clip /path/to/media inpoint=4.0 duration=2.0 start=4.0\n\n" + "Assuming \"media\" is an audio video sample longer than 6 seconds, this will play\n" + "a black frame and silence for 4 seconds, then the sample from its 4th second to\n" + "its sixth second, resulting in a 6-seconds long playback.\n\n" + " ges-launch-1.0 --track-types=audio +clip /path/to/media\n\n" + "Assuming \"media\" is an audio video sample, this will only play the audio of the\n" + "sample in its entirety.\n\n" + " ges-launch-1.0 +clip /path/to/media1 layer=1 set-alpha 0.9 +clip /path/to/media2 layer=0\n\n" + "Assume media1 and media2 both contain audio and video and last for 10 seconds.\n\n" + "This will first add media1 in a new layer of \"priority\" 1, thus implicitly\n" + "creating a layer of \"priority\" 0, the start of the clip will be 0 as no clip\n" + "had been added in that layer before.\n\n" + "It will then add media2 in the layer of \"priority\" 0 which was created\n" + "previously, the start of this new clip will also be 0 as no clip has been added\n" + "in this layer before.\n\n" + "Both clips will thus overlap on two layers for 10 seconds.\n\n" + "The \"alpha\" property of the second clip will finally be set to a value of 0.9.\n\n" + "All this will result in a 10 seconds playback, where media2 is barely visible\n" + "through media1, which is nearly opaque. If alpha was set to 0.5, both clips\n" + "would be equally visible, and if it was set to 0.0, media1 would be invisible\n" + "and media2 completely opaque.\n", + .properties={ + { + "uri", 0, 0, "asset-id", + "The URI of the media file." + }, + { + "name", "n", 0, NULL, + "The name of the clip, can be used as an ID later." + }, + { + "start", "s", GST_TYPE_CLOCK_TIME, NULL, + "The starting position of the clip in the timeline." + }, + { + "duration", "d", GST_TYPE_CLOCK_TIME, NULL, + "The duration of the clip." + }, + { + "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL, + "The inpoint of the clip (time in the input file to start playing from)." + }, + { + "track-types", "tt", 0, NULL, + "The type of the tracks where the clip should be used (audio or video or audio+video)." + }, + { + "layer", "l", 0, NULL, + "The priority of the layer into which the clip should be added." + }, + {NULL, 0, 0, NULL, FALSE}, + }, + }, + { + .long_name="effect", + .short_name='e', + .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_effect, + .synopsis="", + .description="Adds an effect as specified by 'bin-description', similar to gst-launch-style" + " pipeline description, without setting properties (see `set-` for information" + " about how to set properties).", + .examples=" ges-launch-1.0 +clip /path/to/media +effect \"agingtv\"\n\n" + "This will apply the agingtv effect to \"media\" and play it back.", + { + { + "bin-description", "d", 0, "asset-id", + "gst-launch style bin description." + }, + { + "element-name", "e", 0, NULL, + "The name of the element to apply the effect on." + }, + { + "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL, + "Implies that the effect has 'internal content'" + "(see [ges_track_element_set_has_internal_source](ges_track_element_set_has_internal_source))", + }, + { + "name", "n", 0, "child-name", + "The name to be given to the effect." + }, + {NULL, NULL, 0, NULL, FALSE}, + }, + }, + { + .long_name="test-clip", + .short_name=0, + .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_test_clip, + .synopsis="", + .description="Add a test clip in the timeline.", + .examples=NULL, + .properties={ + { + "vpattern", "p", 0, NULL, + "The testsource pattern name." + }, + { + "name", "n", 0, NULL, + "The name of the clip, can be used as an ID later." + }, + { + "start", "s", GST_TYPE_CLOCK_TIME, NULL, + "The starting position of the clip in the timeline." + }, + { + "duration", "d", GST_TYPE_CLOCK_TIME, NULL, + "The duration of the clip." + }, + { + "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL, + "The inpoint of the clip (time in the input file to start playing)." + }, + { + "layer", "l", 0, NULL, + "The priority of the layer into which the clip should be added." + }, + {NULL, 0, 0, NULL, FALSE}, + }, + }, + { + .long_name="title", + .short_name='c', + .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_title_clip, + .synopsis="", + .description="Adds a clip in the timeline.", + .examples=NULL, + .properties={ + { + "text", "t", 0, NULL, + "The text to be used as title." + }, + { + "name", "n", 0, NULL, + "The name of the clip, can be used as an ID later." + }, + { + "start", "s",GST_TYPE_CLOCK_TIME, NULL, + "The starting position of the clip in the timeline." + }, + { + "duration", "d", GST_TYPE_CLOCK_TIME, NULL, + "The duration of the clip." + }, + { + "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL, + "The inpoint of the clip (time in the input file to start playing from)." + }, + { + "track-types", "tt", 0, NULL, + "The type of the tracks where the clip should be used (audio or video or audio+video)." + }, + { + "layer", "l", G_TYPE_INT, NULL, + "The priority of the layer into which the clip should be added." + }, + {NULL, 0, 0, NULL, FALSE}, + }, + }, + { + .long_name="track", + .short_name='t', + .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_track, + .synopsis="<track type>", + .description="Adds a track to the timeline.", + .examples=NULL, + .properties={ + {"track-type", 0, 0, NULL, NULL}, + { + "restrictions", "r", 0, NULL, + "The restriction caps to set on the track." + }, + {NULL, 0, 0, NULL, FALSE}, + }, + }, + { + .long_name="keyframes", + .short_name='k', + .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_keyframes, + .synopsis="<property name>", + .description="Adds keyframes for the specified property in the form:\n\n", + .examples=" ges-launch-1.0 +test-clip blue d=1.0 +keyframes posx 0=0 1.0=1280 t=direct-absolute +k posy 0=0 1.0=720 t=direct-absolute\n\n" + "This add a testclip that will disappear in the bottom right corner", + .properties={ + {"property-name", 0, 0, NULL, NULL}, + { + "binding-type", "t", 0, NULL, + "The type of binding to use, eg. 'direct-absolute', 'direct'" + }, + { + "interpolation-mode", "m", 0, NULL, + "The GstInterpolationMode to user." + }, + { + "...", 0, 0, NULL, + "The list of keyframe_timestamp=value to be set." + }, + {NULL, 0, 0, NULL, FALSE}, + }, + }, + { + .long_name="set-", + .short_name=0, + .callback=NULL, + .synopsis="<property name> <value>", + .description="Set a property on the last added element." + " Any child property that exists on the previously added element" + " can be used as <property name>" + "By default, set-<property-name> will lookup the property on the last added" + "object.", + .examples=" ges-launch-1.0 +clip /path/to/media set-alpha 0.3\n\n" + "This will set the alpha property on \"media\" then play it back, assuming \"media\"" + "contains a video stream.\n\n" + " ges-launch-1.0 +clip /path/to/media +effect \"agingtv\" set-dusts false\n\n" + "This will set the \"dusts\" property of the agingtv to false and play the\n" + "timeline back.", + .properties={ + {NULL, 0, 0, NULL, FALSE}, + }, + }, +}; +/* *INDENT-ON* */ + +/* Should always be in the same order as the options */ +typedef enum +{ + CLIP, + EFFECT, + TEST_CLIP, + TITLE, + TRACK, + KEYFRAMES, + SET, +} GESCommandLineOptionType; + +static gint /* -1: not present, 0: failure, 1: OK */ +_convert_to_clocktime (GstStructure * structure, const gchar * name, + GstClockTime default_value) +{ + gint res = 1; + gdouble val; + GValue d_val = G_VALUE_INIT, converted = G_VALUE_INIT; + GstClockTime timestamp; + const GValue *gvalue = gst_structure_get_value (structure, name); + + if (gvalue == NULL) { + timestamp = default_value; + + res = -1; + + goto done; + } + + if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING) { + const gchar *val_string = g_value_get_string (gvalue); + /* if starts with an 'f', interpret as a frame number, keep as + * a string for now */ + if (val_string && val_string[0] == 'f') + return 1; + /* else, try convert to a GstClockTime, or a double */ + g_value_init (&converted, GST_TYPE_CLOCK_TIME); + if (!gst_value_deserialize (&converted, val_string)) { + g_value_unset (&converted); + g_value_init (&converted, G_TYPE_DOUBLE); + if (!gst_value_deserialize (&converted, val_string)) { + GST_ERROR ("Could not get timestamp for %s by deserializing %s", + name, val_string); + goto error; + } + } + } else { + g_value_init (&converted, G_VALUE_TYPE (gvalue)); + g_value_copy (gvalue, &converted); + } + + if (G_VALUE_TYPE (&converted) == GST_TYPE_CLOCK_TIME) { + timestamp = g_value_get_uint64 (&converted); + goto done; + } + + g_value_init (&d_val, G_TYPE_DOUBLE); + + if (!g_value_transform (&converted, &d_val)) { + GST_ERROR ("Could not get timestamp for %s", name); + goto error; + } + + val = g_value_get_double ((const GValue *) &d_val); + g_value_unset (&d_val); + + if (val == -1.0) + timestamp = GST_CLOCK_TIME_NONE; + else + timestamp = val * GST_SECOND; + +done: + gst_structure_set (structure, name, G_TYPE_UINT64, timestamp, NULL); + g_value_unset (&converted); + + return res; + +error: + g_value_unset (&converted); + + return 0; +} + +static gboolean +_cleanup_fields (const Property * field_names, GstStructure * structure, + GError ** error) +{ + guint i; + + for (i = 0; field_names[i].long_name; i++) { + gboolean exists = FALSE; + + /* Move shortly named fields to longname variante */ + if (field_names[i].short_name && + gst_structure_has_field (structure, field_names[i].short_name)) { + exists = TRUE; + + if (gst_structure_has_field (structure, field_names[i].long_name)) { + gchar *str_info = gst_structure_serialize (structure, 0); + + *error = + g_error_new (GES_ERROR, 0, + "Using short (%s) and long name (%s)" + " at the same time s in %s, which one should I use?!", + field_names[i].short_name, field_names[i].long_name, str_info); + g_free (str_info); + + return FALSE; + } else { + const GValue *val = + gst_structure_get_value (structure, field_names[i].short_name); + + gst_structure_set_value (structure, field_names[i].long_name, val); + gst_structure_remove_field (structure, field_names[i].short_name); + } + } else if (gst_structure_has_field (structure, field_names[i].long_name)) { + exists = TRUE; + } + + if (exists) { + if (field_names[i].type == GST_TYPE_CLOCK_TIME) { + if (_convert_to_clocktime (structure, field_names[i].long_name, 0) == 0) { + *error = g_error_new (GES_ERROR, 0, "Could not convert" + " %s to GstClockTime", field_names[i].long_name); + + return FALSE; + } + } + } + + if (field_names[i].new_name + && gst_structure_has_field (structure, field_names[i].long_name)) { + const GValue *val = + gst_structure_get_value (structure, field_names[i].long_name); + + gst_structure_set_value (structure, field_names[i].new_name, val); + gst_structure_remove_field (structure, field_names[i].long_name); + } + } + + return TRUE; +} + +static gboolean +_ges_command_line_formatter_add_clip (GESTimeline * timeline, + GstStructure * structure, GError ** error) +{ + GESProject *proj; + GESAsset *asset; + if (!_cleanup_fields (options[CLIP].properties, structure, error)) + return FALSE; + + gst_structure_set (structure, "type", G_TYPE_STRING, "GESUriClip", NULL); + + if (!_ges_add_clip_from_struct (timeline, structure, error)) + return FALSE; + + proj = GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline))); + asset = _ges_get_asset_from_timeline (timeline, GES_TYPE_URI_CLIP, + gst_structure_get_string (structure, "asset-id"), NULL); + ges_project_add_asset (proj, asset); + + return TRUE; +} + +static gboolean +_ges_command_line_formatter_add_test_clip (GESTimeline * timeline, + GstStructure * structure, GError ** error) +{ + if (!_cleanup_fields (options[TEST_CLIP].properties, structure, error)) + return FALSE; + + gst_structure_set (structure, "type", G_TYPE_STRING, "GESTestClip", NULL); + + if (!gst_structure_has_field_typed (structure, "asset-id", G_TYPE_STRING)) + gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTestClip", + NULL); + + return _ges_add_clip_from_struct (timeline, structure, error); +} + +static gboolean +_ges_command_line_formatter_add_title_clip (GESTimeline * timeline, + GstStructure * structure, GError ** error) +{ + if (!_cleanup_fields (options[TITLE].properties, structure, error)) + return FALSE; + + gst_structure_set (structure, "type", G_TYPE_STRING, "GESTitleClip", NULL); + gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTitleClip", + NULL); + + return _ges_add_clip_from_struct (timeline, structure, error); +} + +static gboolean +_ges_command_line_formatter_add_keyframes (GESTimeline * timeline, + GstStructure * structure, GError ** error) +{ + if (!_cleanup_fields (options[KEYFRAMES].properties, structure, error)) + return FALSE; + + if (!_ges_set_control_source_from_struct (timeline, structure, error)) + return FALSE; + + return _ges_add_remove_keyframe_from_struct (timeline, structure, error); +} + +static gboolean +_ges_command_line_formatter_add_track (GESTimeline * timeline, + GstStructure * structure, GError ** error) +{ + if (!_cleanup_fields (options[TRACK].properties, structure, error)) + return FALSE; + + return _ges_add_track_from_struct (timeline, structure, error); +} + +static gboolean +_ges_command_line_formatter_add_effect (GESTimeline * timeline, + GstStructure * structure, GError ** error) +{ + if (!_cleanup_fields (options[EFFECT].properties, structure, error)) + return FALSE; + + gst_structure_set (structure, "child-type", G_TYPE_STRING, "GESEffect", NULL); + + return _ges_container_add_child_from_struct (timeline, structure, error); +} + +gchar * +ges_command_line_formatter_get_help (gint nargs, gchar ** commands) +{ + gint i; + GString *help = g_string_new (NULL); + + for (i = 0; i < G_N_ELEMENTS (options); i++) { + gboolean print = nargs == 0; + GESCommandLineOption option = options[i]; + + if (!print) { + gint j; + + for (j = 0; j < nargs; j++) { + gchar *cname = commands[j][0] == '+' ? &commands[j][1] : commands[j]; + + if (!g_strcmp0 (cname, option.long_name)) { + print = TRUE; + break; + } + } + } + + if (print) { + gint j; + + gchar *tmp = g_strdup_printf (" `%s%s` - %s\n", + option.properties[0].long_name ? "+" : "", + option.long_name, option.synopsis); + + g_string_append (help, tmp); + g_string_append (help, " "); + g_string_append (help, "\n\n "); + g_free (tmp); + + for (j = 0; option.description[j] != '\0'; j++) { + + if (j && (j % 80) == 0) { + while (option.description[j] != '\0' && option.description[j] != ' ') + g_string_append_c (help, option.description[j++]); + g_string_append (help, "\n "); + continue; + } + + g_string_append_c (help, option.description[j]); + } + g_string_append_c (help, '\n'); + + if (option.properties[0].long_name) { + gint j; + + g_string_append (help, "\n Properties:\n\n"); + + for (j = 1; option.properties[j].long_name; j++) { + Property prop = option.properties[j]; + g_string_append_printf (help, " * `%s`: %s\n", prop.long_name, + prop.desc); + } + } + if (option.examples) { + gint j; + gchar **examples = g_strsplit (option.examples, "\n", -1); + + g_string_append (help, "\n Examples:\n\n"); + for (j = 0; examples[j]; j++) { + if (examples[j]) + g_string_append_printf (help, " %s", examples[j]); + g_string_append_c (help, '\n'); + } + g_strfreev (examples); + } + + g_string_append_c (help, '\n'); + } + } + + return g_string_free (help, FALSE); +} + + +static gboolean +_set_child_property (GESTimeline * timeline, GstStructure * structure, + GError ** error) +{ + return _ges_set_child_property_from_struct (timeline, structure, error); +} + +#define EXEC(func,structure,error) G_STMT_START { \ + gboolean res = ((ActionFromStructureFunc)func)(timeline, structure, error); \ + if (!res) {\ + GST_ERROR ("Could not execute: %" GST_PTR_FORMAT ", error: %s", structure, (*error)->message); \ + goto fail; \ + } \ +} G_STMT_END + + +static GESStructureParser * +_parse_structures (const gchar * string) +{ + yyscan_t scanner; + GESStructureParser *parser = ges_structure_parser_new (); + + priv_ges_parse_yylex_init_extra (parser, &scanner); + priv_ges_parse_yy_scan_string (string, scanner); + priv_ges_parse_yylex (scanner); + priv_ges_parse_yylex_destroy (scanner); + + ges_structure_parser_end_of_file (parser); + return parser; +} + +/* @uri: (transfer full): */ +static gchar * +get_timeline_desc_from_uri (GstUri * uri) +{ + gchar *res, *path; + + if (!uri) + return NULL; + + /* Working around parser requiring a space to begin with */ + path = gst_uri_get_path (uri); + res = g_strconcat (" ", path, NULL); + g_free (path); + + gst_uri_unref (uri); + + return res; +} + +static gboolean +_can_load (GESFormatter * dummy_formatter, const gchar * string, + GError ** error) +{ + gboolean res = FALSE; + GstUri *uri; + const gchar *scheme; + gchar *timeline_desc = NULL; + GESStructureParser *parser; + + if (string == NULL) { + GST_ERROR ("No URI!"); + return FALSE; + } + + uri = gst_uri_from_string (string); + if (!uri) { + GST_INFO_OBJECT (dummy_formatter, "Wrong uri: %s", string); + return FALSE; + } + + scheme = gst_uri_get_scheme (uri); + if (!g_strcmp0 (scheme, "ges:")) { + GST_INFO_OBJECT (dummy_formatter, "Wrong scheme: %s", string); + gst_uri_unref (uri); + + return FALSE; + } + + timeline_desc = get_timeline_desc_from_uri (uri); + parser = _parse_structures (timeline_desc); + if (parser->structures) + res = TRUE; + + gst_object_unref (parser); + g_free (timeline_desc); + + return res; +} + +static gboolean +_set_project_loaded (GESFormatter * self) +{ + ges_project_set_loaded (self->project, self, NULL); + gst_object_unref (self); + + return FALSE; +} + +static gboolean +_load (GESFormatter * self, GESTimeline * timeline, const gchar * string, + GError ** error) +{ + guint i; + GList *tmp; + GError *err; + gchar *timeline_desc = + get_timeline_desc_from_uri (gst_uri_from_string (string)); + GESStructureParser *parser = _parse_structures (timeline_desc); + + g_free (timeline_desc); + + err = ges_structure_parser_get_error (parser); + + if (err) { + if (error) + *error = err; + + return FALSE; + } + + g_object_set (timeline, "auto-transition", TRUE, NULL); + + /* Here we've finished initializing our timeline, we're + * ready to start using it... by solely working with the layer !*/ + for (tmp = parser->structures; tmp; tmp = tmp->next) { + const gchar *name = gst_structure_get_name (tmp->data); + if (g_str_has_prefix (name, "set-")) { + EXEC (_set_child_property, tmp->data, &err); + continue; + } + + for (i = 0; i < G_N_ELEMENTS (options); i++) { + if (gst_structure_has_name (tmp->data, options[i].long_name) + || (strlen (name) == 1 && *name == options[i].short_name)) { + EXEC (((ActionFromStructureFunc) options[i].callback), tmp->data, &err); + break; + } + } + } + + gst_object_unref (parser); + + ges_idle_add ((GSourceFunc) _set_project_loaded, g_object_ref (self), NULL); + + return TRUE; + +fail: + gst_object_unref (parser); + if (err) { + if (error) + *error = err; + } + + return FALSE; +} + +static void +ges_command_line_formatter_init (GESCommandLineFormatter * formatter) +{ + formatter->priv = ges_command_line_formatter_get_instance_private (formatter); +} + +static void +ges_command_line_formatter_finalize (GObject * object) +{ + G_OBJECT_CLASS (ges_command_line_formatter_parent_class)->finalize (object); +} + +static void +ges_command_line_formatter_class_init (GESCommandLineFormatterClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (klass); + + object_class->finalize = ges_command_line_formatter_finalize; + + formatter_klass->can_load_uri = _can_load; + formatter_klass->load_from_uri = _load; + formatter_klass->rank = GST_RANK_MARGINAL; +} + +/* Copy of GST_ASCII_IS_STRING */ +#define ASCII_IS_STRING(c) (g_ascii_isalnum((c)) || ((c) == '_') || \ + ((c) == '-') || ((c) == '+') || ((c) == '/') || ((c) == ':') || \ + ((c) == '.')) + +static void +_sanitize_argument (const gchar * arg, GString * res) +{ + gboolean need_wrap = FALSE; + const gchar *tmp_string; + + for (tmp_string = arg; *tmp_string != '\0'; tmp_string++) { + if (!ASCII_IS_STRING (*tmp_string) || (*tmp_string == '\n')) { + need_wrap = TRUE; + break; + } + } + + if (!need_wrap) { + g_string_append (res, arg); + return; + } + + g_string_append_c (res, '"'); + while (*arg != '\0') { + if (*arg == '"' || *arg == '\\') { + g_string_append_c (res, '\\'); + } else if (*arg == '\n') { + g_string_append (res, "\\n"); + arg++; + continue; + } + + g_string_append_c (res, *(arg++)); + } + g_string_append_c (res, '"'); +} + +static gboolean +_serialize_control_binding (GESTrackElement * e, const gchar * prop, + GString * res) +{ + GstInterpolationMode mode; + GstControlSource *source = NULL; + GList *timed_values, *tmp; + gboolean absolute = FALSE; + GstControlBinding *binding = ges_track_element_get_control_binding (e, prop); + + if (!binding) + return FALSE; + + if (!GST_IS_DIRECT_CONTROL_BINDING (binding)) { + g_warning ("Unsupported control binding type: %s", + G_OBJECT_TYPE_NAME (binding)); + goto done; + } + + g_object_get (binding, "control-source", &source, + "absolute", &absolute, NULL); + + if (!GST_IS_INTERPOLATION_CONTROL_SOURCE (source)) { + g_warning ("Unsupported control source type: %s", + G_OBJECT_TYPE_NAME (source)); + goto done; + } + + g_object_get (source, "mode", &mode, NULL); + g_string_append_printf (res, " +keyframes %s t=%s", + prop, absolute ? "direct-absolute" : "direct"); + + if (mode != GST_INTERPOLATION_MODE_LINEAR) + g_string_append_printf (res, " mode=%s", + g_enum_get_value (g_type_class_peek (GST_TYPE_INTERPOLATION_MODE), + mode)->value_nick); + + timed_values = + gst_timed_value_control_source_get_all + (GST_TIMED_VALUE_CONTROL_SOURCE (source)); + for (tmp = timed_values; tmp; tmp = tmp->next) { + gchar strbuf[G_ASCII_DTOSTR_BUF_SIZE]; + GstTimedValue *value; + + value = (GstTimedValue *) tmp->data; + g_string_append_printf (res, " %f=%s", + (gdouble) value->timestamp / (gdouble) GST_SECOND, + g_ascii_dtostr (strbuf, G_ASCII_DTOSTR_BUF_SIZE, value->value)); + } + g_list_free (timed_values); + +done: + g_clear_object (&source); + return TRUE; +} + +static void +_serialize_object_properties (GObject * object, GESCommandLineOption * option, + gboolean children_props, GString * res) +{ + guint n_props, j; + GParamSpec *spec, **pspecs; + GObjectClass *class = G_OBJECT_GET_CLASS (object); + const gchar *ignored_props[] = { + "max-duration", "supported-formats", "priority", "video-direction", + "is-image", NULL, + }; + + if (!children_props) + pspecs = g_object_class_list_properties (class, &n_props); + else { + pspecs = + ges_timeline_element_list_children_properties (GES_TIMELINE_ELEMENT + (object), &n_props); + g_assert (GES_IS_TRACK_ELEMENT (object)); + } + + for (j = 0; j < n_props; j++) { + const gchar *name; + gchar *value_str = NULL; + GValue val = { 0 }; + gint i; + + spec = pspecs[j]; + if (!ges_util_can_serialize_spec (spec)) + continue; + + g_value_init (&val, spec->value_type); + if (!children_props) + g_object_get_property (object, spec->name, &val); + else + ges_timeline_element_get_child_property_by_pspec (GES_TIMELINE_ELEMENT + (object), spec, &val); + + if (gst_value_compare (g_param_spec_get_default_value (spec), + &val) == GST_VALUE_EQUAL) { + GST_INFO ("Ignoring %s as it is using the default value", spec->name); + goto next; + } + + name = spec->name; + if (!children_props && !g_strcmp0 (name, "in-point")) + name = "inpoint"; + + for (i = 0; option->properties[i].long_name; i++) { + if (!g_strcmp0 (spec->name, option->properties[i].long_name)) { + if (children_props) { + name = NULL; + } else { + name = option->properties[i].short_name; + if (option->properties[i].type == GST_TYPE_CLOCK_TIME) + value_str = + g_strdup_printf ("%f", + (gdouble) (g_value_get_uint64 (&val) / GST_SECOND)); + } + break; + } else if (!g_strcmp0 (spec->name, option->properties[0].long_name)) { + name = NULL; + break; + } + } + + for (i = 0; i < G_N_ELEMENTS (ignored_props); i++) { + if (!g_strcmp0 (spec->name, ignored_props[i])) { + name = NULL; + break; + } + } + + if (!name) { + g_free (value_str); + continue; + } + + if (GES_IS_TRACK_ELEMENT (object) && + _serialize_control_binding (GES_TRACK_ELEMENT (object), name, res)) { + g_free (value_str); + continue; + } + + if (!value_str) + value_str = gst_value_serialize (&val); + + g_string_append_printf (res, " %s%s%s", + children_props ? "set-" : "", name, children_props ? " " : "="); + _sanitize_argument (value_str, res); + g_free (value_str); + + next: + g_value_unset (&val); + } + g_free (pspecs); +} + +static void +_serialize_clip_track_types (GESClip * clip, GESTrackType tt, GString * res) +{ + GValue v = G_VALUE_INIT; + gchar *ttype_str; + + if (ges_clip_get_supported_formats (clip) == tt) + return; + + g_value_init (&v, GES_TYPE_TRACK_TYPE); + g_value_set_flags (&v, ges_clip_get_supported_formats (clip)); + + ttype_str = gst_value_serialize (&v); + + g_string_append_printf (res, " tt=%s", ttype_str); + g_value_reset (&v); + g_free (ttype_str); +} + +static void +_serialize_clip_effects (GESClip * clip, GString * res) +{ + GList *tmpeffect, *effects; + + effects = ges_clip_get_top_effects (clip); + for (tmpeffect = effects; tmpeffect; tmpeffect = tmpeffect->next) { + gchar *bin_desc; + + g_object_get (tmpeffect->data, "bin-description", &bin_desc, NULL); + + g_string_append_printf (res, " +effect %s", bin_desc); + g_free (bin_desc); + } + g_list_free_full (effects, gst_object_unref); + +} + +/** + * ges_command_line_formatter_get_timeline_uri: + * @timeline: A GESTimeline to serialize + * + * Since: 1.20 + */ +gchar * +ges_command_line_formatter_get_timeline_uri (GESTimeline * timeline) +{ + gchar *tmpstr; + GList *tmp; + gint i; + GString *res = g_string_new ("ges:"); + GESTrackType tt = 0; + + if (!timeline) + goto done; + + for (tmp = timeline->tracks; tmp; tmp = tmp->next) { + GstCaps *caps, *default_caps; + GESTrack *tmptrack, *track = tmp->data; + + if (GES_IS_VIDEO_TRACK (track)) + tmptrack = GES_TRACK (ges_video_track_new ()); + else if (GES_IS_AUDIO_TRACK (track)) + tmptrack = GES_TRACK (ges_audio_track_new ()); + else { + g_warning ("Unhandled track type: %s", G_OBJECT_TYPE_NAME (track)); + continue; + } + + tt |= track->type; + + g_string_append_printf (res, " +track %s", + (track->type == GES_TRACK_TYPE_VIDEO) ? "video" : "audio"); + + default_caps = ges_track_get_restriction_caps (tmptrack); + caps = ges_track_get_restriction_caps (track); + if (!gst_caps_is_equal (caps, default_caps)) { + tmpstr = gst_caps_serialize (caps, 0); + + g_string_append (res, " restrictions="); + _sanitize_argument (tmpstr, res); + g_free (tmpstr); + } + gst_caps_unref (default_caps); + gst_caps_unref (caps); + gst_object_unref (tmptrack); + } + + for (tmp = timeline->layers, i = 0; tmp; tmp = tmp->next, i++) { + GList *tmpclip, *clips = ges_layer_get_clips (tmp->data); + GList *tmptrackelem; + + for (tmpclip = clips; tmpclip; tmpclip = tmpclip->next) { + GESClip *clip = tmpclip->data; + GESCommandLineOption *option = NULL; + + if (GES_IS_TEST_CLIP (clip)) { + GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (clip)); + const gchar *id = ges_asset_get_id (asset); + + g_string_append (res, " +test-clip "); + + _sanitize_argument (g_enum_get_value (g_type_class_peek + (GES_VIDEO_TEST_PATTERN_TYPE), + ges_test_clip_get_vpattern (GES_TEST_CLIP (clip)))->value_nick, + res); + + if (g_strcmp0 (id, "GESTestClip")) { + g_string_append (res, " asset-id="); + _sanitize_argument (id, res); + } + + option = &options[TEST_CLIP]; + } else if (GES_IS_TITLE_CLIP (clip)) { + g_string_append (res, " +title "); + _sanitize_argument (ges_title_clip_get_text (GES_TITLE_CLIP (clip)), + res); + option = &options[TITLE]; + } else if (GES_IS_URI_CLIP (clip)) { + g_string_append (res, " +clip "); + + _sanitize_argument (ges_uri_clip_get_uri (GES_URI_CLIP (clip)), res); + option = &options[CLIP]; + } else { + g_warning ("Unhandled clip type: %s", G_OBJECT_TYPE_NAME (clip)); + continue; + } + + _serialize_clip_track_types (clip, tt, res); + + if (i) + g_string_append_printf (res, " layer=%d", i); + + _serialize_object_properties (G_OBJECT (clip), option, FALSE, res); + _serialize_clip_effects (clip, res); + + for (tmptrackelem = GES_CONTAINER_CHILDREN (clip); tmptrackelem; + tmptrackelem = tmptrackelem->next) + _serialize_object_properties (G_OBJECT (tmptrackelem->data), option, + TRUE, res); + } + g_list_free_full (clips, gst_object_unref); + } + +done: + return g_string_free (res, FALSE); + { + GstUri *uri = gst_uri_from_string (res->str); + gchar *uri_str = gst_uri_to_string (uri); + + g_string_free (res, TRUE); + + return uri_str; + } +} diff --git a/ges/ges-command-line-formatter.h b/ges/ges-command-line-formatter.h new file mode 100644 index 0000000000..eaee5234a9 --- /dev/null +++ b/ges/ges-command-line-formatter.h @@ -0,0 +1,52 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2015> Thibault Saunier <tsaunier@gnome.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#pragma once + +#include <glib-object.h> +#include "ges-formatter.h" + +G_BEGIN_DECLS + +typedef struct _GESCommandLineFormatterClass GESCommandLineFormatterClass; +typedef struct _GESCommandLineFormatter GESCommandLineFormatter; + +#define GES_TYPE_COMMAND_LINE_FORMATTER (ges_command_line_formatter_get_type ()) +GES_DECLARE_TYPE(CommandLineFormatter, command_line_formatter, COMMAND_LINE_FORMATTER); + +struct _GESCommandLineFormatterClass +{ + GESFormatterClass parent_class; +}; + +struct _GESCommandLineFormatter +{ + GESFormatter parent_instance; + + GESCommandLineFormatterPrivate *priv; +}; + +GES_API +gchar * ges_command_line_formatter_get_help (gint nargs, gchar ** commands); + +GES_API +gchar * ges_command_line_formatter_get_timeline_uri (GESTimeline *timeline); + +G_END_DECLS diff --git a/ges/ges-container.c b/ges/ges-container.c new file mode 100644 index 0000000000..71c3f3801f --- /dev/null +++ b/ges/ges-container.c @@ -0,0 +1,1089 @@ +/* GStreamer Editing Services + * Copyright (C) <2013> Thibault Saunier <thibault.saunier@collabora.com> + * <2013> Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gescontainer + * @title: GESContainer + * @short_description: Base Class for elements responsible for controlling + * other #GESTimelineElement-s + * + * A #GESContainer is a timeline element that controls other + * #GESTimelineElement-s, which are its children. In particular, it is + * responsible for maintaining the relative #GESTimelineElement:start and + * #GESTimelineElement:duration times of its children. Therefore, if a + * container is temporally adjusted or moved to a new layer, it may + * accordingly adjust and move its children. Similarly, a change in one of + * its children may prompt the parent to correspondingly change its + * siblings. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-container.h" +#include "ges.h" +#include "ges-internal.h" + +#include <string.h> + +GST_DEBUG_CATEGORY_STATIC (ges_container_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT ges_container_debug + +/* Mapping of relationship between a Container and the TimelineElements + * it controls + * + * NOTE : Does it make sense to make it public in the future ? + */ +typedef struct +{ + GESTimelineElement *child; + + GstClockTime start_offset; + GstClockTime duration_offset; + + gulong start_notifyid; + gulong duration_notifyid; + gulong child_property_added_notifyid; + gulong child_property_removed_notifyid; +} ChildMapping; + +enum +{ + CHILD_ADDED_SIGNAL, + CHILD_REMOVED_SIGNAL, + LAST_SIGNAL +}; + +static guint ges_container_signals[LAST_SIGNAL] = { 0 }; + +struct _GESContainerPrivate +{ + /*< public > */ + GESLayer *layer; + + /*< private > */ + /* Set to TRUE when the container is doing updates of track object + * properties so we don't end up in infinite property update loops + */ + GHashTable *mappings; + + /* List of GESTimelineElement being in the "child-added" signal + * emission stage */ + GList *adding_children; + + GList *copied_children; +}; + +enum +{ + PROP_0, + PROP_HEIGHT, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESContainer, ges_container, + GES_TYPE_TIMELINE_ELEMENT); + +/************************ + * Private methods * + ************************/ +static void +_free_mapping (ChildMapping * mapping) +{ + GESTimelineElement *child = mapping->child; + + /* Disconnect all notify listeners */ + if (mapping->start_notifyid) + g_signal_handler_disconnect (child, mapping->start_notifyid); + if (mapping->duration_notifyid) + g_signal_handler_disconnect (child, mapping->duration_notifyid); + if (mapping->child_property_added_notifyid) + g_signal_handler_disconnect (child, mapping->child_property_added_notifyid); + if (mapping->child_property_removed_notifyid) + g_signal_handler_disconnect (child, + mapping->child_property_removed_notifyid); + if (child) { + ges_timeline_element_set_parent (child, NULL); + gst_object_unref (child); + } + + g_slice_free (ChildMapping, mapping); +} + +static gint +compare_grouping_prio (GType * a, GType * b) +{ + gint ret = 0; + GObjectClass *aclass = g_type_class_ref (*a); + GObjectClass *bclass = g_type_class_ref (*b); + + /* We want higher prios to be first */ + if (GES_CONTAINER_CLASS (aclass)->grouping_priority < + GES_CONTAINER_CLASS (bclass)->grouping_priority) + ret = 1; + else if (GES_CONTAINER_CLASS (aclass)->grouping_priority > + GES_CONTAINER_CLASS (bclass)->grouping_priority) + ret = -1; + + g_type_class_unref (aclass); + g_type_class_unref (bclass); + return ret; +} + +static void +_resync_position_offsets (GESTimelineElement * child, + ChildMapping * map, GESContainer * container) +{ + map->start_offset = _START (container) - _START (child); + map->duration_offset = _DURATION (container) - _DURATION (child); +} + +/***************************************************** + * * + * GESTimelineElement virtual methods implementation * + * * + *****************************************************/ +static gboolean +_set_start (GESTimelineElement * element, GstClockTime start) +{ + GList *tmp; + ChildMapping *map; + GESContainer *container = GES_CONTAINER (element); + GESContainerPrivate *priv = container->priv; + + GST_DEBUG_OBJECT (element, "Updating children offsets, (initiated_move: %" + GST_PTR_FORMAT ")", container->initiated_move); + + for (tmp = container->children; tmp; tmp = g_list_next (tmp)) { + GESTimelineElement *child = (GESTimelineElement *) tmp->data; + + map = g_hash_table_lookup (priv->mappings, child); + map->start_offset = start - _START (child); + } + container->children_control_mode = GES_CHILDREN_UPDATE; + + return TRUE; +} + +static gboolean +_set_duration (GESTimelineElement * element, GstClockTime duration) +{ + GList *tmp; + GESContainer *container = GES_CONTAINER (element); + GESContainerPrivate *priv = container->priv; + + for (tmp = container->children; tmp; tmp = g_list_next (tmp)) { + GESTimelineElement *child = (GESTimelineElement *) tmp->data; + ChildMapping *map = g_hash_table_lookup (priv->mappings, child); + + map->duration_offset = duration - _DURATION (child); + } + + return TRUE; +} + +static void +_add_childs_child_property (GESTimelineElement * container_child, + GObject * prop_child, GParamSpec * property, GESContainer * container) +{ + /* the container_child is kept as the owner of this child property when + * we register it on ourselves, but we use the same GObject child + * instance who the property comes from */ + gboolean res = + ges_timeline_element_add_child_property_full (GES_TIMELINE_ELEMENT + (container), container_child, property, prop_child); + if (!res) + GST_INFO_OBJECT (container, "Could not register the child property '%s' " + "of our child %" GES_FORMAT " for the object %" GST_PTR_FORMAT, + property->name, GES_ARGS (container_child), prop_child); +} + +static void +_ges_container_add_child_properties (GESContainer * container, + GESTimelineElement * child) +{ + guint n_props, i; + + /* use get_children_properties, rather than list_children_properties + * to ensure we are getting all the properties, without any interference + * from the ->list_children_properties vmethods */ + GParamSpec **child_props = + ges_timeline_element_get_children_properties (child, + &n_props); + + for (i = 0; i < n_props; i++) { + GParamSpec *property = child_props[i]; + GObject *prop_child = + ges_timeline_element_get_child_from_child_property (child, property); + if (prop_child) + _add_childs_child_property (child, prop_child, property, container); + g_param_spec_unref (property); + } + + g_free (child_props); +} + +static void +_remove_childs_child_property (GESTimelineElement * container_child, + GObject * prop_child, GParamSpec * property, GESContainer * container) +{ + /* NOTE: some children may share the same GParamSpec. Currently, only + * the first such child added will have its children properties + * successfully registered for the container (even though the GObject + * child who the properties belong to will be a different instance). As + * such, we only want to remove the child property if it corresponds to + * the same instance that the parent container has. + * E.g. if we add child1 and child2, that have the same (or some + * overlapping) children properties. And child1 is added before child2, + * then child2's overlapping children properties would not be registered. + * If we remove child2, we do *not* want to removed the child properties + * for child1 because they belong to a GObject instance that we still + * have in our control. + * If we remove child1, we *do* want to remove the child properties for + * child1, even though child2 may overlap with some of them, because we + * are loosing the specific GObject instance that it belongs to! + * We could try and register the ones that match for the other children. + * However, it is probably simpler to change + * ges_timeline_element_add_child_property_full to accept the same + * GParamSpec, for different instances. + */ + GESTimelineElement *element = GES_TIMELINE_ELEMENT (container); + GObject *our_prop_child = + ges_timeline_element_get_child_from_child_property (element, property); + if (our_prop_child == prop_child) + ges_timeline_element_remove_child_property (element, property); + else + GST_INFO_OBJECT (container, "Not removing child property '%s' for child" + " %" GES_FORMAT " because it derives from the object %" GST_PTR_FORMAT + "(%p) rather than the object %" GST_PTR_FORMAT "(%p)", property->name, + GES_ARGS (container_child), prop_child, prop_child, our_prop_child, + our_prop_child); +} + +static void +_ges_container_remove_child_properties (GESContainer * container, + GESTimelineElement * child) +{ + guint n_props, i; + + /* use get_children_properties, rather than list_children_properties + * to ensure we are getting all the properties, without any interference + * from the ->list_children_properties vmethods */ + GParamSpec **child_props = + ges_timeline_element_get_children_properties (child, + &n_props); + + for (i = 0; i < n_props; i++) { + GParamSpec *property = child_props[i]; + GObject *prop_child = + ges_timeline_element_get_child_from_child_property (child, property); + if (prop_child) + _remove_childs_child_property (child, prop_child, property, container); + g_param_spec_unref (property); + } + + g_free (child_props); +} + +static gboolean +_lookup_child (GESTimelineElement * self, const gchar * prop_name, + GObject ** child, GParamSpec ** pspec) +{ + GList *tmp; + + /* FIXME Implement a syntax to precisely get properties by path */ + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + if (ges_timeline_element_lookup_child (tmp->data, prop_name, child, pspec)) + return TRUE; + } + + return FALSE; +} + +static GESTrackType +_get_track_types (GESTimelineElement * object) +{ + GESTrackType types = GES_TRACK_TYPE_UNKNOWN; + GList *tmp, *children = ges_container_get_children (GES_CONTAINER (object), + TRUE); + + for (tmp = children; tmp; tmp = tmp->next) { + if (GES_IS_TRACK_ELEMENT (tmp->data)) { + types |= ges_timeline_element_get_track_types (tmp->data); + } + } + + g_list_free_full (children, gst_object_unref); + + return types ^ GES_TRACK_TYPE_UNKNOWN; +} + +static void +_deep_copy (GESTimelineElement * element, GESTimelineElement * copy) +{ + GList *tmp; + GESContainer *self = GES_CONTAINER (element), *ccopy = GES_CONTAINER (copy); + + for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { + ChildMapping *map, *orig_map; + orig_map = g_hash_table_lookup (self->priv->mappings, tmp->data); + map = g_slice_new0 (ChildMapping); + map->child = ges_timeline_element_copy (tmp->data, TRUE); + map->start_offset = orig_map->start_offset; + + ccopy->priv->copied_children = g_list_prepend (ccopy->priv->copied_children, + map); + } +} + +static GESTimelineElement * +_paste (GESTimelineElement * element, GESTimelineElement * ref, + GstClockTime paste_position) +{ + GList *tmp; + ChildMapping *map; + GESContainer *ncontainer = + GES_CONTAINER (ges_timeline_element_copy (element, FALSE)); + GESContainer *self = GES_CONTAINER (element); + + for (tmp = self->priv->copied_children; tmp; tmp = tmp->next) { + GESTimelineElement *nchild; + + map = tmp->data; + nchild = + ges_timeline_element_paste (map->child, + paste_position - map->start_offset); + + if (!nchild) { + while (ncontainer->children) + ges_container_remove (ncontainer, ncontainer->children->data); + + g_object_unref (ncontainer); + return NULL; + } + + /* for GESGroups, this may register the group on the timeline */ + if (!ges_container_add (ncontainer, nchild)) + GST_ERROR ("%" GES_FORMAT " could not add child %p while" + " copying, this should never happen", GES_ARGS (ncontainer), nchild); + } + + return GES_TIMELINE_ELEMENT (ncontainer); +} + + +/****************************************** + * * + * GObject virtual methods implementation * + * * + ******************************************/ +static void +_dispose (GObject * object) +{ + GList *tmp; + GESContainer *self = GES_CONTAINER (object); + GList *children; + + _ges_container_sort_children (self); + children = ges_container_get_children (self, FALSE); + + for (tmp = g_list_last (children); tmp; tmp = tmp->prev) + ges_container_remove (self, tmp->data); + + g_list_free_full (children, gst_object_unref); + self->children = NULL; + + G_OBJECT_CLASS (ges_container_parent_class)->dispose (object); +} + +static void +_finalize (GObject * object) +{ + GESContainer *self = GES_CONTAINER (object); + + g_list_free_full (self->priv->copied_children, + (GDestroyNotify) _free_mapping); + + if (self->priv->mappings) + g_hash_table_destroy (self->priv->mappings); + + G_OBJECT_CLASS (ges_container_parent_class)->finalize (object); +} + +static void +_get_property (GObject * container, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESContainer *tobj = GES_CONTAINER (container); + + switch (property_id) { + case PROP_HEIGHT: + g_value_set_uint (value, tobj->height); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (container, property_id, pspec); + } +} + +static void +_set_property (GObject * container, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (container, property_id, pspec); + } +} + +static void +ges_container_class_init (GESContainerClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass); + + GST_DEBUG_CATEGORY_INIT (ges_container_debug, "gescontainer", + GST_DEBUG_FG_YELLOW, "ges container"); + + object_class->get_property = _get_property; + object_class->set_property = _set_property; + object_class->dispose = _dispose; + object_class->finalize = _finalize; + + /** + * GESContainer:height: + * + * The span of the container's children's #GESTimelineElement:priority + * values, which is the number of integers that lie between (inclusive) + * the minimum and maximum priorities found amongst the container's + * children (maximum - minimum + 1). + */ + properties[PROP_HEIGHT] = g_param_spec_uint ("height", "Height", + "The span of priorities this container occupies", 0, G_MAXUINT, 1, + G_PARAM_READABLE); + g_object_class_install_property (object_class, PROP_HEIGHT, + properties[PROP_HEIGHT]); + + /** + * GESContainer::child-added: + * @container: The #GESContainer + * @element: The child that was added + * + * Will be emitted after a child is added to the container. Usually, + * you should connect with g_signal_connect_after() since the signal + * may be stopped internally. + */ + ges_container_signals[CHILD_ADDED_SIGNAL] = + g_signal_new ("child-added", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESContainerClass, child_added), + NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_TIMELINE_ELEMENT); + + /** + * GESContainer::child-removed: + * @container: The #GESContainer + * @element: The child that was removed + * + * Will be emitted after a child is removed from the container. + */ + ges_container_signals[CHILD_REMOVED_SIGNAL] = + g_signal_new ("child-removed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESContainerClass, child_removed), + NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_TIMELINE_ELEMENT); + + + element_class->set_start = _set_start; + element_class->set_duration = _set_duration; + element_class->lookup_child = _lookup_child; + element_class->get_track_types = _get_track_types; + element_class->paste = _paste; + element_class->deep_copy = _deep_copy; + + /* No default implementations */ + klass->remove_child = NULL; + klass->add_child = NULL; + klass->ungroup = NULL; + klass->group = NULL; + klass->grouping_priority = 0; + klass->edit = NULL; +} + +static void +ges_container_init (GESContainer * self) +{ + self->priv = ges_container_get_instance_private (self); + + /* FIXME, check why default was GST_SECOND? (before the existend of + * ges-container) + * + * _DURATION (self) = GST_SECOND; */ + self->height = 1; /* FIXME Why 1 and not 0? */ + self->children = NULL; + + self->priv->mappings = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) _free_mapping); +} + +/********************************************** + * * + * Property notifications from Children * + * * + **********************************************/ + +static void +_update_start_duration (GESContainer * container, GESTimelineElement * child) +{ + GList *tmp; + GstClockTime duration, end = 0, start = G_MAXUINT64; + gboolean was_being_edited = GES_TIMELINE_ELEMENT_BEING_EDITED (container); + + if (!container->children) { + /* If we are now empty, keep the same duration and start. This works + * well for a clip. For a group, the duration should probably be set + * to 0, but it gets automatically removed from the timeline when it + * is emptied */ + return; + } + + GES_TIMELINE_ELEMENT_SET_BEING_EDITED (container); + + for (tmp = container->children; tmp; tmp = tmp->next) { + start = MIN (start, _START (tmp->data)); + end = MAX (end, _END (tmp->data)); + } + + if (end < start) + duration = 0; + else + duration = end - start; + + if (start != _START (container) || duration != _DURATION (container)) { + GstClockTime prev_dur = _DURATION (container); + GstClockTime prev_start = _START (container); + + _DURATION (container) = duration; + _START (container) = start; + + GST_INFO ("%" GES_FORMAT " child %" GES_FORMAT " move made us move", + GES_ARGS (container), GES_ARGS (child)); + + if (prev_start != start) + g_object_notify (G_OBJECT (container), "start"); + if (prev_dur != duration) + g_object_notify (G_OBJECT (container), "duration"); + } + if (!was_being_edited) + GES_TIMELINE_ELEMENT_UNSET_BEING_EDITED (container); + + g_hash_table_foreach (container->priv->mappings, + (GHFunc) _resync_position_offsets, container); +} + +static void +_child_start_changed_cb (GESTimelineElement * child, + GParamSpec * arg G_GNUC_UNUSED, GESContainer * container) +{ + ChildMapping *map; + + GESContainerPrivate *priv = container->priv; + GESTimelineElement *element = GES_TIMELINE_ELEMENT (container); + GESChildrenControlMode mode = container->children_control_mode; + + if (mode == GES_CHILDREN_IGNORE_NOTIFIES) + return; + + if (GES_TIMELINE_ELEMENT_BEING_EDITED (child)) + mode = GES_CHILDREN_UPDATE_ALL_VALUES; + + map = g_hash_table_lookup (priv->mappings, child); + g_assert (map); + + switch (mode) { + case GES_CHILDREN_UPDATE_ALL_VALUES: + _update_start_duration (container, child); + break; + case GES_CHILDREN_UPDATE_OFFSETS: + map->start_offset = _START (container) - _START (child); + break; + case GES_CHILDREN_UPDATE: + /* We update all the children calling our set_start method */ + container->initiated_move = child; + _set_start0 (element, _START (child) + map->start_offset); + container->initiated_move = NULL; + break; + default: + break; + } +} + +static void +_child_duration_changed_cb (GESTimelineElement * child, + GParamSpec * arg G_GNUC_UNUSED, GESContainer * container) +{ + ChildMapping *map; + + GESContainerPrivate *priv = container->priv; + GESTimelineElement *element = GES_TIMELINE_ELEMENT (container); + GESChildrenControlMode mode = container->children_control_mode; + + if (mode == GES_CHILDREN_IGNORE_NOTIFIES) + return; + + if (GES_TIMELINE_ELEMENT_BEING_EDITED (child)) + mode = GES_CHILDREN_UPDATE_ALL_VALUES; + + map = g_hash_table_lookup (priv->mappings, child); + g_assert (map); + + switch (mode) { + case GES_CHILDREN_UPDATE_ALL_VALUES: + _update_start_duration (container, child); + break; + case GES_CHILDREN_UPDATE_OFFSETS: + map->duration_offset = _DURATION (container) - _DURATION (child); + break; + case GES_CHILDREN_UPDATE: + /* We update all the children calling our set_duration method */ + container->initiated_move = child; + /* FIXME: this is *not* the correct duration for a group! + * the ->set_duration method for GESGroup tries to hack around + * this by calling set_duration on itself to the actual value */ + _set_duration0 (element, _DURATION (child) + map->duration_offset); + container->initiated_move = NULL; + break; + default: + break; + } +} + +/**************************************************** + * * + * Internal methods implementation * + * * + ****************************************************/ + +void +_ges_container_sort_children (GESContainer * container) +{ + container->children = g_list_sort (container->children, + (GCompareFunc) element_start_compare); +} + +void +_ges_container_set_height (GESContainer * container, guint32 height) +{ + if (container->height != height) { + container->height = height; + GST_DEBUG_OBJECT (container, "Updating height %i", container->height); + g_object_notify (G_OBJECT (container), "height"); + } +} + +/********************************************** + * * + * API implementation * + * * + **********************************************/ + +/** + * ges_container_add: + * @container: A #GESContainer + * @child: The element to add as a child + * + * Adds a timeline element to the container. The element will now be a + * child of the container (and the container will be the + * #GESTimelineElement:parent of the added element), which means that it + * is now controlled by the container. This may change the properties of + * the child or the container, depending on the subclass. + * + * Additionally, the children properties of the newly added element will + * be shared with the container, meaning they can also be read and set + * using ges_timeline_element_get_child_property() and + * ges_timeline_element_set_child_property() on the container. + * + * Returns: %TRUE if @child was successfully added to @container. + */ +gboolean +ges_container_add (GESContainer * container, GESTimelineElement * child) +{ + ChildMapping *mapping; + gboolean ret = FALSE; + GESContainerClass *class; + GList *current_children, *tmp; + GESContainerPrivate *priv; + + g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE); + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (child), FALSE); + g_return_val_if_fail (GES_TIMELINE_ELEMENT_PARENT (child) == NULL, FALSE); + + class = GES_CONTAINER_GET_CLASS (container); + priv = container->priv; + + GST_DEBUG_OBJECT (container, "adding timeline element %" GST_PTR_FORMAT, + child); + + /* freeze all notifies */ + g_object_freeze_notify (G_OBJECT (container)); + /* copy to use at end, since container->children may have child + * added to it */ + current_children = g_list_copy_deep (container->children, + (GCopyFunc) gst_object_ref, NULL); + for (tmp = current_children; tmp; tmp = tmp->next) + g_object_freeze_notify (G_OBJECT (tmp->data)); + g_object_freeze_notify (G_OBJECT (child)); + gst_object_ref_sink (child); + + if (class->add_child) { + if (class->add_child (container, child) == FALSE) { + GST_WARNING_OBJECT (container, "Error adding child %p", child); + goto done; + } + } + + mapping = g_slice_new0 (ChildMapping); + mapping->child = gst_object_ref (child); + g_hash_table_insert (priv->mappings, child, mapping); + container->children = g_list_prepend (container->children, child); + + /* Listen to all property changes */ + mapping->start_notifyid = + g_signal_connect (G_OBJECT (child), "notify::start", + G_CALLBACK (_child_start_changed_cb), container); + mapping->duration_notifyid = + g_signal_connect (G_OBJECT (child), "notify::duration", + G_CALLBACK (_child_duration_changed_cb), container); + + if (ges_timeline_element_set_parent (child, GES_TIMELINE_ELEMENT (container)) + == FALSE) { + if (class->remove_child) + class->remove_child (container, child); + + g_hash_table_remove (priv->mappings, child); + container->children = g_list_remove (container->children, child); + + goto done; + } + + _update_start_duration (container, child); + _ges_container_sort_children (container); + + _ges_container_add_child_properties (container, child); + mapping->child_property_added_notifyid = + g_signal_connect (G_OBJECT (child), "child-property-added", + G_CALLBACK (_add_childs_child_property), container); + mapping->child_property_removed_notifyid = + g_signal_connect (G_OBJECT (child), "child-property-removed", + G_CALLBACK (_remove_childs_child_property), container); + + priv->adding_children = g_list_prepend (priv->adding_children, child); + g_signal_emit (container, ges_container_signals[CHILD_ADDED_SIGNAL], 0, + child); + priv->adding_children = g_list_remove (priv->adding_children, child); + + ret = TRUE; + +done: + /* thaw all notifies */ + /* Ignore notifies for the start and duration since the child should + * already be correctly set up */ + container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; + g_object_thaw_notify (G_OBJECT (container)); + for (tmp = current_children; tmp; tmp = tmp->next) + g_object_thaw_notify (G_OBJECT (tmp->data)); + g_object_thaw_notify (G_OBJECT (child)); + g_list_free_full (current_children, gst_object_unref); + gst_object_unref (child); + container->children_control_mode = GES_CHILDREN_UPDATE; + return ret; +} + +/** + * ges_container_remove: + * @container: A #GESContainer + * @child: The child to remove + * + * Removes a timeline element from the container. The element will no + * longer be controlled by the container. + * + * Returns: %TRUE if @child was successfully removed from @container. + */ +gboolean +ges_container_remove (GESContainer * container, GESTimelineElement * child) +{ + GESContainerClass *klass; + GESContainerPrivate *priv; + GList *current_children, *tmp; + gboolean ret = FALSE; + + g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE); + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (child), FALSE); + + GST_DEBUG_OBJECT (container, "removing child: %" GST_PTR_FORMAT, child); + + klass = GES_CONTAINER_GET_CLASS (container); + priv = container->priv; + + if (!(g_hash_table_lookup (priv->mappings, child))) { + GST_WARNING_OBJECT (container, "Element isn't controlled by this " + "container"); + return FALSE; + } + + /* ref the container since it might be destroyed when the child is + * removed! (see GESGroup ->child_removed) */ + gst_object_ref (container); + /* freeze all notifies */ + g_object_freeze_notify (G_OBJECT (container)); + /* copy to use at end, since container->children may have child + * removed from it */ + current_children = g_list_copy_deep (container->children, + (GCopyFunc) gst_object_ref, NULL); + for (tmp = current_children; tmp; tmp = tmp->next) + g_object_freeze_notify (G_OBJECT (tmp->data)); + + + if (klass->remove_child) { + if (klass->remove_child (container, child) == FALSE) + goto done; + } + + container->children = g_list_remove (container->children, child); + g_hash_table_remove (priv->mappings, child); + + _ges_container_remove_child_properties (container, child); + + if (!g_list_find (container->priv->adding_children, child)) { + g_signal_emit (container, ges_container_signals[CHILD_REMOVED_SIGNAL], 0, + child); + } else { + GESContainerClass *klass = GES_CONTAINER_GET_CLASS (container); + + if (klass->child_removed) + klass->child_removed (container, child); + + GST_INFO_OBJECT (container, + "Not emitting 'child-removed' signal as child" + " removal happend during 'child-added' signal emission"); + } + + _update_start_duration (container, child); + + ret = TRUE; + +done: + /* thaw all notifies */ + g_object_thaw_notify (G_OBJECT (container)); + for (tmp = current_children; tmp; tmp = tmp->next) + g_object_thaw_notify (G_OBJECT (tmp->data)); + g_list_free_full (current_children, gst_object_unref); + + gst_object_unref (container); + + return ret; +} + +static void +_get_children_recursively (GESContainer * container, GList ** children) +{ + GList *tmp; + + *children = + g_list_concat (*children, g_list_copy_deep (container->children, + (GCopyFunc) gst_object_ref, NULL)); + + for (tmp = container->children; tmp; tmp = tmp->next) { + GESTimelineElement *element = tmp->data; + + if (GES_IS_CONTAINER (element)) + _get_children_recursively (tmp->data, children); + } +} + +/** + * ges_container_get_children: + * @container: A #GESContainer + * @recursive: Whether to recursively get children in @container + * + * Get the list of timeline elements contained in the container. If + * @recursive is %TRUE, and the container contains other containers as + * children, then their children will be added to the list, in addition to + * themselves, and so on. + * + * Returns: (transfer full) (element-type GESTimelineElement): The list of + * #GESTimelineElement-s contained in @container. + */ +GList * +ges_container_get_children (GESContainer * container, gboolean recursive) +{ + GList *children = NULL; + + g_return_val_if_fail (GES_IS_CONTAINER (container), NULL); + + if (!recursive) + return g_list_copy_deep (container->children, (GCopyFunc) gst_object_ref, + NULL); + + _get_children_recursively (container, &children); + return children; +} + +/** + * ges_container_ungroup: + * @container: (transfer full): The container to ungroup + * @recursive: Whether to recursively ungroup @container + * + * Ungroups the container by splitting it into several containers + * containing various children of the original. The rules for how the + * container splits depends on the subclass. A #GESGroup will simply split + * into its children. A #GESClip will split into one #GESClip per + * #GESTrackType it overlaps with (so an audio-video clip will split into + * an audio clip and a video clip), where each clip contains all the + * #GESTrackElement-s from the original clip with a matching + * #GESTrackElement:track-type. + * + * If @recursive is %TRUE, and the container contains other containers as + * children, then they will also be ungrouped, and so on. + * + * Returns: (transfer full) (element-type GESContainer): The list of + * new #GESContainer-s created from the splitting of @container. + */ +GList * +ges_container_ungroup (GESContainer * container, gboolean recursive) +{ + GESContainerClass *klass; + + g_return_val_if_fail (GES_IS_CONTAINER (container), NULL); + + GST_DEBUG_OBJECT (container, "Ungrouping container %s recursively", + recursive ? "" : "not"); + + klass = GES_CONTAINER_GET_CLASS (container); + if (klass->ungroup == NULL) { + GST_INFO_OBJECT (container, "No ungoup virtual method, doint nothing"); + return NULL; + } + + return klass->ungroup (container, recursive); +} + +/** + * ges_container_group: + * @containers: (transfer none)(element-type GESContainer) (allow-none): + * The #GESContainer-s to group + * + * Groups the containers into a single container by merging them. The + * containers must all belong to the same #GESTimelineElement:timeline. + * + * If the elements are all #GESClip-s then this method will attempt to + * combine them all into a single #GESClip. This should succeed if they: + * share the same #GESTimelineElement:start, #GESTimelineElement:duration + * and #GESTimelineElement:in-point; exist in the same layer; and all of + * the sources share the same #GESAsset. If this fails, or one of the + * elements is not a #GESClip, this method will try to create a #GESGroup + * instead. + * + * Returns: (transfer floating): The container created by merging + * @containers, or %NULL if they could not be merged into a single + * container. + */ +GESContainer * +ges_container_group (GList * containers) +{ + GList *tmp; + guint n_children; + GType *children_types; + GESTimelineElement *element; + GObjectClass *clip_class; + + guint i = 0; + GESContainer *ret = NULL; + GESTimeline *timeline = NULL; + + if (containers) { + element = GES_TIMELINE_ELEMENT (containers->data); + timeline = GES_TIMELINE_ELEMENT_TIMELINE (element); + g_return_val_if_fail (timeline, NULL); + } + + if (g_list_length (containers) == 1) { + /* FIXME: Should return a floating **copy**. API specifies that the + * returned element is created. So users might expect to be able to + * freely dispose of the list, without the risk of the returned + * element being freed as well. + * TODO 2.0: (transfer full) would have been better */ + return containers->data; + } + + for (tmp = containers; tmp; tmp = tmp->next) { + g_return_val_if_fail (GES_IS_CONTAINER (tmp->data), NULL); + g_return_val_if_fail (GES_TIMELINE_ELEMENT_PARENT (tmp->data) == NULL, + NULL); + g_return_val_if_fail (GES_TIMELINE_ELEMENT_TIMELINE (tmp->data) == timeline, + NULL); + } + + /* FIXME: how can user sub-classes interact with this if + * ->grouping_priority is private? */ + children_types = g_type_children (GES_TYPE_CONTAINER, &n_children); + g_qsort_with_data (children_types, n_children, sizeof (GType), + (GCompareDataFunc) compare_grouping_prio, NULL); + + for (i = 0; i < n_children; i++) { + clip_class = g_type_class_peek (children_types[i]); + /* FIXME: handle NULL ->group */ + ret = GES_CONTAINER_CLASS (clip_class)->group (containers); + + if (ret) + break; + } + + g_free (children_types); + return ret; +} + +/** + * ges_container_edit: + * @container: The #GESContainer to edit + * @layers: (element-type GESLayer) (nullable): A whitelist of layers + * where the edit can be performed, %NULL allows all layers in the + * timeline + * @new_layer_priority: The priority/index of the layer @container should + * be moved to. -1 means no move + * @mode: The edit mode + * @edge: The edge of @container where the edit should occur + * @position: The edit position: a new location for the edge of @container + * (in nanoseconds) + * + * Edits the container within its timeline. + * + * Returns: %TRUE if the edit of @container completed, %FALSE on failure. + * + * Deprecated: 1.18: use #ges_timeline_element_edit instead. + */ +gboolean +ges_container_edit (GESContainer * container, GList * layers, + gint new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position) +{ + g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE); + + return ges_timeline_element_edit (GES_TIMELINE_ELEMENT (container), + layers, new_layer_priority, mode, edge, position); +} diff --git a/ges/ges-container.h b/ges/ges-container.h new file mode 100644 index 0000000000..2769ccccda --- /dev/null +++ b/ges/ges-container.h @@ -0,0 +1,158 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <gst/gst.h> +#include <ges/ges-timeline-element.h> +#include <ges/ges-types.h> +#include <ges/ges-track.h> + +G_BEGIN_DECLS + +#define GES_TYPE_CONTAINER ges_container_get_type() +GES_DECLARE_TYPE(Container, container, CONTAINER); + +/** + * GESChildrenControlMode: + * + * To be used by subclasses only. This indicate how to handle a change in + * a child. + */ +typedef enum +{ + GES_CHILDREN_UPDATE, + GES_CHILDREN_IGNORE_NOTIFIES, + GES_CHILDREN_UPDATE_OFFSETS, + GES_CHILDREN_UPDATE_ALL_VALUES, + GES_CHILDREN_LAST +} GESChildrenControlMode; + +/** + * GES_CONTAINER_HEIGHT: + * @obj: a #GESContainer + * + * The #GESContainer:height of @obj. + */ +#define GES_CONTAINER_HEIGHT(obj) (((GESContainer*)obj)->height) + +/** + * GES_CONTAINER_CHILDREN: + * @obj: a #GESContainer + * + * The #GList containing the children of @obj. + */ +#define GES_CONTAINER_CHILDREN(obj) (((GESContainer*)obj)->children) + +/** + * GESContainer: + * @children: (element-type GES.TimelineElement): The list of + * #GESTimelineElement-s controlled by this Container + * @height: The #GESContainer:height of @obj + * + * Note, you may read, but should not modify these properties. + */ +struct _GESContainer +{ + GESTimelineElement parent; + + /*< public > */ + /*< readonly >*/ + GList *children; + + /* We don't add those properties to the priv struct for optimization and code + * readability purposes */ + guint32 height; /* the span of priorities this object needs */ + + /* <protected> */ + GESChildrenControlMode children_control_mode; + /*< readonly >*/ + GESTimelineElement *initiated_move; + + /*< private >*/ + GESContainerPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING_LARGE]; +}; + +/** + * GESContainerClass: + * @child_added: Virtual method that is called right after a #GESTimelineElement is added + * @child_removed: Virtual method that is called right after a #GESTimelineElement is removed + * @remove_child: Virtual method to remove a child + * @add_child: Virtual method to add a child + * @ungroup: Virtual method to ungroup a container into a list of + * containers + * @group: Virtual method to group a list of containers together under a + * single container + * @edit: Deprecated + */ +struct _GESContainerClass +{ + /*< private > */ + GESTimelineElementClass parent_class; + + /*< public > */ + /* signals */ + void (*child_added) (GESContainer *container, GESTimelineElement *element); + void (*child_removed) (GESContainer *container, GESTimelineElement *element); + gboolean (*add_child) (GESContainer *container, GESTimelineElement *element); + gboolean (*remove_child) (GESContainer *container, GESTimelineElement *element); + GList* (*ungroup) (GESContainer *container, gboolean recursive); + GESContainer * (*group) (GList *containers); + + /* Deprecated and not used anymore */ + gboolean (*edit) (GESContainer * container, + GList * layers, gint new_layer_priority, + GESEditMode mode, + GESEdge edge, + guint64 position); + + + + /*< private >*/ + guint grouping_priority; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING_LARGE]; +}; + +/* Children handling */ +GES_API +GList* ges_container_get_children (GESContainer *container, gboolean recursive); +GES_API +gboolean ges_container_add (GESContainer *container, GESTimelineElement *child); +GES_API +gboolean ges_container_remove (GESContainer *container, GESTimelineElement *child); +GES_API +GList * ges_container_ungroup (GESContainer * container, gboolean recursive); +GES_API +GESContainer *ges_container_group (GList *containers); + +GES_DEPRECATED_FOR(ges_timeline_element_edit) +gboolean ges_container_edit (GESContainer * container, + GList * layers, gint new_layer_priority, + GESEditMode mode, + GESEdge edge, + guint64 position); + +G_END_DECLS diff --git a/ges/ges-effect-asset.c b/ges/ges-effect-asset.c new file mode 100644 index 0000000000..fa90a6619f --- /dev/null +++ b/ges/ges-effect-asset.c @@ -0,0 +1,432 @@ +/* Gstreamer Editing Services + * + * Copyright (C) <2012> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION: geseffectasset + * @title: GESEffectAsset + * @short_description: A GESAsset subclass specialized in GESEffect extraction + * + * This asset has a GStreamer bin-description as ID and is able to determine + * to what track type the effect should be used in. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-effect-asset.h" +#include "ges-track-element.h" +#include "ges-internal.h" + +struct _GESEffectAssetPrivate +{ + gpointer nothing; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GESEffectAsset, ges_effect_asset, + GES_TYPE_TRACK_ELEMENT_ASSET); + +static void +_fill_track_type (GESAsset * asset) +{ + GESTrackType ttype; + gchar *bin_desc; + const gchar *id = ges_asset_get_id (asset); + + bin_desc = ges_effect_asset_id_get_type_and_bindesc (id, &ttype, NULL); + + if (bin_desc) { + ges_track_element_asset_set_track_type (GES_TRACK_ELEMENT_ASSET (asset), + ttype); + g_free (bin_desc); + } else { + GST_WARNING_OBJECT (asset, "No track type set, you should" + " specify one in [audio, video] as first component" " in the asset id"); + } +} + +/* GESAsset virtual methods implementation */ +static GESExtractable * +_extract (GESAsset * asset, GError ** error) +{ + GESExtractable *effect; + + effect = GES_ASSET_CLASS (ges_effect_asset_parent_class)->extract (asset, + error); + + if (effect == NULL || (error && *error)) { + effect = NULL; + + return NULL; + } + + return effect; +} + +static void +ges_effect_asset_init (GESEffectAsset * self) +{ + self->priv = ges_effect_asset_get_instance_private (self); +} + +static void +ges_effect_asset_constructed (GObject * object) +{ + _fill_track_type (GES_ASSET (object)); +} + +static void +ges_effect_asset_finalize (GObject * object) +{ + /* TODO: Add deinitalization code here */ + + G_OBJECT_CLASS (ges_effect_asset_parent_class)->finalize (object); +} + +static void +ges_effect_asset_class_init (GESEffectAssetClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESAssetClass *asset_class = GES_ASSET_CLASS (klass); + + object_class->finalize = ges_effect_asset_finalize; + object_class->constructed = ges_effect_asset_constructed; + asset_class->extract = _extract; +} + +static gboolean +find_compatible_pads (GstElement * bin, const gchar * bin_desc, + GstElement * child, GstCaps * valid_caps, GstPad ** srcpad, + GList ** sinkpads, GList ** elems_with_reqsink, + GList ** elems_with_reqsrc, GError ** error) +{ + GList *tmp, *tmptemplate; + + for (tmp = child->pads; tmp; tmp = tmp->next) { + GstCaps *caps; + GstPad *pad = tmp->data; + + if (GST_PAD_PEER (pad)) + continue; + + if (GST_PAD_IS_SRC (pad) && *srcpad) { + g_set_error (error, GES_ERROR, GES_ERROR_INVALID_EFFECT_BIN_DESCRIPTION, + "More than 1 source pad in effect '%s', that is not handled", + bin_desc); + return FALSE; + } + + caps = gst_pad_query_caps (pad, NULL); + if (gst_caps_can_intersect (caps, valid_caps)) { + if (GST_PAD_IS_SINK (pad)) + *sinkpads = g_list_append (*sinkpads, gst_object_ref (pad)); + else + *srcpad = gst_object_ref (pad); + } else { + GST_LOG_OBJECT (pad, "Can't link pad %" GST_PTR_FORMAT, caps); + } + + gst_caps_unref (caps); + } + + tmptemplate = + gst_element_class_get_pad_template_list (GST_ELEMENT_GET_CLASS (child)); + for (; tmptemplate; tmptemplate = tmptemplate->next) { + GstPadTemplate *template = tmptemplate->data; + + if (template->direction == GST_PAD_SINK) { + if (template->presence == GST_PAD_REQUEST) + *elems_with_reqsink = g_list_append (*elems_with_reqsink, child); + } + } + + return TRUE; +} + +static GstPad * +request_pad (GstElement * element, GstPadDirection direction) +{ + GstPad *pad = NULL; + GList *templates; + + templates = gst_element_class_get_pad_template_list + (GST_ELEMENT_GET_CLASS (element)); + + for (; templates; templates = templates->next) { + GstPadTemplate *templ = (GstPadTemplate *) templates->data; + + GST_LOG_OBJECT (element, "Trying template %s", + GST_PAD_TEMPLATE_NAME_TEMPLATE (templ)); + + if ((GST_PAD_TEMPLATE_DIRECTION (templ) == direction) && + (GST_PAD_TEMPLATE_PRESENCE (templ) == GST_PAD_REQUEST)) { + pad = + gst_element_request_pad_simple (element, + GST_PAD_TEMPLATE_NAME_TEMPLATE (templ)); + if (pad) + break; + } + } + + return pad; +} + +static GstPad * +get_pad_from_elements_with_request_pad (GstElement * effect, + const gchar * bin_desc, GList * requestable, GstPadDirection direction, + GError ** error) +{ + GstElement *request_element = NULL; + + if (!requestable) { + g_set_error (error, GES_ERROR, GES_ERROR_INVALID_EFFECT_BIN_DESCRIPTION, + "No %spads available for effect: %s", + (direction == GST_PAD_SRC) ? "src" : "sink", bin_desc); + + return NULL; + } + + request_element = requestable->data; + if (requestable->next) { + GstIterator *it = gst_bin_iterate_sorted (GST_BIN (effect)); + GValue v; + + while (gst_iterator_next (it, &v) != GST_ITERATOR_DONE) { + GstElement *tmpe = g_value_get_object (&v); + + if (g_list_find (requestable, tmpe)) { + request_element = tmpe; + if (direction == GST_PAD_SRC) { + break; + } + } + g_value_reset (&v); + } + gst_iterator_free (it); + } + + return request_pad (request_element, direction); +} + +static gboolean +ghost_pad (GstElement * effect, const gchar * bin_desc, GstPad * pad, + gint n_pad, const gchar * converter_str, GError ** error) +{ + gchar *name; + GstPad *peer, *ghosted; + GstPadLinkReturn lret; + GstElement *converter; + + if (!converter_str) { + ghosted = pad; + goto ghost; + } + + converter = gst_parse_bin_from_description_full (converter_str, TRUE, NULL, + GST_PARSE_FLAG_NO_SINGLE_ELEMENT_BINS | GST_PARSE_FLAG_PLACE_IN_BIN, + error); + + if (!converter) { + GST_ERROR_OBJECT (effect, "Could not create converter '%s'", converter_str); + return FALSE; + } + + peer = + GST_PAD_IS_SINK (pad) ? converter->srcpads->data : converter->sinkpads-> + data; + + gst_bin_add (GST_BIN (effect), converter); + lret = + gst_pad_link (GST_PAD_IS_SINK (pad) ? peer : pad, + GST_PAD_IS_SINK (pad) ? pad : peer); + + if (lret != GST_PAD_LINK_OK) { + gst_object_unref (converter); + g_set_error (error, GES_ERROR, GES_ERROR_INVALID_EFFECT_BIN_DESCRIPTION, + "Effect %s can not link converter %s with %s", bin_desc, converter_str, + gst_pad_link_get_name (lret)); + return FALSE; + } + + ghosted = + GST_PAD_IS_SRC (pad) ? converter->srcpads->data : converter->sinkpads-> + data; + +ghost: + + if (GST_PAD_IS_SINK (pad)) + name = g_strdup_printf ("sink_%d", n_pad); + else + name = g_strdup_printf ("src"); + + gst_element_add_pad (effect, gst_ghost_pad_new (name, ghosted)); + g_free (name); + + return TRUE; +} + +GstElement * +ges_effect_from_description (const gchar * bin_desc, GESTrackType type, + GError ** error) +{ + + gint n_sink = 0; + GstPad *srcpad = NULL; + GstCaps *valid_caps = NULL; + const gchar *converter_str = NULL; + GList *tmp, *sinkpads = NULL, *elems_with_reqsink = NULL, + *elems_with_reqsrc = NULL; + GstElement *effect = + gst_parse_bin_from_description_full (bin_desc, FALSE, NULL, + GST_PARSE_FLAG_PLACE_IN_BIN | GST_PARSE_FLAG_FATAL_ERRORS, error); + + if (!effect) { + GST_ERROR ("An error occurred while creating: %s", + (error && *error) ? (*error)->message : "Unknown error"); + goto err; + } + + if (type == GES_TRACK_TYPE_VIDEO) { + valid_caps = gst_caps_from_string ("video/x-raw(ANY)"); + converter_str = "videoconvert"; + } else if (type == GES_TRACK_TYPE_AUDIO) { + valid_caps = gst_caps_from_string ("audio/x-raw(ANY)"); + converter_str = "audioconvert ! audioresample ! audioconvert"; + } else { + valid_caps = gst_caps_new_any (); + } + + for (tmp = GST_BIN_CHILDREN (effect); tmp; tmp = tmp->next) { + if (!find_compatible_pads (effect, bin_desc, tmp->data, valid_caps, &srcpad, + &sinkpads, &elems_with_reqsink, &elems_with_reqsrc, error)) + goto err; + } + + if (!sinkpads) { + GstPad *sinkpad = get_pad_from_elements_with_request_pad (effect, bin_desc, + elems_with_reqsink, GST_PAD_SINK, error); + if (!sinkpad) + goto err; + sinkpads = g_list_append (sinkpads, sinkpad); + } + + if (!srcpad) { + srcpad = get_pad_from_elements_with_request_pad (effect, bin_desc, + elems_with_reqsrc, GST_PAD_SRC, error); + if (!srcpad) + goto err; + } + + for (tmp = sinkpads; tmp; tmp = tmp->next) { + if (!ghost_pad (effect, bin_desc, tmp->data, n_sink, converter_str, error)) + goto err; + n_sink++; + } + + if (!ghost_pad (effect, bin_desc, srcpad, 0, converter_str, error)) + goto err; + +done: + g_list_free (elems_with_reqsink); + g_list_free (elems_with_reqsrc); + g_list_free_full (sinkpads, gst_object_unref); + gst_clear_caps (&valid_caps); + gst_clear_object (&srcpad); + + return effect; + +err: + gst_clear_object (&effect); + goto done; +} + +gchar * +ges_effect_asset_id_get_type_and_bindesc (const char *id, + GESTrackType * track_type, GError ** error) +{ + GList *tmp; + GstElement *effect; + gchar **typebin_desc = NULL; + const gchar *user_bindesc; + gchar *bindesc = NULL; + + *track_type = GES_TRACK_TYPE_UNKNOWN; + typebin_desc = g_strsplit (id, " ", 2); + if (!g_strcmp0 (typebin_desc[0], "audio")) { + *track_type = GES_TRACK_TYPE_AUDIO; + user_bindesc = typebin_desc[1]; + } else if (!g_strcmp0 (typebin_desc[0], "video")) { + *track_type = GES_TRACK_TYPE_VIDEO; + user_bindesc = typebin_desc[1]; + } else { + *track_type = GES_TRACK_TYPE_UNKNOWN; + user_bindesc = id; + } + + bindesc = g_strdup (user_bindesc); + g_strfreev (typebin_desc); + + effect = gst_parse_bin_from_description (bindesc, TRUE, error); + if (effect == NULL) { + GST_ERROR ("Could not create element from: %s", bindesc); + g_free (bindesc); + return NULL; + } + + if (*track_type != GES_TRACK_TYPE_UNKNOWN) { + gst_object_unref (effect); + + return bindesc; + } + + for (tmp = GST_BIN_CHILDREN (effect); tmp; tmp = tmp->next) { + GstElementFactory *factory = + gst_element_get_factory (GST_ELEMENT (tmp->data)); + const gchar *klass = + gst_element_factory_get_metadata (factory, GST_ELEMENT_METADATA_KLASS); + + if (g_strrstr (klass, "Effect") || g_strrstr (klass, "Filter")) { + if (g_strrstr (klass, "Audio")) { + *track_type = GES_TRACK_TYPE_AUDIO; + break; + } else if (g_strrstr (klass, "Video")) { + *track_type = GES_TRACK_TYPE_VIDEO; + break; + } + } + } + + gst_object_unref (effect); + + if (*track_type == GES_TRACK_TYPE_UNKNOWN) { + *track_type = GES_TRACK_TYPE_VIDEO; + GST_ERROR ("Could not determine track type for %s, defaulting to video", + id); + + } + + if (!(effect = ges_effect_from_description (bindesc, *track_type, error))) { + g_free (bindesc); + + return NULL; + } + gst_object_unref (effect); + + return bindesc; +} diff --git a/ges/ges-effect-asset.h b/ges/ges-effect-asset.h new file mode 100644 index 0000000000..0c624fe59f --- /dev/null +++ b/ges/ges-effect-asset.h @@ -0,0 +1,47 @@ + +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ +/* + * gst-editing-services + * Copyright (C) 2013 Thibault Saunier <thibault.saunier@collabora.com> + * + gst-editing-services 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 3 of the License, or + * (at your option) any later version. + * + * gst-editing-services 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 program. If not, see <http://www.gnu.org/licenses/>."; + */ + +#pragma once + +#include <glib-object.h> +#include "ges-track-element-asset.h" + +G_BEGIN_DECLS + +#define GES_TYPE_EFFECT_ASSET (ges_effect_asset_get_type ()) +GES_DECLARE_TYPE(EffectAsset, effect_asset, EFFECT_ASSET); + +struct _GESEffectAssetClass +{ + GESTrackElementAssetClass parent_class; + + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESEffectAsset +{ + GESTrackElementAsset parent_instance; + + GESEffectAssetPrivate *priv; + + gpointer _ges_reserved[GES_PADDING]; +}; + +G_END_DECLS diff --git a/ges/ges-effect-clip.c b/ges/ges-effect-clip.c new file mode 100644 index 0000000000..4b89054ed5 --- /dev/null +++ b/ges/ges-effect-clip.c @@ -0,0 +1,281 @@ +/* GStreamer Editing Services + * Copyright (C) 2011 Thibault Saunier <thibault.saunier@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION: geseffectclip + * @title: GESEffectClip + * @short_description: An effect created by parse-launch style bin descriptions + * in a GESLayer + * + * The effect will be applied on the sources that have lower priorities + * (higher number) between the inpoint and the end of it. + * + * The asset ID of an effect clip is in the form: + * + * ``` + * "audio ! bin ! description || video ! bin ! description" + * ``` + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <ges/ges.h> +#include "ges-internal.h" +#include "ges-types.h" + +struct _GESEffectClipPrivate +{ + gchar *video_bin_description; + gchar *audio_bin_description; +}; + +static void ges_extractable_interface_init (GESExtractableInterface * iface); +G_DEFINE_TYPE_WITH_CODE (GESEffectClip, ges_effect_clip, + GES_TYPE_BASE_EFFECT_CLIP, G_ADD_PRIVATE (GESEffectClip) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, + ges_extractable_interface_init)); + +enum +{ + PROP_0, + PROP_VIDEO_BIN_DESCRIPTION, + PROP_AUDIO_BIN_DESCRIPTION, +}; + +static void ges_effect_clip_finalize (GObject * object); +static GESTrackElement *_create_track_element (GESClip * self, + GESTrackType type); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS; /* Start ignoring GParameter deprecation */ +static GParameter * +extractable_get_parameters_from_id (const gchar * id, guint * n_params) +{ + gchar *bin_desc; + GESTrackType ttype; + GParameter *params = g_new0 (GParameter, 2); + gchar **effects_desc = g_strsplit (id, "||", -1); + gint i; + + *n_params = 0; + + if (g_strv_length (effects_desc) > 2) + GST_ERROR ("EffectClip id %s contains too many effect descriptions", id); + + for (i = 0; effects_desc[i] && i < 2; i++) { + bin_desc = + ges_effect_asset_id_get_type_and_bindesc (effects_desc[i], &ttype, + NULL); + + if (ttype == GES_TRACK_TYPE_AUDIO) { + *n_params = *n_params + 1; + params[*n_params - 1].name = "audio-bin-description"; + } else if (ttype == GES_TRACK_TYPE_VIDEO) { + *n_params = *n_params + 1; + params[i].name = "video-bin-description"; + } else { + g_free (bin_desc); + GST_ERROR ("Could not find effect type for %s", effects_desc[i]); + continue; + } + + g_value_init (¶ms[*n_params - 1].value, G_TYPE_STRING); + g_value_set_string (¶ms[*n_params - 1].value, bin_desc); + g_free (bin_desc); + } + + g_strfreev (effects_desc); + return params; +} + +G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */ + +static gchar * +extractable_check_id (GType type, const gchar * id, GError ** error) +{ + return g_strdup (id); +} + +static gchar * +extractable_get_id (GESExtractable * self) +{ + GString *id = g_string_new (NULL); + GESEffectClipPrivate *priv = GES_EFFECT_CLIP (self)->priv; + + if (priv->audio_bin_description) + g_string_append_printf (id, "audio %s ||", priv->audio_bin_description); + if (priv->video_bin_description) + g_string_append_printf (id, "video %s", priv->video_bin_description); + + return g_string_free (id, FALSE); +} + +static void +ges_extractable_interface_init (GESExtractableInterface * iface) +{ + iface->asset_type = GES_TYPE_ASSET; + iface->check_id = extractable_check_id; + iface->get_parameters_from_id = extractable_get_parameters_from_id; + iface->get_id = extractable_get_id; +} + + +static void +ges_effect_clip_finalize (GObject * object) +{ + GESEffectClipPrivate *priv = GES_EFFECT_CLIP (object)->priv; + + g_free (priv->audio_bin_description); + g_free (priv->video_bin_description); + + G_OBJECT_CLASS (ges_effect_clip_parent_class)->finalize (object); +} + +static void +ges_effect_clip_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec) +{ + GESEffectClipPrivate *priv = GES_EFFECT_CLIP (object)->priv; + + switch (property_id) { + case PROP_VIDEO_BIN_DESCRIPTION: + g_value_set_string (value, priv->video_bin_description); + break; + case PROP_AUDIO_BIN_DESCRIPTION: + g_value_set_string (value, priv->audio_bin_description); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_effect_clip_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec) +{ + GESEffectClip *self = GES_EFFECT_CLIP (object); + + switch (property_id) { + case PROP_VIDEO_BIN_DESCRIPTION: + self->priv->video_bin_description = g_value_dup_string (value); + break; + case PROP_AUDIO_BIN_DESCRIPTION: + self->priv->audio_bin_description = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_effect_clip_class_init (GESEffectClipClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESClipClass *timobj_class = GES_CLIP_CLASS (klass); + + object_class->get_property = ges_effect_clip_get_property; + object_class->set_property = ges_effect_clip_set_property; + object_class->finalize = ges_effect_clip_finalize; + + /** + * GESEffectClip:video-bin-description: + * + * The description of the video track of the effect bin with a gst-launch-style + * pipeline description. This should be used for test purposes. + * + * Example: "videobalance saturation=1.5 hue=+0.5" + */ + g_object_class_install_property (object_class, PROP_VIDEO_BIN_DESCRIPTION, + g_param_spec_string ("video-bin-description", + "Video bin description", + "Description of the video track of the effect", + NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + /** + * GESEffectClip:audio-bin-description: + * + * The description of the audio track of the effect bin with a gst-launch-style + * pipeline description. This should be used for test purposes. + * + * Example: "audiopanorama panorama=1.0" + */ + g_object_class_install_property (object_class, PROP_AUDIO_BIN_DESCRIPTION, + g_param_spec_string ("audio-bin-description", + "bin description", + "Bin description of the audio track of the effect", + NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + timobj_class->create_track_element = _create_track_element; +} + +static void +ges_effect_clip_init (GESEffectClip * self) +{ + self->priv = ges_effect_clip_get_instance_private (self); +} + +static GESTrackElement * +_create_track_element (GESClip * self, GESTrackType type) +{ + const gchar *bin_description = NULL; + GESEffectClip *effect = GES_EFFECT_CLIP (self); + + if (type == GES_TRACK_TYPE_VIDEO) { + bin_description = effect->priv->video_bin_description; + } else if (type == GES_TRACK_TYPE_AUDIO) { + bin_description = effect->priv->audio_bin_description; + } + + if (bin_description) + return GES_TRACK_ELEMENT (ges_effect_new (bin_description)); + + GST_WARNING ("Effect doesn't handle this track type"); + return NULL; +} + +/** + * ges_effect_clip_new: + * @video_bin_description: The gst-launch like bin description of the effect + * @audio_bin_description: The gst-launch like bin description of the effect + * + * Creates a new #GESEffectClip from the description of the bin. + * + * Returns: (transfer floating) (nullable): a newly created #GESEffectClip, or + * %NULL if something went wrong. + */ +GESEffectClip * +ges_effect_clip_new (const gchar * video_bin_description, + const gchar * audio_bin_description) +{ + GESAsset *asset; + GESEffectClip *res; + GString *id = g_string_new (NULL); + + if (audio_bin_description) + g_string_append_printf (id, "audio %s ||", audio_bin_description); + if (video_bin_description) + g_string_append_printf (id, "video %s", video_bin_description); + + asset = ges_asset_request (GES_TYPE_EFFECT_CLIP, id->str, NULL); + res = GES_EFFECT_CLIP (ges_asset_extract (asset, NULL)); + g_string_free (id, TRUE); + gst_object_unref (asset); + + return res; +} diff --git a/ges/ges-effect-clip.h b/ges/ges-effect-clip.h new file mode 100644 index 0000000000..1a2dc70d3e --- /dev/null +++ b/ges/ges-effect-clip.h @@ -0,0 +1,60 @@ +/* GStreamer Editing Services + * Copyright (C) 2011 Thibault Saunier <thibault.saunier@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> + +G_BEGIN_DECLS + +#define GES_TYPE_EFFECT_CLIP ges_effect_clip_get_type() +GES_DECLARE_TYPE(EffectClip, effect_clip, EFFECT_CLIP); + +/** + * GESEffectClip: + */ +struct _GESEffectClip { + /*< private >*/ + GESBaseEffectClip parent; + + GESEffectClipPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESEffectClipClass: + * + */ + +struct _GESEffectClipClass { + /*< private >*/ + GESBaseEffectClipClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API GESEffectClip * +ges_effect_clip_new (const gchar * video_bin_description, + const gchar * audio_bin_description); + +G_END_DECLS \ No newline at end of file diff --git a/ges/ges-effect.c b/ges/ges-effect.c new file mode 100644 index 0000000000..fb6bf782ee --- /dev/null +++ b/ges/ges-effect.c @@ -0,0 +1,488 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Thibault Saunier <thibault.saunier@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:geseffect + * @title: GESEffect + * @short_description: adds an effect build from a parse-launch style bin + * description to a stream in a GESSourceClip or a GESLayer + * + * Currently we only support effects with N sinkpads and one single srcpad. + * Apart from `gesaudiomixer` and `gescompositor` which can be used as effects + * and where sinkpads will be requested as needed based on the timeline topology + * GES will always request at most one sinkpad per effect (when required). + * + * > Note: GES always adds converters (`audioconvert ! audioresample ! + * > audioconvert` for audio effects and `videoconvert` for video effects) to + * > make it simpler for end users. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-extractable.h" +#include "ges-track-element.h" +#include "ges-base-effect.h" +#include "ges-effect-asset.h" +#include "ges-effect.h" + +static void ges_extractable_interface_init (GESExtractableInterface * iface); + + +static void ges_effect_dispose (GObject * object); +static void ges_effect_finalize (GObject * object); +static GstElement *ges_effect_create_element (GESTrackElement * self); + +struct _GESEffectPrivate +{ + gchar *bin_description; +}; + +enum +{ + PROP_0, + PROP_BIN_DESCRIPTION, +}; + +G_DEFINE_TYPE_WITH_CODE (GESEffect, + ges_effect, GES_TYPE_BASE_EFFECT, G_ADD_PRIVATE (GESEffect) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, + ges_extractable_interface_init)); + +static gchar * +extractable_check_id (GType type, const gchar * id, GError ** error) +{ + gchar *bin_desc, *real_id; + GESTrackType ttype; + + bin_desc = ges_effect_asset_id_get_type_and_bindesc (id, &ttype, error); + + if (bin_desc == NULL) + return NULL; + + if (ttype == GES_TRACK_TYPE_AUDIO) + real_id = g_strdup_printf ("audio %s", bin_desc); + else if (ttype == GES_TRACK_TYPE_VIDEO) + real_id = g_strdup_printf ("video %s", bin_desc); + else + g_assert_not_reached (); + + g_free (bin_desc); + + return real_id; +} + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS; /* Start ignoring GParameter deprecation */ +static GParameter * +extractable_get_parameters_from_id (const gchar * id, guint * n_params) +{ + GParameter *params = g_new0 (GParameter, 3); + gchar *bin_desc; + GESTrackType ttype; + + bin_desc = ges_effect_asset_id_get_type_and_bindesc (id, &ttype, NULL); + + params[0].name = "bin-description"; + g_value_init (¶ms[0].value, G_TYPE_STRING); + g_value_set_string (¶ms[0].value, bin_desc); + + params[1].name = "track-type"; + g_value_init (¶ms[1].value, GES_TYPE_TRACK_TYPE); + g_value_set_flags (¶ms[1].value, ttype); + + *n_params = 2; + + g_free (bin_desc); + return params; +} + +G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */ + +static gchar * +extractable_get_id (GESExtractable * self) +{ + return g_strdup (GES_EFFECT (self)->priv->bin_description); +} + +static void +ges_extractable_interface_init (GESExtractableInterface * iface) +{ + iface->asset_type = GES_TYPE_EFFECT_ASSET; + iface->check_id = (GESExtractableCheckId) extractable_check_id; + iface->get_parameters_from_id = extractable_get_parameters_from_id; + iface->get_id = extractable_get_id; +} + +static int +property_name_compare (gconstpointer s1, gconstpointer s2) +{ + return g_strcmp0 ((const gchar *) s1, (const gchar *) s2); +} + +static void +ges_effect_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec) +{ + GESEffectPrivate *priv = GES_EFFECT (object)->priv; + + switch (property_id) { + case PROP_BIN_DESCRIPTION: + g_value_set_string (value, priv->bin_description); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_effect_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec) +{ + GESEffect *self = GES_EFFECT (object); + + switch (property_id) { + case PROP_BIN_DESCRIPTION: + self->priv->bin_description = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_effect_class_init (GESEffectClass * klass) +{ + GObjectClass *object_class; + GESTrackElementClass *obj_bg_class; + + object_class = G_OBJECT_CLASS (klass); + obj_bg_class = GES_TRACK_ELEMENT_CLASS (klass); + + object_class->get_property = ges_effect_get_property; + object_class->set_property = ges_effect_set_property; + object_class->dispose = ges_effect_dispose; + object_class->finalize = ges_effect_finalize; + + obj_bg_class->create_element = ges_effect_create_element; + + klass->rate_properties = NULL; + ges_effect_class_register_rate_property (klass, "scaletempo", "rate"); + ges_effect_class_register_rate_property (klass, "pitch", "tempo"); + ges_effect_class_register_rate_property (klass, "pitch", "rate"); + ges_effect_class_register_rate_property (klass, "videorate", "rate"); + + /** + * GESEffect:bin-description: + * + * The description of the effect bin with a gst-launch-style + * pipeline description. + * + * Example: "videobalance saturation=1.5 hue=+0.5" + */ + g_object_class_install_property (object_class, PROP_BIN_DESCRIPTION, + g_param_spec_string ("bin-description", + "bin description", + "Bin description of the effect", + NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + +static void +ges_effect_init (GESEffect * self) +{ + self->priv = ges_effect_get_instance_private (self); +} + +static void +ges_effect_dispose (GObject * object) +{ + G_OBJECT_CLASS (ges_effect_parent_class)->dispose (object); +} + +static void +ges_effect_finalize (GObject * object) +{ + GESEffect *self = GES_EFFECT (object); + + if (self->priv->bin_description) + g_free (self->priv->bin_description); + + G_OBJECT_CLASS (ges_effect_parent_class)->finalize (object); +} + +static gdouble +_get_rate_factor (GESBaseEffect * effect, GHashTable * rate_values) +{ + GHashTableIter iter; + gpointer key, val; + gdouble factor = 1.0; + + g_hash_table_iter_init (&iter, rate_values); + while (g_hash_table_iter_next (&iter, &key, &val)) { + GValue *value = val; + gchar *prop_name = key; + gdouble rate = 1.0; + + switch (G_VALUE_TYPE (value)) { + case G_TYPE_DOUBLE: + rate = g_value_get_double (value); + break; + case G_TYPE_FLOAT: + rate = g_value_get_float (value); + break; + default: + GST_ERROR_OBJECT (effect, "Rate property %s has neither a gdouble " + "nor gfloat value", prop_name); + break; + } + factor *= rate; + } + + return factor; +} + +static GstClockTime +_rate_source_to_sink (GESBaseEffect * effect, GstClockTime time, + GHashTable * rate_values, gpointer user_data) +{ + /* multiply by rate factor + * E.g. rate=2.0, then the time 30 at the source would become + * 60 at the sink because we are using up twice as much data in a given + * time */ + gdouble rate_factor = _get_rate_factor (effect, rate_values); + + if (time == 0) + return 0; + if (rate_factor == 0.0) { + GST_ERROR_OBJECT (effect, "The rate effect has a rate of 0"); + return 0; + } + return (GstClockTime) (time * rate_factor); +} + +static GstClockTime +_rate_sink_to_source (GESBaseEffect * effect, GstClockTime time, + GHashTable * rate_values, gpointer user_data) +{ + /* divide by rate factor */ + gdouble rate_factor = _get_rate_factor (effect, rate_values); + + if (time == 0) + return 0; + if (rate_factor == 0.0) { + GST_ERROR_OBJECT (effect, "The rate effect has a rate of 0"); + return GST_CLOCK_TIME_NONE; + } + return (GstClockTime) (time / rate_factor); +} + +static GstElement * +ges_effect_create_element (GESTrackElement * object) +{ + GList *tmp; + GESEffectClass *class; + GstElement *effect; + gboolean is_rate_effect = FALSE; + GESBaseEffect *base_effect = GES_BASE_EFFECT (object); + + GError *error = NULL; + GESEffect *self = GES_EFFECT (object); + const gchar *blacklisted_factories[] = + { "audioconvert", "audioresample", "videoconvert", NULL }; + + GESTrackType type = ges_track_element_get_track_type (object); + + if (!g_strcmp0 (self->priv->bin_description, "gesaudiomixer") || + !g_strcmp0 (self->priv->bin_description, "gescompositor")) + return gst_element_factory_make (self->priv->bin_description, NULL); + + effect = + ges_effect_from_description (self->priv->bin_description, type, &error); + if (error != NULL) { + GST_ERROR ("An error occurred while creating the GstElement: %s", + error->message); + g_error_free (error); + goto fail; + } + + ges_track_element_add_children_props (object, effect, NULL, + blacklisted_factories, NULL); + + class = GES_EFFECT_CLASS (g_type_class_peek (GES_TYPE_EFFECT)); + + for (tmp = class->rate_properties; tmp; tmp = tmp->next) { + gchar *prop = tmp->data; + if (ges_timeline_element_lookup_child (GES_TIMELINE_ELEMENT (object), prop, + NULL, NULL)) { + if (!ges_base_effect_register_time_property (base_effect, prop)) + GST_ERROR_OBJECT (object, "Failed to register rate property %s", prop); + is_rate_effect = TRUE; + } + } + if (is_rate_effect + && !ges_base_effect_set_time_translation_funcs (base_effect, + _rate_source_to_sink, _rate_sink_to_source, NULL, NULL)) + GST_ERROR_OBJECT (object, "Failed to set rate translation functions"); + +done: + + return effect; + +fail: + gst_clear_object (&effect); + + goto done; +} + +/** + * ges_effect_new: + * @bin_description: The gst-launch like bin description of the effect + * + * Creates a new #GESEffect from the description of the bin. It should be + * possible to determine the type of the effect through the element + * 'klass' metadata of the GstElements that will be created. + * In that corner case, you should use: + * #ges_asset_request (GES_TYPE_EFFECT, "audio your ! bin ! description", NULL); + * and extract that asset to be in full control. + * + * Returns: (nullable): a newly created #GESEffect, or %NULL if something went + * wrong. + */ +GESEffect * +ges_effect_new (const gchar * bin_description) +{ + GESEffect *effect; + GESAsset *asset = ges_asset_request (GES_TYPE_EFFECT, + bin_description, NULL); + + g_return_val_if_fail (asset, NULL); + + effect = GES_EFFECT (ges_asset_extract (asset, NULL)); + + gst_object_unref (asset); + + return effect; +} + +/** + * ges_effect_class_register_rate_property: + * @klass: Instance of the GESEffectClass + * @element_name: The #GstElementFactory name of the element that changes + * the rate + * @property_name: The name of the property that changes the rate + * + * Register an element that can change the rate at which media is playing. + * The property type must be float or double, and must be a factor of the + * rate, i.e. a value of 2.0 must mean that the media plays twice as fast. + * Several properties may be registered for a single element type, + * provided they all contribute to the rate as independent factors. For + * example, this is true for the "GstPitch::rate" and "GstPitch::tempo" + * properties. These are already registered by default in GES, along with + * #videorate:rate for #videorate and #scaletempo:rate for #scaletempo. + * + * If such a rate property becomes a child property of a #GESEffect upon + * its creation (the element is part of its #GESEffect:bin-description), + * it will be automatically registered as a time property (see + * ges_base_effect_register_time_property()) and will have its time + * translation functions set (see + * ges_base_effect_set_time_translation_funcs()) to use the overall rate + * of the rate properties. Note that if an effect contains a rate + * property as well as a non-rate time property, you should ensure to set + * the time translation functions to some other methods using + * ges_base_effect_set_time_translation_funcs(). + * + * Note, you can obtain a reference to the GESEffectClass using + * + * ``` + * GES_EFFECT_CLASS (g_type_class_ref (GES_TYPE_EFFECT)); + * ``` + * + * Returns: %TRUE if the rate property was successfully registered. When + * this method returns %FALSE, a warning is emitted with more information. + */ +gboolean +ges_effect_class_register_rate_property (GESEffectClass * klass, + const gchar * element_name, const gchar * property_name) +{ + GstElementFactory *element_factory = NULL; + GstElement *element = NULL; + GParamSpec *pspec = NULL; + gchar *full_property_name = NULL; + GType param_type; + gboolean res = FALSE; + + element_factory = gst_element_factory_find (element_name); + if (element_factory == NULL) { + GST_WARNING + ("Did not add rate property '%s' for element '%s': the element factory could not be found", + property_name, element_name); + goto fail; + } + + element = gst_element_factory_create (element_factory, NULL); + if (element == NULL) { + GST_WARNING + ("Did not add rate property '%s' for element '%s': the element could not be constructed", + property_name, element_name); + goto fail; + } + + pspec = + g_object_class_find_property (G_OBJECT_GET_CLASS (element), + property_name); + if (pspec == NULL) { + GST_WARNING + ("Did not add rate property '%s' for element '%s': the element did not have the property name specified", + property_name, element_name); + goto fail; + } + + param_type = G_PARAM_SPEC_VALUE_TYPE (pspec); + if (param_type != G_TYPE_FLOAT && param_type != G_TYPE_DOUBLE) { + GST_WARNING + ("Did not add rate property '%s' for element '%s': the property is not of float or double type", + property_name, element_name); + goto fail; + } + + full_property_name = g_strdup_printf ("%s::%s", + g_type_name (gst_element_factory_get_element_type (element_factory)), + property_name); + + if (g_list_find_custom (klass->rate_properties, full_property_name, + property_name_compare) == NULL) { + klass->rate_properties = + g_list_append (klass->rate_properties, full_property_name); + GST_DEBUG ("Added rate property %s", full_property_name); + } else { + g_free (full_property_name); + } + + res = TRUE; + +fail: + if (element_factory != NULL) + gst_object_unref (element_factory); + if (element != NULL) + gst_object_unref (element); + if (pspec != NULL) + g_param_spec_unref (pspec); + + return res; +} diff --git a/ges/ges-effect.h b/ges/ges-effect.h new file mode 100644 index 0000000000..e8f476aa0e --- /dev/null +++ b/ges/ges-effect.h @@ -0,0 +1,67 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Thibault Saunier <thibault.saunier@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> +#include <ges/ges-base-effect.h> + +G_BEGIN_DECLS +#define GES_TYPE_EFFECT ges_effect_get_type() +GES_DECLARE_TYPE(Effect, effect, EFFECT); + +/** + * GESEffect: + * + */ +struct _GESEffect +{ + /*< private > */ + GESBaseEffect parent; + GESEffectPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESEffectClass: + * @parent_class: parent class + */ + +struct _GESEffectClass +{ + /*< private > */ + GESBaseEffectClass parent_class; + + GList *rate_properties; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; + +}; + +GES_API GESEffect* +ges_effect_new (const gchar * bin_description); + +GES_API gboolean +ges_effect_class_register_rate_property (GESEffectClass *klass, const gchar *element_name, const gchar *property_name); + +G_END_DECLS \ No newline at end of file diff --git a/ges/ges-enums.c b/ges/ges-enums.c new file mode 100644 index 0000000000..83b5cd7961 --- /dev/null +++ b/ges/ges-enums.c @@ -0,0 +1,630 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:ges-enums + * @title: GES Enumerations + * @short_description: Various enums for the Gstreamer Editing Services + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-enums.h" +#include "ges-internal.h" +#include "ges-asset.h" +#include "ges-meta-container.h" +#include "ges-transition-clip.h" + +#define C_ENUM(v) ((guint) v) + +static const GFlagsValue track_types_values[] = { + {C_ENUM (GES_TRACK_TYPE_UNKNOWN), "GES_TRACK_TYPE_UNKNOWN", "unknown"}, + {C_ENUM (GES_TRACK_TYPE_AUDIO), "GES_TRACK_TYPE_AUDIO", "audio"}, + {C_ENUM (GES_TRACK_TYPE_VIDEO), "GES_TRACK_TYPE_VIDEO", "video"}, + {C_ENUM (GES_TRACK_TYPE_TEXT), "GES_TRACK_TYPE_TEXT", "text"}, + {C_ENUM (GES_TRACK_TYPE_CUSTOM), "GES_TRACK_TYPE_CUSTOM", "custom"}, + {0, NULL, NULL} +}; + +static void +register_ges_track_type_select_result (GType * id) +{ + *id = g_flags_register_static ("GESTrackType", track_types_values); +} + +const gchar * +ges_track_type_name (GESTrackType type) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (track_types_values); i++) { + if (type == track_types_values[i].value) + return track_types_values[i].value_nick; + } + + return "Unknown (mixed?) "; +} + +GType +ges_track_type_get_type (void) +{ + static GType id; + static GOnce once = G_ONCE_INIT; + + g_once (&once, (GThreadFunc) register_ges_track_type_select_result, &id); + return id; +} + +static void +register_ges_pipeline_flags (GType * id) +{ + static const GFlagsValue values[] = { + {C_ENUM (GES_PIPELINE_MODE_PREVIEW_AUDIO), + "GES_PIPELINE_MODE_PREVIEW_AUDIO", + "audio_preview"}, + {C_ENUM (GES_PIPELINE_MODE_PREVIEW_VIDEO), + "GES_PIPELINE_MODE_PREVIEW_VIDEO", + "video_preview"}, + {C_ENUM (GES_PIPELINE_MODE_PREVIEW), "GES_PIPELINE_MODE_PREVIEW", + "full_preview"}, + {C_ENUM (GES_PIPELINE_MODE_RENDER), "GES_PIPELINE_MODE_RENDER", "render"}, + {C_ENUM (GES_PIPELINE_MODE_SMART_RENDER), "GES_PIPELINE_MODE_SMART_RENDER", + "smart_render"}, + {0, NULL, NULL} + }; + + *id = g_flags_register_static ("GESPipelineFlags", values); +} + +GType +ges_pipeline_flags_get_type (void) +{ + static GType id; + static GOnce once = G_ONCE_INIT; + + g_once (&once, (GThreadFunc) register_ges_pipeline_flags, &id); + return id; +} + +static void +register_ges_edit_mode (GType * id) +{ + static const GEnumValue edit_mode[] = { + {C_ENUM (GES_EDIT_MODE_NORMAL), "GES_EDIT_MODE_NORMAL", + "edit_normal"}, + + {C_ENUM (GES_EDIT_MODE_NORMAL), "GES_EDIT_MODE_NORMAL", + "normal"}, + + {C_ENUM (GES_EDIT_MODE_RIPPLE), "GES_EDIT_MODE_RIPPLE", + "edit_ripple"}, + + {C_ENUM (GES_EDIT_MODE_RIPPLE), "GES_EDIT_MODE_RIPPLE", + "ripple"}, + + {C_ENUM (GES_EDIT_MODE_ROLL), "GES_EDIT_MODE_ROLL", + "edit_roll"}, + + {C_ENUM (GES_EDIT_MODE_ROLL), "GES_EDIT_MODE_ROLL", + "roll"}, + + {C_ENUM (GES_EDIT_MODE_TRIM), "GES_EDIT_MODE_TRIM", + "edit_trim"}, + + {C_ENUM (GES_EDIT_MODE_TRIM), "GES_EDIT_MODE_TRIM", + "trim"}, + + {C_ENUM (GES_EDIT_MODE_SLIDE), "GES_EDIT_MODE_SLIDE", + "edit_slide"}, + + {C_ENUM (GES_EDIT_MODE_SLIDE), "GES_EDIT_MODE_SLIDE", + "slide"}, + + {0, NULL, NULL} + }; + + *id = g_enum_register_static ("GESEditMode", edit_mode); +} + +const gchar * +ges_edit_mode_name (GESEditMode mode) +{ + switch (mode) { + case GES_EDIT_MODE_NORMAL: + return "normal"; + case GES_EDIT_MODE_RIPPLE: + return "ripple"; + case GES_EDIT_MODE_ROLL: + return "roll"; + case GES_EDIT_MODE_TRIM: + return "trim"; + case GES_EDIT_MODE_SLIDE: + return "slide"; + default: + return "unknown"; + } +} + +GType +ges_edit_mode_get_type (void) +{ + static GType id; + static GOnce once = G_ONCE_INIT; + + g_once (&once, (GThreadFunc) register_ges_edit_mode, &id); + return id; +} + +static void +register_ges_edge (GType * id) +{ + static const GEnumValue edges[] = { + {C_ENUM (GES_EDGE_START), "GES_EDGE_START", "edge_start"}, + {C_ENUM (GES_EDGE_START), "GES_EDGE_START", "start"}, + {C_ENUM (GES_EDGE_END), "GES_EDGE_END", "edge_end"}, + {C_ENUM (GES_EDGE_END), "GES_EDGE_END", "end"}, + {C_ENUM (GES_EDGE_NONE), "GES_EDGE_NONE", "edge_none"}, + {C_ENUM (GES_EDGE_NONE), "GES_EDGE_NONE", "none"}, + {0, NULL, NULL} + }; + + *id = g_enum_register_static ("GESEdge", edges); +} + +/** + * ges_edge_name: + * @edge: The #GESEdge to get the name of + * + * Returns: A human friendly name for @edge + * + * Since: 1.16 + */ +const gchar * +ges_edge_name (GESEdge edge) +{ + switch (edge) { + case GES_EDGE_START: + return "start"; + case GES_EDGE_END: + return "end"; + default: + return "none"; + } +} + +GType +ges_edge_get_type (void) +{ + static GType id; + static GOnce once = G_ONCE_INIT; + + g_once (&once, (GThreadFunc) register_ges_edge, &id); + return id; +} + +static GEnumValue transition_types[] = { + {GES_VIDEO_STANDARD_TRANSITION_TYPE_NONE, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_NONE", + "none"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BAR_WIPE_LR, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BAR_WIPE_LR", + "bar-wipe-lr"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BAR_WIPE_TB, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BAR_WIPE_TB", + "bar-wipe-tb"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_TL, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_TL", + "box-wipe-tl"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_TR, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_TR", + "box-wipe-tr"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_BR, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_BR", + "box-wipe-br"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_BL, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_BL", + "box-wipe-bl"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_FOUR_BOX_WIPE_CI, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_FOUR_BOX_WIPE_CI", + "four-box-wipe-ci"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_FOUR_BOX_WIPE_CO, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_FOUR_BOX_WIPE_CO", + "four-box-wipe-co"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_V, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_V", + "barndoor-v"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_H, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_H", + "barndoor-h"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_TC, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_TC", + "box-wipe-tc"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_RC, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_RC", + "box-wipe-rc"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_BC, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_BC", + "box-wipe-bc"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_LC, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_LC", + "box-wipe-lc"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_DIAGONAL_TL, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_DIAGONAL_TL", + "diagonal-tl"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_DIAGONAL_TR, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_DIAGONAL_TR", + "diagonal-tr"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BOWTIE_V, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BOWTIE_V", + "bowtie-v"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BOWTIE_H, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BOWTIE_H", + "bowtie-h"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_DBL, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_DBL", + "barndoor-dbl"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_DTL, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_DTL", + "barndoor-dtl"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_MISC_DIAGONAL_DBD, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_MISC_DIAGONAL_DBD", + "misc-diagonal-dbd"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_MISC_DIAGONAL_DD, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_MISC_DIAGONAL_DD", + "misc-diagonal-dd"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_D, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_D", + "vee-d"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_L, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_L", + "vee-l"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_U, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_U", + "vee-u"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_R, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_R", + "vee-r"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_D, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_D", + "barnvee-d"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_L, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_L", + "barnvee-l"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_U, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_U", + "barnvee-u"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_R, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_R", + "barnvee-r"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_IRIS_RECT, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_IRIS_RECT", + "iris-rect"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW12, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW12", + "clock-cw12"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW3, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW3", + "clock-cw3"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW6, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW6", + "clock-cw6"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW9, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW9", + "clock-cw9"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_PINWHEEL_TBV, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_PINWHEEL_TBV", + "pinwheel-tbv"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_PINWHEEL_TBH, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_PINWHEEL_TBH", + "pinwheel-tbh"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_PINWHEEL_FB, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_PINWHEEL_FB", + "pinwheel-fb"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_CT, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_CT", + "fan-ct"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_CR, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_CR", + "fan-cr"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FOV, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FOV", + "doublefan-fov"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FOH, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FOH", + "doublefan-foh"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWT, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWT", + "singlesweep-cwt"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWR, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWR", + "singlesweep-cwr"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWB, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWB", + "singlesweep-cwb"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWL, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWL", + "singlesweep-cwl"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PV, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PV", + "doublesweep-pv"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PD, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PD", + "doublesweep-pd"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_OV, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_OV", + "doublesweep-ov"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_OH, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_OH", + "doublesweep-oh"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_T, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_T", + "fan-t"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_R, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_R", + "fan-r"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_B, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_B", + "fan-b"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_L, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_L", + "fan-l"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FIV, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FIV", + "doublefan-fiv"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FIH, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FIH", + "doublefan-fih"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWTL, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWTL", + "singlesweep-cwtl"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWBL, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWBL", + "singlesweep-cwbl"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWBR, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWBR", + "singlesweep-cwbr"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWTR, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWTR", + "singlesweep-cwtr"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PDTL, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PDTL", + "doublesweep-pdtl"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PDBL, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PDBL", + "doublesweep-pdbl"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_T, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_T", + "saloondoor-t"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_L, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_L", + "saloondoor-l"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_B, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_B", + "saloondoor-b"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_R, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_R", + "saloondoor-r"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_R, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_R", + "windshield-r"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_U, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_U", + "windshield-u"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_V, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_V", + "windshield-v"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_H, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_H", + "windshield-h"}, + {GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE, + "GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE", + "crossfade"}, + {0, NULL, NULL} +}; + +void +_init_standard_transition_assets (void) +{ + guint i; + + for (i = 1; i < G_N_ELEMENTS (transition_types) - 1; i++) { + GESAsset *asset = ges_asset_request (GES_TYPE_TRANSITION_CLIP, + transition_types[i].value_nick, NULL); + + ges_meta_container_register_meta_string (GES_META_CONTAINER (asset), + GES_META_READABLE, GES_META_DESCRIPTION, + transition_types[i].value_name); + + gst_object_unref (asset); + } + +} + +GType +ges_video_standard_transition_type_get_type (void) +{ + static GType the_type = 0; + static gsize once = 0; + + if (g_once_init_enter (&once)) { + g_assert (!once); + + the_type = g_enum_register_static ("GESVideoStandardTransitionType", + transition_types); + g_once_init_leave (&once, 1); + } + + return the_type; +} + +GType +ges_text_valign_get_type (void) +{ + static GType text_overlay_valign_type = 0; + static gsize initialized = 0; + static const GEnumValue text_overlay_valign[] = { + {GES_TEXT_VALIGN_BASELINE, "GES_TEXT_VALIGN_BASELINE", "baseline"}, + {GES_TEXT_VALIGN_BOTTOM, "GES_TEXT_VALIGN_BOTTOM", "bottom"}, + {GES_TEXT_VALIGN_TOP, "GES_TEXT_VALIGN_TOP", "top"}, + {GES_TEXT_VALIGN_POSITION, "GES_TEXT_VALIGN_POSITION", "position"}, + {GES_TEXT_VALIGN_CENTER, "GES_TEXT_VALIGN_CENTER", "center"}, + {GES_TEXT_VALIGN_ABSOLUTE, "GES_TEXT_VALIGN_ABSOLUTE", "absolute"}, + {0, NULL, NULL}, + }; + + if (g_once_init_enter (&initialized)) { + text_overlay_valign_type = + g_enum_register_static ("GESTextVAlign", text_overlay_valign); + g_once_init_leave (&initialized, 1); + } + return text_overlay_valign_type; +} + +GType +ges_text_halign_get_type (void) +{ + static GType text_overlay_halign_type = 0; + static gsize initialized = 0; + static const GEnumValue text_overlay_halign[] = { + {GES_TEXT_HALIGN_LEFT, "GES_TEXT_HALIGN_LEFT", "left"}, + {GES_TEXT_HALIGN_CENTER, "GES_TEXT_HALIGN_CENTER", "center"}, + {GES_TEXT_HALIGN_RIGHT, "GES_TEXT_HALIGN_RIGHT", "right"}, + {GES_TEXT_HALIGN_POSITION, "GES_TEXT_HALIGN_POSITION", "position"}, + {GES_TEXT_HALIGN_ABSOLUTE, "GES_TEXT_HALIGN_ABSOLUTE", "absolute"}, + {0, NULL, NULL}, + }; + + if (g_once_init_enter (&initialized)) { + text_overlay_halign_type = + g_enum_register_static ("GESTextHAlign", text_overlay_halign); + g_once_init_leave (&initialized, 1); + } + return text_overlay_halign_type; +} + +/* table more-or-less copied from gstvideotestsrc.c */ +static GEnumValue vpattern_enum_values[] = { + {GES_VIDEO_TEST_PATTERN_SMPTE, "GES_VIDEO_TEST_PATTERN_SMPTE", "smpte"} + , + {GES_VIDEO_TEST_PATTERN_SNOW, "GES_VIDEO_TEST_PATTERN_SNOW", "snow"} + , + {GES_VIDEO_TEST_PATTERN_BLACK, "GES_VIDEO_TEST_PATTERN_BLACK", "black"} + , + {GES_VIDEO_TEST_PATTERN_WHITE, "GES_VIDEO_TEST_PATTERN_WHITE", "white"} + , + {GES_VIDEO_TEST_PATTERN_RED, "GES_VIDEO_TEST_PATTERN_RED", "red"} + , + {GES_VIDEO_TEST_PATTERN_GREEN, "GES_VIDEO_TEST_PATTERN_GREEN", "green"} + , + {GES_VIDEO_TEST_PATTERN_BLUE, "GES_VIDEO_TEST_PATTERN_BLUE", "blue"} + , + {GES_VIDEO_TEST_PATTERN_CHECKERS1, + "GES_VIDEO_TEST_PATTERN_CHECKERS1", "checkers-1"} + , + {GES_VIDEO_TEST_PATTERN_CHECKERS2, + "GES_VIDEO_TEST_PATTERN_CHECKERS2", "checkers-2"} + , + {GES_VIDEO_TEST_PATTERN_CHECKERS4, + "GES_VIDEO_TEST_PATTERN_CHECKERS4", "checkers-4"} + , + {GES_VIDEO_TEST_PATTERN_CHECKERS8, + "GES_VIDEO_TEST_PATTERN_CHECKERS8", "checkers-8"} + , + {GES_VIDEO_TEST_PATTERN_CIRCULAR, + "GES_VIDEO_TEST_PATTERN_CIRCULAR", "circular"} + , + {GES_VIDEO_TEST_PATTERN_BLINK, "GES_VIDEO_TEST_PATTERN_BLINK", "blink"} + , + {GES_VIDEO_TEST_PATTERN_SMPTE75, "GES_VIDEO_TEST_PATTERN_SMPTE75", "smpte75"} + , + {GES_VIDEO_TEST_ZONE_PLATE, "GES_VIDEO_TEST_ZONE_PLATE", "zone-plate"} + , + {GES_VIDEO_TEST_GAMUT, "GES_VIDEO_TEST_GAMUT", "gamut"} + , + {GES_VIDEO_TEST_CHROMA_ZONE_PLATE, "GES_VIDEO_TEST_CHROMA_ZONE_PLATE", + "chroma-zone-plate"} + , + {GES_VIDEO_TEST_PATTERN_SOLID, "GES_VIDEO_TEST_PATTERN_SOLID", "solid-color"} + , + {0, NULL, NULL} +}; + +GType +ges_video_test_pattern_get_type (void) +{ + + static gsize once = 0; + static GType theType = 0; + + if (g_once_init_enter (&once)) { + theType = g_enum_register_static ("GESVideoTestPattern", + vpattern_enum_values); + g_once_init_leave (&once, 1); + }; + + return theType; +} + +static void +register_ges_meta_flag (GType * id) +{ + static const GFlagsValue values[] = { + {C_ENUM (GES_META_READABLE), "GES_META_READABLE", "readable"}, + {C_ENUM (GES_META_WRITABLE), "GES_META_WRITABLE", "writable"}, + {C_ENUM (GES_META_READ_WRITE), "GES_META_READ_WRITE", "readwrite"}, + {0, NULL, NULL} + }; + + *id = g_flags_register_static ("GESMetaFlag", values); +} + +GType +ges_meta_flag_get_type (void) +{ + static GType id; + static GOnce once = G_ONCE_INIT; + + g_once (&once, (GThreadFunc) register_ges_meta_flag, &id); + return id; +} + +static void +register_ges_marker_flags (GType * id) +{ + static const GFlagsValue values[] = { + {C_ENUM (GES_MARKER_FLAG_NONE), "GES_MARKER_FLAG_NONE", "none"}, + {C_ENUM (GES_MARKER_FLAG_SNAPPABLE), "GES_MARKER_FLAG_SNAPPABLE", + "snappable"}, + {0, NULL, NULL} + }; + + *id = g_flags_register_static ("GESMarkerFlags", values); +} + +GType +ges_marker_flags_get_type (void) +{ + static GType id; + static GOnce once = G_ONCE_INIT; + + g_once (&once, (GThreadFunc) register_ges_marker_flags, &id); + return id; +} diff --git a/ges/ges-enums.h b/ges/ges-enums.h new file mode 100644 index 0000000000..ff7a013625 --- /dev/null +++ b/ges/ges-enums.h @@ -0,0 +1,587 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <gst/gst.h> +#include <ges/ges-prelude.h> + +G_BEGIN_DECLS + +#define GES_TYPE_TRACK_TYPE (ges_track_type_get_type ()) +GES_API +GType ges_track_type_get_type (void); + +/** + * GESTrackType: + * @GES_TRACK_TYPE_UNKNOWN: A track of unknown type (i.e. invalid) + * @GES_TRACK_TYPE_AUDIO: An audio track + * @GES_TRACK_TYPE_VIDEO: A video track + * @GES_TRACK_TYPE_TEXT: A text (subtitle) track + * @GES_TRACK_TYPE_CUSTOM: A custom-content track + * + * Types of content handled by a track. If the content is not one of + * @GES_TRACK_TYPE_AUDIO, @GES_TRACK_TYPE_VIDEO or @GES_TRACK_TYPE_TEXT, + * the user of the #GESTrack must set the type to @GES_TRACK_TYPE_CUSTOM. + * + * @GES_TRACK_TYPE_UNKNOWN is for internal purposes and should not be used + * by users + */ + +typedef enum { + GES_TRACK_TYPE_UNKNOWN = 1 << 0, + GES_TRACK_TYPE_AUDIO = 1 << 1, + GES_TRACK_TYPE_VIDEO = 1 << 2, + GES_TRACK_TYPE_TEXT = 1 << 3, + GES_TRACK_TYPE_CUSTOM = 1 << 4, +} GESTrackType; + +#define GES_META_FLAG_TYPE (ges_meta_flag_get_type ()) +GES_API +GType ges_meta_flag_get_type (void); + +/** + * GESMetaFlag: + * @GES_META_READABLE: The metadata is readable + * @GES_META_WRITABLE: The metadata is writable + * @GES_META_READ_WRITE: The metadata is readable and writable + */ +typedef enum { + GES_META_READABLE = 1 << 0, + GES_META_WRITABLE = 1 << 1, + GES_META_READ_WRITE = GES_META_READABLE | GES_META_WRITABLE +} GESMetaFlag; + +/** + * GESVideoStandardTransitionType: + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_NONE: Transition type has not been set, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BAR_WIPE_LR: A bar moves from left to right, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BAR_WIPE_TB: A bar moves from top to bottom, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_TL: A box expands from the upper-left corner to the lower-right corner, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_TR: A box expands from the upper-right corner to the lower-left corner, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_BR: A box expands from the lower-right corner to the upper-left corner, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_BL: A box expands from the lower-left corner to the upper-right corner, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_FOUR_BOX_WIPE_CI: A box shape expands from each of the four corners toward the center, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_FOUR_BOX_WIPE_CO: A box shape expands from the center of each quadrant toward the corners of each quadrant, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_V: A central, vertical line splits and expands toward the left and right edges, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_H: A central, horizontal line splits and expands toward the top and bottom edges, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_TC: A box expands from the top edge's midpoint to the bottom corners, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_RC: A box expands from the right edge's midpoint to the left corners, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_BC: A box expands from the bottom edge's midpoint to the top corners, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_LC: A box expands from the left edge's midpoint to the right corners, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_DIAGONAL_TL: A diagonal line moves from the upper-left corner to the lower-right corner, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_DIAGONAL_TR: A diagonal line moves from the upper right corner to the lower-left corner, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BOWTIE_V: Two wedge shapes slide in from the top and bottom edges toward the center, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BOWTIE_H: Two wedge shapes slide in from the left and right edges toward the center, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_DBL: A diagonal line from the lower-left to upper-right corners splits and expands toward the opposite corners, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_DTL: A diagonal line from upper-left to lower-right corners splits and expands toward the opposite corners, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_MISC_DIAGONAL_DBD: Four wedge shapes split from the center and retract toward the four edges, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_MISC_DIAGONAL_DD: A diamond connecting the four edge midpoints simultaneously contracts toward the center and expands toward the edges, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_D: A wedge shape moves from top to bottom, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_L: A wedge shape moves from right to left, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_U: A wedge shape moves from bottom to top, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_R: A wedge shape moves from left to right, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_D: A 'V' shape extending from the bottom edge's midpoint to the opposite corners contracts toward the center and expands toward the edges, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_L: A 'V' shape extending from the left edge's midpoint to the opposite corners contracts toward the center and expands toward the edges, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_U: A 'V' shape extending from the top edge's midpoint to the opposite corners contracts toward the center and expands toward the edges, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_R: A 'V' shape extending from the right edge's midpoint to the opposite corners contracts toward the center and expands toward the edges, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_IRIS_RECT: A rectangle expands from the center., + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW12: A radial hand sweeps clockwise from the twelve o'clock position, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW3: A radial hand sweeps clockwise from the three o'clock position, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW6: A radial hand sweeps clockwise from the six o'clock position, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW9: A radial hand sweeps clockwise from the nine o'clock position, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_PINWHEEL_TBV: Two radial hands sweep clockwise from the twelve and six o'clock positions, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_PINWHEEL_TBH: Two radial hands sweep clockwise from the nine and three o'clock positions, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_PINWHEEL_FB: Four radial hands sweep clockwise, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_CT: A fan unfolds from the top edge, the fan axis at the center, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_CR: A fan unfolds from the right edge, the fan axis at the center, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FOV: Two fans, their axes at the center, unfold from the top and bottom, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FOH: Two fans, their axes at the center, unfold from the left and right, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWT: A radial hand sweeps clockwise from the top edge's midpoint, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWR: A radial hand sweeps clockwise from the right edge's midpoint, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWB: A radial hand sweeps clockwise from the bottom edge's midpoint, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWL: A radial hand sweeps clockwise from the left edge's midpoint, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PV: Two radial hands sweep clockwise and counter-clockwise from the top and bottom edges' midpoints, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PD: Two radial hands sweep clockwise and counter-clockwise from the left and right edges' midpoints, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_OV: Two radial hands attached at the top and bottom edges' midpoints sweep from right to left, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_OH: Two radial hands attached at the left and right edges' midpoints sweep from top to bottom, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_T: A fan unfolds from the bottom, the fan axis at the top edge's midpoint, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_R: A fan unfolds from the left, the fan axis at the right edge's midpoint, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_B: A fan unfolds from the top, the fan axis at the bottom edge's midpoint, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_L: A fan unfolds from the right, the fan axis at the left edge's midpoint, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FIV: Two fans, their axes at the top and bottom, unfold from the center, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FIH: Two fans, their axes at the left and right, unfold from the center, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWTL: A radial hand sweeps clockwise from the upper-left corner, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWBL: A radial hand sweeps counter-clockwise from the lower-left corner., + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWBR: A radial hand sweeps clockwise from the lower-right corner, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWTR: A radial hand sweeps counter-clockwise from the upper-right corner, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PDTL: Two radial hands attached at the upper-left and lower-right corners sweep down and up, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PDBL: Two radial hands attached at the lower-left and upper-right corners sweep down and up, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_T: Two radial hands attached at the upper-left and upper-right corners sweep down, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_L: Two radial hands attached at the upper-left and lower-left corners sweep to the right, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_B: Two radial hands attached at the lower-left and lower-right corners sweep up, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_R: Two radial hands attached at the upper-right and lower-right corners sweep to the left, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_R: Two radial hands attached at the midpoints of the top and bottom halves sweep from right to left, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_U: Two radial hands attached at the midpoints of the left and right halves sweep from top to bottom, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_V: Two sets of radial hands attached at the midpoints of the top and bottom halves sweep from top to bottom and bottom to top, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_H: Two sets of radial hands attached at the midpoints of the left and right halves sweep from left to right and right to left, + * @GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE: Crossfade + * + */ + +typedef enum { + GES_VIDEO_STANDARD_TRANSITION_TYPE_NONE = 0, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BAR_WIPE_LR = 1, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BAR_WIPE_TB = 2, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_TL = 3, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_TR = 4, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_BR = 5, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_BL = 6, + GES_VIDEO_STANDARD_TRANSITION_TYPE_FOUR_BOX_WIPE_CI = 7, + GES_VIDEO_STANDARD_TRANSITION_TYPE_FOUR_BOX_WIPE_CO = 8, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_V = 21, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_H = 22, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_TC = 23, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_RC = 24, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_BC = 25, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BOX_WIPE_LC = 26, + GES_VIDEO_STANDARD_TRANSITION_TYPE_DIAGONAL_TL = 41, + GES_VIDEO_STANDARD_TRANSITION_TYPE_DIAGONAL_TR = 42, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BOWTIE_V = 43, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BOWTIE_H = 44, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_DBL = 45, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_DTL = 46, + GES_VIDEO_STANDARD_TRANSITION_TYPE_MISC_DIAGONAL_DBD = 47, + GES_VIDEO_STANDARD_TRANSITION_TYPE_MISC_DIAGONAL_DD = 48, + GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_D = 61, + GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_L = 62, + GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_U = 63, + GES_VIDEO_STANDARD_TRANSITION_TYPE_VEE_R = 64, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_D = 65, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_L = 66, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_U = 67, + GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNVEE_R = 68, + GES_VIDEO_STANDARD_TRANSITION_TYPE_IRIS_RECT = 101, + GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW12 = 201, + GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW3 = 202, + GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW6 = 203, + GES_VIDEO_STANDARD_TRANSITION_TYPE_CLOCK_CW9 = 204, + GES_VIDEO_STANDARD_TRANSITION_TYPE_PINWHEEL_TBV = 205, + GES_VIDEO_STANDARD_TRANSITION_TYPE_PINWHEEL_TBH = 206, + GES_VIDEO_STANDARD_TRANSITION_TYPE_PINWHEEL_FB = 207, + GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_CT = 211, + GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_CR = 212, + GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FOV = 213, + GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FOH = 214, + GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWT = 221, + GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWR = 222, + GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWB = 223, + GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWL = 224, + GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PV = 225, + GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PD = 226, + GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_OV = 227, + GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_OH = 228, + GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_T = 231, + GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_R = 232, + GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_B = 233, + GES_VIDEO_STANDARD_TRANSITION_TYPE_FAN_L = 234, + GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FIV = 235, + GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLEFAN_FIH = 236, + GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWTL = 241, + GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWBL = 242, + GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWBR = 243, + GES_VIDEO_STANDARD_TRANSITION_TYPE_SINGLESWEEP_CWTR = 244, + GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PDTL = 245, + GES_VIDEO_STANDARD_TRANSITION_TYPE_DOUBLESWEEP_PDBL = 246, + GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_T = 251, + GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_L = 252, + GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_B = 253, + GES_VIDEO_STANDARD_TRANSITION_TYPE_SALOONDOOR_R = 254, + GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_R = 261, + GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_U = 262, + GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_V = 263, + GES_VIDEO_STANDARD_TRANSITION_TYPE_WINDSHIELD_H = 264, + GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE = 512 +} GESVideoStandardTransitionType; + +#define GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE \ + (ges_video_standard_transition_type_get_type()) + +GES_API +GType ges_video_standard_transition_type_get_type (void); + +/** + * GESTextVAlign: + * @GES_TEXT_VALIGN_BASELINE: draw text on the baseline + * @GES_TEXT_VALIGN_BOTTOM: draw text on the bottom + * @GES_TEXT_VALIGN_TOP: draw text on top + * @GES_TEXT_VALIGN_POSITION: draw text on ypos position + * @GES_TEXT_VALIGN_CENTER: draw text on the center + * + * Vertical alignment of the text. + */ +typedef enum { + GES_TEXT_VALIGN_BASELINE, + GES_TEXT_VALIGN_BOTTOM, + GES_TEXT_VALIGN_TOP, + GES_TEXT_VALIGN_POSITION, + GES_TEXT_VALIGN_CENTER, + GES_TEXT_VALIGN_ABSOLUTE +} GESTextVAlign; + +#define DEFAULT_VALIGNMENT GES_TEXT_VALIGN_BASELINE + +#define GES_TEXT_VALIGN_TYPE\ + (ges_text_valign_get_type ()) + +GES_API +GType ges_text_valign_get_type (void); + +/** + * GESTextHAlign: + * @GES_TEXT_HALIGN_LEFT: align text left + * @GES_TEXT_HALIGN_CENTER: align text center + * @GES_TEXT_HALIGN_RIGHT: align text right + * @GES_TEXT_HALIGN_POSITION: align text on xpos position + * + * Horizontal alignment of the text. + */ +typedef enum { + GES_TEXT_HALIGN_LEFT = 0, + GES_TEXT_HALIGN_CENTER = 1, + GES_TEXT_HALIGN_RIGHT = 2, + GES_TEXT_HALIGN_POSITION = 4, + GES_TEXT_HALIGN_ABSOLUTE = 5 +} GESTextHAlign; + +#define DEFAULT_HALIGNMENT GES_TEXT_HALIGN_CENTER + +#define GES_TEXT_HALIGN_TYPE\ + (ges_text_halign_get_type ()) + +GES_API +GType ges_text_halign_get_type (void); + +/** + * GESVideoTestPattern: + * @GES_VIDEO_TEST_PATTERN_SMPTE: A standard SMPTE test pattern + * @GES_VIDEO_TEST_PATTERN_SNOW: Random noise + * @GES_VIDEO_TEST_PATTERN_BLACK: A black image + * @GES_VIDEO_TEST_PATTERN_WHITE: A white image + * @GES_VIDEO_TEST_PATTERN_RED: A red image + * @GES_VIDEO_TEST_PATTERN_GREEN: A green image + * @GES_VIDEO_TEST_PATTERN_BLUE: A blue image + * @GES_VIDEO_TEST_PATTERN_CHECKERS1: Checkers pattern (1px) + * @GES_VIDEO_TEST_PATTERN_CHECKERS2: Checkers pattern (2px) + * @GES_VIDEO_TEST_PATTERN_CHECKERS4: Checkers pattern (4px) + * @GES_VIDEO_TEST_PATTERN_CHECKERS8: Checkers pattern (8px) + * @GES_VIDEO_TEST_PATTERN_CIRCULAR: Circular pattern + * @GES_VIDEO_TEST_PATTERN_SOLID: Solid color + * @GES_VIDEO_TEST_PATTERN_BLINK: Alternate between black and white + * @GES_VIDEO_TEST_ZONE_PLATE: Zone plate + * @GES_VIDEO_TEST_GAMUT: Gamut checkers + * @GES_VIDEO_TEST_CHROMA_ZONE_PLATE: Chroma zone plate + * @GES_VIDEO_TEST_PATTERN_SMPTE75: SMPTE test pattern (75% color bars) + * + * The test pattern to produce + */ + +typedef enum { + GES_VIDEO_TEST_PATTERN_SMPTE, + GES_VIDEO_TEST_PATTERN_SNOW, + GES_VIDEO_TEST_PATTERN_BLACK, + GES_VIDEO_TEST_PATTERN_WHITE, + GES_VIDEO_TEST_PATTERN_RED, + GES_VIDEO_TEST_PATTERN_GREEN, + GES_VIDEO_TEST_PATTERN_BLUE, + GES_VIDEO_TEST_PATTERN_CHECKERS1, + GES_VIDEO_TEST_PATTERN_CHECKERS2, + GES_VIDEO_TEST_PATTERN_CHECKERS4, + GES_VIDEO_TEST_PATTERN_CHECKERS8, + GES_VIDEO_TEST_PATTERN_CIRCULAR, + GES_VIDEO_TEST_PATTERN_BLINK, + GES_VIDEO_TEST_PATTERN_SMPTE75, + GES_VIDEO_TEST_ZONE_PLATE, + GES_VIDEO_TEST_GAMUT, + GES_VIDEO_TEST_CHROMA_ZONE_PLATE, + GES_VIDEO_TEST_PATTERN_SOLID, +} GESVideoTestPattern; + + +#define GES_VIDEO_TEST_PATTERN_TYPE\ + ges_video_test_pattern_get_type() + +GES_API +GType ges_video_test_pattern_get_type (void); + +/** + * GESPipelineFlags: + * @GES_PIPELINE_MODE_PREVIEW_AUDIO: Output the #GESPipeline:timeline's + * audio to the soundcard + * @GES_PIPELINE_MODE_PREVIEW_VIDEO: Output the #GESPipeline:timeline's + * video to the screen + * @GES_PIPELINE_MODE_PREVIEW: Output both the #GESPipeline:timeline's + * audio and video to the soundcard and screen (default) + * @GES_PIPELINE_MODE_RENDER: Render the #GESPipeline:timeline with + * forced decoding (the underlying #encodebin has its + * #encodebin:avoid-reencoding property set to %FALSE) + * @GES_PIPELINE_MODE_SMART_RENDER: Render the #GESPipeline:timeline, + * avoiding decoding/reencoding (the underlying #encodebin has its + * #encodebin:avoid-reencoding property set to %TRUE). + * > NOTE: Smart rendering can not work in tracks where #GESTrack:mixing + * > is enabled. + * + * The various modes a #GESPipeline can be configured to. + */ +typedef enum { + GES_PIPELINE_MODE_PREVIEW_AUDIO = 1 << 0, + GES_PIPELINE_MODE_PREVIEW_VIDEO = 1 << 1, + GES_PIPELINE_MODE_PREVIEW = GES_PIPELINE_MODE_PREVIEW_AUDIO | GES_PIPELINE_MODE_PREVIEW_VIDEO, + GES_PIPELINE_MODE_RENDER = 1 << 2, + GES_PIPELINE_MODE_SMART_RENDER = 1 << 3 +} GESPipelineFlags; + +#define GES_TYPE_PIPELINE_FLAGS\ + ges_pipeline_flags_get_type() + +GES_API +GType ges_pipeline_flags_get_type (void); + +/** + * GESEditMode: + * @GES_EDIT_MODE_NORMAL: The element is edited the normal way (default). + * If acting on the element as a whole (#GES_EDGE_NONE), this will MOVE + * the element by MOVING its toplevel. When acting on the start of the + * element (#GES_EDGE_START), this will only MOVE the element, but not + * its toplevel parent. This can allow you to move a #GESClip or + * #GESGroup to a new start time or layer within its container group, + * without effecting other members of the group. When acting on the end + * of the element (#GES_EDGE_END), this will END-TRIM the element, + * leaving its toplevel unchanged. + * @GES_EDIT_MODE_RIPPLE: The element is edited in ripple mode: moving + * itself as well as later elements, keeping their relative times. This + * edits the element the same as #GES_EDIT_MODE_NORMAL. In addition, if + * acting on the element as a whole, or the start of the element, any + * toplevel element in the same timeline (including different layers) + * whose start time is later than the *current* start time of the MOVED + * element will also be MOVED by the same shift as the edited element. + * If acting on the end of the element, any toplevel element whose start + * time is later than the *current* end time of the edited element will + * also be MOVED by the same shift as the change in the end of the + * edited element. These additional elements will also be shifted by + * the same shift in layers as the edited element. + * @GES_EDIT_MODE_ROLL: The element is edited in roll mode: swapping its + * content for its neighbour's, or vis versa, in the timeline output. + * This edits the element the same as #GES_EDIT_MODE_TRIM. In addition, + * any neighbours are also TRIMMED at their opposite edge to the same + * timeline position. When acting on the start of the element, a + * neighbour is any earlier element in the timeline whose end time + * matches the *current* start time of the edited element. When acting on + * the end of the element, a neighbour is any later element in the + * timeline whose start time matches the *current* start time of the + * edited element. In addition, a neighbour have a #GESSource at its + * end/start edge that shares a track with a #GESSource at the start/end + * edge of the edited element. Basically, a neighbour is an element that + * can be extended, or cut, to have its content replace, or be replaced + * by, the content of the edited element. Acting on the element as a + * whole (#GES_EDGE_NONE) is not defined. The element can not shift + * layers under this mode. + * @GES_EDIT_MODE_TRIM: The element is edited in trim mode. When acting + * on the start of the element, this will START-TRIM it. When acting on + * the end of the element, this will END-TRIM it. Acting on the element + * as a whole (#GES_EDGE_NONE) is not defined. + * @GES_EDIT_MODE_SLIDE: The element is edited in slide mode (not yet + * implemented): moving the element replacing or consuming content on + * each end. When acting on the element as a whole, this will MOVE the + * element, and TRIM any neighbours on either side. A neighbour is + * defined in the same way as in #GES_EDIT_MODE_ROLL, but they may be on + * either side of the edited elements. Elements at the end with be + * START-TRIMMED to the new end position of the edited element. Elements + * at the start will be END-TRIMMED to the new start position of the + * edited element. Acting on the start or end of the element + * (#GES_EDGE_START and #GES_EDGE_END) is not defined. The element can + * not shift layers under this mode. + * + * When a single timeline element is edited within its timeline at some + * position, using ges_timeline_element_edit(), depending on the edit + * mode, its #GESTimelineElement:start, #GESTimelineElement:duration or + * #GESTimelineElement:in-point will be adjusted accordingly. In addition, + * any clips may change #GESClip:layer. + * + * Each edit can be broken down into a combination of three basic edits: + * + * + MOVE: This moves the start of the element to the edit position. + * + START-TRIM: This cuts or grows the start of the element, whilst + * maintaining the time at which its internal content appears in the + * timeline data output. If the element is made shorter, the data that + * appeared at the edit position will still appear in the timeline at + * the same time. If the element is made longer, the data that appeared + * at the previous start of the element will still appear in the + * timeline at the same time. + * + END-TRIM: Similar to START-TRIM, but the end of the element is cut or + * grown. + * + * In particular, when editing a #GESClip: + * + * + MOVE: This will set the #GESTimelineElement:start of the clip to the + * edit position. + * + START-TRIM: This will set the #GESTimelineElement:start of the clip + * to the edit position. To keep the end time the same, the + * #GESTimelineElement:duration of the clip will be adjusted in the + * opposite direction. In addition, the #GESTimelineElement:in-point of + * the clip will be shifted such that the content that appeared at the + * new or previous start time, whichever is latest, still appears at the + * same timeline time. For example, if a frame appeared at the start of + * the clip, and the start of the clip is reduced, the in-point of the + * clip will also reduce such that the frame will appear later within + * the clip, but at the same timeline position. + * + END-TRIM: This will set the #GESTimelineElement:duration of the clip + * such that its end time will match the edit position. + * + * When editing a #GESGroup: + * + * + MOVE: This will set the #GESGroup:start of the clip to the edit + * position by shifting all of its children by the same amount. So each + * child will maintain their relative positions. + * + START-TRIM: If the group is made shorter, this will START-TRIM any + * clips under the group that start after the edit position to the same + * edit position. If the group is made longer, this will START-TRIM any + * clip under the group whose start matches the start of the group to + * the same edit position. + * + END-TRIM: If the group is made shorter, this will END-TRIM any clips + * under the group that end after the edit position to the same edit + * position. If the group is made longer, this will END-TRIM any clip + * under the group whose end matches the end of the group to the same + * edit position. + * + * When editing a #GESTrackElement, if it has a #GESClip parent, this + * will be edited instead. Otherwise it is edited in the same way as a + * #GESClip. + * + * The layer priority of a #GESGroup is the lowest layer priority of any + * #GESClip underneath it. When a group is edited to a new layer + * priority, it will shift all clips underneath it by the same amount, + * such that their relative layers stay the same. + * + * If the #GESTimeline has a #GESTimeline:snapping-distance, then snapping + * may occur for some of the edges of the **main** edited element: + * + * + MOVE: The start or end edge of *any* #GESSource under the element may + * be snapped. + * + START-TRIM: The start edge of a #GESSource whose start edge touches + * the start edge of the element may snap. + * + END-TRIM: The end edge of a #GESSource whose end edge touches the end + * edge of the element may snap. + * + * These edges may snap with either the start or end edge of *any* other + * #GESSource in the timeline that is not also being moved by the element, + * including those in different layers, if they are within the + * #GESTimeline:snapping-distance. During an edit, only up to one snap can + * occur. This will shift the edit position such that the snapped edges + * will touch once the edit has completed. + * + * Note that snapping can cause an edit to fail where it would have + * otherwise succeeded because it may push the edit position such that the + * edit would result in an unsupported timeline configuration. Similarly, + * snapping can cause an edit to succeed where it would have otherwise + * failed. + * + * For example, in #GES_EDIT_MODE_RIPPLE acting on #GES_EDGE_NONE, the + * main element is the MOVED toplevel of the edited element. Any source + * under the main MOVED toplevel may have its start or end edge snapped. + * Note, these sources cannot snap with each other. The edit may also + * push other elements, but any sources under these elements cannot snap, + * nor can they be snapped with. If a snap does occur, the MOVE of the + * toplevel *and* all other elements pushed by the ripple will be shifted + * by the same amount such that the snapped edges will touch. + * + * You can also find more explanation about the behaviour of those modes at: + * [trim, ripple and roll](http://pitivi.org/manual/trimming.html) + * and [clip management](http://pitivi.org/manual/usingclips.html). + */ +typedef enum { + GES_EDIT_MODE_NORMAL, + GES_EDIT_MODE_RIPPLE, + GES_EDIT_MODE_ROLL, + GES_EDIT_MODE_TRIM, + GES_EDIT_MODE_SLIDE +} GESEditMode; + +/** + * ges_edit_mode_name: + * @mode: a #GESEditMode + * + * Return a string representation of @mode. + * + * Returns: (transfer none): a string representation of @mode. + * Since: 1.18 + */ +GES_API +const gchar * ges_edit_mode_name (GESEditMode mode); + +#define GES_TYPE_EDIT_MODE ges_edit_mode_get_type() + +GES_API +GType ges_edit_mode_get_type (void); + +/** + * GESEdge: + * @GES_EDGE_START: Represents the start of an object. + * @GES_EDGE_END: Represents the end of an object. + * @GES_EDGE_NONE: Represent the fact we are not working with any edge of an + * object. + * + * The edges of an object contain in a #GESTimeline or #GESTrack + */ +typedef enum { + GES_EDGE_START, + GES_EDGE_END, + GES_EDGE_NONE +} GESEdge; + +GES_API +const gchar * ges_edge_name (GESEdge edge); + +#define GES_TYPE_EDGE ges_edge_get_type() + +GES_API +GType ges_edge_get_type (void); + +#define GES_TYPE_MARKER_FLAGS (ges_marker_flags_get_type ()) + +GES_API +GType ges_marker_flags_get_type (void); + +/** + * GESMarkerFlags: + * @GES_MARKER_FLAG_NONE: Marker does not serve any special purpose. + * @GES_MARKER_FLAG_SNAPPABLE: Marker can be a snapping target. + * + * Since: 1.20 + */ +typedef enum { + GES_MARKER_FLAG_NONE = 0, + GES_MARKER_FLAG_SNAPPABLE = 1 << 0, +} GESMarkerFlags; + + +GES_API +const gchar * ges_track_type_name (GESTrackType type); +G_END_DECLS diff --git a/ges/ges-extractable.c b/ges/ges-extractable.c new file mode 100644 index 0000000000..6ce50dd7a8 --- /dev/null +++ b/ges/ges-extractable.c @@ -0,0 +1,359 @@ +/* GStreamer Editing Services + * + * Copyright (C) 2012 Thibault Saunier <thibault.saunier@collabora.com> + * Copyright (C) 2012 Volodymyr Rudyi <vladimir.rudoy@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION: gesextractable + * @title: GESExtractable Interface + * @short_description: An interface for objects which can be extracted + * from a #GESAsset + * + * A #GObject that implements the #GESExtractable interface can be + * extracted from a #GESAsset using ges_asset_extract(). + * + * Each extractable type will have its own way of interpreting the + * #GESAsset:id of an asset (or, if it is associated with a specific + * subclass of #GESAsset, the asset subclass may handle the + * interpretation of the #GESAsset:id). By default, the requested asset + * #GESAsset:id will be ignored by a #GESExtractable and will be set to + * the type name of the extractable instead. Also by default, when the + * requested asset is extracted, the returned object will simply be a + * newly created default object of that extractable type. You should check + * the documentation for each extractable type to see if they differ from + * the default. + * + * After the object is extracted, it will have a reference to the asset it + * came from, which you can retrieve using ges_extractable_get_asset(). + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-asset.h" +#include "ges-internal.h" +#include "ges-extractable.h" +#include "ges-uri-clip.h" + +static GQuark ges_asset_key; + +G_DEFINE_INTERFACE_WITH_CODE (GESExtractable, ges_extractable, + G_TYPE_INITIALLY_UNOWNED, + ges_asset_key = g_quark_from_static_string ("ges-extractable-data")); + +static gchar * +ges_extractable_check_id_default (GType type, const gchar * id, GError ** error) +{ + return g_strdup (g_type_name (type)); +} + +static GType +ges_extractable_get_real_extractable_type_default (GType type, const gchar * id) +{ + return type; +} + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS; /* Start ignoring GParameter deprecation */ +static GParameter * +extractable_get_parameters_from_id (const gchar * id, guint * n_params) +{ + *n_params = 0; + + return NULL; +} + +G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */ +static gchar * +extractable_get_id (GESExtractable * self) +{ + GESAsset *asset; + + if ((asset = ges_extractable_get_asset (self))) + return g_strdup (ges_asset_get_id (asset)); + + return g_strdup (g_type_name (G_OBJECT_TYPE (self))); + +} + +static void +ges_extractable_default_init (GESExtractableInterface * iface) +{ + iface->asset_type = GES_TYPE_ASSET; + iface->check_id = ges_extractable_check_id_default; + iface->get_real_extractable_type = + ges_extractable_get_real_extractable_type_default; + iface->get_parameters_from_id = extractable_get_parameters_from_id; + iface->set_asset = NULL; + iface->set_asset_full = NULL; + iface->get_id = extractable_get_id; + iface->register_metas = NULL; + iface->can_update_asset = FALSE; +} + +/** + * ges_extractable_get_asset: + * @self: A #GESExtractable + * + * Get the asset that has been set on the extractable object. + * + * Returns: (transfer none) (nullable): The asset set on @self, or %NULL + * if no asset has been set. + */ +GESAsset * +ges_extractable_get_asset (GESExtractable * self) +{ + g_return_val_if_fail (GES_IS_EXTRACTABLE (self), NULL); + + return g_object_get_qdata (G_OBJECT (self), ges_asset_key);; +} + +/** + * ges_extractable_set_asset: + * @self: A #GESExtractable + * @asset: (transfer none): The asset to set + * + * Sets the asset for this extractable object. + * + * When an object is extracted from an asset using ges_asset_extract() its + * asset will be automatically set. Note that many classes that implement + * #GESExtractable will automatically create their objects using assets + * when you call their @new methods. However, you can use this method to + * associate an object with a compatible asset if it was created by other + * means and does not yet have an asset. Or, for some implementations of + * #GESExtractable, you can use this to change the asset of the given + * extractable object, which will lead to a change in its state to + * match the new asset #GESAsset:id. + * + * Returns: %TRUE if @asset could be successfully set on @self. + */ +gboolean +ges_extractable_set_asset (GESExtractable * self, GESAsset * asset) +{ + GESExtractableInterface *iface; + GType extract_type; + + g_return_val_if_fail (GES_IS_EXTRACTABLE (self), FALSE); + + iface = GES_EXTRACTABLE_GET_INTERFACE (self); + GST_DEBUG_OBJECT (self, "Setting asset to %" GST_PTR_FORMAT, asset); + + if (iface->can_update_asset == FALSE && + g_object_get_qdata (G_OBJECT (self), ges_asset_key)) { + GST_WARNING_OBJECT (self, "Can not reset asset on object"); + /* FIXME: do not fail if the same asset */ + + return FALSE; + } + + extract_type = ges_asset_get_extractable_type (asset); + if (G_OBJECT_TYPE (self) != extract_type) { + GST_WARNING_OBJECT (self, "Can not set the asset to %" GST_PTR_FORMAT + " because its extractable-type is %s, rather than %s", + asset, g_type_name (extract_type), G_OBJECT_TYPE_NAME (self)); + + return FALSE; + } + + g_object_set_qdata_full (G_OBJECT (self), ges_asset_key, + gst_object_ref (asset), gst_object_unref); + + /* Let classes that implement the interface know that a asset has been set */ + if (iface->set_asset_full) + /* FIXME: return to the previous asset if the setting fails */ + return iface->set_asset_full (self, asset); + + if (iface->set_asset) + iface->set_asset (self, asset); + + return TRUE; +} + +/** + * ges_extractable_get_id: + * @self: A #GESExtractable + * + * Gets the #GESAsset:id of some associated asset. It may be the case + * that the object has no set asset, or even that such an asset does not + * yet exist in the GES cache. Instead, this will return the asset + * #GESAsset:id that is _compatible_ with the current state of the object, + * as determined by the #GESExtractable implementer. If it was indeed + * extracted from an asset, this should return the same as its + * corresponding asset #GESAsset:id. + * + * Returns: (transfer full): The #GESAsset:id of some associated #GESAsset + * that is compatible with @self's current state. + */ +gchar * +ges_extractable_get_id (GESExtractable * self) +{ + g_return_val_if_fail (GES_IS_EXTRACTABLE (self), NULL); + + return GES_EXTRACTABLE_GET_INTERFACE (self)->get_id (self); +} + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS; /* Start ignoring GParameter deprecation */ +/** + * ges_extractable_type_get_parameters_for_id: + * @type: The #GType implementing #GESExtractable + * @id: The ID of the Extractable + * @n_params: (out): Return location for the returned array + * + * Returns: (transfer full) (array length=n_params): an array of #GParameter + * needed to extract the #GESExtractable from a #GESAsset of @id + */ +GParameter * +ges_extractable_type_get_parameters_from_id (GType type, const gchar * id, + guint * n_params) +{ + GObjectClass *klass; + GESExtractableInterface *iface; + + GParameter *ret = NULL; + + g_return_val_if_fail (g_type_is_a (type, G_TYPE_OBJECT), NULL); + g_return_val_if_fail (g_type_is_a (type, GES_TYPE_EXTRACTABLE), NULL); + + klass = g_type_class_ref (type); + iface = g_type_interface_peek (klass, GES_TYPE_EXTRACTABLE); + + ret = iface->get_parameters_from_id (id, n_params); + + g_type_class_unref (klass); + + return ret; +} + +G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */ + +/** + * ges_extractable_type_get_asset_type: + * @type: The #GType implementing #GESExtractable + * + * Get the #GType, subclass of #GES_TYPE_ASSET to instanciate + * to be able to extract a @type + * + * Returns: the #GType to use to create a asset to extract @type + */ +GType +ges_extractable_type_get_asset_type (GType type) +{ + GObjectClass *klass; + GESExtractableInterface *iface; + + g_return_val_if_fail (g_type_is_a (type, G_TYPE_OBJECT), G_TYPE_INVALID); + g_return_val_if_fail (g_type_is_a (type, GES_TYPE_EXTRACTABLE), + G_TYPE_INVALID); + + klass = g_type_class_ref (type); + + iface = g_type_interface_peek (klass, GES_TYPE_EXTRACTABLE); + + g_type_class_unref (klass); + + return iface->asset_type; +} + +/** + * ges_extractable_type_check_id: + * @type: The #GType implementing #GESExtractable + * @id: The ID to check + * + * Check if @id is valid for @type + * + * Returns: (transfer full) (nullable): A newly allocated string containing + * the actual ID (after some processing) or %NULL if the ID is wrong. + */ +gchar * +ges_extractable_type_check_id (GType type, const gchar * id, GError ** error) +{ + GObjectClass *klass; + GESExtractableInterface *iface; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + g_return_val_if_fail (g_type_is_a (type, G_TYPE_OBJECT), NULL); + g_return_val_if_fail (g_type_is_a (type, GES_TYPE_EXTRACTABLE), NULL); + + klass = g_type_class_ref (type); + + iface = g_type_interface_peek (klass, GES_TYPE_EXTRACTABLE); + + g_type_class_unref (klass); + + return iface->check_id (type, id, error); +} + +/** + * ges_extractable_get_real_extractable_type: + * @type: The #GType implementing #GESExtractable + * @id: The ID to check + * + * Get the #GType that should be used as extractable_type for @type and + * @id. Usually this will be the same as @type but in some cases they can + * be some subclasses of @type. For example, in the case of #GESFormatter, + * the returned #GType will be a subclass of #GESFormatter that can be used + * to load the file pointed by @id. + * + * Returns: Return the #GESExtractable type that should be used for @id + */ +GType +ges_extractable_get_real_extractable_type_for_id (GType type, const gchar * id) +{ + GType ret; + GObjectClass *klass; + GESExtractableInterface *iface; + + klass = g_type_class_ref (type); + iface = g_type_interface_peek (klass, GES_TYPE_EXTRACTABLE); + g_type_class_unref (klass); + + ret = iface->get_real_extractable_type (type, id); + + GST_DEBUG ("Extractable type for id %s and wanted type %s is: %s", + id, g_type_name (type), g_type_name (ret)); + + return ret; +} + +/** + * ges_extractable_register_metas: + * @self: A #GESExtractable + * @asset: The #GESAsset on which metadatas should be registered + * + * Lets you register standard method for @extractable_type on @asset + * + * Returns: %TRUE if metas could be register %FALSE otherwize + */ +gboolean +ges_extractable_register_metas (GType extractable_type, GESAsset * asset) +{ + GObjectClass *klass; + gboolean ret = FALSE; + GESExtractableInterface *iface; + + g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE), + FALSE); + + klass = g_type_class_ref (extractable_type); + iface = g_type_interface_peek (klass, GES_TYPE_EXTRACTABLE); + + if (iface->register_metas) + ret = iface->register_metas (iface, klass, asset); + + g_type_class_unref (klass); + return ret; +} diff --git a/ges/ges-extractable.h b/ges/ges-extractable.h new file mode 100644 index 0000000000..77ad495fe9 --- /dev/null +++ b/ges/ges-extractable.h @@ -0,0 +1,150 @@ +/* GStreamer Editing Services + * + * Copyright (C) 2012 Thibault Saunier <thibault.saunier@collabora.com> + * Copyright (C) 2012 Volodymyr Rudyi <vladimir.rudoy@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#pragma once + +typedef struct _GESExtractable GESExtractable; + +#include <glib-object.h> +#include <gio/gio.h> +#include <gst/gst.h> +#include <ges/ges-types.h> +#include <ges/ges-asset.h> + +G_BEGIN_DECLS + +/* GESExtractable interface declarations */ +#define GES_TYPE_EXTRACTABLE (ges_extractable_get_type ()) +#define GES_EXTRACTABLE_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GES_TYPE_EXTRACTABLE, GESExtractableInterface)) +GES_API +G_DECLARE_INTERFACE(GESExtractable, ges_extractable, GES, EXTRACTABLE, GInitiallyUnowned); + +/** + * GESExtractableCheckId: + * @type: The #GESExtractable type to check @id for + * @id: The ID to check + * @error: An error that can be set if needed + * + * Method for checking that an ID is valid for the given #GESExtractable + * type. If the given ID is considered valid, it can be adjusted into some + * standard and returned to prevent the creation of separate #GESAsset-s, + * with different #GESAsset:id, that would otherwise act the same. + * + * Returns (transfer full) (nullable): The actual #GESAsset:id to set on + * any corresponding assets, based on @id, or %NULL if @id is not valid. + */ + +typedef gchar* (*GESExtractableCheckId) (GType type, const gchar *id, + GError **error); + +/** + * GESExtractableInterface: + * @asset_type: The subclass type of #GESAsset that should be created when + * an asset with the corresponding #GESAsset:extractable-type is + * requested. + * @check_id: The method to call to check whether a given ID is valid as + * an asset #GESAsset:id for the given #GESAsset:extractable-type. The + * returned ID is the actual #GESAsset:id that is set on the asset. The + * default implementation will simply always return the type name of the + * #GESAsset:extractable-type, even if the received ID is %NULL. As such, + * any given ID is considered valid (or is ignored), but only one is + * actually ever set on an asset, which means the given + * #GESAsset:extractable-type can only have one associated asset. + * @can_update_asset: Whether an object of this class can have its + * #GESAsset change over its lifetime. This should be set to %TRUE if one + * of the object's parameters that is associated with its ID can change + * after construction, which would require an asset with a new ID. Note + * that the subclass is required to handle the requesting and setting of + * the new asset on the object. + * @set_asset: This method is called after the #GESAsset of an object is + * set. If your class supports the asset of an object changing, then you + * can use this method to change the parameters of the object to match the + * new asset #GESAsset:id. If setting the asset should be able to fail, + * you should implement @set_asset_full instead. + * @set_asset_full: Like @set_asset, but also allows you to return %FALSE + * to indicate a failure to change the object in response to a change in + * its asset. + * @get_parameters_from_id: The method to call to get the object + * properties corresponding to a given asset #GESAsset:id. The default + * implementation will simply return no parameters. The default #GESAsset + * will call this to set the returned properties on the extracted object, + * but other subclasses may ignore this method. + * @get_id: The method to fetch the #GESAsset:id of some associated asset. + * Note that it may be the case that the object does not have its asset + * set, or even that an asset with such an #GESAsset:id does not exist in + * the GES cache. Instead, this should return the #GESAsset:id that is + * _compatible_ with the current state of the object. The default + * implementation simply returns the currently set asset ID, or the type name + * of the object, which is what is used as the #GESAsset:id by default, + * if no asset is set. + * @get_real_extractable_type: The method to call to get the actual + * #GESAsset:extractable-type an asset should have set, given the + * requested #GESAsset:id. The default implementation simply returns the + * same type as given. You can overwrite this if it is more appropriate + * to extract the object from a subclass, depending on the requested + * #GESAsset:id. Note that when an asset is requested, this method will be + * called before the other class methods. In particular, this means that + * the @check_id and @get_parameters_from_id class methods of the returned + * type will be used (instead of our own). + * @register_metas: The method to set metadata on an asset. This is called + * on initiation of the asset, but before it begins to load its state. + */ +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +struct _GESExtractableInterface +{ + GTypeInterface parent; + + GType asset_type; + + GESExtractableCheckId check_id; + gboolean can_update_asset; + + void (*set_asset) (GESExtractable *self, + GESAsset *asset); + + gboolean (*set_asset_full) (GESExtractable *self, + GESAsset *asset); + + GParameter *(*get_parameters_from_id) (const gchar *id, + guint *n_params); + + gchar * (*get_id) (GESExtractable *self); + + GType (*get_real_extractable_type) (GType wanted_type, + const gchar *id); + + gboolean (*register_metas) (GESExtractableInterface *self, + GObjectClass *klass, + GESAsset *asset); + + gpointer _ges_reserved[GES_PADDING]; +}; +G_GNUC_END_IGNORE_DEPRECATIONS + +GES_API +GESAsset* ges_extractable_get_asset (GESExtractable *self); +GES_API +gboolean ges_extractable_set_asset (GESExtractable *self, + GESAsset *asset); + +GES_API +gchar * ges_extractable_get_id (GESExtractable *self); + +G_END_DECLS \ No newline at end of file diff --git a/ges/ges-formatter.c b/ges/ges-formatter.c new file mode 100644 index 0000000000..c5b39eb2de --- /dev/null +++ b/ges/ges-formatter.c @@ -0,0 +1,801 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesformatter + * @title: GESFormatter + * @short_description: Timeline saving and loading. + * + **/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> +#include <gio/gio.h> +#include <stdlib.h> +#include <string.h> + +#include "ges-formatter.h" +#include "ges-internal.h" +#include "ges.h" +#ifndef DISABLE_XPTV +#include "ges-pitivi-formatter.h" +#endif + +#ifdef HAS_PYTHON +#include <Python.h> +#include "ges-resources.h" +#endif + +GST_DEBUG_CATEGORY_STATIC (ges_formatter_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT ges_formatter_debug +static gboolean initialized = FALSE; + +/* TODO Add a GCancellable somewhere in the API */ +static void ges_extractable_interface_init (GESExtractableInterface * iface); + +struct _GESFormatterPrivate +{ + gpointer nothing; +}; + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESFormatter, ges_formatter, + G_TYPE_INITIALLY_UNOWNED, G_ADD_PRIVATE (GESFormatter) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, + ges_extractable_interface_init)); + +static void ges_formatter_dispose (GObject * object); +static gboolean default_can_load_uri (GESFormatter * dummy_instance, + const gchar * uri, GError ** error); + +/* GESExtractable implementation */ +static gchar * +extractable_check_id (GType type, const gchar * id) +{ + GESFormatterClass *class; + + if (id) + return g_strdup (id); + + class = g_type_class_peek (type); + return g_strdup (class->name); +} + +static gchar * +extractable_get_id (GESExtractable * self) +{ + GESAsset *asset; + + if (!(asset = ges_extractable_get_asset (self))) + return NULL; + + return g_strdup (ges_asset_get_id (asset)); +} + +static gboolean +_register_metas (GESExtractableInterface * iface, GObjectClass * class, + GESAsset * asset) +{ + GESFormatterClass *fclass = GES_FORMATTER_CLASS (class); + GESMetaContainer *container = GES_META_CONTAINER (asset); + + ges_meta_container_register_meta_string (container, GES_META_READABLE, + GES_META_FORMATTER_NAME, fclass->name); + ges_meta_container_register_meta_string (container, GES_META_READABLE, + GES_META_DESCRIPTION, fclass->description); + ges_meta_container_register_meta_string (container, GES_META_READABLE, + GES_META_FORMATTER_MIMETYPE, fclass->mimetype); + ges_meta_container_register_meta_string (container, GES_META_READABLE, + GES_META_FORMATTER_EXTENSION, fclass->extension); + ges_meta_container_register_meta_double (container, GES_META_READABLE, + GES_META_FORMATTER_VERSION, fclass->version); + ges_meta_container_register_meta_uint (container, GES_META_READABLE, + GES_META_FORMATTER_RANK, fclass->rank); + ges_meta_container_register_meta_string (container, GES_META_READ_WRITE, + GES_META_FORMAT_VERSION, NULL); + + /* We are leaking the metadata but we don't really have choice here + * as calling ges_init() after deinit() is allowed. + */ + + return TRUE; +} + +static void +ges_extractable_interface_init (GESExtractableInterface * iface) +{ + iface->check_id = (GESExtractableCheckId) extractable_check_id; + iface->get_id = extractable_get_id; + iface->asset_type = GES_TYPE_ASSET; + iface->register_metas = _register_metas; +} + +static void +ges_formatter_class_init (GESFormatterClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = ges_formatter_dispose; + + klass->can_load_uri = default_can_load_uri; + klass->load_from_uri = NULL; + klass->save_to_uri = NULL; + + /* We set dummy metas */ + klass->name = g_strdup ("base-formatter"); + klass->extension = g_strdup ("noextension"); + klass->description = g_strdup ("Formatter base class, you should give" + " a name to your formatter"); + klass->mimetype = g_strdup ("No mimetype"); + klass->version = 0.0; + klass->rank = GST_RANK_NONE; +} + +static void +ges_formatter_init (GESFormatter * object) +{ + object->priv = ges_formatter_get_instance_private (object); + object->project = NULL; +} + +static void +ges_formatter_dispose (GObject * object) +{ + ges_formatter_set_project (GES_FORMATTER (object), NULL); + + G_OBJECT_CLASS (ges_formatter_parent_class)->dispose (object); +} + +static gboolean +default_can_load_uri (GESFormatter * dummy_instance, const gchar * uri, + GError ** error) +{ + GST_DEBUG ("%s: no 'can_load_uri' vmethod implementation", + G_OBJECT_TYPE_NAME (dummy_instance)); + + return FALSE; +} + +static gchar * +_get_extension (const gchar * uri) +{ + gchar *result; + gsize len; + gint find; + + GST_DEBUG ("finding extension of %s", uri); + + if (uri == NULL) + goto no_uri; + + /* find the extension on the uri, this is everything after a '.' */ + len = strlen (uri); + find = len - 1; + + while (find >= 0) { + if (uri[find] == '.') + break; + find--; + } + if (find < 0) + goto no_extension; + + result = g_strdup (&uri[find + 1]); + + GST_DEBUG ("found extension %s", result); + + return result; + + /* ERRORS */ +no_uri: + { + GST_WARNING ("could not parse the peer uri"); + return NULL; + } +no_extension: + { + GST_WARNING ("could not find uri extension in %s", uri); + return NULL; + } +} + +/** + * ges_formatter_can_load_uri: + * @uri: a #gchar * pointing to the URI + * @error: A #GError that will be set in case of error + * + * Checks if there is a #GESFormatter available which can load a #GESTimeline + * from the given URI. + * + * Returns: TRUE if there is a #GESFormatter that can support the given uri + * or FALSE if not. + */ + +gboolean +ges_formatter_can_load_uri (const gchar * uri, GError ** error) +{ + gboolean ret = FALSE; + gchar *extension; + GList *formatter_assets, *tmp; + GESFormatterClass *class = NULL; + + if (!(gst_uri_is_valid (uri))) { + GST_ERROR ("Invalid uri!"); + return FALSE; + } + + extension = _get_extension (uri); + + formatter_assets = ges_list_assets (GES_TYPE_FORMATTER); + for (tmp = formatter_assets; tmp; tmp = tmp->next) { + GESAsset *asset = GES_ASSET (tmp->data); + GESFormatter *dummy_instance; + gchar **valid_exts = + g_strsplit (ges_meta_container_get_string (GES_META_CONTAINER (asset), + GES_META_FORMATTER_EXTENSION), ",", -1); + gint i; + + if (extension) { + gboolean found = FALSE; + + for (i = 0; valid_exts[i]; i++) { + if (!g_strcmp0 (extension, valid_exts[i])) { + found = TRUE; + break; + } + } + + if (!found) + goto next; + } + + class = g_type_class_ref (ges_asset_get_extractable_type (asset)); + dummy_instance = + g_object_ref_sink (g_object_new (ges_asset_get_extractable_type (asset), + NULL)); + if (class->can_load_uri (dummy_instance, uri, error)) { + g_type_class_unref (class); + gst_object_unref (dummy_instance); + ret = TRUE; + break; + } + g_type_class_unref (class); + gst_object_unref (dummy_instance); + next: + g_strfreev (valid_exts); + } + g_free (extension); + + g_list_free (formatter_assets); + return ret; +} + +/** + * ges_formatter_can_save_uri: + * @uri: a #gchar * pointing to a URI + * @error: A #GError that will be set in case of error + * + * Returns TRUE if there is a #GESFormatter available which can save a + * #GESTimeline to the given URI. + * + * Returns: TRUE if the given @uri is supported, else FALSE. + */ + +gboolean +ges_formatter_can_save_uri (const gchar * uri, GError ** error) +{ + GFile *file = NULL; + GFile *dir = NULL; + gboolean ret = TRUE; + GFileInfo *info = NULL; + + if (!(gst_uri_is_valid (uri))) { + GST_ERROR ("%s invalid uri!", uri); + return FALSE; + } + + if (!(gst_uri_has_protocol (uri, "file"))) { + gchar *proto = gst_uri_get_protocol (uri); + GST_ERROR ("Unsupported protocol '%s'", proto); + g_free (proto); + return FALSE; + } + + /* Check if URI or parent directory is writeable */ + file = g_file_new_for_uri (uri); + if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NONE, NULL) + == G_FILE_TYPE_DIRECTORY) { + dir = g_object_ref (file); + } else { + dir = g_file_get_parent (file); + + if (dir == NULL) + goto error; + } + + info = g_file_query_info (dir, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, + G_FILE_QUERY_INFO_NONE, NULL, error); + + if (error && *error != NULL) { + GST_ERROR ("Unable to write to directory: %s", (*error)->message); + + goto error; + } else { + gboolean writeable = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE); + if (!writeable) { + GST_ERROR ("Unable to write to directory"); + goto error; + } + } + +done: + if (file) + g_object_unref (file); + + if (dir) + g_object_unref (dir); + + if (info) + g_object_unref (info); + + /* TODO: implement file format registry */ + /* TODO: search through the registry and chose a GESFormatter class that can + * handle the URI.*/ + + return ret; +error: + ret = FALSE; + goto done; +} + +/** + * ges_formatter_load_from_uri: + * @formatter: a #GESFormatter + * @timeline: a #GESTimeline + * @uri: a #gchar * pointing to a URI + * @error: A #GError that will be set in case of error + * + * Load data from the given URI into timeline. + * + * Returns: TRUE if the timeline data was successfully loaded from the URI, + * else FALSE. + * + * Deprecated: 1.18: Use @ges_timeline_load_from_uri + */ + +gboolean +ges_formatter_load_from_uri (GESFormatter * formatter, + GESTimeline * timeline, const gchar * uri, GError ** error) +{ + gboolean ret = FALSE; + GESFormatterClass *klass = GES_FORMATTER_GET_CLASS (formatter); + + g_return_val_if_fail (GES_IS_FORMATTER (formatter), FALSE); + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + + if (klass->load_from_uri) { + formatter->timeline = timeline; + ret = klass->load_from_uri (formatter, timeline, uri, error); + } + + return ret; +} + +/** + * ges_formatter_save_to_uri: + * @formatter: a #GESFormatter + * @timeline: a #GESTimeline + * @uri: a #gchar * pointing to a URI + * @overwrite: %TRUE to overwrite file if it exists + * @error: A #GError that will be set in case of error + * + * Save data from timeline to the given URI. + * + * Returns: TRUE if the timeline data was successfully saved to the URI + * else FALSE. + * + * Deprecated: 1.18: Use @ges_timeline_save_to_uri + */ + +gboolean +ges_formatter_save_to_uri (GESFormatter * formatter, GESTimeline * + timeline, const gchar * uri, gboolean overwrite, GError ** error) +{ + GError *lerr = NULL; + gboolean ret = FALSE; + GESFormatterClass *klass = GES_FORMATTER_GET_CLASS (formatter); + + GST_DEBUG_OBJECT (formatter, "Saving %" GST_PTR_FORMAT " to %s", + timeline, uri); + if (klass->save_to_uri) + ret = klass->save_to_uri (formatter, timeline, uri, overwrite, &lerr); + else + GST_ERROR_OBJECT (formatter, "save_to_uri not implemented!"); + + if (lerr) { + GST_WARNING_OBJECT (formatter, "%" GST_PTR_FORMAT + " not saved to %s error: %s", timeline, uri, lerr->message); + g_propagate_error (error, lerr); + } else + GST_INFO_OBJECT (formatter, "%" GST_PTR_FORMAT + " saved to %s", timeline, uri); + + return ret; +} + +/** + * ges_formatter_get_default: + * + * Get the default #GESAsset to use as formatter. It will return + * the asset for the #GESFormatter that has the highest @rank + * + * Returns: (transfer none): The #GESAsset for the formatter with highest @rank + */ +GESAsset * +ges_formatter_get_default (void) +{ + GList *assets, *tmp; + GESAsset *ret = NULL; + guint tmprank, rank = GST_RANK_NONE; + + assets = ges_list_assets (GES_TYPE_FORMATTER); + for (tmp = assets; tmp; tmp = tmp->next) { + tmprank = GST_RANK_NONE; + ges_meta_container_get_uint (GES_META_CONTAINER (tmp->data), + GES_META_FORMATTER_RANK, &tmprank); + + if (tmprank > rank) { + rank = tmprank; + ret = GES_ASSET (tmp->data); + } + } + g_list_free (assets); + + return ret; +} + +/** + * ges_formatter_class_register_metas: + * @klass: The class to register metas on + * @name: The name of the formatter + * @description: The formatter description + * @extensions: A list of coma separated file extensions handled + * by the formatter. The order of the extensions should match the + * list of the structures inside @caps + * @caps: The caps the formatter handled, they should match what + * gstreamer typefind mechanism will report for the files the formatter + * handles. + * @version: The version of the formatter + * @rank: The rank of the formatter + */ +void +ges_formatter_class_register_metas (GESFormatterClass * klass, + const gchar * name, const gchar * description, const gchar * extensions, + const gchar * caps, gdouble version, GstRank rank) +{ + g_return_if_fail (klass->name); + klass->name = g_strdup (name); + klass->description = g_strdup (description); + klass->extension = g_strdup (extensions); + klass->mimetype = g_strdup (caps); + klass->version = version; + klass->rank = rank; + + if (g_atomic_int_get (&initialized) + && g_type_class_peek (G_OBJECT_CLASS_TYPE (klass))) + gst_object_unref (ges_asset_request (G_OBJECT_CLASS_TYPE (klass), NULL, + NULL)); +} + +/* Main Formatter methods */ + +/*< protected >*/ +void +ges_formatter_set_project (GESFormatter * formatter, GESProject * project) +{ + formatter->project = project; +} + +GESProject * +ges_formatter_get_project (GESFormatter * formatter) +{ + return formatter->project; +} + +static void +_list_formatters (GType * formatters, guint n_formatters) +{ + GType *tmptypes, type; + guint tmp_n_types, i; + + for (i = 0; i < n_formatters; i++) { + type = formatters[i]; + tmptypes = g_type_children (type, &tmp_n_types); + if (tmp_n_types) { + /* Recurse as g_type_children does not */ + _list_formatters (tmptypes, tmp_n_types); + } + g_free (tmptypes); + + if (G_TYPE_IS_ABSTRACT (type)) { + GST_DEBUG ("%s is abstract, not using", g_type_name (type)); + } else { + gst_object_unref (ges_asset_request (type, NULL, NULL)); + } + } +} + +static void +load_python_formatters (void) +{ +#ifdef HAS_PYTHON + PyGILState_STATE state = 0; + PyObject *main_module, *main_locals; + GError *err = NULL; + GResource *resource = ges_get_resource (); + GBytes *bytes = + g_resource_lookup_data (resource, "/ges/python/gesotioformatter.py", + G_RESOURCE_LOOKUP_FLAGS_NONE, &err); + PyObject *code = NULL, *res = NULL; + gboolean we_initialized = FALSE; + GModule *libpython; + gpointer has_python = NULL; + + GST_LOG ("Checking to see if libpython is already loaded"); + if (g_module_symbol (g_module_open (NULL, G_MODULE_BIND_LOCAL), + "_Py_NoneStruct", &has_python) && has_python) { + GST_LOG ("libpython is already loaded"); + } else { + const gchar *libpython_path = + PY_LIB_LOC "/libpython" PYTHON_VERSION PY_ABI_FLAGS "." PY_LIB_SUFFIX; + GST_LOG ("loading libpython from '%s'", libpython_path); + libpython = g_module_open (libpython_path, 0); + if (!libpython) { + GST_ERROR ("Couldn't g_module_open libpython. Reason: %s", + g_module_error ()); + return; + } + } + + if (!Py_IsInitialized ()) { + GST_LOG ("python wasn't initialized"); + /* set the correct plugin for registering stuff */ + Py_Initialize (); + we_initialized = TRUE; + } else { + GST_LOG ("python was already initialized"); + state = PyGILState_Ensure (); + } + + if (!bytes) { + GST_DEBUG ("Could not load gesotioformatter: %s\n", err->message); + + g_clear_error (&err); + + goto done; + } + + main_module = PyImport_AddModule ("__main__"); + if (main_module == NULL) { + GST_WARNING ("Could not add main module"); + PyErr_Print (); + PyErr_Clear (); + goto done; + } + + main_locals = PyModule_GetDict (main_module); + /* Compiling the code ourself so it has a proper filename */ + code = + Py_CompileString (g_bytes_get_data (bytes, NULL), "gesotioformatter.py", + Py_file_input); + if (PyErr_Occurred ()) { + PyErr_Print (); + PyErr_Clear (); + goto done; + } + res = PyEval_EvalCode ((gpointer) code, main_locals, main_locals); + Py_XDECREF (code); + Py_XDECREF (res); + if (PyErr_Occurred ()) { + PyObject *exception_backtrace; + PyObject *exception_type; + PyObject *exception_value, *exception_value_repr, *exception_value_str; + + PyErr_Fetch (&exception_type, &exception_value, &exception_backtrace); + PyErr_NormalizeException (&exception_type, &exception_value, + &exception_backtrace); + + exception_value_repr = PyObject_Repr (exception_value); + exception_value_str = + PyUnicode_AsEncodedString (exception_value_repr, "utf-8", "Error ~"); + GST_INFO ("Could not load OpenTimelineIO formatter: %s", + PyBytes_AS_STRING (exception_value_str)); + + Py_XDECREF (exception_type); + Py_XDECREF (exception_value); + Py_XDECREF (exception_backtrace); + + Py_XDECREF (exception_value_repr); + Py_XDECREF (exception_value_str); + PyErr_Clear (); + } + +done: + if (bytes) + g_bytes_unref (bytes); + + if (we_initialized) { + PyEval_SaveThread (); + } else { + PyGILState_Release (state); + } +#endif /* HAS_PYTHON */ +} + +void +_init_formatter_assets (void) +{ + GType *formatters; + guint n_formatters; + static gsize init_debug = 0; + + if (g_once_init_enter (&init_debug)) { + + GST_DEBUG_CATEGORY_INIT (ges_formatter_debug, "gesformatter", + GST_DEBUG_FG_YELLOW, "ges formatter"); + g_once_init_leave (&init_debug, TRUE); + } + + if (g_atomic_int_compare_and_exchange (&initialized, FALSE, TRUE)) { + /* register formatter types with the system */ +#ifndef DISABLE_XPTV + g_type_class_ref (GES_TYPE_PITIVI_FORMATTER); +#endif + g_type_class_ref (GES_TYPE_COMMAND_LINE_FORMATTER); + g_type_class_ref (GES_TYPE_XML_FORMATTER); + + load_python_formatters (); + + formatters = g_type_children (GES_TYPE_FORMATTER, &n_formatters); + _list_formatters (formatters, n_formatters); + g_free (formatters); + } +} + +void +_deinit_formatter_assets (void) +{ + if (g_atomic_int_compare_and_exchange (&initialized, TRUE, FALSE)) { + +#ifndef DISABLE_XPTV + g_type_class_unref (g_type_class_peek (GES_TYPE_PITIVI_FORMATTER)); +#endif + + g_type_class_unref (g_type_class_peek (GES_TYPE_COMMAND_LINE_FORMATTER)); + g_type_class_unref (g_type_class_peek (GES_TYPE_XML_FORMATTER)); + } +} + +static gint +_sort_formatters (GESAsset * asset, GESAsset * asset1) +{ + GESFormatterClass *class = + g_type_class_peek (ges_asset_get_extractable_type (asset)); + GESFormatterClass *class1 = + g_type_class_peek (ges_asset_get_extractable_type (asset1)); + + /* We want the highest ranks to be first! */ + if (class->rank > class1->rank) + return -1; + else if (class->rank < class1->rank) + return 1; + + return 0; +} + +GESAsset * +_find_formatter_asset_for_id (const gchar * id) +{ + GESFormatterClass *class = NULL; + GList *formatter_assets, *tmp; + GESAsset *asset = NULL; + + formatter_assets = g_list_sort (ges_list_assets (GES_TYPE_FORMATTER), + (GCompareFunc) _sort_formatters); + for (tmp = formatter_assets; tmp; tmp = tmp->next) { + GESFormatter *dummy_instance; + + asset = GES_ASSET (tmp->data); + class = g_type_class_ref (ges_asset_get_extractable_type (asset)); + dummy_instance = + g_object_ref_sink (g_object_new (ges_asset_get_extractable_type (asset), + NULL)); + if (class->can_load_uri (dummy_instance, id, NULL)) { + g_type_class_unref (class); + asset = gst_object_ref (asset); + gst_object_unref (dummy_instance); + break; + } + + asset = NULL; + g_type_class_unref (class); + gst_object_unref (dummy_instance); + } + + g_list_free (formatter_assets); + + return asset; +} + +/** + * ges_find_formatter_for_uri: + * + * Get the best formatter for @uri. It tries to find a formatter + * compatible with @uri extension, if none is found, it returns the default + * formatter asset. + * + * Returns: (transfer none): The #GESAsset for the best formatter to save to @uri + * + * Since: 1.18 + */ +GESAsset * +ges_find_formatter_for_uri (const gchar * uri) +{ + GList *formatter_assets, *tmp; + GESAsset *asset = NULL; + + gchar *extension = _get_extension (uri); + if (!extension) + return ges_formatter_get_default (); + + formatter_assets = g_list_sort (ges_list_assets (GES_TYPE_FORMATTER), + (GCompareFunc) _sort_formatters); + + for (tmp = formatter_assets; tmp; tmp = tmp->next) { + gint i; + gchar **valid_exts = + g_strsplit (ges_meta_container_get_string (GES_META_CONTAINER + (tmp->data), + GES_META_FORMATTER_EXTENSION), ",", -1); + + for (i = 0; valid_exts[i]; i++) { + if (!g_strcmp0 (extension, valid_exts[i])) { + asset = GES_ASSET (tmp->data); + break; + } + } + + g_strfreev (valid_exts); + if (asset) + break; + } + g_free (extension); + g_list_free (formatter_assets); + + if (asset) { + GST_INFO_OBJECT (asset, "Using for URI %s", uri); + return asset; + } + + return ges_formatter_get_default (); +} diff --git a/ges/ges-formatter.h b/ges/ges-formatter.h new file mode 100644 index 0000000000..98e57abf57 --- /dev/null +++ b/ges/ges-formatter.h @@ -0,0 +1,158 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-timeline.h> + +G_BEGIN_DECLS + +#define GES_TYPE_FORMATTER ges_formatter_get_type() +GES_DECLARE_TYPE(Formatter, formatter, FORMATTER); + +/** + * GESFormatter: + * + * Base class for timeline data serialization and deserialization. + */ + +struct _GESFormatter +{ + GInitiallyUnowned parent; + + /*< private >*/ + GESFormatterPrivate *priv; + + /*< protected >*/ + GESProject *project; + GESTimeline *timeline; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +typedef gboolean (*GESFormatterCanLoadURIMethod) (GESFormatter *dummy_instance, const gchar * uri, GError **error); + +/** + * GESFormatterLoadFromURIMethod: + * @formatter: a #GESFormatter + * @timeline: a #GESTimeline + * @uri: the URI to load from + * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL + * + * Virtual method for loading a timeline from a given URI. + * + * Every #GESFormatter subclass needs to implement this method. + * + * Returns: TRUE if the @timeline was properly loaded from the given @uri, + * else FALSE. + **/ +typedef gboolean (*GESFormatterLoadFromURIMethod) (GESFormatter *formatter, + GESTimeline *timeline, + const gchar * uri, + GError **error); + +/** + * GESFormatterSaveToURIMethod: + * @formatter: a #GESFormatter + * @timeline: a #GESTimeline + * @uri: the URI to save to + * @overwrite: Whether the file should be overwritten in case it exists + * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL + * + * Virtual method for saving a timeline to a uri. + * + * Every #GESFormatter subclass needs to implement this method. + * + * Returns: TRUE if the @timeline was properly stored to the given @uri, + * else FALSE. + */ +typedef gboolean (*GESFormatterSaveToURIMethod) (GESFormatter *formatter, + GESTimeline *timeline, const gchar * uri, gboolean overwrite, + GError **error); + +/** + * GESFormatterClass: + * @parent_class: the parent class structure + * @can_load_uri: Whether the URI can be loaded + * @load_from_uri: class method to deserialize data from a URI + * @save_to_uri: class method to serialize data to a URI + * + * GES Formatter class. Override the vmethods to implement the formatter functionnality. + */ + +struct _GESFormatterClass { + GInitiallyUnownedClass parent_class; + + /* TODO 2.0: Rename the loading method to can_load and load. + * Technically we just pass data to load, it should not necessarily + * be a URI */ + GESFormatterCanLoadURIMethod can_load_uri; + GESFormatterLoadFromURIMethod load_from_uri; + GESFormatterSaveToURIMethod save_to_uri; + + /* < private > */ + gchar *name; + gchar *description; + gchar *extension; + gchar *mimetype; + gdouble version; + GstRank rank; + + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API +void ges_formatter_class_register_metas (GESFormatterClass * klass, + const gchar *name, + const gchar *description, + const gchar *extensions, + const gchar *caps, + gdouble version, + GstRank rank); + +GES_API +gboolean ges_formatter_can_load_uri (const gchar * uri, GError **error); +GES_API +gboolean ges_formatter_can_save_uri (const gchar * uri, GError **error); + +GES_DEPRECATED_FOR(ges_timeline_load_from_uri) +gboolean ges_formatter_load_from_uri (GESFormatter * formatter, + GESTimeline *timeline, + const gchar *uri, + GError **error); + +GES_DEPRECATED_FOR(ges_timeline_save_to_uri) +gboolean ges_formatter_save_to_uri (GESFormatter * formatter, + GESTimeline *timeline, + const gchar *uri, + gboolean overwrite, + GError **error); + +GES_API +GESAsset *ges_formatter_get_default (void); + +GES_API +GESAsset *ges_find_formatter_for_uri (const gchar *uri); + +G_END_DECLS diff --git a/ges/ges-gerror.h b/ges/ges-gerror.h new file mode 100644 index 0000000000..ad2782a160 --- /dev/null +++ b/ges/ges-gerror.h @@ -0,0 +1,66 @@ +/* GStreamer Editing Services + * Copyright (C) 2013 Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#pragma once + +/** + * SECTION: ges-gerror + * @title: GESErrors + * @short_description: GError — Categorized error messages + */ + +G_BEGIN_DECLS + +/** + * GES_ERROR: + * + * An error happened in GES + */ +#define GES_ERROR g_quark_from_static_string("GES_ERROR") + +/** + * GESError: + * @GES_ERROR_ASSET_WRONG_ID: The ID passed is malformed + * @GES_ERROR_ASSET_LOADING: An error happened while loading the asset + * @GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE: The formatted files was malformed + * @GES_ERROR_INVALID_FRAME_NUMBER: The frame number is invalid + * @GES_ERROR_NEGATIVE_LAYER: The operation would lead to a negative + * #GES_TIMELINE_ELEMENT_LAYER_PRIORITY. (Since: 1.18) + * @GES_ERROR_NEGATIVE_TIME: The operation would lead to a negative time. + * E.g. for the #GESTimelineElement:start #GESTimelineElement:duration or + * #GESTimelineElement:in-point. (Since: 1.18) + * @GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT: Some #GESTimelineElement does + * not have a large enough #GESTimelineElement:max-duration to cover the + * desired operation. (Since: 1.18) + * @GES_ERROR_INVALID_OVERLAP_IN_TRACK: The operation would break one of + * the overlap conditions for the #GESTimeline. (Since: 1.18) + */ +typedef enum +{ + GES_ERROR_ASSET_WRONG_ID, + GES_ERROR_ASSET_LOADING, + GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE, + GES_ERROR_INVALID_FRAME_NUMBER, + GES_ERROR_NEGATIVE_LAYER, + GES_ERROR_NEGATIVE_TIME, + GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT, + GES_ERROR_INVALID_OVERLAP_IN_TRACK, + GES_ERROR_INVALID_EFFECT_BIN_DESCRIPTION, +} GESError; + +G_END_DECLS diff --git a/ges/ges-group.c b/ges/ges-group.c new file mode 100644 index 0000000000..0685170ecd --- /dev/null +++ b/ges/ges-group.c @@ -0,0 +1,686 @@ +/* GStreamer Editing Services + * Copyright (C) 2013 Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesgroup + * @title: GESGroup + * @short_description: Class for a collection of #GESContainer-s within + * a single timeline. + * + * A #GESGroup controls one or more #GESContainer-s (usually #GESClip-s, + * but it can also control other #GESGroup-s). Its children must share + * the same #GESTimeline, but can otherwise lie in separate #GESLayer-s + * and have different timings. + * + * To initialise a group, you may want to use ges_container_group(), + * and similarly use ges_container_ungroup() to dispose of it. + * + * A group will maintain the relative #GESTimelineElement:start times of + * its children, as well as their relative layer #GESLayer:priority. + * Therefore, if one of its children has its #GESTimelineElement:start + * set, all other children will have their #GESTimelineElement:start + * shifted by the same amount. Similarly, if one of its children moves to + * a new layer, the other children will also change layers to maintain the + * difference in their layer priorities. For example, if a child moves + * from a layer with #GESLayer:priority 1 to a layer with priority 3, then + * another child that was in a layer with priority 0 will move to the + * layer with priority 2. + * + * The #GESGroup:start of a group refers to the earliest start + * time of its children. If the group's #GESGroup:start is set, all the + * children will be shifted equally such that the earliest start time + * will match the set value. The #GESGroup:duration of a group is the + * difference between the earliest start time and latest end time of its + * children. If the group's #GESGroup:duration is increased, the children + * whose end time matches the end of the group will be extended + * accordingly. If it is decreased, then any child whose end time exceeds + * the new end time will also have their duration decreased accordingly. + * + * A group may span several layers, but for methods such as + * ges_timeline_element_get_layer_priority() and + * ges_timeline_element_edit() a group is considered to have a layer + * priority that is the highest #GESLayer:priority (numerically, the + * smallest) of all the layers it spans. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-group.h" +#include "ges.h" +#include "ges-internal.h" + +#include <string.h> + +#define parent_class ges_group_parent_class + +struct _GESGroupPrivate +{ + gboolean reseting_start; + + guint32 max_layer_prio; + + gboolean updating_priority; + /* This is used while were are setting ourselve a proper timing value, + * in this case the value should always be kept */ + gboolean setting_value; + GHashTable *child_sigids; +}; + +typedef struct +{ + GESLayer *layer; + gulong child_clip_changed_layer_sid; + gulong child_priority_changed_sid; + gulong child_group_priority_changed_sid; +} ChildSignalIds; + +enum +{ + PROP_0, + PROP_START, + PROP_INPOINT, + PROP_DURATION, + PROP_MAX_DURATION, + PROP_PRIORITY, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST] = { NULL, }; + +G_DEFINE_TYPE_WITH_PRIVATE (GESGroup, ges_group, GES_TYPE_CONTAINER); + +/**************************************************** + * Our listening of children * + ****************************************************/ +static void +_update_our_values (GESGroup * group) +{ + GList *tmp; + GESContainer *container = GES_CONTAINER (group); + guint32 min_layer_prio = G_MAXINT32, max_layer_prio = 0; + + for (tmp = container->children; tmp; tmp = tmp->next) { + GESContainer *child = tmp->data; + + if (GES_IS_CLIP (child)) { + GESLayer *layer = ges_clip_get_layer (GES_CLIP (child)); + guint32 prio; + + if (!layer) + continue; + + prio = ges_layer_get_priority (layer); + + min_layer_prio = MIN (prio, min_layer_prio); + max_layer_prio = MAX (prio, max_layer_prio); + gst_object_unref (layer); + } else if (GES_IS_GROUP (child)) { + guint32 prio = _PRIORITY (child), height = GES_CONTAINER_HEIGHT (child); + + min_layer_prio = MIN (prio, min_layer_prio); + max_layer_prio = MAX ((prio + height - 1), max_layer_prio); + } + } + + if (min_layer_prio != _PRIORITY (group)) { + group->priv->updating_priority = TRUE; + _set_priority0 (GES_TIMELINE_ELEMENT (group), min_layer_prio); + group->priv->updating_priority = FALSE; + } + + /* FIXME: max_layer_prio not used elsewhere + * We could use it to inform our parent group when our maximum has + * changed (which we don't currently do, to allow it to change its + * height) */ + group->priv->max_layer_prio = max_layer_prio; + _ges_container_set_height (container, max_layer_prio - min_layer_prio + 1); +} + +/* layer changed its priority in the timeline */ +static void +_child_priority_changed_cb (GESLayer * layer, + GParamSpec * arg G_GNUC_UNUSED, GESTimelineElement * clip) +{ + _update_our_values (GES_GROUP (clip->parent)); +} + +static void +_child_clip_changed_layer_cb (GESClip * clip, GParamSpec * arg G_GNUC_UNUSED, + GESGroup * group) +{ + ChildSignalIds *sigids; + + sigids = g_hash_table_lookup (group->priv->child_sigids, clip); + + g_assert (sigids); + + if (sigids->layer) { + g_signal_handler_disconnect (sigids->layer, + sigids->child_priority_changed_sid); + sigids->child_priority_changed_sid = 0; + gst_object_unref (sigids->layer); + } + + sigids->layer = ges_clip_get_layer (GES_CLIP (clip)); + + if (sigids->layer) { + sigids->child_priority_changed_sid = + g_signal_connect (sigids->layer, "notify::priority", + (GCallback) _child_priority_changed_cb, clip); + } + + _update_our_values (group); +} + +static void +_child_group_priority_changed (GESTimelineElement * child, + GParamSpec * arg G_GNUC_UNUSED, GESGroup * group) +{ + _update_our_values (group); +} + +/**************************************************** + * GESTimelineElement vmethods * + ****************************************************/ + +static gboolean +_set_priority (GESTimelineElement * element, guint32 priority) +{ + GList *layers; + GESTimeline *timeline = element->timeline; + + if (GES_GROUP (element)->priv->updating_priority == TRUE + || GES_TIMELINE_ELEMENT_BEING_EDITED (element)) + return TRUE; + + layers = timeline ? timeline->layers : NULL; + if (layers == NULL) { + GST_WARNING_OBJECT (element, "Not any layer in the timeline, not doing" + "anything, timeline: %" GST_PTR_FORMAT, timeline); + + return FALSE; + } + + /* FIXME: why are we not shifting ->max_layer_prio? */ + + return timeline_tree_move (timeline_get_tree (timeline), + element, (gint64) (element->priority) - (gint64) priority, 0, + GES_EDGE_NONE, 0, NULL); +} + +static gboolean +_set_start (GESTimelineElement * element, GstClockTime start) +{ + GList *tmp, *children; + gint64 diff = start - _START (element); + GESContainer *container = GES_CONTAINER (element); + + if (GES_GROUP (element)->priv->setting_value == TRUE) + /* Let GESContainer update itself */ + return GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_start (element, + start); + + /* get copy of children, since GESContainer may resort the group */ + children = ges_container_get_children (container, FALSE); + container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; + for (tmp = children; tmp; tmp = tmp->next) + _set_start0 (tmp->data, _START (tmp->data) + diff); + container->children_control_mode = GES_CHILDREN_UPDATE; + g_list_free_full (children, gst_object_unref); + + return TRUE; +} + +static gboolean +_set_inpoint (GESTimelineElement * element, GstClockTime inpoint) +{ + if (inpoint != 0) { + GST_WARNING_OBJECT (element, "The in-point of a group has no meaning," + " it can not be set to a non-zero value"); + return FALSE; + } + return TRUE; +} + +static gboolean +_set_max_duration (GESTimelineElement * element, GstClockTime max_duration) +{ + if (GST_CLOCK_TIME_IS_VALID (max_duration)) { + GST_WARNING_OBJECT (element, "The max-duration of a group has no " + "meaning, it can not be set to a valid GstClockTime value"); + return FALSE; + } + return TRUE; +} + +static gboolean +_set_duration (GESTimelineElement * element, GstClockTime duration) +{ + GList *tmp, *children; + GstClockTime last_child_end = 0, new_end; + GESContainer *container = GES_CONTAINER (element); + GESGroupPrivate *priv = GES_GROUP (element)->priv; + + if (priv->setting_value == TRUE) + /* Let GESContainer update itself */ + return GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_duration (element, + duration); + + if (container->initiated_move == NULL) { + gboolean expending = (_DURATION (element) < duration); + + new_end = _START (element) + duration; + /* get copy of children, since GESContainer may resort the group */ + children = ges_container_get_children (container, FALSE); + container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; + for (tmp = children; tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + GstClockTime n_dur; + + if ((!expending && _END (child) > new_end) || + (expending && (_END (child) >= _END (element)))) { + n_dur = MAX (0, ((gint64) (new_end - _START (child)))); + _set_duration0 (child, n_dur); + } + } + container->children_control_mode = GES_CHILDREN_UPDATE; + g_list_free_full (children, gst_object_unref); + } + + for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { + if (_DURATION (tmp->data)) + last_child_end = + MAX (GES_TIMELINE_ELEMENT_END (tmp->data), last_child_end); + } + + priv->setting_value = TRUE; + _set_duration0 (element, last_child_end - _START (element)); + priv->setting_value = FALSE; + + return -1; +} + +/**************************************************** + * * + * GESContainer virtual methods implementation * + * * + ****************************************************/ + +static gboolean +_add_child (GESContainer * group, GESTimelineElement * child) +{ + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (group); + g_return_val_if_fail (GES_IS_CONTAINER (child), FALSE); + + if (timeline && child->timeline != timeline) { + GST_WARNING_OBJECT (group, "Cannot add child %" GES_FORMAT + " because it belongs to timeline %" GST_PTR_FORMAT + " rather than the group's timeline %" GST_PTR_FORMAT, + GES_ARGS (child), child->timeline, timeline); + return FALSE; + } + + return TRUE; +} + +static void +_child_added (GESContainer * group, GESTimelineElement * child) +{ + GESGroup *self = GES_GROUP (group); + ChildSignalIds *sigids; + + /* NOTE: notifies are currently frozen by ges_container_add */ + if (!GES_TIMELINE_ELEMENT_TIMELINE (group) && child->timeline) { + timeline_add_group (child->timeline, self); + timeline_emit_group_added (child->timeline, self); + } + + _update_our_values (self); + + sigids = g_new0 (ChildSignalIds, 1); + /* doesn't take a ref to child since no data */ + g_hash_table_insert (self->priv->child_sigids, child, sigids); + + if (GES_IS_CLIP (child)) { + sigids->layer = ges_clip_get_layer (GES_CLIP (child)); + + sigids->child_clip_changed_layer_sid = g_signal_connect (child, + "notify::layer", (GCallback) _child_clip_changed_layer_cb, group); + + if (sigids->layer) { + sigids->child_priority_changed_sid = g_signal_connect (sigids->layer, + "notify::priority", (GCallback) _child_priority_changed_cb, child); + } + } else if (GES_IS_GROUP (child)) { + sigids->child_group_priority_changed_sid = g_signal_connect (child, + "notify::priority", (GCallback) _child_group_priority_changed, group); + } +} + +static void +_disconnect_signals (GESGroup * group, GESTimelineElement * child, + ChildSignalIds * sigids) +{ + if (sigids->child_group_priority_changed_sid) { + g_signal_handler_disconnect (child, + sigids->child_group_priority_changed_sid); + sigids->child_group_priority_changed_sid = 0; + } + + if (sigids->child_clip_changed_layer_sid) { + g_signal_handler_disconnect (child, sigids->child_clip_changed_layer_sid); + sigids->child_clip_changed_layer_sid = 0; + } + + if (sigids->child_priority_changed_sid) { + g_signal_handler_disconnect (sigids->layer, + sigids->child_priority_changed_sid); + sigids->child_priority_changed_sid = 0; + } +} + +static void +_child_removed (GESContainer * group, GESTimelineElement * child) +{ + GESGroup *self = GES_GROUP (group); + ChildSignalIds *sigids; + + /* NOTE: notifies are currently frozen by ges_container_add */ + _ges_container_sort_children (group); + + sigids = g_hash_table_lookup (self->priv->child_sigids, child); + + g_assert (sigids); + _disconnect_signals (self, child, sigids); + g_hash_table_remove (self->priv->child_sigids, child); + + if (group->children == NULL) { + GST_FIXME_OBJECT (group, "Auto destroy myself?"); + if (GES_TIMELINE_ELEMENT_TIMELINE (group)) + timeline_remove_group (GES_TIMELINE_ELEMENT_TIMELINE (group), self); + return; + } + + _update_our_values (GES_GROUP (group)); +} + +static GList * +_ungroup (GESContainer * group, gboolean recursive) +{ + GPtrArray *children_array; + GESTimeline *timeline; + GList *children, *tmp, *ret = NULL; + + /* We choose 16 just as an arbitrary value */ + children_array = g_ptr_array_sized_new (16); + timeline = GES_TIMELINE_ELEMENT_TIMELINE (group); + + children = ges_container_get_children (group, FALSE); + for (tmp = children; tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + + gst_object_ref (child); + ges_container_remove (group, child); + g_ptr_array_add (children_array, child); + ret = g_list_append (ret, child); + } + + if (timeline) + timeline_emit_group_removed (timeline, (GESGroup *) group, children_array); + g_ptr_array_free (children_array, TRUE); + g_list_free_full (children, gst_object_unref); + + /* No need to remove from the timeline here, this will be done in _child_removed */ + + return ret; +} + +static GESContainer * +_group (GList * containers) +{ + GList *tmp; + GESTimeline *timeline = NULL; + GESContainer *ret = GES_CONTAINER (ges_group_new ()); + + if (!containers) + return ret; + + for (tmp = containers; tmp; tmp = tmp->next) { + if (!timeline) { + timeline = GES_TIMELINE_ELEMENT_TIMELINE (tmp->data); + } else if (timeline != GES_TIMELINE_ELEMENT_TIMELINE (tmp->data)) { + g_object_unref (ret); + + return NULL; + } + + if (!ges_container_add (ret, tmp->data)) { + GST_INFO ("%" GES_FORMAT " could not add child %p while" + " grouping", GES_ARGS (ret), tmp->data); + g_object_unref (ret); + + return NULL; + } + } + + /* No need to add to the timeline here, this will be done in _child_added */ + + return ret; +} + +/**************************************************** + * * + * GObject virtual methods implementation * + * * + ****************************************************/ +static void +ges_group_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESTimelineElement *self = GES_TIMELINE_ELEMENT (object); + + switch (property_id) { + case PROP_START: + g_value_set_uint64 (value, self->start); + break; + case PROP_INPOINT: + g_value_set_uint64 (value, self->inpoint); + break; + case PROP_DURATION: + g_value_set_uint64 (value, self->duration); + break; + case PROP_MAX_DURATION: + g_value_set_uint64 (value, self->maxduration); + break; + case PROP_PRIORITY: + g_value_set_uint (value, self->priority); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec); + } +} + +static void +ges_group_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESTimelineElement *self = GES_TIMELINE_ELEMENT (object); + + switch (property_id) { + case PROP_START: + ges_timeline_element_set_start (self, g_value_get_uint64 (value)); + break; + case PROP_INPOINT: + ges_timeline_element_set_inpoint (self, g_value_get_uint64 (value)); + break; + case PROP_DURATION: + ges_timeline_element_set_duration (self, g_value_get_uint64 (value)); + break; + case PROP_PRIORITY: + ges_timeline_element_set_priority (self, g_value_get_uint (value)); + break; + case PROP_MAX_DURATION: + ges_timeline_element_set_max_duration (self, g_value_get_uint64 (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec); + } +} + +static void +ges_group_dispose (GObject * object) +{ + GESGroup *self = GES_GROUP (object); + + g_clear_pointer (&self->priv->child_sigids, g_hash_table_unref); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +ges_group_class_init (GESGroupClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESContainerClass *container_class = GES_CONTAINER_CLASS (klass); + GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass); + + object_class->get_property = ges_group_get_property; + object_class->set_property = ges_group_set_property; + object_class->dispose = ges_group_dispose; + + element_class->set_duration = _set_duration; + element_class->set_inpoint = _set_inpoint; + element_class->set_max_duration = _set_max_duration; + element_class->set_start = _set_start; + element_class->set_priority = _set_priority; + + /* We override start, inpoint, duration and max-duration from GESTimelineElement + * in order to makes sure those fields are not serialized. + */ + /** + * GESGroup:start: + * + * An overwrite of the #GESTimelineElement:start property. For a + * #GESGroup, this is the earliest #GESTimelineElement:start time + * amongst its children. + */ + properties[PROP_START] = g_param_spec_uint64 ("start", "Start", + "The position in the container", 0, G_MAXUINT64, 0, + G_PARAM_READWRITE | GES_PARAM_NO_SERIALIZATION); + + /** + * GESGroup:in-point: + * + * An overwrite of the #GESTimelineElement:in-point property. This has + * no meaning for a group and should not be set. + */ + properties[PROP_INPOINT] = + g_param_spec_uint64 ("in-point", "In-point", "The in-point", 0, + G_MAXUINT64, 0, G_PARAM_READWRITE | GES_PARAM_NO_SERIALIZATION); + + /** + * GESGroup:duration: + * + * An overwrite of the #GESTimelineElement:duration property. For a + * #GESGroup, this is the difference between the earliest + * #GESTimelineElement:start time and the latest end time (given by + * #GESTimelineElement:start + #GESTimelineElement:duration) amongst + * its children. + */ + properties[PROP_DURATION] = + g_param_spec_uint64 ("duration", "Duration", "The duration to use", 0, + G_MAXUINT64, GST_CLOCK_TIME_NONE, + G_PARAM_READWRITE | GES_PARAM_NO_SERIALIZATION); + + /** + * GESGroup:max-duration: + * + * An overwrite of the #GESTimelineElement:max-duration property. This + * has no meaning for a group and should not be set. + */ + properties[PROP_MAX_DURATION] = + g_param_spec_uint64 ("max-duration", "Maximum duration", + "The maximum duration of the object", 0, G_MAXUINT64, GST_CLOCK_TIME_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | GES_PARAM_NO_SERIALIZATION); + + /** + * GESGroup:priority: + * + * An overwrite of the #GESTimelineElement:priority property. + * Setting #GESTimelineElement priorities is deprecated as all priority + * management is now done by GES itself. + */ + properties[PROP_PRIORITY] = g_param_spec_uint ("priority", "Priority", + "The priority of the object", 0, G_MAXUINT, 0, + G_PARAM_READWRITE | GES_PARAM_NO_SERIALIZATION); + + g_object_class_install_properties (object_class, PROP_LAST, properties); + + container_class->add_child = _add_child; + container_class->child_added = _child_added; + container_class->child_removed = _child_removed; + container_class->ungroup = _ungroup; + container_class->group = _group; + container_class->grouping_priority = 0; +} + +static void +_free_sigid (gpointer mem) +{ + ChildSignalIds *sigids = mem; + gst_clear_object (&sigids->layer); + g_free (mem); +} + +static void +ges_group_init (GESGroup * self) +{ + self->priv = ges_group_get_instance_private (self); + + self->priv->child_sigids = + g_hash_table_new_full (NULL, NULL, NULL, _free_sigid); +} + +/**************************************************** + * * + * API implementation * + * * + ****************************************************/ + +/** + * ges_group_new: + * + * Created a new empty group. You may wish to use + * ges_container_group() instead, which can return a different + * #GESContainer subclass if possible. + * + * Returns: (transfer floating): The new empty group. + */ +GESGroup * +ges_group_new (void) +{ + GESGroup *res; + GESAsset *asset = ges_asset_request (GES_TYPE_GROUP, NULL, NULL); + + res = GES_GROUP (ges_asset_extract (asset, NULL)); + gst_object_unref (asset); + + return res; +} diff --git a/ges/ges-group.h b/ges/ges-group.h new file mode 100644 index 0000000000..7268780621 --- /dev/null +++ b/ges/ges-group.h @@ -0,0 +1,50 @@ +/* * Gstreamer + * + * Copyright (C) <2013> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> +#include "ges-clip.h" +#include "ges-container.h" + +G_BEGIN_DECLS + +#define GES_TYPE_GROUP (ges_group_get_type ()) +GES_DECLARE_TYPE(Group, group, GROUP); + +struct _GESGroup { + GESContainer parent; + + /*< private >*/ + GESGroupPrivate *priv; + + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESGroupClass { + GESContainerClass parent_class; + + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API +GESGroup *ges_group_new (void); + +G_END_DECLS diff --git a/ges/ges-image-source.c b/ges/ges-image-source.c new file mode 100644 index 0000000000..04f003cc69 --- /dev/null +++ b/ges/ges-image-source.c @@ -0,0 +1,207 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesimagesource + * @title: GESImageSource + * @short_description: outputs the video stream from a media file as a still + * image. + * + * Outputs the video stream from a given file as a still frame. The frame chosen + * will be determined by the in-point property on the track element. For image + * files, do not set the in-point property. + * + * Deprecated: 1.18: This won't be used anymore and has been replaced by + * #GESUriSource instead which now plugs an `imagefreeze` element when + * #ges_uri_source_asset_is_image returns %TRUE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-track-element.h" +#include "ges-image-source.h" + + +struct _GESImageSourcePrivate +{ + /* Dummy variable */ + void *nothing; +}; + +enum +{ + PROP_0, + PROP_URI +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GESImageSource, ges_image_source, + GES_TYPE_VIDEO_SOURCE); +static void +ges_image_source_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESImageSource *uriclip = GES_IMAGE_SOURCE (object); + + switch (property_id) { + case PROP_URI: + g_value_set_string (value, uriclip->uri); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_image_source_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESImageSource *uriclip = GES_IMAGE_SOURCE (object); + + switch (property_id) { + case PROP_URI: + uriclip->uri = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_image_source_dispose (GObject * object) +{ + GESImageSource *uriclip = GES_IMAGE_SOURCE (object); + + if (uriclip->uri) + g_free (uriclip->uri); + + G_OBJECT_CLASS (ges_image_source_parent_class)->dispose (object); +} + +static void +pad_added_cb (GstElement * source, GstPad * pad, GstElement * scale) +{ + GstPad *sinkpad; + GstPadLinkReturn ret; + + sinkpad = gst_element_get_static_pad (scale, "sink"); + if (sinkpad) { + GST_DEBUG ("got sink pad, trying to link"); + + ret = gst_pad_link (pad, sinkpad); + gst_object_unref (sinkpad); + if (GST_PAD_LINK_SUCCESSFUL (ret)) { + GST_DEBUG ("linked ok, returning"); + return; + } + } + + GST_DEBUG ("pad failed to link properly"); +} + +static GstElement * +ges_image_source_create_source (GESSource * source) +{ + GstElement *bin, *src, *scale, *freeze, *iconv; + GstPad *srcpad, *target; + + bin = GST_ELEMENT (gst_bin_new ("still-image-bin")); + src = gst_element_factory_make ("uridecodebin", NULL); + scale = gst_element_factory_make ("videoscale", NULL); + freeze = gst_element_factory_make ("imagefreeze", NULL); + iconv = gst_element_factory_make ("videoconvert", NULL); + + g_object_set (scale, "add-borders", TRUE, NULL); + + gst_bin_add_many (GST_BIN (bin), src, scale, freeze, iconv, NULL); + + gst_element_link_pads_full (scale, "src", iconv, "sink", + GST_PAD_LINK_CHECK_NOTHING); + gst_element_link_pads_full (iconv, "src", freeze, "sink", + GST_PAD_LINK_CHECK_NOTHING); + + /* FIXME: add capsfilter here with sink caps (see 626518) */ + + target = gst_element_get_static_pad (freeze, "src"); + + srcpad = gst_ghost_pad_new ("src", target); + gst_element_add_pad (bin, srcpad); + gst_object_unref (target); + + g_object_set (src, "uri", ((GESImageSource *) source)->uri, NULL); + + g_signal_connect (G_OBJECT (src), "pad-added", + G_CALLBACK (pad_added_cb), scale); + + return bin; +} + +static void +ges_image_source_class_init (GESImageSourceClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESSourceClass *source_class = GES_SOURCE_CLASS (klass); + GESVideoSourceClass *vsource_class = GES_VIDEO_SOURCE_CLASS (klass); + + object_class->get_property = ges_image_source_get_property; + object_class->set_property = ges_image_source_set_property; + object_class->dispose = ges_image_source_dispose; + + /** + * GESImageSource:uri: + * + * The location of the file/resource to use. + */ + g_object_class_install_property (object_class, PROP_URI, + g_param_spec_string ("uri", "URI", "uri of the resource", + NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + source_class->create_source = ges_image_source_create_source; + vsource_class->ABI.abi.get_natural_size = + ges_video_uri_source_get_natural_size; + + GES_TRACK_ELEMENT_CLASS_DEFAULT_HAS_INTERNAL_SOURCE (klass) = FALSE; +} + +static void +ges_image_source_init (GESImageSource * self) +{ + self->priv = ges_image_source_get_instance_private (self); +} + +/* @uri: the URI the source should control + * + * Creates a new #GESImageSource for the provided @uri. + * + * Returns: (transfer floating): A new #GESImageSource. + */ +GESImageSource * +ges_image_source_new (gchar * uri) +{ + GESImageSource *res; + GESAsset *asset = ges_asset_request (GES_TYPE_IMAGE_SOURCE, uri, NULL); + + res = GES_IMAGE_SOURCE (ges_asset_extract (asset, NULL)); + res->uri = g_strdup (uri); + gst_object_unref (asset); + + return res; +} diff --git a/ges/ges-image-source.h b/ges/ges-image-source.h new file mode 100644 index 0000000000..9d05727df2 --- /dev/null +++ b/ges/ges-image-source.h @@ -0,0 +1,54 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> +#include <ges/ges-video-source.h> + +G_BEGIN_DECLS + +#define GES_TYPE_IMAGE_SOURCE ges_image_source_get_type() +GES_DECLARE_TYPE(ImageSource, image_source, IMAGE_SOURCE); + +/** + * GESImageSource: + */ +struct _GESImageSource { + /*< private >*/ + GESVideoSource parent; + + gchar *uri; + + GESImageSourcePrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESImageSourceClass { + GESVideoSourceClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +G_END_DECLS diff --git a/ges/ges-internal.h b/ges/ges-internal.h new file mode 100644 index 0000000000..66b2ba807d --- /dev/null +++ b/ges/ges-internal.h @@ -0,0 +1,605 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <gst/gst.h> +#include <gst/pbutils/encoding-profile.h> +#include <gio/gio.h> + +G_BEGIN_DECLS + +#ifndef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT (_ges_debug ()) +#endif + +#include "ges-timeline.h" +#include "ges-track-element.h" +#include "ges-timeline-element.h" + +#include "ges-asset.h" +#include "ges-base-xml-formatter.h" +#include "ges-timeline-tree.h" + +G_GNUC_INTERNAL +GstDebugCategory * _ges_debug (void); + +/* The first 2 NLE priorities are used for: + * 0- The Mixing element + * 1- The Gaps + */ +#define MIN_NLE_PRIO 2 +#define LAYER_HEIGHT 1000 + +#define _START(obj) GES_TIMELINE_ELEMENT_START (obj) +#define _INPOINT(obj) GES_TIMELINE_ELEMENT_INPOINT (obj) +#define _DURATION(obj) GES_TIMELINE_ELEMENT_DURATION (obj) +#define _MAXDURATION(obj) GES_TIMELINE_ELEMENT_MAX_DURATION (obj) +#define _PRIORITY(obj) GES_TIMELINE_ELEMENT_PRIORITY (obj) +#ifndef _END +#define _END(obj) (_START (obj) + _DURATION (obj)) +#endif +#define _set_start0 ges_timeline_element_set_start +#define _set_inpoint0 ges_timeline_element_set_inpoint +#define _set_duration0 ges_timeline_element_set_duration +#define _set_priority0 ges_timeline_element_set_priority + +#define GES_CLOCK_TIME_IS_LESS(first, second) \ + (GST_CLOCK_TIME_IS_VALID (first) && (!GST_CLOCK_TIME_IS_VALID (second) \ + || (first) < (second))) + +#define DEFAULT_FRAMERATE_N 30 +#define DEFAULT_FRAMERATE_D 1 +#define DEFAULT_WIDTH 1280 +#define DEFAULT_HEIGHT 720 + +#define GES_TIMELINE_ELEMENT_FORMAT \ + "s<%p>" \ + " [ %" GST_TIME_FORMAT \ + " (%" GST_TIME_FORMAT \ + ") - %" GST_TIME_FORMAT "(%" GST_TIME_FORMAT") layer: %" G_GINT32_FORMAT "] " + +#define GES_TIMELINE_ELEMENT_ARGS(element) \ + GES_TIMELINE_ELEMENT_NAME(element), element, \ + GST_TIME_ARGS(GES_TIMELINE_ELEMENT_START(element)), \ + GST_TIME_ARGS(GES_TIMELINE_ELEMENT_INPOINT(element)), \ + GST_TIME_ARGS(GES_TIMELINE_ELEMENT_DURATION(element)), \ + GST_TIME_ARGS(GES_TIMELINE_ELEMENT_MAX_DURATION(element)), \ + GES_TIMELINE_ELEMENT_LAYER_PRIORITY(element) + +#define GES_FORMAT GES_TIMELINE_ELEMENT_FORMAT +#define GES_ARGS GES_TIMELINE_ELEMENT_ARGS + +#define GES_IS_TIME_EFFECT(element) \ + (GES_IS_BASE_EFFECT (element) \ + && ges_base_effect_is_time_effect (GES_BASE_EFFECT (element))) + +#define GES_TIMELINE_ELEMENT_SET_BEING_EDITED(element) \ + ELEMENT_SET_FLAG ( \ + ges_timeline_element_peak_toplevel (GES_TIMELINE_ELEMENT (element)), \ + GES_TIMELINE_ELEMENT_SET_SIMPLE) + +#define GES_TIMELINE_ELEMENT_UNSET_BEING_EDITED(element) \ + ELEMENT_UNSET_FLAG ( \ + ges_timeline_element_peak_toplevel (GES_TIMELINE_ELEMENT (element)), \ + GES_TIMELINE_ELEMENT_SET_SIMPLE) + +#define GES_TIMELINE_ELEMENT_BEING_EDITED(element) \ + ELEMENT_FLAG_IS_SET ( \ + ges_timeline_element_peak_toplevel (GES_TIMELINE_ELEMENT (element)), \ + GES_TIMELINE_ELEMENT_SET_SIMPLE) + +/************************ + * Our property masks * + ************************/ +#define GES_PARAM_NO_SERIALIZATION (1 << (G_PARAM_USER_SHIFT + 1)) + +#define SUPRESS_UNUSED_WARNING(a) (void)a + +G_GNUC_INTERNAL void +ges_timeline_freeze_auto_transitions (GESTimeline * timeline, gboolean freeze); + +G_GNUC_INTERNAL GESAutoTransition * +ges_timeline_get_auto_transition_at_edge (GESTimeline * timeline, GESTrackElement * source, + GESEdge edge); + +G_GNUC_INTERNAL gboolean ges_timeline_is_disposed (GESTimeline* timeline); + +G_GNUC_INTERNAL gboolean +ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element, + gint64 new_layer_priority, GESEditMode mode, GESEdge edge, + guint64 position, GError ** error); + +G_GNUC_INTERNAL gboolean +ges_timeline_layer_priority_in_gap (GESTimeline * timeline, guint layer_priority); + +G_GNUC_INTERNAL void +ges_timeline_set_track_selection_error (GESTimeline * timeline, + gboolean was_error, + GError * error); +G_GNUC_INTERNAL gboolean +ges_timeline_take_track_selection_error (GESTimeline * timeline, + GError ** error); + +G_GNUC_INTERNAL void +timeline_add_group (GESTimeline *timeline, + GESGroup *group); +G_GNUC_INTERNAL void +timeline_remove_group (GESTimeline *timeline, + GESGroup *group); +G_GNUC_INTERNAL void +timeline_emit_group_added (GESTimeline *timeline, + GESGroup *group); +G_GNUC_INTERNAL void +timeline_emit_group_removed (GESTimeline * timeline, + GESGroup * group, GPtrArray * array); + +G_GNUC_INTERNAL +gboolean +timeline_add_element (GESTimeline *timeline, + GESTimelineElement *element); +G_GNUC_INTERNAL +gboolean +timeline_remove_element (GESTimeline *timeline, + GESTimelineElement *element); + +G_GNUC_INTERNAL +GNode * +timeline_get_tree (GESTimeline *timeline); + +G_GNUC_INTERNAL +void +timeline_fill_gaps (GESTimeline *timeline); + +G_GNUC_INTERNAL void +timeline_create_transitions (GESTimeline * timeline, GESTrackElement * track_element); + +G_GNUC_INTERNAL void timeline_get_framerate(GESTimeline *self, gint *fps_n, + gint *fps_d); +G_GNUC_INTERNAL void +ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving); + +G_GNUC_INTERNAL gboolean +ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip, GError ** error); + +G_GNUC_INTERNAL void +ges_timeline_remove_clip (GESTimeline * timeline, GESClip * clip); + +G_GNUC_INTERNAL void +ges_timeline_set_smart_rendering (GESTimeline * timeline, gboolean rendering_smartly); + +G_GNUC_INTERNAL gboolean +ges_timeline_get_smart_rendering (GESTimeline *timeline); + +G_GNUC_INTERNAL void +ges_auto_transition_set_source (GESAutoTransition * self, GESTrackElement * source, GESEdge edge); + + + +G_GNUC_INTERNAL +void +track_resort_and_fill_gaps (GESTrack *track); + +G_GNUC_INTERNAL +void +track_disable_last_gap (GESTrack *track, gboolean disabled); + +G_GNUC_INTERNAL void +ges_asset_cache_init (void); + +G_GNUC_INTERNAL void +ges_asset_cache_deinit (void); + +G_GNUC_INTERNAL void +ges_asset_set_id (GESAsset *asset, const gchar *id); + +G_GNUC_INTERNAL void +ges_asset_cache_put (GESAsset * asset, GTask *task); + +G_GNUC_INTERNAL gboolean +ges_asset_cache_set_loaded(GType extractable_type, const gchar * id, GError *error); + +/* FIXME: marked as GES_API just so they can be used in tests! */ + +GES_API GESAsset* +ges_asset_cache_lookup(GType extractable_type, const gchar * id); + +GES_API gboolean +ges_asset_try_proxy (GESAsset *asset, const gchar *new_id); + +G_GNUC_INTERNAL gboolean +ges_asset_finish_proxy (GESAsset * proxy); + +G_GNUC_INTERNAL gboolean +ges_asset_request_id_update (GESAsset *asset, gchar **proposed_id, + GError *error); +G_GNUC_INTERNAL gchar * +ges_effect_asset_id_get_type_and_bindesc (const char *id, + GESTrackType *track_type, + GError **error); + +G_GNUC_INTERNAL void _ges_uri_asset_cleanup (void); + +G_GNUC_INTERNAL gboolean _ges_uri_asset_ensure_setup (gpointer uriasset_class); + +/* GESExtractable internall methods + * + * FIXME Check if that should be public later + */ +G_GNUC_INTERNAL GType +ges_extractable_type_get_asset_type (GType type); + +G_GNUC_INTERNAL gchar * +ges_extractable_type_check_id (GType type, const gchar *id, GError **error); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS; +G_GNUC_INTERNAL GParameter * +ges_extractable_type_get_parameters_from_id (GType type, const gchar *id, + guint *n_params); +G_GNUC_END_IGNORE_DEPRECATIONS; + +G_GNUC_INTERNAL GType +ges_extractable_get_real_extractable_type_for_id (GType type, const gchar * id); + +G_GNUC_INTERNAL gboolean +ges_extractable_register_metas (GType extractable_type, GESAsset *asset); + +/************************************************ + * * + * GESFormatter internal methods * + * * + ************************************************/ +G_GNUC_INTERNAL void +ges_formatter_set_project (GESFormatter *formatter, + GESProject *project); +G_GNUC_INTERNAL GESProject * +ges_formatter_get_project (GESFormatter *formatter); +G_GNUC_INTERNAL GESAsset * +_find_formatter_asset_for_id (const gchar *id); + + + +/************************************************ + * * + * GESProject internal methods * + * * + ************************************************/ + +/* FIXME This should probably become public, but we need to make sure it + * is the right API before doing so */ +G_GNUC_INTERNAL gboolean ges_project_set_loaded (GESProject * project, + GESFormatter *formatter, + GError *error); +G_GNUC_INTERNAL gchar * ges_project_try_updating_id (GESProject *self, + GESAsset *asset, + GError *error); +G_GNUC_INTERNAL void ges_project_add_loading_asset (GESProject *project, + GType extractable_type, + const gchar *id); +G_GNUC_INTERNAL gchar* ges_uri_asset_try_update_id (GError *error, GESAsset *wrong_asset); +/************************************************ + * * + * GESBaseXmlFormatter internal methods * + * * + ************************************************/ + +/* FIXME GESBaseXmlFormatter is all internal for now, the API is not stable + * fo now, so do not expose it */ +G_GNUC_INTERNAL void ges_base_xml_formatter_add_clip (GESBaseXmlFormatter * self, + const gchar *id, + const char *asset_id, + GType type, + GstClockTime start, + GstClockTime inpoint, + GstClockTime duration, + guint layer_prio, + GESTrackType track_types, + GstStructure *properties, + GstStructure * children_properties, + const gchar *metadatas, + GError **error); +G_GNUC_INTERNAL void ges_base_xml_formatter_add_asset (GESBaseXmlFormatter * self, + const gchar * id, + GType extractable_type, + GstStructure *properties, + const gchar *metadatas, + const gchar *proxy_id, + GError **error); +G_GNUC_INTERNAL void ges_base_xml_formatter_add_layer (GESBaseXmlFormatter *self, + GType extractable_type, + guint priority, + GstStructure *properties, + const gchar *metadatas, + gchar **deactivated_tracks, + GError **error); +G_GNUC_INTERNAL void ges_base_xml_formatter_add_track (GESBaseXmlFormatter *self, + GESTrackType track_type, + GstCaps *caps, + const gchar *id, + GstStructure *properties, + const gchar *metadatas, + GError **error); +G_GNUC_INTERNAL void ges_base_xml_formatter_add_encoding_profile(GESBaseXmlFormatter * self, + const gchar *type, + const gchar *parent, + const gchar * name, + const gchar * description, + GstCaps * format, + const gchar * preset, + GstStructure * preset_properties, + const gchar * preset_name, + guint id, + guint presence, + GstCaps * restriction, + guint pass, + gboolean variableframerate, + GstStructure * properties, + gboolean enabled, + GError ** error); +G_GNUC_INTERNAL void ges_base_xml_formatter_add_track_element (GESBaseXmlFormatter *self, + GType effect_type, + const gchar *asset_id, + const gchar * track_id, + const gchar *timeline_obj_id, + GstStructure *children_properties, + GstStructure *properties, + const gchar *metadatas, + GError **error); + +G_GNUC_INTERNAL void ges_base_xml_formatter_add_source (GESBaseXmlFormatter *self, + const gchar * track_id, + GstStructure *children_properties, + GstStructure *properties, + const gchar *metadatas); + +G_GNUC_INTERNAL void ges_base_xml_formatter_add_group (GESBaseXmlFormatter *self, + const gchar *name, + const gchar *properties, + const gchar *metadatas); + +G_GNUC_INTERNAL void ges_base_xml_formatter_last_group_add_child(GESBaseXmlFormatter *self, + const gchar * id, + const gchar * name); + +G_GNUC_INTERNAL void ges_base_xml_formatter_add_control_binding (GESBaseXmlFormatter * self, + const gchar * binding_type, + const gchar * source_type, + const gchar * property_name, + gint mode, + const gchar *track_id, + GSList * timed_values); + +G_GNUC_INTERNAL void ges_base_xml_formatter_set_timeline_properties(GESBaseXmlFormatter * self, + GESTimeline *timeline, + const gchar *properties, + const gchar *metadatas); + +G_GNUC_INTERNAL void ges_base_xml_formatter_end_current_clip (GESBaseXmlFormatter *self); + +G_GNUC_INTERNAL void ges_xml_formatter_deinit (void); + +G_GNUC_INTERNAL gboolean set_property_foreach (GQuark field_id, + const GValue * value, + GObject * object); + +G_GNUC_INTERNAL GstElement * get_element_for_encoding_profile (GstEncodingProfile *prof, + GstElementFactoryListType type); + +/* Function to initialise GES */ +G_GNUC_INTERNAL void _init_standard_transition_assets (void); +G_GNUC_INTERNAL void _init_formatter_assets (void); +G_GNUC_INTERNAL void _deinit_formatter_assets (void); + +/* Utilities */ +G_GNUC_INTERNAL gint element_start_compare (GESTimelineElement * a, + GESTimelineElement * b); +G_GNUC_INTERNAL gint element_end_compare (GESTimelineElement * a, + GESTimelineElement * b); +G_GNUC_INTERNAL GstElementFactory * +ges_get_compositor_factory (void); + +G_GNUC_INTERNAL void +ges_idle_add (GSourceFunc func, gpointer udata, GDestroyNotify notify); + +G_GNUC_INTERNAL gboolean +ges_util_structure_get_clocktime (GstStructure *structure, const gchar *name, + GstClockTime *val, GESFrameNumber *frames); + +G_GNUC_INTERNAL gboolean /* From ges-xml-formatter.c */ +ges_util_can_serialize_spec (GParamSpec * spec); + +/**************************************************** + * GESContainer * + ****************************************************/ +G_GNUC_INTERNAL void _ges_container_sort_children (GESContainer *container); +G_GNUC_INTERNAL void _ges_container_set_height (GESContainer * container, + guint32 height); + +/**************************************************** + * GESClip * + ****************************************************/ +G_GNUC_INTERNAL void ges_clip_set_layer (GESClip *clip, GESLayer *layer); +G_GNUC_INTERNAL gboolean ges_clip_is_moving_from_layer (GESClip *clip); +G_GNUC_INTERNAL void ges_clip_set_moving_from_layer (GESClip *clip, gboolean is_moving); +G_GNUC_INTERNAL GESTrackElement* ges_clip_create_track_element (GESClip *clip, GESTrackType type); +G_GNUC_INTERNAL GList* ges_clip_create_track_elements (GESClip *clip, GESTrackType type); +G_GNUC_INTERNAL gboolean ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTrackElement * child, GstClockTime inpoint, GError ** error); +G_GNUC_INTERNAL gboolean ges_clip_can_set_max_duration_of_all_core (GESClip * clip, GstClockTime max_duration, GError ** error); +G_GNUC_INTERNAL gboolean ges_clip_can_set_max_duration_of_child (GESClip * clip, GESTrackElement * child, GstClockTime max_duration, GError ** error); +G_GNUC_INTERNAL gboolean ges_clip_can_set_active_of_child (GESClip * clip, GESTrackElement * child, gboolean active, GError ** error); +G_GNUC_INTERNAL gboolean ges_clip_can_set_priority_of_child (GESClip * clip, GESTrackElement * child, guint32 priority, GError ** error); +G_GNUC_INTERNAL gboolean ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, GESTrack * tack, GError ** error); +G_GNUC_INTERNAL gboolean ges_clip_can_set_time_property_of_child (GESClip * clip, GESTrackElement * child, GObject * prop_object, GParamSpec * pspec, const GValue * value, GError ** error); +G_GNUC_INTERNAL GstClockTime ges_clip_duration_limit_with_new_children_inpoints (GESClip * clip, GHashTable * child_inpoints); +G_GNUC_INTERNAL GstClockTime ges_clip_get_core_internal_time_from_timeline_time (GESClip * clip, GstClockTime timeline_time, gboolean * no_core, GError ** error); +G_GNUC_INTERNAL void ges_clip_empty_from_track (GESClip * clip, GESTrack * track); +G_GNUC_INTERNAL void ges_clip_set_add_error (GESClip * clip, GError * error); +G_GNUC_INTERNAL void ges_clip_take_add_error (GESClip * clip, GError ** error); +G_GNUC_INTERNAL void ges_clip_set_remove_error (GESClip * clip, GError * error); +G_GNUC_INTERNAL void ges_clip_take_remove_error (GESClip * clip, GError ** error); + +/**************************************************** + * GESLayer * + ****************************************************/ +G_GNUC_INTERNAL gboolean ges_layer_resync_priorities (GESLayer * layer); +G_GNUC_INTERNAL void layer_set_priority (GESLayer * layer, guint priority, gboolean emit); + +/**************************************************** + * GESTrackElement * + ****************************************************/ +#define NLE_OBJECT_TRACK_ELEMENT_QUARK (g_quark_from_string ("nle_object_track_element_quark")) +G_GNUC_INTERNAL gboolean ges_track_element_set_track (GESTrackElement * object, GESTrack * track, GError ** error); +G_GNUC_INTERNAL void ges_track_element_copy_properties (GESTimelineElement * element, + GESTimelineElement * elementcopy); +G_GNUC_INTERNAL void ges_track_element_set_layer_active (GESTrackElement *element, gboolean active); + +G_GNUC_INTERNAL void ges_track_element_copy_bindings (GESTrackElement *element, + GESTrackElement *new_element, + guint64 position); +G_GNUC_INTERNAL void ges_track_element_freeze_control_sources (GESTrackElement * object, + gboolean freeze); +G_GNUC_INTERNAL void ges_track_element_update_outpoint (GESTrackElement * self); + +G_GNUC_INTERNAL void +ges_track_element_set_creator_asset (GESTrackElement * self, + GESAsset *creator_asset); +G_GNUC_INTERNAL GESAsset * +ges_track_element_get_creator_asset (GESTrackElement * self); + +G_GNUC_INTERNAL void +ges_track_element_set_has_internal_source_is_forbidden (GESTrackElement * element); + +G_GNUC_INTERNAL GstElement* ges_source_create_topbin (GESSource *source, + const gchar* bin_name, + GstElement* sub_element, + GPtrArray* elements); +G_GNUC_INTERNAL void ges_source_set_rendering_smartly (GESSource *source, + gboolean rendering_smartly); +G_GNUC_INTERNAL gboolean +ges_source_get_rendering_smartly (GESSource *source); + +G_GNUC_INTERNAL void ges_track_set_smart_rendering (GESTrack* track, gboolean rendering_smartly); +G_GNUC_INTERNAL GstElement * ges_track_get_composition (GESTrack *track); + + +/********************************************* + * GESTrackElement subclasses contructores * + ********************************************/ +G_GNUC_INTERNAL GESAudioTestSource * ges_audio_test_source_new (void); +G_GNUC_INTERNAL GESAudioUriSource * ges_audio_uri_source_new (gchar *uri); +G_GNUC_INTERNAL GESVideoUriSource * ges_video_uri_source_new (gchar *uri); +G_GNUC_INTERNAL GESImageSource * ges_image_source_new (gchar *uri); +G_GNUC_INTERNAL GESTitleSource * ges_title_source_new (void); +G_GNUC_INTERNAL GESVideoTestSource * ges_video_test_source_new (void); + +/**************************************************** + * GES*Effect * + ****************************************************/ +G_GNUC_INTERNAL gchar * +ges_base_effect_get_time_property_name (GESBaseEffect * effect, + GObject * child, + GParamSpec * pspec); +G_GNUC_INTERNAL GHashTable * +ges_base_effect_get_time_property_values (GESBaseEffect * effect); +G_GNUC_INTERNAL GstClockTime +ges_base_effect_translate_source_to_sink_time (GESBaseEffect * effect, + GstClockTime time, + GHashTable * time_property_values); +G_GNUC_INTERNAL GstClockTime +ges_base_effect_translate_sink_to_source_time (GESBaseEffect * effect, + GstClockTime time, + GHashTable * time_property_values); +G_GNUC_INTERNAL GstElement * +ges_effect_from_description (const gchar *bin_desc, + GESTrackType type, + GError **error); + +/**************************************************** + * GESTimelineElement * + ****************************************************/ +typedef enum +{ + GES_CLIP_IS_MOVING = (1 << 0), + GES_TIMELINE_ELEMENT_SET_SIMPLE = (1 << 1), +} GESTimelineElementFlags; + +G_GNUC_INTERNAL GESTimelineElement * ges_timeline_element_peak_toplevel (GESTimelineElement * self); +G_GNUC_INTERNAL GESTimelineElement * ges_timeline_element_get_copied_from (GESTimelineElement *self); +G_GNUC_INTERNAL GESTimelineElementFlags ges_timeline_element_flags (GESTimelineElement *self); +G_GNUC_INTERNAL void ges_timeline_element_set_flags (GESTimelineElement *self, GESTimelineElementFlags flags); +G_GNUC_INTERNAL gboolean ges_timeline_element_add_child_property_full (GESTimelineElement *self, + GESTimelineElement *owner, + GParamSpec *pspec, + GObject *child); + +G_GNUC_INTERNAL GObject * ges_timeline_element_get_child_from_child_property (GESTimelineElement * self, + GParamSpec * pspec); +G_GNUC_INTERNAL GParamSpec ** ges_timeline_element_get_children_properties (GESTimelineElement * self, + guint * n_properties); + +#define ELEMENT_FLAGS(obj) (ges_timeline_element_flags (GES_TIMELINE_ELEMENT(obj))) +#define ELEMENT_SET_FLAG(obj,flag) (ges_timeline_element_set_flags(GES_TIMELINE_ELEMENT(obj), (ELEMENT_FLAGS(obj) | (flag)))) +#define ELEMENT_UNSET_FLAG(obj,flag) (ges_timeline_element_set_flags(GES_TIMELINE_ELEMENT(obj), (ELEMENT_FLAGS(obj) & ~(flag)))) +#define ELEMENT_FLAG_IS_SET(obj,flag) ((ELEMENT_FLAGS (obj) & (flag)) == (flag)) + +/****************************** + * GESMultiFile internal API * + ******************************/ +typedef struct GESMultiFileURI +{ + gchar *location; + gint start; + gint end; +} GESMultiFileURI; + +G_GNUC_INTERNAL GESMultiFileURI * ges_multi_file_uri_new (const gchar * uri); + +/****************************** + * GESUriSource internal API * + ******************************/ +G_GNUC_INTERNAL gboolean +ges_video_uri_source_get_natural_size(GESVideoSource* source, gint* width, gint* height); + +/********************************** + * GESTestClipAsset internal API * + **********************************/ +G_GNUC_INTERNAL gboolean ges_test_clip_asset_get_natural_size (GESAsset *self, + gint *width, + gint *height); +G_GNUC_INTERNAL gchar *ges_test_source_asset_check_id (GType type, const gchar *id, + GError **error); + +/******************************* + * GESMarkerList * + *******************************/ + +G_GNUC_INTERNAL GESMarker * ges_marker_list_get_closest (GESMarkerList *list, GstClockTime position); +G_GNUC_INTERNAL gchar * ges_marker_list_serialize (const GValue * v); +G_GNUC_INTERNAL gboolean ges_marker_list_deserialize (GValue *dest, const gchar *s); + +/******************** + * Gnonlin helpers * + ********************/ + +G_GNUC_INTERNAL gboolean ges_nle_composition_add_object (GstElement *comp, GstElement *object); +G_GNUC_INTERNAL gboolean ges_nle_composition_remove_object (GstElement *comp, GstElement *object); +G_GNUC_INTERNAL gboolean ges_nle_object_commit (GstElement * nlesource, gboolean recurse); + +G_END_DECLS diff --git a/ges/ges-layer.c b/ges/ges-layer.c new file mode 100644 index 0000000000..5a0372aeb5 --- /dev/null +++ b/ges/ges-layer.c @@ -0,0 +1,1108 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * 2011 Mathieu Duponchelle <mathieu.duponchelle@epitech.eu> + * 2013 Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:geslayer + * @title: GESLayer + * @short_description: Non-overlapping sequence of #GESClip + * + * #GESLayer-s are responsible for collecting and ordering #GESClip-s. + * + * A layer within a timeline will have an associated priority, + * corresponding to their index within the timeline. A layer with the + * index/priority 0 will have the highest priority and the layer with the + * largest index will have the lowest priority (the order of priorities, + * in this sense, is the _reverse_ of the numerical ordering of the + * indices). ges_timeline_move_layer() should be used if you wish to + * change how layers are prioritised in a timeline. + * + * Layers with higher priorities will have their content priorities + * over content from lower priority layers, similar to how layers are + * used in image editing. For example, if two separate layers both + * display video content, then the layer with the higher priority will + * have its images shown first. The other layer will only have its image + * shown if the higher priority layer has no content at the given + * playtime, or is transparent in some way. Audio content in separate + * layers will simply play in addition. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-layer.h" +#include "ges.h" +#include "ges-source-clip.h" + +static void ges_meta_container_interface_init + (GESMetaContainerInterface * iface); + +struct _GESLayerPrivate +{ + /*< private > */ + GList *clips_start; /* The Clips sorted by start and + * priority */ + + guint32 priority; /* The priority of the layer within the + * containing timeline */ + gboolean auto_transition; + + GHashTable *tracks_activness; +}; + +typedef struct +{ + GESClip *clip; + GESLayer *layer; +} NewAssetUData; + +typedef struct +{ + GESTrack *track; + GESLayer *layer; + gboolean active; + gboolean track_disposed; +} LayerActivnessData; + +static void +_track_disposed_cb (LayerActivnessData * data, GObject * disposed_track) +{ + data->track_disposed = TRUE; + g_hash_table_remove (data->layer->priv->tracks_activness, data->track); +} + +static void +layer_activness_data_free (LayerActivnessData * data) +{ + if (!data->track_disposed) + g_object_weak_unref ((GObject *) data->track, + (GWeakNotify) _track_disposed_cb, data); + g_free (data); +} + +static LayerActivnessData * +layer_activness_data_new (GESTrack * track, GESLayer * layer, gboolean active) +{ + LayerActivnessData *data = g_new0 (LayerActivnessData, 1); + + data->layer = layer; + data->track = track; + data->active = active; + g_object_weak_ref (G_OBJECT (track), (GWeakNotify) _track_disposed_cb, data); + + return data; +} + +enum +{ + PROP_0, + PROP_PRIORITY, + PROP_AUTO_TRANSITION, + PROP_LAST +}; + +enum +{ + OBJECT_ADDED, + OBJECT_REMOVED, + ACTIVE_CHANGED, + LAST_SIGNAL +}; + +static guint ges_layer_signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE_WITH_CODE (GESLayer, ges_layer, + G_TYPE_INITIALLY_UNOWNED, G_ADD_PRIVATE (GESLayer) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, NULL) + G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, + ges_meta_container_interface_init)); + +/* GObject standard vmethods */ +static void +ges_layer_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESLayer *layer = GES_LAYER (object); + + switch (property_id) { + case PROP_PRIORITY: + g_value_set_uint (value, layer->priv->priority); + break; + case PROP_AUTO_TRANSITION: + g_value_set_boolean (value, layer->priv->auto_transition); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_layer_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESLayer *layer = GES_LAYER (object); + + switch (property_id) { + case PROP_PRIORITY: + GST_FIXME ("Deprecated, use ges_timeline_move_layer instead"); + layer_set_priority (layer, g_value_get_uint (value), FALSE); + break; + case PROP_AUTO_TRANSITION: + ges_layer_set_auto_transition (layer, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_layer_dispose (GObject * object) +{ + GESLayer *layer = GES_LAYER (object); + GESLayerPrivate *priv = layer->priv; + + GST_DEBUG ("Disposing layer"); + + while (priv->clips_start) + ges_layer_remove_clip (layer, (GESClip *) priv->clips_start->data); + + g_clear_pointer (&layer->priv->tracks_activness, g_hash_table_unref); + + G_OBJECT_CLASS (ges_layer_parent_class)->dispose (object); +} + +static gboolean +_register_metas (GESLayer * layer) +{ + ges_meta_container_register_meta_float (GES_META_CONTAINER (layer), + GES_META_READ_WRITE, GES_META_VOLUME, 1.0); + + return TRUE; +} + +static void +ges_meta_container_interface_init (GESMetaContainerInterface * iface) +{ + +} + +static void +ges_layer_class_init (GESLayerClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = ges_layer_get_property; + object_class->set_property = ges_layer_set_property; + object_class->dispose = ges_layer_dispose; + + /** + * GESLayer:priority: + * + * The priority of the layer in the #GESTimeline. 0 is the highest + * priority. Conceptually, a timeline is a stack of layers, + * and the priority of the layer represents its position in the stack. Two + * layers should not have the same priority within a given GESTimeline. + * + * Note that the timeline needs to be committed (with #ges_timeline_commit) + * for the change to be taken into account. + * + * Deprecated:1.16.0: use #ges_timeline_move_layer instead. This deprecation means + * that you will not need to handle layer priorities at all yourself, GES + * will make sure there is never 'gaps' between layer priorities. + */ + g_object_class_install_property (object_class, PROP_PRIORITY, + g_param_spec_uint ("priority", "Priority", + "The priority of the layer", 0, G_MAXUINT, 0, G_PARAM_READWRITE)); + + /** + * GESLayer:auto-transition: + * + * Whether to automatically create a #GESTransitionClip whenever two + * #GESSource-s that both belong to a #GESClip in the layer overlap. + * See #GESTimeline for what counts as an overlap. + * + * When a layer is added to a #GESTimeline, if this property is left as + * %FALSE, but the timeline's #GESTimeline:auto-transition is %TRUE, it + * will be set to %TRUE as well. + */ + g_object_class_install_property (object_class, PROP_AUTO_TRANSITION, + g_param_spec_boolean ("auto-transition", "Auto-Transition", + "whether the transitions are added", FALSE, G_PARAM_READWRITE)); + + /** + * GESLayer::clip-added: + * @layer: The #GESLayer + * @clip: The clip that was added + * + * Will be emitted after the clip is added to the layer. + */ + ges_layer_signals[OBJECT_ADDED] = + g_signal_new ("clip-added", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESLayerClass, object_added), + NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_CLIP); + + /** + * GESLayer::clip-removed: + * @layer: The #GESLayer + * @clip: The clip that was removed + * + * Will be emitted after the clip is removed from the layer. + */ + ges_layer_signals[OBJECT_REMOVED] = + g_signal_new ("clip-removed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESLayerClass, + object_removed), NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_CLIP); + + /** + * GESLayer::active-changed: + * @layer: The #GESLayer + * @active: Whether @layer has been made active or de-active in the @tracks + * @tracks: (element-type GESTrack) (transfer none): A list of #GESTrack + * which have been activated or deactivated + * + * Will be emitted whenever the layer is activated or deactivated + * for some #GESTrack. See ges_layer_set_active_for_tracks(). + * + * Since: 1.18 + */ + ges_layer_signals[ACTIVE_CHANGED] = + g_signal_new ("active-changed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, + G_TYPE_BOOLEAN, G_TYPE_PTR_ARRAY); +} + +static void +ges_layer_init (GESLayer * self) +{ + self->priv = ges_layer_get_instance_private (self); + + self->priv->priority = 0; + self->priv->auto_transition = FALSE; + self->min_nle_priority = MIN_NLE_PRIO; + self->max_nle_priority = LAYER_HEIGHT + MIN_NLE_PRIO; + + self->priv->tracks_activness = + g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) layer_activness_data_free); + + _register_metas (self); +} + +static gint +ges_layer_resync_priorities_by_type (GESLayer * layer, + gint starting_priority, GType type) +{ + GstClockTime next_reset = 0; + gint priority = starting_priority, max_priority = priority; + GList *tmp; + GESTimelineElement *element; + + layer->priv->clips_start = + g_list_sort (layer->priv->clips_start, + (GCompareFunc) element_start_compare); + for (tmp = layer->priv->clips_start; tmp; tmp = tmp->next) { + + element = GES_TIMELINE_ELEMENT (tmp->data); + + if (GES_IS_TRANSITION_CLIP (element)) { + /* Blindly set transitions priorities to 0 */ + _set_priority0 (element, 0); + continue; + } else if (!g_type_is_a (G_OBJECT_TYPE (element), type)) + continue; + + if (element->start > next_reset) { + priority = starting_priority; + next_reset = 0; + } + + if (element->start + element->duration > next_reset) + next_reset = element->start + element->duration; + + _set_priority0 (element, priority); + priority = priority + GES_CONTAINER_HEIGHT (element); + + if (priority > max_priority) + max_priority = priority; + } + + return max_priority; +} + +/** + * ges_layer_resync_priorities: + * @layer: The #GESLayer + * + * Resyncs the priorities of the clips controlled by @layer. + */ +gboolean +ges_layer_resync_priorities (GESLayer * layer) +{ + gint min_source_prios; + + g_return_val_if_fail (GES_IS_LAYER (layer), FALSE); + + GST_INFO_OBJECT (layer, "Resync priorities (prio: %d)", + layer->priv->priority); + + min_source_prios = ges_layer_resync_priorities_by_type (layer, 1, + GES_TYPE_OPERATION_CLIP); + + ges_layer_resync_priorities_by_type (layer, min_source_prios, + GES_TYPE_SOURCE_CLIP); + + return TRUE; +} + +void +layer_set_priority (GESLayer * layer, guint priority, gboolean emit) +{ + GST_DEBUG ("layer:%p, priority:%d", layer, priority); + + if (priority != layer->priv->priority) { + layer->priv->priority = priority; + layer->min_nle_priority = (priority * LAYER_HEIGHT) + MIN_NLE_PRIO; + layer->max_nle_priority = ((priority + 1) * LAYER_HEIGHT) + MIN_NLE_PRIO; + + ges_layer_resync_priorities (layer); + } + + if (emit) + g_object_notify (G_OBJECT (layer), "priority"); +} + +static void +new_asset_cb (GESAsset * source, GAsyncResult * res, NewAssetUData * udata) +{ + GError *error = NULL; + + GESAsset *asset = ges_asset_request_finish (res, &error); + + GST_DEBUG_OBJECT (udata->layer, "%" GST_PTR_FORMAT " Asset loaded, " + "setting its asset", udata->clip); + + if (error) { + GESProject *project = udata->layer->timeline ? + GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE + (udata->layer->timeline))) : NULL; + if (project) { + gchar *possible_id; + + possible_id = ges_project_try_updating_id (project, source, error); + if (possible_id) { + ges_asset_request_async (ges_asset_get_extractable_type (source), + possible_id, NULL, (GAsyncReadyCallback) new_asset_cb, udata); + g_free (possible_id); + return; + } + } + + GST_ERROR ("Asset could not be created for uri %s, error: %s", + ges_asset_get_id (asset), error->message); + } else { + GESProject *project = udata->layer->timeline ? + GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE + (udata->layer->timeline))) : NULL; + ges_extractable_set_asset (GES_EXTRACTABLE (udata->clip), asset); + + ges_project_add_asset (project, asset); + + /* clip was already ref-sinked when creating udata, + * gst_layer_add_clip() creates a new ref as such and + * below we unref the ref from udata */ + ges_layer_add_clip (udata->layer, udata->clip); + } + + gst_object_unref (asset); + gst_object_unref (udata->clip); + g_slice_free (NewAssetUData, udata); +} + +/** + * ges_layer_get_duration: + * @layer: The layer to get the duration from + * + * Retrieves the duration of the layer, which is the difference + * between the start of the layer (always time 0) and the end (which will + * be the end time of the final clip). + * + * Returns: The duration of @layer. + */ +GstClockTime +ges_layer_get_duration (GESLayer * layer) +{ + GList *tmp; + GstClockTime duration = 0; + + g_return_val_if_fail (GES_IS_LAYER (layer), 0); + + for (tmp = layer->priv->clips_start; tmp; tmp = tmp->next) { + duration = MAX (duration, _END (tmp->data)); + } + + return duration; +} + +static gboolean +ges_layer_remove_clip_internal (GESLayer * layer, GESClip * clip, + gboolean emit_removed) +{ + GESLayer *current_layer; + GList *tmp; + GESTimeline *timeline = layer->timeline; + + GST_DEBUG ("layer:%p, clip:%p", layer, clip); + + current_layer = ges_clip_get_layer (clip); + if (G_UNLIKELY (current_layer != layer)) { + GST_WARNING ("Clip doesn't belong to this layer"); + + if (current_layer != NULL) + gst_object_unref (current_layer); + + return FALSE; + } + gst_object_unref (current_layer); + + /* Remove it from our list of controlled objects */ + layer->priv->clips_start = g_list_remove (layer->priv->clips_start, clip); + + if (emit_removed) { + /* emit 'clip-removed' */ + g_signal_emit (layer, ges_layer_signals[OBJECT_REMOVED], 0, clip); + } + + /* inform the clip it's no longer in a layer */ + ges_clip_set_layer (clip, NULL); + /* so neither in a timeline */ + if (timeline) + ges_timeline_remove_clip (timeline, clip); + + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) + ges_track_element_set_layer_active (tmp->data, TRUE); + + /* Remove our reference to the clip */ + gst_object_unref (clip); + + return TRUE; +} + +/* Public methods */ +/** + * ges_layer_remove_clip: + * @layer: The #GESLayer + * @clip: The clip to remove + * + * Removes the given clip from the layer. + * + * Returns: %TRUE if @clip was removed from @layer, or %FALSE if the + * operation failed. + */ +gboolean +ges_layer_remove_clip (GESLayer * layer, GESClip * clip) +{ + g_return_val_if_fail (GES_IS_LAYER (layer), FALSE); + g_return_val_if_fail (GES_IS_CLIP (clip), FALSE); + + return ges_layer_remove_clip_internal (layer, clip, TRUE); +} + +/** + * ges_layer_set_priority: + * @layer: The #GESLayer + * @priority: The priority to set + * + * Sets the layer to the given priority. See #GESLayer:priority. + * + * Deprecated:1.16.0: use #ges_timeline_move_layer instead. This deprecation means + * that you will not need to handle layer priorities at all yourself, GES + * will make sure there is never 'gaps' between layer priorities. + */ +void +ges_layer_set_priority (GESLayer * layer, guint priority) +{ + g_return_if_fail (GES_IS_LAYER (layer)); + + GST_FIXME ("Deprecated, use ges_timeline_move_layer instead"); + + layer_set_priority (layer, priority, TRUE); +} + +/** + * ges_layer_get_auto_transition: + * @layer: The #GESLayer + * + * Gets the #GESLayer:auto-transition of the layer. + * + * Returns: %TRUE if transitions are automatically added to @layer. + */ +gboolean +ges_layer_get_auto_transition (GESLayer * layer) +{ + g_return_val_if_fail (GES_IS_LAYER (layer), 0); + + return layer->priv->auto_transition; +} + +/** + * ges_layer_set_auto_transition: + * @layer: The #GESLayer + * @auto_transition: Whether transitions should be automatically added to + * the layer + * + * Sets #GESLayer:auto-transition for the layer. Use + * ges_timeline_set_auto_transition() if you want all layers within a + * #GESTimeline to have #GESLayer:auto-transition set to %TRUE. Use this + * method if you want different values for different layers (and make sure + * to keep #GESTimeline:auto-transition as %FALSE for the corresponding + * timeline). + */ +void +ges_layer_set_auto_transition (GESLayer * layer, gboolean auto_transition) +{ + + g_return_if_fail (GES_IS_LAYER (layer)); + + if (layer->priv->auto_transition == auto_transition) + return; + + layer->priv->auto_transition = auto_transition; + g_object_notify (G_OBJECT (layer), "auto-transition"); +} + +/** + * ges_layer_get_priority: + * @layer: The #GESLayer + * + * Get the priority of the layer. When inside a timeline, this is its + * index in the timeline. See ges_timeline_move_layer(). + * + * Returns: The priority of @layer within its timeline. + */ +guint +ges_layer_get_priority (GESLayer * layer) +{ + g_return_val_if_fail (GES_IS_LAYER (layer), 0); + + return layer->priv->priority; +} + +/** + * ges_layer_get_clips: + * @layer: The #GESLayer + * + * Get the #GESClip-s contained in this layer. + * + * Returns: (transfer full) (element-type GESClip): A list of clips in + * @layer. + */ + +GList * +ges_layer_get_clips (GESLayer * layer) +{ + GESLayerClass *klass; + + g_return_val_if_fail (GES_IS_LAYER (layer), NULL); + + klass = GES_LAYER_GET_CLASS (layer); + + if (klass->get_objects) { + return klass->get_objects (layer); + } + + return g_list_sort (g_list_copy_deep (layer->priv->clips_start, + (GCopyFunc) gst_object_ref, NULL), + (GCompareFunc) element_start_compare); +} + +/** + * ges_layer_is_empty: + * @layer: The #GESLayer to check + * + * Convenience method to check if the layer is empty (doesn't contain + * any #GESClip), or not. + * + * Returns: %TRUE if @layer is empty, %FALSE if it contains at least + * one clip. + */ +gboolean +ges_layer_is_empty (GESLayer * layer) +{ + g_return_val_if_fail (GES_IS_LAYER (layer), FALSE); + + return (layer->priv->clips_start == NULL); +} + +/** + * ges_layer_add_clip_full: + * @layer: The #GESLayer + * @clip: (transfer floating): The clip to add + * @error: (nullable): Return location for an error + * + * Adds the given clip to the layer. If the method succeeds, the layer + * will take ownership of the clip. + * + * This method will fail and return %FALSE if @clip already resides in + * some layer. It can also fail if the additional clip breaks some + * compositional rules (see #GESTimelineElement). + * + * Returns: %TRUE if @clip was properly added to @layer, or %FALSE + * if @layer refused to add @clip. + * Since: 1.18 + */ +gboolean +ges_layer_add_clip_full (GESLayer * layer, GESClip * clip, GError ** error) +{ + GList *tmp, *prev_children, *new_children; + GESAsset *asset; + GESLayerPrivate *priv; + GESLayer *current_layer; + GESTimeline *timeline; + GESContainer *container; + GError *timeline_error = NULL; + + g_return_val_if_fail (GES_IS_LAYER (layer), FALSE); + g_return_val_if_fail (GES_IS_CLIP (clip), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip); + container = GES_CONTAINER (clip); + + GST_DEBUG_OBJECT (layer, "adding clip:%p", clip); + gst_object_ref_sink (clip); + + priv = layer->priv; + current_layer = ges_clip_get_layer (clip); + if (G_UNLIKELY (current_layer)) { + GST_WARNING_OBJECT (layer, "Clip %" GES_FORMAT " already belongs to " + "another layer", GES_ARGS (clip)); + gst_object_unref (clip); + gst_object_unref (current_layer); + return FALSE; + } + + if (timeline && timeline != layer->timeline) { + /* if a clip is not in any layer, its timeline should not be set */ + GST_ERROR_OBJECT (layer, "Clip %" GES_FORMAT " timeline %" + GST_PTR_FORMAT " does not match that of the layer %" + GST_PTR_FORMAT, GES_ARGS (clip), timeline, layer->timeline); + gst_object_unref (clip); + return FALSE; + } + + timeline = layer->timeline; + + asset = ges_extractable_get_asset (GES_EXTRACTABLE (clip)); + if (asset == NULL) { + gchar *id; + NewAssetUData *mudata = g_slice_new (NewAssetUData); + + mudata->clip = clip; + mudata->layer = layer; + + GST_DEBUG_OBJECT (layer, "%" GST_PTR_FORMAT " as no reference to any " + "assets creating a asset... trying sync", clip); + + id = ges_extractable_get_id (GES_EXTRACTABLE (clip)); + asset = ges_asset_request (G_OBJECT_TYPE (clip), id, NULL); + if (asset == NULL) { + GESProject *project = layer->timeline ? + GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE + (layer->timeline))) : NULL; + + ges_asset_request_async (G_OBJECT_TYPE (clip), + id, NULL, (GAsyncReadyCallback) new_asset_cb, mudata); + + if (project) + ges_project_add_loading_asset (project, G_OBJECT_TYPE (clip), id); + g_free (id); + + GST_LOG_OBJECT (layer, "Object added async"); + return TRUE; + } + g_free (id); + + ges_extractable_set_asset (GES_EXTRACTABLE (clip), asset); + + g_slice_free (NewAssetUData, mudata); + gst_clear_object (&asset); + } + + /* Take a reference to the clip and store it stored by start/priority */ + priv->clips_start = g_list_insert_sorted (priv->clips_start, clip, + (GCompareFunc) element_start_compare); + + /* Inform the clip it's now in this layer */ + ges_clip_set_layer (clip, layer); + + GST_DEBUG ("current clip priority : %d, Height: %d", _PRIORITY (clip), + LAYER_HEIGHT); + + /* Set the priority. */ + if (_PRIORITY (clip) > LAYER_HEIGHT) { + GST_WARNING_OBJECT (layer, + "%p is out of the layer space, setting its priority to " + "%d, setting it to the maximum priority of the layer: %d", clip, + _PRIORITY (clip), LAYER_HEIGHT - 1); + _set_priority0 (GES_TIMELINE_ELEMENT (clip), LAYER_HEIGHT - 1); + } + + ges_layer_resync_priorities (layer); + + /* FIXME: ideally we would only emit if we are going to return TRUE. + * However, for backward-compatibility, we ensure the "clip-added" + * signal is released before the clip's "child-added" signal, which is + * invoked by ges_timeline_add_clip */ + g_signal_emit (layer, ges_layer_signals[OBJECT_ADDED], 0, clip); + + prev_children = ges_container_get_children (container, FALSE); + + if (timeline && !ges_timeline_add_clip (timeline, clip, &timeline_error)) { + GST_INFO_OBJECT (layer, "Could not add the clip %" GES_FORMAT + " to the timeline %" GST_PTR_FORMAT, GES_ARGS (clip), timeline); + + if (timeline_error) { + if (error) { + *error = timeline_error; + } else { + GST_WARNING_OBJECT (timeline, "Adding the clip %" GES_FORMAT + " to the timeline failed: %s", GES_ARGS (clip), + timeline_error->message); + g_error_free (timeline_error); + } + } + + /* remove any track elements that were newly created */ + new_children = ges_container_get_children (container, FALSE); + for (tmp = new_children; tmp; tmp = tmp->next) { + if (!g_list_find (prev_children, tmp->data)) + ges_container_remove (container, tmp->data); + } + g_list_free_full (prev_children, gst_object_unref); + g_list_free_full (new_children, gst_object_unref); + + /* FIXME: change emit signal to FALSE once we are able to delay the + * "clip-added" signal until after ges_timeline_add_clip */ + ges_layer_remove_clip_internal (layer, clip, TRUE); + return FALSE; + } + + g_list_free_full (prev_children, gst_object_unref); + + for (tmp = container->children; tmp; tmp = tmp->next) { + GESTrack *track = ges_track_element_get_track (tmp->data); + + if (track) + ges_track_element_set_layer_active (tmp->data, + ges_layer_get_active_for_track (layer, track)); + } + + return TRUE; +} + +/** + * ges_layer_add_clip: + * @layer: The #GESLayer + * @clip: (transfer floating): The clip to add + * + * See ges_layer_add_clip_full(), which also gives an error. + * + * Returns: %TRUE if @clip was properly added to @layer, or %FALSE + * if @layer refused to add @clip. + */ +gboolean +ges_layer_add_clip (GESLayer * layer, GESClip * clip) +{ + return ges_layer_add_clip_full (layer, clip, NULL); +} + +/** + * ges_layer_add_asset_full: + * @layer: The #GESLayer + * @asset: The asset to extract the new clip from + * @start: The #GESTimelineElement:start value to set on the new clip + * If `start == #GST_CLOCK_TIME_NONE`, it will be added to the end + * of @layer, i.e. it will be set to @layer's duration + * @inpoint: The #GESTimelineElement:in-point value to set on the new + * clip + * @duration: The #GESTimelineElement:duration value to set on the new + * clip + * @track_types: The #GESClip:supported-formats to set on the the new + * clip, or #GES_TRACK_TYPE_UNKNOWN to use the default + * @error: (nullable): Return location for an error + * + * Extracts a new clip from an asset and adds it to the layer with + * the given properties. + * + * Returns: (transfer none): The newly created clip. + * Since: 1.18 + */ +GESClip * +ges_layer_add_asset_full (GESLayer * layer, + GESAsset * asset, GstClockTime start, GstClockTime inpoint, + GstClockTime duration, GESTrackType track_types, GError ** error) +{ + GESClip *clip; + + g_return_val_if_fail (GES_IS_LAYER (layer), NULL); + g_return_val_if_fail (GES_IS_ASSET (asset), NULL); + g_return_val_if_fail (!error || !*error, NULL); + g_return_val_if_fail (g_type_is_a (ges_asset_get_extractable_type + (asset), GES_TYPE_CLIP), NULL); + + GST_DEBUG_OBJECT (layer, "Adding asset %s with: start: %" GST_TIME_FORMAT + " inpoint: %" GST_TIME_FORMAT " duration: %" GST_TIME_FORMAT + " track types: %d (%s)", ges_asset_get_id (asset), GST_TIME_ARGS (start), + GST_TIME_ARGS (inpoint), GST_TIME_ARGS (duration), track_types, + ges_track_type_name (track_types)); + + clip = GES_CLIP (ges_asset_extract (asset, NULL)); + + if (!GST_CLOCK_TIME_IS_VALID (start)) { + start = ges_layer_get_duration (layer); + + GST_DEBUG_OBJECT (layer, + "No start specified, setting it to %" GST_TIME_FORMAT, + GST_TIME_ARGS (start)); + } + + _set_start0 (GES_TIMELINE_ELEMENT (clip), start); + _set_inpoint0 (GES_TIMELINE_ELEMENT (clip), inpoint); + if (track_types != GES_TRACK_TYPE_UNKNOWN) + ges_clip_set_supported_formats (clip, track_types); + + if (GST_CLOCK_TIME_IS_VALID (duration)) { + _set_duration0 (GES_TIMELINE_ELEMENT (clip), duration); + } + + if (!ges_layer_add_clip_full (layer, clip, error)) { + return NULL; + } + + return clip; +} + +/** + * ges_layer_add_asset: + * @layer: The #GESLayer + * @asset: The asset to extract the new clip from + * @start: The #GESTimelineElement:start value to set on the new clip + * If `start == #GST_CLOCK_TIME_NONE`, it will be added to the end + * of @layer, i.e. it will be set to @layer's duration + * @inpoint: The #GESTimelineElement:in-point value to set on the new + * clip + * @duration: The #GESTimelineElement:duration value to set on the new + * clip + * @track_types: The #GESClip:supported-formats to set on the the new + * clip, or #GES_TRACK_TYPE_UNKNOWN to use the default + * + * See ges_layer_add_asset_full(), which also gives an error. + * + * Returns: (transfer none): The newly created clip. + */ +GESClip * +ges_layer_add_asset (GESLayer * layer, + GESAsset * asset, GstClockTime start, GstClockTime inpoint, + GstClockTime duration, GESTrackType track_types) +{ + return ges_layer_add_asset_full (layer, asset, start, inpoint, duration, + track_types, NULL); +} + +/** + * ges_layer_new: + * + * Creates a new layer. + * + * Returns: (transfer floating): A new layer. + */ +GESLayer * +ges_layer_new (void) +{ + return g_object_new (GES_TYPE_LAYER, NULL); +} + +/** + * ges_layer_get_timeline: + * @layer: The #GESLayer + * + * Gets the timeline that the layer is a part of. + * + * Returns: (transfer none) (nullable): The timeline that @layer + * is currently part of, or %NULL if it is not associated with any + * timeline. + */ +GESTimeline * +ges_layer_get_timeline (GESLayer * layer) +{ + g_return_val_if_fail (GES_IS_LAYER (layer), NULL); + + return layer->timeline; +} + +void +ges_layer_set_timeline (GESLayer * layer, GESTimeline * timeline) +{ + GList *tmp; + + g_return_if_fail (GES_IS_LAYER (layer)); + + GST_DEBUG ("layer:%p, timeline:%p", layer, timeline); + + for (tmp = layer->priv->clips_start; tmp; tmp = tmp->next) { + ges_timeline_element_set_timeline (tmp->data, timeline); + } + + layer->timeline = timeline; +} + +/** + * ges_layer_get_clips_in_interval: + * @layer: The #GESLayer + * @start: Start of the interval + * @end: End of the interval + * + * Gets the clips within the layer that appear between @start and @end. + * + * Returns: (transfer full) (element-type GESClip): A list of #GESClip-s + * that intersect the interval `[start, end)` in @layer. + */ +GList * +ges_layer_get_clips_in_interval (GESLayer * layer, GstClockTime start, + GstClockTime end) +{ + GList *tmp; + GList *intersecting_clips = NULL; + GstClockTime clip_start, clip_end; + gboolean clip_intersects; + + g_return_val_if_fail (GES_IS_LAYER (layer), NULL); + + layer->priv->clips_start = + g_list_sort (layer->priv->clips_start, + (GCompareFunc) element_start_compare); + for (tmp = layer->priv->clips_start; tmp; tmp = tmp->next) { + clip_intersects = FALSE; + clip_start = ges_timeline_element_get_start (tmp->data); + clip_end = clip_start + ges_timeline_element_get_duration (tmp->data); + if (start <= clip_start && clip_start < end) + clip_intersects = TRUE; + else if (start < clip_end && clip_end <= end) + clip_intersects = TRUE; + else if (clip_start < start && clip_end > end) + clip_intersects = TRUE; + + if (clip_intersects) + intersecting_clips = + g_list_insert_sorted (intersecting_clips, + gst_object_ref (tmp->data), (GCompareFunc) element_start_compare); + } + return intersecting_clips; +} + +/** + * ges_layer_get_active_for_track: + * @layer: The #GESLayer + * @track: The #GESTrack to check if @layer is currently active for + * + * Gets whether the layer is active for the given track. See + * ges_layer_set_active_for_tracks(). + * + * Returns: %TRUE if @layer is active for @track, or %FALSE otherwise. + * + * Since: 1.18 + */ +gboolean +ges_layer_get_active_for_track (GESLayer * layer, GESTrack * track) +{ + LayerActivnessData *d; + + g_return_val_if_fail (GES_IS_LAYER (layer), FALSE); + g_return_val_if_fail (GES_IS_TRACK (track), FALSE); + g_return_val_if_fail (layer->timeline == ges_track_get_timeline (track), + FALSE); + + d = g_hash_table_lookup (layer->priv->tracks_activness, track); + + return d ? d->active : TRUE; +} + +/** + * ges_layer_set_active_for_tracks: + * @layer: The #GESLayer + * @active: Whether elements in @tracks should be active or not + * @tracks: (transfer none) (element-type GESTrack) (allow-none): The list of + * tracks @layer should be (de-)active in, or %NULL to include all the tracks + * in the @layer's timeline + * + * Activate or deactivate track elements in @tracks (or in all tracks if @tracks + * is %NULL). + * + * When a layer is deactivated for a track, all the #GESTrackElement-s in + * the track that belong to a #GESClip in the layer will no longer be + * active in the track, regardless of their individual + * #GESTrackElement:active value. + * + * Note that by default a layer will be active for all of its + * timeline's tracks. + * + * Returns: %TRUE if the operation worked %FALSE otherwise. + * + * Since: 1.18 + */ +gboolean +ges_layer_set_active_for_tracks (GESLayer * layer, gboolean active, + GList * tracks) +{ + GList *tmp, *owned_tracks = NULL; + GPtrArray *changed_tracks = NULL; + + g_return_val_if_fail (GES_IS_LAYER (layer), FALSE); + + if (!tracks && layer->timeline) + owned_tracks = tracks = ges_timeline_get_tracks (layer->timeline); + + for (tmp = tracks; tmp; tmp = tmp->next) { + GESTrack *track = tmp->data; + + /* Handle setting timeline later */ + g_return_val_if_fail (layer->timeline == ges_track_get_timeline (track), + FALSE); + + if (ges_layer_get_active_for_track (layer, track) != active) { + if (changed_tracks == NULL) + changed_tracks = g_ptr_array_new (); + g_ptr_array_add (changed_tracks, track); + } + g_hash_table_insert (layer->priv->tracks_activness, track, + layer_activness_data_new (track, layer, active)); + } + + if (changed_tracks) { + g_signal_emit (layer, ges_layer_signals[ACTIVE_CHANGED], 0, active, + changed_tracks); + g_ptr_array_unref (changed_tracks); + } + g_list_free_full (owned_tracks, gst_object_unref); + + return TRUE; +} diff --git a/ges/ges-layer.h b/ges/ges-layer.h new file mode 100644 index 0000000000..c9d2d06e1e --- /dev/null +++ b/ges/ges-layer.h @@ -0,0 +1,146 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> + +G_BEGIN_DECLS + +#define GES_TYPE_LAYER ges_layer_get_type() +GES_DECLARE_TYPE(Layer, layer, LAYER); + +/** + * GESLayer: + * @timeline: the #GESTimeline where this layer is being used. + */ +struct _GESLayer { + GInitiallyUnowned parent; + + /*< public >*/ + + GESTimeline *timeline; + + /*< protected >*/ + guint32 min_nle_priority, max_nle_priority; + + GESLayerPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESLayerClass: + * @get_objects: method to get the objects contained in the layer + * + * Subclasses can override the @get_objects if they can provide a more + * efficient way of providing the list of contained #GESClip-s. + */ +struct _GESLayerClass { + /*< private >*/ + GInitiallyUnownedClass parent_class; + + /*< public >*/ + /* virtual methods for subclasses */ + GList *(*get_objects) (GESLayer * layer); + + /*< private >*/ + /* Signals */ + void (*object_added) (GESLayer * layer, GESClip * object); + void (*object_removed) (GESLayer * layer, GESClip * object); + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API +GESLayer* ges_layer_new (void); + +GES_API +void ges_layer_set_timeline (GESLayer * layer, + GESTimeline * timeline); +GES_API +GESTimeline * ges_layer_get_timeline (GESLayer * layer); + +GES_API +gboolean ges_layer_add_clip (GESLayer * layer, + GESClip * clip); +GES_API +gboolean ges_layer_add_clip_full (GESLayer * layer, + GESClip * clip, + GError ** error); +GES_API +GESClip * ges_layer_add_asset (GESLayer *layer, + GESAsset *asset, + GstClockTime start, + GstClockTime inpoint, + GstClockTime duration, + GESTrackType track_types); +GES_API +GESClip * ges_layer_add_asset_full (GESLayer *layer, + GESAsset *asset, + GstClockTime start, + GstClockTime inpoint, + GstClockTime duration, + GESTrackType track_types, + GError ** error); + +GES_API +gboolean ges_layer_remove_clip (GESLayer * layer, + GESClip * clip); + +GES_DEPRECATED_FOR(ges_timeline_move_layer) +void ges_layer_set_priority (GESLayer * layer, + guint priority); + +GES_API +gboolean ges_layer_is_empty (GESLayer * layer); + +GES_API +GList* ges_layer_get_clips_in_interval (GESLayer * layer, + GstClockTime start, + GstClockTime end); + +GES_API +guint ges_layer_get_priority (GESLayer * layer); + +GES_API +gboolean ges_layer_get_auto_transition (GESLayer * layer); + +GES_API +void ges_layer_set_auto_transition (GESLayer * layer, + gboolean auto_transition); + +GES_API +GList* ges_layer_get_clips (GESLayer * layer); +GES_API +GstClockTime ges_layer_get_duration (GESLayer *layer); +GES_API +gboolean ges_layer_set_active_for_tracks (GESLayer *layer, + gboolean active, + GList *tracks); + +GES_API +gboolean ges_layer_get_active_for_track (GESLayer *layer, + GESTrack *track); + +G_END_DECLS diff --git a/ges/ges-marker-list.c b/ges/ges-marker-list.c new file mode 100644 index 0000000000..30ed78e6b1 --- /dev/null +++ b/ges/ges-marker-list.c @@ -0,0 +1,632 @@ +/* GStreamer Editing Services + + * Copyright (C) <2019> Mathieu Duponchelle <mathieu@centricular.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION: gesmarkerlist + * @title: GESMarkerList + * @short_description: implements a list of markers with metadata asociated to time positions + * @see_also: #GESMarker + * + * A #GESMarker can be colored by setting the #GES_META_MARKER_COLOR meta. + * + * Since: 1.18 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-marker-list.h" +#include "ges.h" +#include "ges-internal.h" +#include "ges-meta-container.h" + +static void ges_meta_container_interface_init (GESMetaContainerInterface * + iface); + +struct _GESMarker +{ + GObject parent; + GstClockTime position; +}; + +G_DEFINE_TYPE_WITH_CODE (GESMarker, ges_marker, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, + ges_meta_container_interface_init)); + +enum +{ + PROP_MARKER_0, + PROP_MARKER_POSITION, + PROP_MARKER_LAST +}; + +static GParamSpec *marker_properties[PROP_MARKER_LAST]; + +/* GObject Standard vmethods*/ +static void +ges_marker_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESMarker *marker = GES_MARKER (object); + + switch (property_id) { + case PROP_MARKER_POSITION: + g_value_set_uint64 (value, marker->position); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +ges_marker_init (GESMarker * self) +{ + ges_meta_container_register_static_meta (GES_META_CONTAINER (self), + GES_META_READ_WRITE, GES_META_MARKER_COLOR, G_TYPE_UINT); +} + +static void +ges_marker_class_init (GESMarkerClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = ges_marker_get_property; + /** + * GESMarker:position: + * + * Current position (in nanoseconds) of the #GESMarker + * + * Since: 1.18 + */ + marker_properties[PROP_MARKER_POSITION] = + g_param_spec_uint64 ("position", "Position", + "The position of the marker", 0, G_MAXUINT64, + GST_CLOCK_TIME_NONE, G_PARAM_READABLE); + g_object_class_install_property (object_class, PROP_MARKER_POSITION, + marker_properties[PROP_MARKER_POSITION]); + +} + +static void +ges_meta_container_interface_init (GESMetaContainerInterface * iface) +{ +} + +/* GESMarkerList */ + +struct _GESMarkerList +{ + GObject parent; + + GSequence *markers; + GHashTable *markers_iters; + GESMarkerFlags flags; +}; + +enum +{ + PROP_MARKER_LIST_0, + PROP_MARKER_LIST_FLAGS, + PROP_MARKER_LIST_LAST +}; + +static GParamSpec *list_properties[PROP_MARKER_LIST_LAST]; + +enum +{ + MARKER_ADDED, + MARKER_REMOVED, + MARKER_MOVED, + LAST_SIGNAL +}; + +static guint ges_marker_list_signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (GESMarkerList, ges_marker_list, G_TYPE_OBJECT); + +static void +ges_marker_list_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESMarkerList *self = GES_MARKER_LIST (object); + + switch (property_id) { + case PROP_MARKER_LIST_FLAGS: + g_value_set_flags (value, self->flags); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +ges_marker_list_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec) +{ + GESMarkerList *self = GES_MARKER_LIST (object); + + switch (property_id) { + case PROP_MARKER_LIST_FLAGS: + self->flags = g_value_get_flags (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +remove_marker (gpointer data) +{ + GESMarker *marker = (GESMarker *) data; + + g_object_unref (marker); +} + +static void +ges_marker_list_init (GESMarkerList * self) +{ + self->markers = g_sequence_new (remove_marker); + self->markers_iters = g_hash_table_new (g_direct_hash, g_direct_equal); +} + +static void +ges_marker_list_finalize (GObject * object) +{ + GESMarkerList *self = GES_MARKER_LIST (object); + + g_sequence_free (self->markers); + g_hash_table_unref (self->markers_iters); + + G_OBJECT_CLASS (ges_marker_list_parent_class)->finalize (object); +} + +static void +ges_marker_list_class_init (GESMarkerListClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = ges_marker_list_finalize; + object_class->get_property = ges_marker_list_get_property; + object_class->set_property = ges_marker_list_set_property; + +/** + * GESMarkerList:flags: + * + * Flags indicating how markers on the list should be treated. + * + * Since: 1.20 + */ + list_properties[PROP_MARKER_LIST_FLAGS] = + g_param_spec_flags ("flags", "Flags", + "Functionalities the marker list should be used for", + GES_TYPE_MARKER_FLAGS, GES_MARKER_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + g_object_class_install_property (object_class, PROP_MARKER_LIST_FLAGS, + list_properties[PROP_MARKER_LIST_FLAGS]); + +/** + * GESMarkerList::marker-added: + * @marker-list: the #GESMarkerList + * @position: the position of the added marker + * @marker: the #GESMarker that was added. + * + * Will be emitted after the marker was added to the marker-list. + * Since: 1.18 + */ + ges_marker_list_signals[MARKER_ADDED] = + g_signal_new ("marker-added", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_UINT64, GES_TYPE_MARKER); + +/** + * GESMarkerList::marker-removed: + * @marker_list: the #GESMarkerList + * @marker: the #GESMarker that was removed. + * + * Will be emitted after the marker was removed the marker-list. + * Since: 1.18 + */ + ges_marker_list_signals[MARKER_REMOVED] = + g_signal_new ("marker-removed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_MARKER); + +/** + * GESMarkerList::marker-moved: + * @marker_list: the #GESMarkerList + * @previous_position: the previous position of the marker + * @new_position: the new position of the marker + * @marker: the #GESMarker that was moved. + * + * Will be emitted after the marker was moved to. + * Since: 1.18 + */ + ges_marker_list_signals[MARKER_MOVED] = + g_signal_new ("marker-moved", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 3, G_TYPE_UINT64, G_TYPE_UINT64, GES_TYPE_MARKER); +} + +static gint +cmp_marker (gconstpointer a, gconstpointer b, G_GNUC_UNUSED gpointer data) +{ + GESMarker *marker_a = (GESMarker *) a; + GESMarker *marker_b = (GESMarker *) b; + + if (marker_a->position < marker_b->position) + return -1; + else if (marker_a->position == marker_b->position) + return 0; + else + return 1; +} + +/** + * ges_marker_list_new: + * + * Creates a new #GESMarkerList. + + * Returns: A new #GESMarkerList + * Since: 1.18 + */ +GESMarkerList * +ges_marker_list_new (void) +{ + GESMarkerList *ret; + + ret = (GESMarkerList *) g_object_new (GES_TYPE_MARKER_LIST, NULL); + + return ret; +} + +/** + * ges_marker_list_add: + * @position: The position of the new marker + * + * Returns: (transfer none): The newly-added marker, the list keeps ownership + * of the marker + * Since: 1.18 + */ +GESMarker * +ges_marker_list_add (GESMarkerList * self, GstClockTime position) +{ + GESMarker *ret; + GSequenceIter *iter; + + g_return_val_if_fail (GES_IS_MARKER_LIST (self), NULL); + + ret = (GESMarker *) g_object_new (GES_TYPE_MARKER, NULL); + + ret->position = position; + + iter = g_sequence_insert_sorted (self->markers, ret, cmp_marker, NULL); + + g_hash_table_insert (self->markers_iters, ret, iter); + + g_signal_emit (self, ges_marker_list_signals[MARKER_ADDED], 0, position, ret); + + return ret; +} + +/** + * ges_marker_list_size: + * + * Returns: The number of markers in @list + * Since: 1.18 + */ +guint +ges_marker_list_size (GESMarkerList * self) +{ + g_return_val_if_fail (GES_IS_MARKER_LIST (self), 0); + + return g_sequence_get_length (self->markers); +} + +/** + * ges_marker_list_remove: + * + * Removes @marker from @list, this decreases the refcount of the + * marker by 1. + * + * Returns: %TRUE if the marker could be removed, %FALSE otherwise + * (if the marker was not present in the list for example) + * Since: 1.18 + */ +gboolean +ges_marker_list_remove (GESMarkerList * self, GESMarker * marker) +{ + GSequenceIter *iter; + gboolean ret = FALSE; + + g_return_val_if_fail (GES_IS_MARKER_LIST (self), FALSE); + + if (!g_hash_table_lookup_extended (self->markers_iters, + marker, NULL, (gpointer *) & iter)) + goto done; + g_assert (iter != NULL); + g_hash_table_remove (self->markers_iters, marker); + + g_signal_emit (self, ges_marker_list_signals[MARKER_REMOVED], 0, marker); + + g_sequence_remove (iter); + + ret = TRUE; + +done: + return ret; +} + +/** + * ges_marker_list_get_markers: + * + * Returns: (element-type GESMarker) (transfer full): a #GList + * of the #GESMarker within the GESMarkerList. The user will have + * to unref each #GESMarker and free the #GList. + * + * Since: 1.18 + */ +GList * +ges_marker_list_get_markers (GESMarkerList * self) +{ + GESMarker *marker; + GSequenceIter *iter; + GList *ret; + + g_return_val_if_fail (GES_IS_MARKER_LIST (self), NULL); + ret = NULL; + + for (iter = g_sequence_get_begin_iter (self->markers); + !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) { + marker = GES_MARKER (g_sequence_get (iter)); + + ret = g_list_append (ret, g_object_ref (marker)); + } + + return ret; +} + +/* + * ges_marker_list_get_closest: + * @position: The position which we want to find the closest marker to + * + * Returns: (transfer full): The marker found to be the closest + * to the given position. If two markers are at equal distance from position, + * the "earlier" one will be returned. + */ +GESMarker * +ges_marker_list_get_closest (GESMarkerList * self, GstClockTime position) +{ + GESMarker *new_marker, *ret = NULL; + GstClockTime distance_next, distance_prev; + GSequenceIter *iter; + + if (g_sequence_is_empty (self->markers)) + goto done; + + new_marker = (GESMarker *) g_object_new (GES_TYPE_MARKER, NULL); + new_marker->position = position; + iter = g_sequence_search (self->markers, new_marker, cmp_marker, NULL); + g_object_unref (new_marker); + + if (g_sequence_iter_is_begin (iter)) { + /* We know the sequence isn't empty, this is safe */ + ret = g_sequence_get (iter); + } else if (g_sequence_iter_is_end (iter)) { + /* We know the sequence isn't empty, this is safe */ + ret = g_sequence_get (g_sequence_iter_prev (iter)); + } else { + GESMarker *next_marker, *prev_marker; + + prev_marker = g_sequence_get (g_sequence_iter_prev (iter)); + next_marker = g_sequence_get (iter); + + distance_next = next_marker->position - position; + distance_prev = position - prev_marker->position; + + ret = distance_prev <= distance_next ? prev_marker : next_marker; + } + +done: + if (ret) + return g_object_ref (ret); + return NULL; +} + +/** + * ges_marker_list_move: + * + * Moves a @marker in a @list to a new @position + * + * Returns: %TRUE if the marker could be moved, %FALSE otherwise + * (if the marker was not present in the list for example) + * + * Since: 1.18 + */ +gboolean +ges_marker_list_move (GESMarkerList * self, GESMarker * marker, + GstClockTime position) +{ + GSequenceIter *iter; + gboolean ret = FALSE; + GstClockTime previous_position; + + g_return_val_if_fail (GES_IS_MARKER_LIST (self), FALSE); + + if (!g_hash_table_lookup_extended (self->markers_iters, + marker, NULL, (gpointer *) & iter)) { + GST_WARNING ("GESMarkerList doesn't contain GESMarker"); + goto done; + } + + previous_position = marker->position; + marker->position = position; + + g_signal_emit (self, ges_marker_list_signals[MARKER_MOVED], 0, + previous_position, position, marker); + + g_sequence_sort_changed (iter, cmp_marker, NULL); + + ret = TRUE; + +done: + return ret; +} + +gboolean +ges_marker_list_deserialize (GValue * dest, const gchar * s) +{ + gboolean ret = FALSE; + GstCaps *caps = NULL; + GESMarkerList *list = ges_marker_list_new (); + guint caps_len, i = 0; + gsize string_len; + gchar *escaped, *caps_str; + GstStructure *data_s; + gint flags; + + string_len = strlen (s); + if (G_UNLIKELY (*s != '"' || string_len < 2 || s[string_len - 1] != '"')) { + /* "\"" is not an accepted string, so len must be at least 2 */ + GST_ERROR ("Failed deserializing marker list: expected string to start " + "and end with '\"'"); + goto done; + } + escaped = g_strdup (s + 1); + escaped[string_len - 2] = '\0'; + /* removed trailing '"' */ + caps_str = g_strcompress (escaped); + g_free (escaped); + + caps = gst_caps_from_string (caps_str); + g_free (caps_str); + if (G_UNLIKELY (caps == NULL)) { + GST_ERROR ("Failed deserializing marker list: could not extract caps"); + goto done; + } + + caps_len = gst_caps_get_size (caps); + if (G_UNLIKELY (caps_len == 0)) { + GST_DEBUG ("Got empty caps: %s", s); + goto done; + } + + data_s = gst_caps_get_structure (caps, i); + if (gst_structure_has_name (data_s, "marker-list-flags")) { + if (!gst_structure_get_int (data_s, "flags", &flags)) { + GST_ERROR_OBJECT (dest, + "Failed deserializing marker list: unexpected structure %" + GST_PTR_FORMAT, data_s); + goto done; + } + + list->flags = flags; + i += 1; + } + + if (G_UNLIKELY ((caps_len - i) % 2)) { + GST_ERROR ("Failed deserializing marker list: incomplete marker caps"); + } + + for (; i < caps_len - 1; i += 2) { + const GstStructure *pos_s = gst_caps_get_structure (caps, i); + const GstStructure *meta_s = gst_caps_get_structure (caps, i + 1); + GstClockTime position; + GESMarker *marker; + gchar *metas; + + if (!gst_structure_has_name (pos_s, "marker-times")) { + GST_ERROR_OBJECT (dest, + "Failed deserializing marker list: unexpected structure %" + GST_PTR_FORMAT, pos_s); + goto done; + } + + if (!gst_structure_get_uint64 (pos_s, "position", &position)) { + GST_ERROR_OBJECT (dest, + "Failed deserializing marker list: unexpected structure %" + GST_PTR_FORMAT, pos_s); + goto done; + } + + marker = ges_marker_list_add (list, position); + + metas = gst_structure_to_string (meta_s); + ges_meta_container_add_metas_from_string (GES_META_CONTAINER (marker), + metas); + g_free (metas); + } + + ret = TRUE; + +done: + if (caps) + gst_caps_unref (caps); + + if (!ret) + g_object_unref (list); + else + g_value_take_object (dest, list); + + return ret; +} + +gchar * +ges_marker_list_serialize (const GValue * v) +{ + GESMarkerList *list = GES_MARKER_LIST (g_value_get_object (v)); + GSequenceIter *iter; + GstCaps *caps = gst_caps_new_empty (); + gchar *caps_str, *escaped, *res; + GstStructure *s; + + s = gst_structure_new ("marker-list-flags", "flags", G_TYPE_INT, + list->flags, NULL); + gst_caps_append_structure (caps, s); + + iter = g_sequence_get_begin_iter (list->markers); + + while (!g_sequence_iter_is_end (iter)) { + GESMarker *marker = (GESMarker *) g_sequence_get (iter); + gchar *metas; + + metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (marker)); + + s = gst_structure_new ("marker-times", "position", G_TYPE_UINT64, + marker->position, NULL); + gst_caps_append_structure (caps, s); + s = gst_structure_from_string (metas, NULL); + gst_caps_append_structure (caps, s); + + g_free (metas); + + iter = g_sequence_iter_next (iter); + } + + caps_str = gst_caps_to_string (caps); + escaped = g_strescape (caps_str, NULL); + g_free (caps_str); + res = g_strdup_printf ("\"%s\"", escaped); + g_free (escaped); + gst_caps_unref (caps); + + return res; +} diff --git a/ges/ges-marker-list.h b/ges/ges-marker-list.h new file mode 100644 index 0000000000..51c7ed14d5 --- /dev/null +++ b/ges/ges-marker-list.h @@ -0,0 +1,70 @@ +/* GStreamer Editing Services + + * Copyright (C) <2019> Mathieu Duponchelle <mathieu@centricular.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> + +G_BEGIN_DECLS + +#define GES_TYPE_MARKER ges_marker_get_type () + +/** + * GESMarker: + * + * A timed #GESMetaContainer object. + * + * Since: 1.18 + */ +GES_API +G_DECLARE_FINAL_TYPE (GESMarker, ges_marker, GES, MARKER, GObject) +#define GES_TYPE_MARKER_LIST ges_marker_list_get_type () + +/** + * GESMarkerList: + * + * A list of #GESMarker + * + * Since: 1.18 + */ +GES_API +G_DECLARE_FINAL_TYPE (GESMarkerList, ges_marker_list, GES, MARKER_LIST, GObject) + +GES_API +GESMarkerList * ges_marker_list_new (void); + +GES_API +GESMarker * ges_marker_list_add (GESMarkerList * list, GstClockTime position); + +GES_API +gboolean ges_marker_list_remove (GESMarkerList * list, GESMarker *marker); + +GES_API +guint ges_marker_list_size (GESMarkerList * list); + + +GES_API +GList * ges_marker_list_get_markers (GESMarkerList *list); + +GES_API +gboolean ges_marker_list_move (GESMarkerList *list, GESMarker *marker, GstClockTime position); + +G_END_DECLS diff --git a/ges/ges-meta-container.c b/ges/ges-meta-container.c new file mode 100644 index 0000000000..85fd902e98 --- /dev/null +++ b/ges/ges-meta-container.c @@ -0,0 +1,1222 @@ +/* GStreamer Editing Services + * Copyright (C) 2012 Paul Lange <palango@gmx.de> + * Copyright (C) <2014> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <glib-object.h> +#include <gst/gst.h> + +#include "ges-meta-container.h" +#include "ges-marker-list.h" + +/** + * SECTION: gesmetacontainer + * @title: GESMetaContainer Interface + * @short_description: An interface for storing metadata + * + * A #GObject that implements #GESMetaContainer can have metadata set on + * it, that is data that is unimportant to its function within GES, but + * may hold some useful information. In particular, + * ges_meta_container_set_meta() can be used to store any #GValue under + * any generic field (specified by a string key). The same method can also + * be used to remove the field by passing %NULL. A number of convenience + * methods are also provided to make it easier to set common value types. + * The metadata can then be read with ges_meta_container_get_meta() and + * similar convenience methods. + * + * ## Registered Fields + * + * By default, any #GValue can be set for a metadata field. However, you + * can register some fields as static, that is they only allow values of a + * specific type to be set under them, using + * ges_meta_container_register_meta() or + * ges_meta_container_register_static_meta(). The set #GESMetaFlag will + * determine whether the value can be changed, but even if it can be + * changed, it must be changed to a value of the same type. + * + * Internally, some GES objects will be initialized with static metadata + * fields. These will correspond to some standard keys, such as + * #GES_META_VOLUME. + */ + +static GQuark ges_meta_key; + +G_DEFINE_INTERFACE_WITH_CODE (GESMetaContainer, ges_meta_container, + G_TYPE_OBJECT, ges_meta_key = + g_quark_from_static_string ("ges-meta-container-data");); + +enum +{ + NOTIFY_SIGNAL, + LAST_SIGNAL +}; + +static guint _signals[LAST_SIGNAL] = { 0 }; + +typedef struct RegisteredMeta +{ + GType item_type; + GESMetaFlag flags; +} RegisteredMeta; + +typedef struct ContainerData +{ + GstStructure *structure; + GHashTable *static_items; +} ContainerData; + +static void +ges_meta_container_default_init (GESMetaContainerInterface * iface) +{ + + /** + * GESMetaContainer::notify-meta: + * @container: A #GESMetaContainer + * @key: The key for the @container field that changed + * @value: (nullable): The new value under @key + * + * This is emitted for a meta container whenever the metadata under one + * of its fields changes, is set for the first time, or is removed. In + * the latter case, @value will be %NULL. + */ + _signals[NOTIFY_SIGNAL] = + g_signal_new ("notify-meta", G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED | + G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VALUE); +} + +static void +_free_meta_container_data (ContainerData * data) +{ + gst_structure_free (data->structure); + g_hash_table_unref (data->static_items); + + g_slice_free (ContainerData, data); +} + +static void +_free_static_item (RegisteredMeta * item) +{ + g_slice_free (RegisteredMeta, item); +} + +static ContainerData * +_create_container_data (GESMetaContainer * container) +{ + ContainerData *data = g_slice_new (ContainerData); + data->structure = gst_structure_new_empty ("metadatas"); + data->static_items = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) (GDestroyNotify) _free_static_item); + g_object_set_qdata_full (G_OBJECT (container), ges_meta_key, data, + (GDestroyNotify) _free_meta_container_data); + + return data; +} + +static GstStructure * +_meta_container_get_structure (GESMetaContainer * container) +{ + ContainerData *data; + + data = g_object_get_qdata (G_OBJECT (container), ges_meta_key); + if (!data) + data = _create_container_data (container); + + return data->structure; +} + +typedef struct +{ + GESMetaForeachFunc func; + const GESMetaContainer *container; + gpointer data; +} MetadataForeachData; + +static gboolean +structure_foreach_wrapper (GQuark field_id, const GValue * value, + gpointer user_data) +{ + MetadataForeachData *data = (MetadataForeachData *) user_data; + + data->func (data->container, g_quark_to_string (field_id), value, data->data); + return TRUE; +} + +static gboolean +_append_foreach (GQuark field_id, const GValue * value, GESMetaContainer * self) +{ + ges_meta_container_set_meta (self, g_quark_to_string (field_id), value); + + return TRUE; +} + +/** + * ges_meta_container_foreach: + * @container: A #GESMetaContainer + * @func: (scope call): A function to call on each of @container's set + * metadata fields + * @user_data: (closure): User data to send to @func + * + * Calls the given function on each of the meta container's set metadata + * fields. + */ +void +ges_meta_container_foreach (GESMetaContainer * container, + GESMetaForeachFunc func, gpointer user_data) +{ + GstStructure *structure; + MetadataForeachData foreach_data; + + g_return_if_fail (GES_IS_META_CONTAINER (container)); + g_return_if_fail (func != NULL); + + structure = _meta_container_get_structure (container); + + foreach_data.func = func; + foreach_data.container = container; + foreach_data.data = user_data; + + gst_structure_foreach (structure, + (GstStructureForeachFunc) structure_foreach_wrapper, &foreach_data); +} + +static gboolean +_register_meta (GESMetaContainer * container, GESMetaFlag flags, + const gchar * meta_item, GType type) +{ + ContainerData *data; + RegisteredMeta *static_item; + + data = g_object_get_qdata (G_OBJECT (container), ges_meta_key); + if (!data) + data = _create_container_data (container); + else if (g_hash_table_lookup (data->static_items, meta_item)) { + GST_WARNING_OBJECT (container, "Static meta %s already registered", + meta_item); + + return FALSE; + } + + static_item = g_slice_new0 (RegisteredMeta); + static_item->item_type = type; + static_item->flags = flags; + g_hash_table_insert (data->static_items, g_strdup (meta_item), static_item); + + return TRUE; +} + +/* _can_write_value should have been checked before calling */ +static gboolean +_set_value (GESMetaContainer * container, const gchar * meta_item, + const GValue * value) +{ + GstStructure *structure; + gchar *val = gst_value_serialize (value); + + if (val == NULL) { + GST_WARNING_OBJECT (container, "Could not set value on item: %s", + meta_item); + + g_free (val); + return FALSE; + } + + structure = _meta_container_get_structure (container); + + GST_DEBUG_OBJECT (container, "Setting meta_item %s value: %s::%s", + meta_item, G_VALUE_TYPE_NAME (value), val); + + gst_structure_set_value (structure, meta_item, value); + g_signal_emit (container, _signals[NOTIFY_SIGNAL], 0, meta_item, value); + + g_free (val); + return TRUE; +} + +static gboolean +_can_write_value (GESMetaContainer * container, const gchar * item_name, + GType type) +{ + ContainerData *data; + RegisteredMeta *static_item = NULL; + + data = g_object_get_qdata (G_OBJECT (container), ges_meta_key); + if (!data) { + _create_container_data (container); + return TRUE; + } + + static_item = g_hash_table_lookup (data->static_items, item_name); + + if (static_item == NULL) + return TRUE; + + if ((static_item->flags & GES_META_WRITABLE) == FALSE) { + GST_WARNING_OBJECT (container, "Can not write %s", item_name); + return FALSE; + } + + if (static_item->item_type != type) { + GST_WARNING_OBJECT (container, "Can not set value of type %s on %s " + "its type is: %s", g_type_name (static_item->item_type), item_name, + g_type_name (type)); + return FALSE; + } + + return TRUE; +} + +#define CREATE_SETTER(name, value_ctype, value_gtype, setter_name) \ +gboolean \ +ges_meta_container_set_ ## name (GESMetaContainer *container, \ + const gchar *meta_item, value_ctype value) \ +{ \ + GValue gval = { 0 }; \ + gboolean ret; \ + \ + g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE); \ + g_return_val_if_fail (meta_item != NULL, FALSE); \ + \ + if (_can_write_value (container, meta_item, value_gtype) == FALSE) \ + return FALSE; \ + \ + g_value_init (&gval, value_gtype); \ + g_value_set_ ##setter_name (&gval, value); \ + \ + ret = _set_value (container, meta_item, &gval); \ + g_value_unset (&gval); \ + return ret; \ +} + +/** + * ges_meta_container_set_boolean: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to set + * @value: The value to set under @meta_item + * + * Sets the value of the specified field of the meta container to the + * given boolean value. + * + * Returns: %TRUE if @value was set under @meta_item for @container. + */ +CREATE_SETTER (boolean, gboolean, G_TYPE_BOOLEAN, boolean); + +/** + * ges_meta_container_set_int: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to set + * @value: The value to set under @meta_item + * + * Sets the value of the specified field of the meta container to the + * given int value. + * + * Returns: %TRUE if @value was set under @meta_item for @container. + */ +CREATE_SETTER (int, gint, G_TYPE_INT, int); + +/** + * ges_meta_container_set_uint: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to set + * @value: The value to set under @meta_item + * + * Sets the value of the specified field of the meta container to the + * given uint value. + * + * Returns: %TRUE if @value was set under @meta_item for @container. + */ +CREATE_SETTER (uint, guint, G_TYPE_UINT, uint); + +/** + * ges_meta_container_set_int64: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to set + * @value: The value to set under @meta_item + * + * Sets the value of the specified field of the meta container to the + * given int64 value. + * + * Returns: %TRUE if @value was set under @meta_item for @container. + */ +CREATE_SETTER (int64, gint64, G_TYPE_INT64, int64); + +/** + * ges_meta_container_set_uint64: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to set + * @value: The value to set under @meta_item + * + * Sets the value of the specified field of the meta container to the + * given uint64 value. + * + * Returns: %TRUE if @value was set under @meta_item for @container. + */ +CREATE_SETTER (uint64, guint64, G_TYPE_UINT64, uint64); + +/** + * ges_meta_container_set_float: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to set + * @value: The value to set under @meta_item + * + * Sets the value of the specified field of the meta container to the + * given float value. + * + * Returns: %TRUE if @value was set under @meta_item for @container. + */ +CREATE_SETTER (float, float, G_TYPE_FLOAT, float); + +/** + * ges_meta_container_set_double: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to set + * @value: The value to set under @meta_item + * + * Sets the value of the specified field of the meta container to the + * given double value. + * + * Returns: %TRUE if @value was set under @meta_item for @container. + */ +CREATE_SETTER (double, double, G_TYPE_DOUBLE, double); + +/** + * ges_meta_container_set_date: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to set + * @value: The value to set under @meta_item + * + * Sets the value of the specified field of the meta container to the + * given date value. + * + * Returns: %TRUE if @value was set under @meta_item for @container. + */ +CREATE_SETTER (date, const GDate *, G_TYPE_DATE, boxed); + +/** + * ges_meta_container_set_date_time: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to set + * @value: The value to set under @meta_item + * + * Sets the value of the specified field of the meta container to the + * given date time value. + * + * Returns: %TRUE if @value was set under @meta_item for @container. + */ +CREATE_SETTER (date_time, const GstDateTime *, GST_TYPE_DATE_TIME, boxed); + +/** + * ges_meta_container_set_string: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to set + * @value: The value to set under @meta_item + * + * Sets the value of the specified field of the meta container to the + * given string value. + * + * Returns: %TRUE if @value was set under @meta_item for @container. + */ +CREATE_SETTER (string, const gchar *, G_TYPE_STRING, string); + +/** + * ges_meta_container_set_meta: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to set + * @value: (nullable): The value to set under @meta_item, or %NULL to + * remove the corresponding field + * + * Sets the value of the specified field of the meta container to a + * copy of the given value. If the given @value is %NULL, the field + * given by @meta_item is removed and %TRUE is returned. + * + * Returns: %TRUE if @value was set under @meta_item for @container. + */ +gboolean +ges_meta_container_set_meta (GESMetaContainer * container, + const gchar * meta_item, const GValue * value) +{ + g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE); + g_return_val_if_fail (meta_item != NULL, FALSE); + + if (value == NULL) { + GstStructure *structure = _meta_container_get_structure (container); + gst_structure_remove_field (structure, meta_item); + + g_signal_emit (container, _signals[NOTIFY_SIGNAL], 0, meta_item, value); + + return TRUE; + } + + if (_can_write_value (container, meta_item, G_VALUE_TYPE (value)) == FALSE) + return FALSE; + + return _set_value (container, meta_item, value); +} + +/** + * ges_meta_container_set_marker_list: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to set + * @list: The value to set under @meta_item + * + * Sets the value of the specified field of the meta container to the + * given marker list value. + * + * Returns: %TRUE if @value was set under @meta_item for @container. + * + * Since: 1.18 + */ +gboolean +ges_meta_container_set_marker_list (GESMetaContainer * container, + const gchar * meta_item, const GESMarkerList * list) +{ + gboolean ret; + GValue v = G_VALUE_INIT; + g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE); + g_return_val_if_fail (meta_item != NULL, FALSE); + + if (list == NULL) { + GstStructure *structure = _meta_container_get_structure (container); + gst_structure_remove_field (structure, meta_item); + + g_signal_emit (container, _signals[NOTIFY_SIGNAL], 0, meta_item, list); + + return TRUE; + } + + g_return_val_if_fail (GES_IS_MARKER_LIST ((gpointer) list), FALSE); + + if (_can_write_value (container, meta_item, GES_TYPE_MARKER_LIST) == FALSE) + return FALSE; + + g_value_init_from_instance (&v, (gpointer) list); + + ret = _set_value (container, meta_item, &v); + + g_value_unset (&v); + + return ret; +} + +/** + * ges_meta_container_metas_to_string: + * @container: A #GESMetaContainer + * + * Serializes the set metadata fields of the meta container to a string. + * + * Returns: (transfer full): A serialized @container, or %NULL if an error + * occurred. + */ +gchar * +ges_meta_container_metas_to_string (GESMetaContainer * container) +{ + GstStructure *structure; + + g_return_val_if_fail (GES_IS_META_CONTAINER (container), NULL); + + structure = _meta_container_get_structure (container); + + return gst_structure_to_string (structure); +} + +/** + * ges_meta_container_add_metas_from_string: + * @container: A #GESMetaContainer + * @str: A string to deserialize and add to @container + * + * Deserializes the given string, and adds and sets the found fields and + * their values on the container. The string should be the return of + * ges_meta_container_metas_to_string(). + * + * Returns: %TRUE if the fields in @str was successfully deserialized + * and added to @container. + */ +gboolean +ges_meta_container_add_metas_from_string (GESMetaContainer * container, + const gchar * str) +{ + GstStructure *n_structure; + + g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE); + + n_structure = gst_structure_from_string (str, NULL); + if (n_structure == NULL) { + GST_WARNING_OBJECT (container, "Could not add metas: %s", str); + return FALSE; + } + + gst_structure_foreach (n_structure, (GstStructureForeachFunc) _append_foreach, + container); + + gst_structure_free (n_structure); + return TRUE; +} + +/** + * ges_meta_container_register_static_meta: + * @container: A #GESMetaContainer + * @flags: Flags to be used for the registered field + * @meta_item: The key for the @container field to register + * @type: The required value type for the registered field + * + * Registers a static metadata field on the container to only hold the + * specified type. After calling this, setting a value under this field + * can only succeed if its type matches the registered type of the field. + * + * Unlike ges_meta_container_register_meta(), no (initial) value is set + * for this field, which means you can use this method to reserve the + * space to be _optionally_ set later. + * + * Note that if a value has already been set for the field being + * registered, then its type must match the registering type, and its + * value will be left in place. If the field has no set value, then + * you will likely want to include #GES_META_WRITABLE in @flags to allow + * the value to be set later. + * + * Returns: %TRUE if the @meta_item field was successfully registered on + * @container to only hold @type values, with the given @flags. + * + * Since: 1.18 + */ +gboolean +ges_meta_container_register_static_meta (GESMetaContainer * container, + GESMetaFlag flags, const gchar * meta_item, GType type) +{ + GstStructure *structure; + + g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE); + g_return_val_if_fail (meta_item != NULL, FALSE); + + /* If the meta is already in use, and is of a different type, then we + * want to fail since, unlike ges_meta_container_register_meta, we will + * not be overwriting this value! If we didn't fail, the user could have + * a false sense that this meta will always be of the reserved type. + */ + structure = _meta_container_get_structure (container); + if (gst_structure_has_field (structure, meta_item) && + gst_structure_get_field_type (structure, meta_item) != type) { + gchar *value_string = + g_strdup_value_contents (gst_structure_get_value (structure, + meta_item)); + GST_WARNING_OBJECT (container, + "Meta %s already assigned a value of %s, which is a different type", + meta_item, value_string); + g_free (value_string); + return FALSE; + } + return _register_meta (container, flags, meta_item, type); +} + +#define CREATE_REGISTER_STATIC(name, value_ctype, value_gtype, setter_name) \ +gboolean \ +ges_meta_container_register_meta_ ## name (GESMetaContainer *container,\ + GESMetaFlag flags, const gchar *meta_item, value_ctype value) \ +{ \ + gboolean ret; \ + GValue gval = { 0 }; \ + \ + g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE); \ + g_return_val_if_fail (meta_item != NULL, FALSE); \ + \ + if (!_register_meta (container, flags, meta_item, value_gtype)) \ + return FALSE; \ + \ + g_value_init (&gval, value_gtype); \ + g_value_set_ ##setter_name (&gval, value); \ + \ + ret = _set_value (container, meta_item, &gval); \ + \ + g_value_unset (&gval); \ + return ret; \ +} + +/** + * ges_meta_container_register_meta_boolean: + * @container: A #GESMetaContainer + * @flags: Flags to be used for the registered field + * @meta_item: The key for the @container field to register + * @value: The value to set for the registered field + * + * Sets the value of the specified field of the meta container to the + * given boolean value, and registers the field to only hold a boolean + * typed value. After calling this, only boolean values can be set for + * this field. The given flags can be set to make this field only + * readable after calling this method. + * + * Returns: %TRUE if the @meta_item field was successfully registered on + * @container to only hold boolean typed values, with the given @flags, + * and the field was successfully set to @value. + */ +CREATE_REGISTER_STATIC (boolean, gboolean, G_TYPE_BOOLEAN, boolean); + +/** + * ges_meta_container_register_meta_int: + * @container: A #GESMetaContainer + * @flags: Flags to be used for the registered field + * @meta_item: The key for the @container field to register + * @value: The value to set for the registered field + * + * Sets the value of the specified field of the meta container to the + * given int value, and registers the field to only hold an int + * typed value. After calling this, only int values can be set for + * this field. The given flags can be set to make this field only + * readable after calling this method. + * + * Returns: %TRUE if the @meta_item field was successfully registered on + * @container to only hold int typed values, with the given @flags, + * and the field was successfully set to @value. + */ +CREATE_REGISTER_STATIC (int, gint, G_TYPE_INT, int); + +/** + * ges_meta_container_register_meta_uint: + * @container: A #GESMetaContainer + * @flags: Flags to be used for the registered field + * @meta_item: The key for the @container field to register + * @value: The value to set for the registered field + * + * Sets the value of the specified field of the meta container to the + * given uint value, and registers the field to only hold a uint + * typed value. After calling this, only uint values can be set for + * this field. The given flags can be set to make this field only + * readable after calling this method. + * + * Returns: %TRUE if the @meta_item field was successfully registered on + * @container to only hold uint typed values, with the given @flags, + * and the field was successfully set to @value. + */ +CREATE_REGISTER_STATIC (uint, guint, G_TYPE_UINT, uint); + +/** + * ges_meta_container_register_meta_int64: + * @container: A #GESMetaContainer + * @flags: Flags to be used for the registered field + * @meta_item: The key for the @container field to register + * @value: The value to set for the registered field + * + * Sets the value of the specified field of the meta container to the + * given int64 value, and registers the field to only hold an int64 + * typed value. After calling this, only int64 values can be set for + * this field. The given flags can be set to make this field only + * readable after calling this method. + * + * Returns: %TRUE if the @meta_item field was successfully registered on + * @container to only hold int64 typed values, with the given @flags, + * and the field was successfully set to @value. + */ +CREATE_REGISTER_STATIC (int64, gint64, G_TYPE_INT64, int64); + +/** + * ges_meta_container_register_meta_uint64: + * @container: A #GESMetaContainer + * @flags: Flags to be used for the registered field + * @meta_item: The key for the @container field to register + * @value: The value to set for the registered field + * + * Sets the value of the specified field of the meta container to the + * given uint64 value, and registers the field to only hold a uint64 + * typed value. After calling this, only uint64 values can be set for + * this field. The given flags can be set to make this field only + * readable after calling this method. + * + * Returns: %TRUE if the @meta_item field was successfully registered on + * @container to only hold uint64 typed values, with the given @flags, + * and the field was successfully set to @value. + */ +CREATE_REGISTER_STATIC (uint64, guint64, G_TYPE_UINT64, uint64); + +/** + * ges_meta_container_register_meta_float: + * @container: A #GESMetaContainer + * @flags: Flags to be used for the registered field + * @meta_item: The key for the @container field to register + * @value: The value to set for the registered field + * + * Sets the value of the specified field of the meta container to the + * given float value, and registers the field to only hold a float + * typed value. After calling this, only float values can be set for + * this field. The given flags can be set to make this field only + * readable after calling this method. + * + * Returns: %TRUE if the @meta_item field was successfully registered on + * @container to only hold float typed values, with the given @flags, + * and the field was successfully set to @value. +*/ +CREATE_REGISTER_STATIC (float, float, G_TYPE_FLOAT, float); + +/** + * ges_meta_container_register_meta_double: + * @container: A #GESMetaContainer + * @flags: Flags to be used for the registered field + * @meta_item: The key for the @container field to register + * @value: The value to set for the registered field + * + * Sets the value of the specified field of the meta container to the + * given double value, and registers the field to only hold a double + * typed value. After calling this, only double values can be set for + * this field. The given flags can be set to make this field only + * readable after calling this method. + * + * Returns: %TRUE if the @meta_item field was successfully registered on + * @container to only hold double typed values, with the given @flags, + * and the field was successfully set to @value. + */ +CREATE_REGISTER_STATIC (double, double, G_TYPE_DOUBLE, double); + +/** + * ges_meta_container_register_meta_date: + * @container: A #GESMetaContainer + * @flags: Flags to be used for the registered field + * @meta_item: The key for the @container field to register + * @value: The value to set for the registered field + * + * Sets the value of the specified field of the meta container to the + * given date value, and registers the field to only hold a date + * typed value. After calling this, only date values can be set for + * this field. The given flags can be set to make this field only + * readable after calling this method. + * + * Returns: %TRUE if the @meta_item field was successfully registered on + * @container to only hold date typed values, with the given @flags, + * and the field was successfully set to @value. + */ +CREATE_REGISTER_STATIC (date, const GDate *, G_TYPE_DATE, boxed); + +/** + * ges_meta_container_register_meta_date_time: + * @container: A #GESMetaContainer + * @flags: Flags to be used for the registered field + * @meta_item: The key for the @container field to register + * @value: The value to set for the registered field + * + * Sets the value of the specified field of the meta container to the + * given date time value, and registers the field to only hold a date time + * typed value. After calling this, only date time values can be set for + * this field. The given flags can be set to make this field only + * readable after calling this method. + * + * Returns: %TRUE if the @meta_item field was successfully registered on + * @container to only hold date time typed values, with the given @flags, + * and the field was successfully set to @value. + */ +CREATE_REGISTER_STATIC (date_time, const GstDateTime *, GST_TYPE_DATE_TIME, + boxed); + +/** + * ges_meta_container_register_meta_string: + * @container: A #GESMetaContainer + * @flags: Flags to be used for the registered field + * @meta_item: The key for the @container field to register + * @value: The value to set for the registered field + * + * Sets the value of the specified field of the meta container to the + * given string value, and registers the field to only hold a string + * typed value. After calling this, only string values can be set for + * this field. The given flags can be set to make this field only + * readable after calling this method. + * + * Returns: %TRUE if the @meta_item field was successfully registered on + * @container to only hold string typed values, with the given @flags, + * and the field was successfully set to @value. + */ +CREATE_REGISTER_STATIC (string, const gchar *, G_TYPE_STRING, string); + +/** + * ges_meta_container_register_meta: + * @container: A #GESMetaContainer + * @flags: Flags to be used for the registered field + * @meta_item: The key for the @container field to register + * @value: The value to set for the registered field + * + * Sets the value of the specified field of the meta container to the + * given value, and registers the field to only hold a value of the + * same type. After calling this, only values of the same type as @value + * can be set for this field. The given flags can be set to make this + * field only readable after calling this method. + * + * Returns: %TRUE if the @meta_item field was successfully registered on + * @container to only hold @value types, with the given @flags, and the + * field was successfully set to @value. + */ +gboolean +ges_meta_container_register_meta (GESMetaContainer * container, + GESMetaFlag flags, const gchar * meta_item, const GValue * value) +{ + g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE); + g_return_val_if_fail (meta_item != NULL, FALSE); + + if (!_register_meta (container, flags, meta_item, G_VALUE_TYPE (value))) + return FALSE; + + return _set_value (container, meta_item, value); +} + +/** + * ges_meta_container_check_meta_registered: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to check + * @flags: (out) (nullable): A destination to get the registered flags of + * the field, or %NULL to ignore + * @type: (out) (nullable): A destination to get the registered type of + * the field, or %NULL to ignore + * + * Checks whether the specified field has been registered as static, and + * gets the registered type and flags of the field, as used in + * ges_meta_container_register_meta() and + * ges_meta_container_register_static_meta(). + * + * Returns: %TRUE if the @meta_item field has been registered on + * @container. + */ +gboolean +ges_meta_container_check_meta_registered (GESMetaContainer * container, + const gchar * meta_item, GESMetaFlag * flags, GType * type) +{ + ContainerData *data; + RegisteredMeta *static_item; + + data = g_object_get_qdata (G_OBJECT (container), ges_meta_key); + if (!data) + return FALSE; + + static_item = g_hash_table_lookup (data->static_items, meta_item); + if (static_item == NULL) { + GST_WARNING_OBJECT (container, "Static meta %s has not been registered yet", + meta_item); + + return FALSE; + } + + if (type) + *type = static_item->item_type; + + if (flags) + *flags = static_item->flags; + + return TRUE; +} + +/* Copied from gsttaglist.c */ +/***** evil macros to get all the *_get_* functions right *****/ + +#define CREATE_GETTER(name,type) \ +gboolean \ +ges_meta_container_get_ ## name (GESMetaContainer *container, \ + const gchar *meta_item, type value) \ +{ \ + GstStructure *structure; \ + \ + g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE); \ + g_return_val_if_fail (meta_item != NULL, FALSE); \ + g_return_val_if_fail (value != NULL, FALSE); \ + \ + structure = _meta_container_get_structure (container); \ + \ + return gst_structure_get_ ## name (structure, meta_item, value); \ +} + +/** + * ges_meta_container_get_boolean: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to get + * @dest: (out): Destination into which the value under @meta_item + * should be copied. + * + * Gets the current boolean value of the specified field of the meta + * container. If the field does not have a set value, or it is of the + * wrong type, the method will fail. + * + * Returns: %TRUE if the boolean value under @meta_item was copied + * to @dest. + */ +CREATE_GETTER (boolean, gboolean *); + +/** + * ges_meta_container_get_int: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to get + * @dest: (out): Destination into which the value under @meta_item + * should be copied. + * + * Gets the current int value of the specified field of the meta + * container. If the field does not have a set value, or it is of the + * wrong type, the method will fail. + * + * Returns: %TRUE if the int value under @meta_item was copied + * to @dest. + */ +CREATE_GETTER (int, gint *); + +/** + * ges_meta_container_get_uint: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to get + * @dest: (out): Destination into which the value under @meta_item + * should be copied. + * + * Gets the current uint value of the specified field of the meta + * container. If the field does not have a set value, or it is of the + * wrong type, the method will fail. + * + * Returns: %TRUE if the uint value under @meta_item was copied + * to @dest. + */ +CREATE_GETTER (uint, guint *); + +/** + * ges_meta_container_get_double: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to get + * @dest: (out): Destination into which the value under @meta_item + * should be copied. + * + * Gets the current double value of the specified field of the meta + * container. If the field does not have a set value, or it is of the + * wrong type, the method will fail. + * + * Returns: %TRUE if the double value under @meta_item was copied + * to @dest. + */ +CREATE_GETTER (double, gdouble *); + +/** + * ges_meta_container_get_int64: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to get + * @dest: (out): Destination into which the value under @meta_item + * should be copied. + * + * Gets the current int64 value of the specified field of the meta + * container. If the field does not have a set value, or it is of the + * wrong type, the method will fail. + * + * Returns: %TRUE if the int64 value under @meta_item was copied + * to @dest. + */ +gboolean +ges_meta_container_get_int64 (GESMetaContainer * container, + const gchar * meta_item, gint64 * dest) +{ + GstStructure *structure; + const GValue *value; + + g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE); + g_return_val_if_fail (meta_item != NULL, FALSE); + g_return_val_if_fail (dest != NULL, FALSE); + + structure = _meta_container_get_structure (container); + + value = gst_structure_get_value (structure, meta_item); + if (!value || G_VALUE_TYPE (value) != G_TYPE_INT64) + return FALSE; + + *dest = g_value_get_int64 (value); + + return TRUE; +} + +/** + * ges_meta_container_get_uint64: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to get + * @dest: (out): Destination into which the value under @meta_item + * should be copied. + * + * Gets the current uint64 value of the specified field of the meta + * container. If the field does not have a set value, or it is of the + * wrong type, the method will fail. + * + * Returns: %TRUE if the uint64 value under @meta_item was copied + * to @dest. + */ +gboolean +ges_meta_container_get_uint64 (GESMetaContainer * container, + const gchar * meta_item, guint64 * dest) +{ + GstStructure *structure; + const GValue *value; + + g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE); + g_return_val_if_fail (meta_item != NULL, FALSE); + g_return_val_if_fail (dest != NULL, FALSE); + + structure = _meta_container_get_structure (container); + + value = gst_structure_get_value (structure, meta_item); + if (!value || G_VALUE_TYPE (value) != G_TYPE_UINT64) + return FALSE; + + *dest = g_value_get_uint64 (value); + + return TRUE; +} + +/** + * ges_meta_container_get_float: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to get + * @dest: (out): Destination into which the value under @meta_item + * should be copied. + * + * Gets the current float value of the specified field of the meta + * container. If the field does not have a set value, or it is of the + * wrong type, the method will fail. + * + * Returns: %TRUE if the float value under @meta_item was copied + * to @dest. + */ +gboolean +ges_meta_container_get_float (GESMetaContainer * container, + const gchar * meta_item, gfloat * dest) +{ + GstStructure *structure; + const GValue *value; + + g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE); + g_return_val_if_fail (meta_item != NULL, FALSE); + g_return_val_if_fail (dest != NULL, FALSE); + + structure = _meta_container_get_structure (container); + + value = gst_structure_get_value (structure, meta_item); + if (!value || G_VALUE_TYPE (value) != G_TYPE_FLOAT) + return FALSE; + + *dest = g_value_get_float (value); + + return TRUE; +} + +/** + * ges_meta_container_get_string: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to get + * + * Gets the current string value of the specified field of the meta + * container. If the field does not have a set value, or it is of the + * wrong type, the method will fail. + * + * Returns: (transfer none): The string value under @meta_item, or %NULL + * if it could not be fetched. + */ +const gchar * +ges_meta_container_get_string (GESMetaContainer * container, + const gchar * meta_item) +{ + GstStructure *structure; + + g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE); + g_return_val_if_fail (meta_item != NULL, FALSE); + + structure = _meta_container_get_structure (container); + + return gst_structure_get_string (structure, meta_item); +} + +/** + * ges_meta_container_get_meta: + * @container: A #GESMetaContainer + * @key: The key for the @container field to get + * + * Gets the current value of the specified field of the meta container. + * + * Returns: (transfer none): The value under @key, or %NULL if @container + * does not have the field set. + */ +const GValue * +ges_meta_container_get_meta (GESMetaContainer * container, const gchar * key) +{ + GstStructure *structure; + + g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE); + g_return_val_if_fail (key != NULL, FALSE); + + structure = _meta_container_get_structure (container); + + return gst_structure_get_value (structure, key); +} + +/** + * ges_meta_container_get_marker_list: + * @container: A #GESMetaContainer + * @key: The key for the @container field to get + * + * Gets the current marker list value of the specified field of the meta + * container. If the field does not have a set value, or it is of the + * wrong type, the method will fail. + * + * Returns: (transfer full): A copy of the marker list value under @key, + * or %NULL if it could not be fetched. + * Since: 1.18 + */ +GESMarkerList * +ges_meta_container_get_marker_list (GESMetaContainer * container, + const gchar * key) +{ + GstStructure *structure; + const GValue *v; + + g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE); + g_return_val_if_fail (key != NULL, FALSE); + + structure = _meta_container_get_structure (container); + + v = gst_structure_get_value (structure, key); + + if (v == NULL) { + return NULL; + } + + return GES_MARKER_LIST (g_value_dup_object (v)); +} + +/** + * ges_meta_container_get_date: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to get + * @dest: (out): Destination into which the value under @meta_item + * should be copied. + * + * Gets the current date value of the specified field of the meta + * container. If the field does not have a set value, or it is of the + * wrong type, the method will fail. + * + * Returns: %TRUE if the date value under @meta_item was copied + * to @dest. + */ +CREATE_GETTER (date, GDate **); + +/** + * ges_meta_container_get_date_time: + * @container: A #GESMetaContainer + * @meta_item: The key for the @container field to get + * @dest: (out): Destination into which the value under @meta_item + * should be copied. + * + * Gets the current date time value of the specified field of the meta + * container. If the field does not have a set value, or it is of the + * wrong type, the method will fail. + * + * Returns: %TRUE if the date time value under @meta_item was copied + * to @dest. + */ +CREATE_GETTER (date_time, GstDateTime **); diff --git a/ges/ges-meta-container.h b/ges/ges-meta-container.h new file mode 100644 index 0000000000..bc805954d5 --- /dev/null +++ b/ges/ges-meta-container.h @@ -0,0 +1,334 @@ +/* GStreamer Editing Services + * Copyright (C) 2012 Paul Lange <palango@gmx.de> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#pragma once + +#include <glib-object.h> +#include <gst/gst.h> +#include <ges/ges-types.h> +#include "ges-enums.h" + +G_BEGIN_DECLS + +#define GES_TYPE_META_CONTAINER (ges_meta_container_get_type ()) +#define GES_META_CONTAINER_GET_INTERFACE (inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GES_TYPE_META_CONTAINER, GESMetaContainerInterface)) +GES_API +G_DECLARE_INTERFACE(GESMetaContainer, ges_meta_container, GES, META_CONTAINER, GObject); + +/** + * GES_META_FORMATTER_NAME: + * + * The name of a formatter, used as the #GESAsset:id for #GESFormatter + * assets (string). + */ +#define GES_META_FORMATTER_NAME "name" + +/** + * GES_META_DESCRIPTION: + * + * The description of the object, to be used in various contexts (string). + */ +#define GES_META_DESCRIPTION "description" + +/** + * GES_META_FORMATTER_MIMETYPE: + * + * The mimetype used for the file produced by a #GESFormatter (string). + */ +#define GES_META_FORMATTER_MIMETYPE "mimetype" + +/** + * GES_META_FORMATTER_EXTENSION: + * + * The file extension of files produced by a #GESFormatter (string). + */ +#define GES_META_FORMATTER_EXTENSION "extension" + +/** + * GES_META_FORMATTER_VERSION: + * + * The version of a #GESFormatter (double). + */ +#define GES_META_FORMATTER_VERSION "version" + +/** + * GES_META_FORMATTER_RANK: + * + * The rank of a #GESFormatter (a #GstRank). + */ +#define GES_META_FORMATTER_RANK "rank" + +/** + * GES_META_VOLUME: + * + * The volume for a #GESTrack or a #GESLayer (float). + */ +#define GES_META_VOLUME "volume" + +/** + * GES_META_VOLUME_DEFAULT: + * + * The default volume for a #GESTrack or a #GESLayer as a float. + */ +#define GES_META_VOLUME_DEFAULT 1.0 + +/** + * GES_META_FORMAT_VERSION: + * + * The version of the format in which a project is serialized (string). + */ +#define GES_META_FORMAT_VERSION "format-version" + +/** + * GES_META_MARKER_COLOR: + * + * The ARGB color of a #GESMarker (an AARRGGBB hex as a uint). + */ +#define GES_META_MARKER_COLOR "marker-color" + +typedef struct _GESMetaContainer GESMetaContainer; +typedef struct _GESMetaContainerInterface GESMetaContainerInterface; + +struct _GESMetaContainerInterface { + GTypeInterface parent_iface; + + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API gboolean +ges_meta_container_set_boolean (GESMetaContainer *container, + const gchar* meta_item, + gboolean value); + +GES_API gboolean +ges_meta_container_set_int (GESMetaContainer *container, + const gchar* meta_item, + gint value); + +GES_API gboolean +ges_meta_container_set_uint (GESMetaContainer *container, + const gchar* meta_item, + guint value); + +GES_API gboolean +ges_meta_container_set_int64 (GESMetaContainer *container, + const gchar* meta_item, + gint64 value); + +GES_API gboolean +ges_meta_container_set_uint64 (GESMetaContainer *container, + const gchar* meta_item, + guint64 value); + +GES_API gboolean +ges_meta_container_set_float (GESMetaContainer *container, + const gchar* meta_item, + gfloat value); + +GES_API gboolean +ges_meta_container_set_double (GESMetaContainer *container, + const gchar* meta_item, + gdouble value); + +GES_API gboolean +ges_meta_container_set_date (GESMetaContainer *container, + const gchar* meta_item, + const GDate* value); + +GES_API gboolean +ges_meta_container_set_date_time (GESMetaContainer *container, + const gchar* meta_item, + const GstDateTime* value); + +GES_API gboolean +ges_meta_container_set_string (GESMetaContainer *container, + const gchar* meta_item, + const gchar* value); + +GES_API gboolean +ges_meta_container_set_meta (GESMetaContainer * container, + const gchar* meta_item, + const GValue *value); + +GES_API gboolean +ges_meta_container_set_marker_list (GESMetaContainer * container, + const gchar * meta_item, + const GESMarkerList *list); + +GES_API gboolean +ges_meta_container_register_static_meta (GESMetaContainer * container, + GESMetaFlag flags, + const gchar * meta_item, + GType type); + +GES_API gboolean +ges_meta_container_register_meta_boolean (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + gboolean value); + +GES_API gboolean +ges_meta_container_register_meta_int (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + gint value); + +GES_API gboolean +ges_meta_container_register_meta_uint (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + guint value); + +GES_API gboolean +ges_meta_container_register_meta_int64 (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + gint64 value); + +GES_API gboolean +ges_meta_container_register_meta_uint64 (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + guint64 value); + +GES_API gboolean +ges_meta_container_register_meta_float (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + gfloat value); + +GES_API gboolean +ges_meta_container_register_meta_double (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + gdouble value); + +GES_API gboolean +ges_meta_container_register_meta_date (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + const GDate* value); + +GES_API gboolean +ges_meta_container_register_meta_date_time (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + const GstDateTime* value); + +GES_API gboolean +ges_meta_container_register_meta_string (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + const gchar* value); + +GES_API gboolean +ges_meta_container_register_meta (GESMetaContainer *container, + GESMetaFlag flags, + const gchar* meta_item, + const GValue * value); + +GES_API gboolean +ges_meta_container_check_meta_registered (GESMetaContainer *container, + const gchar * meta_item, + GESMetaFlag * flags, + GType * type); + +GES_API gboolean +ges_meta_container_get_boolean (GESMetaContainer *container, + const gchar* meta_item, + gboolean* dest); + +GES_API gboolean +ges_meta_container_get_int (GESMetaContainer *container, + const gchar* meta_item, + gint* dest); + +GES_API gboolean +ges_meta_container_get_uint (GESMetaContainer *container, + const gchar* meta_item, + guint* dest); + +GES_API gboolean +ges_meta_container_get_int64 (GESMetaContainer *container, + const gchar* meta_item, + gint64* dest); + +GES_API gboolean +ges_meta_container_get_uint64 (GESMetaContainer *container, + const gchar* meta_item, + guint64* dest); + +GES_API gboolean +ges_meta_container_get_float (GESMetaContainer *container, + const gchar* meta_item, + gfloat* dest); + +GES_API gboolean +ges_meta_container_get_double (GESMetaContainer *container, + const gchar* meta_item, + gdouble* dest); + +GES_API gboolean +ges_meta_container_get_date (GESMetaContainer *container, + const gchar* meta_item, + GDate** dest); + +GES_API gboolean +ges_meta_container_get_date_time (GESMetaContainer *container, + const gchar* meta_item, + GstDateTime** dest); + +GES_API const gchar * +ges_meta_container_get_string (GESMetaContainer * container, + const gchar * meta_item); + +GES_API GESMarkerList * +ges_meta_container_get_marker_list (GESMetaContainer * container, + const gchar * key); + +GES_API const GValue * +ges_meta_container_get_meta (GESMetaContainer * container, + const gchar * key); +/** + * GESMetaForeachFunc: + * @container: A #GESMetaContainer + * @key: The key for one of @container's fields + * @value: The set value under @key + * @user_data: User data + * + * A method to be called on all of a meta container's fields. + */ +typedef void +(*GESMetaForeachFunc) (const GESMetaContainer *container, + const gchar *key, + const GValue *value, + gpointer user_data); + +GES_API void +ges_meta_container_foreach (GESMetaContainer *container, + GESMetaForeachFunc func, + gpointer user_data); + +GES_API gchar * +ges_meta_container_metas_to_string (GESMetaContainer *container); + +GES_API gboolean +ges_meta_container_add_metas_from_string (GESMetaContainer *container, + const gchar *str); + +G_END_DECLS diff --git a/ges/ges-multi-file-source.c b/ges/ges-multi-file-source.c new file mode 100644 index 0000000000..25f600f433 --- /dev/null +++ b/ges/ges-multi-file-source.c @@ -0,0 +1,283 @@ +/* GStreamer Editing Services + * Copyright (C) 2013 Lubosz Sarnecki <lubosz@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesmultifilesource + * @title: GESMultiFileSource + * @short_description: outputs the video stream from a sequence of images. + * + * Outputs the video stream from a given image sequence. The start frame chosen + * will be determined by the in-point property on the track element. + * + * This should not be used anymore, the `imagesequence://` protocol should be + * used instead. Check the #imagesequencesrc GStreamer element for more + * information. + * + * Deprecated: 1.18: Use #GESUriSource instead + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <string.h> +#include "ges-internal.h" +#include "ges-track-element.h" +#include "ges-multi-file-source.h" +#include "ges-extractable.h" +#include "ges-uri-asset.h" +#include "ges-internal.h" + +/* Extractable interface implementation */ + +static gchar * +ges_extractable_check_id (GType type, const gchar * id, GError ** error) +{ + return g_strdup (id); +} + +static void +ges_extractable_interface_init (GESExtractableInterface * iface) +{ + iface->check_id = ges_extractable_check_id; +} + +struct _GESMultiFileSourcePrivate +{ + /* Dummy variable */ + void *nothing; +}; + +enum +{ + PROP_0, + PROP_URI +}; + +G_DEFINE_TYPE_WITH_CODE (GESMultiFileSource, ges_multi_file_source, + GES_TYPE_VIDEO_SOURCE, G_ADD_PRIVATE (GESMultiFileSource) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, + ges_extractable_interface_init)); + +static void +ges_multi_file_source_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESMultiFileSource *uriclip = GES_MULTI_FILE_SOURCE (object); + + switch (property_id) { + case PROP_URI: + g_value_set_string (value, uriclip->uri); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_multi_file_source_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESMultiFileSource *uriclip = GES_MULTI_FILE_SOURCE (object); + + switch (property_id) { + case PROP_URI: + uriclip->uri = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_multi_file_source_dispose (GObject * object) +{ + GESMultiFileSource *uriclip = GES_MULTI_FILE_SOURCE (object); + + if (uriclip->uri) + g_free (uriclip->uri); + + G_OBJECT_CLASS (ges_multi_file_source_parent_class)->dispose (object); +} + +static void +pad_added_cb (GstElement * decodebin, GstPad * pad, GstElement * bin) +{ + GstPad *srcpad; + + srcpad = gst_ghost_pad_new ("src", pad); + + gst_pad_set_active (srcpad, TRUE); + gst_element_add_pad (bin, srcpad); +} + +/** + * ges_multi_file_uri_new: (skip) + * + * Reads start/stop index and location from a multifile uri. + * + */ +GESMultiFileURI * +ges_multi_file_uri_new (const gchar * uri) +{ + gchar *colon = NULL; + gchar *at = NULL; + gchar *indices; + int charpos; + GESMultiFileURI *uri_data; + const int prefix_size = strlen (GES_MULTI_FILE_URI_PREFIX); + + uri_data = malloc (sizeof (GESMultiFileURI)); + + uri_data->start = 0; + uri_data->end = -1; + + at = strchr (uri, '@'); + if (at != NULL) { + charpos = (int) (at - uri); + indices = g_strdup_printf ("%.*s", charpos, uri); + indices = &indices[prefix_size]; + colon = strchr (indices, ':'); + if (colon != NULL) { + charpos = (int) (colon - indices); + uri_data->end = atoi (colon + 1); + uri_data->start = atoi (g_strdup_printf ("%.*s", charpos, indices)); + GST_DEBUG ("indices start: %d end %d\n", uri_data->start, uri_data->end); + } else { + GST_ERROR + ("Malformated multifile uri. You are using '@' and are missing ':'"); + } + uri_data->location = at + 1; + } else { + uri_data->location = g_strdup (&uri[prefix_size]); + } + GST_DEBUG ("location: %s\n", uri_data->location); + + return uri_data; +} + +static GstElement * +ges_multi_file_source_create_source (GESSource * source) +{ + GESMultiFileSource *self; + GstElement *bin, *src, *decodebin; + GstCaps *disc_caps; + GstDiscovererStreamInfo *stream_info; + GValue fps = G_VALUE_INIT; + GstCaps *caps; + GESUriSourceAsset *asset; + GESMultiFileURI *uri_data; + + self = (GESMultiFileSource *) source; + + asset = + GES_URI_SOURCE_ASSET (ges_extractable_get_asset (GES_EXTRACTABLE (self))); + + if (asset != NULL) { + stream_info = ges_uri_source_asset_get_stream_info (asset); + g_assert (stream_info); + disc_caps = gst_discoverer_stream_info_get_caps (stream_info); + caps = gst_caps_copy (disc_caps); + GST_DEBUG_OBJECT (disc_caps, "Got some nice caps"); + gst_object_unref (stream_info); + gst_caps_unref (disc_caps); + } else { + caps = gst_caps_new_empty (); + GST_WARNING ("Could not extract asset."); + } + + g_value_init (&fps, GST_TYPE_FRACTION); + gst_value_set_fraction (&fps, 25, 1); + gst_caps_set_value (caps, "framerate", &fps); + + bin = GST_ELEMENT (gst_bin_new ("multi-image-bin")); + src = gst_element_factory_make ("multifilesrc", NULL); + + uri_data = ges_multi_file_uri_new (self->uri); + g_object_set (src, "start-index", uri_data->start, "stop-index", + uri_data->end, "caps", caps, "location", uri_data->location, NULL); + g_free (uri_data); + + decodebin = gst_element_factory_make ("decodebin", NULL); + + gst_bin_add_many (GST_BIN (bin), src, decodebin, NULL); + gst_element_link_pads_full (src, "src", decodebin, "sink", + GST_PAD_LINK_CHECK_NOTHING); + + g_signal_connect (G_OBJECT (decodebin), "pad-added", + G_CALLBACK (pad_added_cb), bin); + + return bin; +} + +static void +ges_multi_file_source_class_init (GESMultiFileSourceClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESSourceClass *source_class = GES_SOURCE_CLASS (klass); + + object_class->get_property = ges_multi_file_source_get_property; + object_class->set_property = ges_multi_file_source_set_property; + object_class->dispose = ges_multi_file_source_dispose; + + /** + * GESMultiFileSource:uri: + * + * The uri of the file/resource to use. You can set a start index, + * a stop index and a sequence pattern. + * The format is <multifile://start:stop\@location-pattern>. + * The pattern uses printf string formating. + * + * Example uris: + * + * multifile:///home/you/image\%03d.jpg + * + * multifile://20:50@/home/you/sequence/\%04d.png + * + */ + g_object_class_install_property (object_class, PROP_URI, + g_param_spec_string ("uri", "URI", "multifile uri", + NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + source_class->create_source = ges_multi_file_source_create_source; +} + +static void +ges_multi_file_source_init (GESMultiFileSource * self) +{ + self->priv = ges_multi_file_source_get_instance_private (self); +} + +/* @uri: the URI the source should control + * + * Creates a new #GESMultiFileSource for the provided @uri. + * + * Returns: (transfer floating): A new #GESMultiFileSource. + */ +GESMultiFileSource * +ges_multi_file_source_new (gchar * uri) +{ + GESMultiFileSource *res; + GESAsset *asset = ges_asset_request (GES_TYPE_MULTI_FILE_SOURCE, uri, NULL); + + res = GES_MULTI_FILE_SOURCE (ges_asset_extract (asset, NULL)); + res->uri = g_strdup (uri); + gst_object_unref (asset); + + return res; +} diff --git a/ges/ges-multi-file-source.h b/ges/ges-multi-file-source.h new file mode 100644 index 0000000000..33da6bd90d --- /dev/null +++ b/ges/ges-multi-file-source.h @@ -0,0 +1,59 @@ +/* GStreamer Editing Services + * Copyright (C) 2013 Lubosz Sarnecki <lubosz@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> +#include <ges/ges-video-source.h> + +G_BEGIN_DECLS +#define GES_TYPE_MULTI_FILE_SOURCE ges_multi_file_source_get_type() +GES_DECLARE_TYPE(MultiFileSource, multi_file_source, MULTI_FILE_SOURCE); + +/** + * GESMultiFileSource: + */ +struct _GESMultiFileSource +{ + /*< private > */ + GESVideoSource parent; + + gchar *uri; + + GESMultiFileSourcePrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESMultiFileSourceClass +{ + GESVideoSourceClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API +GESMultiFileSource *ges_multi_file_source_new (gchar * uri); + +#define GES_MULTI_FILE_URI_PREFIX "multifile://" + +G_END_DECLS diff --git a/ges/ges-operation-clip.c b/ges/ges-operation-clip.c new file mode 100644 index 0000000000..d8be92dc97 --- /dev/null +++ b/ges/ges-operation-clip.c @@ -0,0 +1,53 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesoperationclip + * @title: GESOperationClip + * @short_description: Base Class for operations in a GESLayer + * + * Operations are any kind of object that both outputs AND consumes data. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges.h" +#include "ges-internal.h" +#include "ges-operation-clip.h" + +struct _GESOperationClipPrivate +{ + void *nada; +}; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESOperationClip, ges_operation_clip, + GES_TYPE_CLIP); + +static void +ges_operation_clip_class_init (GESOperationClipClass * klass) +{ +} + +static void +ges_operation_clip_init (GESOperationClip * self) +{ + self->priv = ges_operation_clip_get_instance_private (self); +} diff --git a/ges/ges-operation-clip.h b/ges/ges-operation-clip.h new file mode 100644 index 0000000000..cb4e1952c9 --- /dev/null +++ b/ges/ges-operation-clip.h @@ -0,0 +1,60 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Edward Hervey <edward.hervey@collabora.co.uk> + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <gst/gst.h> +#include <ges/ges-types.h> +#include <ges/ges-clip.h> + +G_BEGIN_DECLS + +#define GES_TYPE_OPERATION_CLIP ges_operation_clip_get_type() +GES_DECLARE_TYPE(OperationClip, operation_clip, OPERATION_CLIP); + +/** + * GESOperationClip: + * + * The GESOperationClip subclass. Subclasses can access these fields. + */ +struct _GESOperationClip { + /*< private >*/ + GESClip parent; + + GESOperationClipPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESOperationClipClass: + */ +struct _GESOperationClipClass { + /*< private >*/ + GESClipClass parent_class; + + /*< private >*/ + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +G_END_DECLS diff --git a/ges/ges-operation.c b/ges/ges-operation.c new file mode 100644 index 0000000000..4b0bece4f8 --- /dev/null +++ b/ges/ges-operation.c @@ -0,0 +1,47 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesoperation + * @title: GESOperation + * @short_description: Base Class for effects and overlays + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-track-element.h" +#include "ges-operation.h" + +G_DEFINE_ABSTRACT_TYPE (GESOperation, ges_operation, GES_TYPE_TRACK_ELEMENT); + +static void +ges_operation_class_init (GESOperationClass * klass) +{ + GESTrackElementClass *track_class = GES_TRACK_ELEMENT_CLASS (klass); + + track_class->nleobject_factorytype = "nleoperation"; +} + +static void +ges_operation_init (GESOperation * self) +{ +} diff --git a/ges/ges-operation.h b/ges/ges-operation.h new file mode 100644 index 0000000000..6d25a89b2d --- /dev/null +++ b/ges/ges-operation.h @@ -0,0 +1,62 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <gst/gst.h> +#include <ges/ges-types.h> +#include <ges/ges-track-element.h> + +G_BEGIN_DECLS + +#define GES_TYPE_OPERATION ges_operation_get_type() +GES_DECLARE_TYPE(Operation, operation, OPERATION); + +/** + * GESOperation: + * + * Base class for overlays, transitions, and effects + */ + +struct _GESOperation { + /*< private >*/ + GESTrackElement parent; + + GESOperationPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESOperationClass: + */ + +struct _GESOperationClass { + /*< private >*/ + GESTrackElementClass parent_class; + + /*< private >*/ + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +G_END_DECLS diff --git a/ges/ges-overlay-clip.c b/ges/ges-overlay-clip.c new file mode 100644 index 0000000000..8ab9695b92 --- /dev/null +++ b/ges/ges-overlay-clip.c @@ -0,0 +1,59 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesoverlayclip + * @title: GESOverlayClip + * @short_description: Base Class for overlays in a GESLayer + * + * Overlays are objects which modify the underlying layer(s). + * + * Examples of overlays include text, image watermarks, or audio dubbing. + * + * Transitions, which change from one source to another over time, are + * not considered overlays. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-operation-clip.h" +#include "ges-overlay-clip.h" + +struct _GESOverlayClipPrivate +{ + /* Dummy variable */ + void *nothing; +}; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESOverlayClip, ges_overlay_clip, + GES_TYPE_OPERATION_CLIP); + +static void +ges_overlay_clip_class_init (GESOverlayClipClass * klass) +{ +} + +static void +ges_overlay_clip_init (GESOverlayClip * self) +{ + self->priv = ges_overlay_clip_get_instance_private (self); +} diff --git a/ges/ges-overlay-clip.h b/ges/ges-overlay-clip.h new file mode 100644 index 0000000000..d556bf0c85 --- /dev/null +++ b/ges/ges-overlay-clip.h @@ -0,0 +1,58 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> +#include <ges/ges-operation-clip.h> + +G_BEGIN_DECLS + +#define GES_TYPE_OVERLAY_CLIP ges_overlay_clip_get_type() +GES_DECLARE_TYPE(OverlayClip, overlay_clip, OVERLAY_CLIP); + +/** + * GESOverlayClip: + */ + +struct _GESOverlayClip { + /*< private >*/ + GESOperationClip parent; + + GESOverlayClipPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESOverlayClipClass: + * @parent_class: parent class + */ + +struct _GESOverlayClipClass { + GESOperationClipClass parent_class; + + /*< private >*/ + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; +G_END_DECLS diff --git a/ges/ges-pipeline.c b/ges/ges-pipeline.c new file mode 100644 index 0000000000..9934059a78 --- /dev/null +++ b/ges/ges-pipeline.c @@ -0,0 +1,1500 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gespipeline + * @title: GESPipeline + * @short_description: Convenience GstPipeline for editing. + * @symbols: + * - ges_play_sink_convert_frame + * + * A #GESPipeline can take an audio-video #GESTimeline and conveniently + * link its #GESTrack-s to an internal #playsink element, for + * preview/playback, and an internal #encodebin element, for rendering. + * You can switch between these modes using ges_pipeline_set_mode(). + * + * You can choose the specific audio and video sinks used for previewing + * the timeline by setting the #GESPipeline:audio-sink and + * #GESPipeline:video-sink properties. + * + * You can set the encoding and save location used in rendering by calling + * ges_pipeline_set_render_settings(). + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> +#include <gst/video/videooverlay.h> +#include <stdio.h> + +#include "ges-internal.h" +#include "ges-pipeline.h" +#include "ges-screenshot.h" +#include "ges-audio-track.h" +#include "ges-video-track.h" + +GST_DEBUG_CATEGORY_STATIC (ges_pipeline_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT ges_pipeline_debug + +#define DEFAULT_TIMELINE_MODE GES_PIPELINE_MODE_PREVIEW +#define IN_RENDERING_MODE(timeline) ((timeline->priv->mode) & (GES_PIPELINE_MODE_RENDER | GES_PIPELINE_MODE_SMART_RENDER)) +#define CHECK_THREAD(pipeline) g_assert(pipeline->priv->valid_thread == g_thread_self()) + +/* Structure corresponding to a timeline - sink link */ + +typedef struct +{ + GESTrack *track; + GstElement *tee; + GstPad *srcpad; /* Timeline source pad */ + GstPad *playsinkpad; + GstPad *encodebinpad; +} OutputChain; + + +struct _GESPipelinePrivate +{ + GESTimeline *timeline; + GstElement *playsink; + GstElement *encodebin; + /* Note : urisink is only created when a URI has been provided */ + GstElement *urisink; + + GESPipelineFlags mode; + + GMutex dyn_mutex; + GList *chains; + GList *not_rendered_tracks; + + GstEncodingProfile *profile; + + GThread *valid_thread; +}; + +enum +{ + PROP_0, + PROP_AUDIO_SINK, + PROP_VIDEO_SINK, + PROP_TIMELINE, + PROP_MODE, + PROP_AUDIO_FILTER, + PROP_VIDEO_FILTER, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +static GstStateChangeReturn ges_pipeline_change_state (GstElement * + element, GstStateChange transition); + +static OutputChain *get_output_chain_for_track (GESPipeline * self, + GESTrack * track); +static OutputChain *new_output_chain_for_track (GESPipeline * self, + GESTrack * track); +static void _link_track (GESPipeline * self, GESTrack * track); +static void _unlink_track (GESPipeline * self, GESTrack * track); + +/**************************************************** + * Video Overlay vmethods implementation * + ****************************************************/ +static void +_overlay_expose (GstVideoOverlay * overlay) +{ + GESPipeline *pipeline = GES_PIPELINE (overlay); + + gst_video_overlay_expose (GST_VIDEO_OVERLAY (pipeline->priv->playsink)); +} + +static void +_overlay_handle_events (GstVideoOverlay * overlay, gboolean handle_events) +{ + GESPipeline *pipeline = GES_PIPELINE (overlay); + + gst_video_overlay_handle_events (GST_VIDEO_OVERLAY (pipeline->priv->playsink), + handle_events); +} + +static void +_overlay_set_render_rectangle (GstVideoOverlay * overlay, gint x, + gint y, gint width, gint height) +{ + GESPipeline *pipeline = GES_PIPELINE (overlay); + + gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (pipeline->priv-> + playsink), x, y, width, height); +} + +static void +_overlay_set_window_handle (GstVideoOverlay * overlay, guintptr handle) +{ + GESPipeline *pipeline = GES_PIPELINE (overlay); + + gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (pipeline-> + priv->playsink), handle); +} + +static void +video_overlay_init (gpointer g_iface, gpointer g_iface_data) +{ + GstVideoOverlayInterface *iface = (GstVideoOverlayInterface *) g_iface; + + iface->expose = _overlay_expose; + iface->handle_events = _overlay_handle_events; + iface->set_render_rectangle = _overlay_set_render_rectangle; + iface->set_window_handle = _overlay_set_window_handle; +} + +G_DEFINE_TYPE_WITH_CODE (GESPipeline, ges_pipeline, + GST_TYPE_PIPELINE, G_ADD_PRIVATE (GESPipeline) + G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY, video_overlay_init)); + +static void +ges_pipeline_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESPipeline *self = GES_PIPELINE (object); + + switch (property_id) { + case PROP_AUDIO_SINK: + g_object_get_property (G_OBJECT (self->priv->playsink), "audio-sink", + value); + break; + case PROP_VIDEO_SINK: + g_object_get_property (G_OBJECT (self->priv->playsink), "video-sink", + value); + break; + case PROP_TIMELINE: + g_value_set_object (value, self->priv->timeline); + break; + case PROP_MODE: + g_value_set_flags (value, self->priv->mode); + break; + case PROP_AUDIO_FILTER: + g_object_get_property (G_OBJECT (self->priv->playsink), "audio-filter", + value); + break; + case PROP_VIDEO_FILTER: + g_object_get_property (G_OBJECT (self->priv->playsink), "video-filter", + value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_pipeline_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESPipeline *self = GES_PIPELINE (object); + + switch (property_id) { + case PROP_AUDIO_SINK: + g_object_set_property (G_OBJECT (self->priv->playsink), "audio-sink", + value); + break; + case PROP_VIDEO_SINK: + g_object_set_property (G_OBJECT (self->priv->playsink), "video-sink", + value); + break; + case PROP_TIMELINE: + ges_pipeline_set_timeline (GES_PIPELINE (object), + g_value_get_object (value)); + break; + case PROP_MODE: + ges_pipeline_set_mode (GES_PIPELINE (object), g_value_get_flags (value)); + break; + case PROP_AUDIO_FILTER: + g_object_set (self->priv->playsink, "audio-filter", + GST_ELEMENT (g_value_get_object (value)), NULL); + break; + case PROP_VIDEO_FILTER: + g_object_set (self->priv->playsink, "video-filter", + GST_ELEMENT (g_value_get_object (value)), NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +_timeline_track_added_cb (GESTimeline * timeline, GESTrack * track, + GESPipeline * pipeline) +{ + track_disable_last_gap (track, + ! !(pipeline->priv->mode & (GES_PIPELINE_MODE_RENDER | + GES_PIPELINE_MODE_SMART_RENDER))); + _link_track (pipeline, track); +} + +static void +_timeline_track_removed_cb (GESTimeline * timeline, GESTrack * track, + GESPipeline * pipeline) +{ + _unlink_track (pipeline, track); +} + +static void +ges_pipeline_dispose (GObject * object) +{ + GESPipeline *self = GES_PIPELINE (object); + + if (self->priv->playsink) { + if (self->priv->mode & (GES_PIPELINE_MODE_PREVIEW)) + gst_bin_remove (GST_BIN (object), self->priv->playsink); + else + gst_object_unref (self->priv->playsink); + self->priv->playsink = NULL; + } + + if (self->priv->encodebin) { + if (self->priv->mode & (GES_PIPELINE_MODE_RENDER | + GES_PIPELINE_MODE_SMART_RENDER)) + gst_bin_remove (GST_BIN (object), self->priv->encodebin); + else + gst_object_unref (self->priv->encodebin); + self->priv->encodebin = NULL; + } + + if (self->priv->profile) { + gst_encoding_profile_unref (self->priv->profile); + self->priv->profile = NULL; + } + + if (self->priv->timeline) { + g_signal_handlers_disconnect_by_func (self->priv->timeline, + _timeline_track_added_cb, self); + g_signal_handlers_disconnect_by_func (self->priv->timeline, + _timeline_track_removed_cb, self); + gst_element_set_state (GST_ELEMENT (self->priv->timeline), GST_STATE_NULL); + } + + G_OBJECT_CLASS (ges_pipeline_parent_class)->dispose (object); +} + +static void +ges_pipeline_class_init (GESPipelineClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + GST_DEBUG_CATEGORY_INIT (ges_pipeline_debug, "gespipeline", + GST_DEBUG_FG_YELLOW, "ges pipeline"); + + object_class->dispose = ges_pipeline_dispose; + object_class->get_property = ges_pipeline_get_property; + object_class->set_property = ges_pipeline_set_property; + + /** + * GESPipeline:audio-sink: + * + * The audio sink used for preview. This exposes the + * #playsink:audio-sink property of the internal #playsink. + */ + properties[PROP_AUDIO_SINK] = g_param_spec_object ("audio-sink", "Audio Sink", + "Audio sink for the preview.", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * GESPipeline:video-sink: + * + * The video sink used for preview. This exposes the + * #playsink:video-sink property of the internal #playsink. + */ + properties[PROP_VIDEO_SINK] = g_param_spec_object ("video-sink", "Video Sink", + "Video sink for the preview.", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * GESPipeline:timeline: + * + * The timeline used by this pipeline, whose content it will play and + * render, or %NULL if the pipeline does not yet have a timeline. + * + * Note that after you set the timeline for the first time, subsequent + * calls to change the timeline will fail. + */ + properties[PROP_TIMELINE] = g_param_spec_object ("timeline", "Timeline", + "The timeline to use in this pipeline.", + GES_TYPE_TIMELINE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * GESPipeline:mode: + * + * The pipeline's mode. In preview mode (for audio or video, or both) + * the pipeline can display the timeline's content to an end user. In + * rendering mode the pipeline can encode the timeline's content and + * save it to a file. + */ + properties[PROP_MODE] = g_param_spec_flags ("mode", "Mode", + "The pipeline's mode.", + GES_TYPE_PIPELINE_FLAGS, DEFAULT_TIMELINE_MODE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * GESPipeline:audio-filter: + * + * The audio filter(s) to apply during playback in preview mode, + * immediately before the #GESPipeline:audio-sink. This exposes the + * #playsink:audio-filter property of the internal #playsink. + * + * Since: 1.6.0 + */ + properties[PROP_AUDIO_FILTER] = + g_param_spec_object ("audio-filter", "Audio filter", + "the audio filter(s) to apply, if possible", GST_TYPE_ELEMENT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * GESPipeline:video-filter: + * + * The video filter(s) to apply during playback in preview mode, + * immediately before the #GESPipeline:video-sink. This exposes the + * #playsink:video-filter property of the internal #playsink. + * + * Since: 1.6.0 + */ + properties[PROP_VIDEO_FILTER] = + g_param_spec_object ("video-filter", "Video filter", + "the Video filter(s) to apply, if possible", GST_TYPE_ELEMENT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, PROP_LAST, properties); + + element_class->change_state = GST_DEBUG_FUNCPTR (ges_pipeline_change_state); + + /* TODO : Add state_change handlers + * Don't change state if we don't have a timeline */ +} + +static void +ges_pipeline_init (GESPipeline * self) +{ + GST_INFO_OBJECT (self, "Creating new 'playsink'"); + self->priv = ges_pipeline_get_instance_private (self); + self->priv->valid_thread = g_thread_self (); + + self->priv->playsink = + gst_element_factory_make ("playsink", "internal-sinks"); + self->priv->encodebin = + gst_element_factory_make ("encodebin", "internal-encodebin"); + g_object_set (self->priv->encodebin, "avoid-reencoding", TRUE, NULL); + + if (G_UNLIKELY (self->priv->playsink == NULL)) + goto no_playsink; + if (G_UNLIKELY (self->priv->encodebin == NULL)) + goto no_encodebin; + + ges_pipeline_set_mode (self, DEFAULT_TIMELINE_MODE); + + return; + +no_playsink: + { + GST_ERROR_OBJECT (self, "Can't create playsink instance !"); + return; + } +no_encodebin: + { + GST_ERROR_OBJECT (self, "Can't create encodebin instance !"); + return; + } +} + +/** + * ges_pipeline_new: + * + * Creates a new pipeline. + * + * Returns: (transfer floating): The newly created pipeline. + */ +GESPipeline * +ges_pipeline_new (void) +{ + return GES_PIPELINE (gst_element_factory_make ("gespipeline", NULL)); +} + +#define TRACK_COMPATIBLE_PROFILE(tracktype, profile) \ + ( (GST_IS_ENCODING_AUDIO_PROFILE (profile) && (tracktype) == GES_TRACK_TYPE_AUDIO) || \ + (GST_IS_ENCODING_VIDEO_PROFILE (profile) && (tracktype) == GES_TRACK_TYPE_VIDEO)) + +static gboolean +_track_is_compatible_with_profile (GESPipeline * self, GESTrack * track, + GstEncodingProfile * prof) +{ + if (TRACK_COMPATIBLE_PROFILE (track->type, prof)) + return TRUE; + + return FALSE; +} + +static gboolean +ges_pipeline_update_caps (GESPipeline * self) +{ + GList *ltrack, *tracks, *lstream; + + if (!self->priv->profile) + return TRUE; + + GST_DEBUG ("Updating track caps"); + + tracks = ges_timeline_get_tracks (self->priv->timeline); + + /* Take each stream of the encoding profile and find a matching + * track to set the caps on */ + for (ltrack = tracks; ltrack; ltrack = ltrack->next) { + GESTrack *track = (GESTrack *) ltrack->data; + GList *allstreams; + + if (!GST_IS_ENCODING_CONTAINER_PROFILE (self->priv->profile)) { + if (_track_is_compatible_with_profile (self, track, self->priv->profile)) { + gst_object_unref (track); + + goto done; + } else { + gst_object_unref (track); + continue; + } + } + + allstreams = (GList *) + gst_encoding_container_profile_get_profiles ( + (GstEncodingContainerProfile *) self->priv->profile); + + /* Find a matching stream setting */ + for (lstream = allstreams; lstream; lstream = lstream->next) { + GstEncodingProfile *prof = (GstEncodingProfile *) lstream->data; + if (_track_is_compatible_with_profile (self, track, prof)) + break; + } + + gst_object_unref (track); + } + +done: + if (tracks) + g_list_free (tracks); + + GST_DEBUG ("Done updating caps"); + + return TRUE; +} + +static void +_link_tracks (GESPipeline * pipeline) +{ + GList *tmp; + + GST_DEBUG_OBJECT (pipeline, "Linking tracks"); + + if (!pipeline->priv->timeline) { + GST_INFO_OBJECT (pipeline, "Not timeline set yet, doing nothing"); + + return; + } + + for (tmp = pipeline->priv->timeline->tracks; tmp; tmp = tmp->next) + _link_track (pipeline, tmp->data); + + if (IN_RENDERING_MODE (pipeline)) { + GString *unlinked_issues = NULL; + GstIterator *pads; + gboolean done = FALSE; + GValue paditem = { 0, }; + + pads = gst_element_iterate_sink_pads (pipeline->priv->encodebin); + while (!done) { + switch (gst_iterator_next (pads, &paditem)) { + case GST_ITERATOR_OK: + { + GstPad *testpad = g_value_get_object (&paditem); + if (!gst_pad_is_linked (testpad)) { + GstCaps *sinkcaps = gst_pad_query_caps (testpad, NULL); + gchar *caps_string = gst_caps_to_string (sinkcaps); + gchar *path_string = + gst_object_get_path_string (GST_OBJECT (testpad)); + gst_caps_unref (sinkcaps); + + if (!unlinked_issues) + unlinked_issues = + g_string_new ("Following encodebin pads are not linked:\n"); + + g_string_append_printf (unlinked_issues, " - %s: %s", path_string, + caps_string); + g_free (caps_string); + g_free (path_string); + } + g_value_reset (&paditem); + } + break; + case GST_ITERATOR_DONE: + case GST_ITERATOR_ERROR: + done = TRUE; + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (pads); + break; + } + } + g_value_reset (&paditem); + gst_iterator_free (pads); + + if (unlinked_issues) { + GST_ELEMENT_ERROR (pipeline, STREAM, FAILED, (NULL), ("%s", + unlinked_issues->str)); + g_string_free (unlinked_issues, TRUE); + } + } +} + +static void +_unlink_tracks (GESPipeline * pipeline) +{ + GList *tmp; + + GST_DEBUG_OBJECT (pipeline, "Disconnecting all tracks"); + if (!pipeline->priv->timeline) { + GST_INFO_OBJECT (pipeline, "Not timeline set yet, doing nothing"); + + return; + } + + for (tmp = pipeline->priv->timeline->tracks; tmp; tmp = tmp->next) + _unlink_track (pipeline, tmp->data); +} + +static GstStateChangeReturn +ges_pipeline_change_state (GstElement * element, GstStateChange transition) +{ + GESPipeline *self; + GstStateChangeReturn ret; + + self = GES_PIPELINE (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + if (G_UNLIKELY (self->priv->timeline == NULL)) { + GST_ERROR_OBJECT (element, + "No GESTimeline set on the pipeline, cannot play !"); + ret = GST_STATE_CHANGE_FAILURE; + goto done; + } + if (IN_RENDERING_MODE (self)) { + GST_DEBUG ("rendering => Updating pipeline caps"); + /* Set caps on all tracks according to profile if present */ + if (!ges_pipeline_update_caps (self)) { + GST_ERROR_OBJECT (element, "Error setting the caps for rendering"); + ret = GST_STATE_CHANGE_FAILURE; + goto done; + } + } + _link_tracks (self); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + { + GList *tmp; + + for (tmp = self->priv->not_rendered_tracks; tmp; tmp = tmp->next) + gst_element_set_locked_state (tmp->data, FALSE); + } + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + { + GstElement *queue = gst_bin_get_by_name (GST_BIN (self->priv->playsink), + "vqueue"); + + if (queue) { + GST_INFO_OBJECT (self, "Setting playsink video queue max-size-time to" + " 2 seconds."); + g_object_set (G_OBJECT (queue), "max-size-buffers", 0, + "max-size-bytes", 0, "max-size-time", (gint64) 2 * GST_SECOND, + NULL); + gst_object_unref (queue); + } + break; + } + default: + break; + } + + ret = + GST_ELEMENT_CLASS (ges_pipeline_parent_class)->change_state + (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + case GST_STATE_CHANGE_READY_TO_NULL: + case GST_STATE_CHANGE_NULL_TO_NULL: + _unlink_tracks (self); + break; + default: + break; + } + +done: + return ret; +} + +static OutputChain * +new_output_chain_for_track (GESPipeline * self, GESTrack * track) +{ + OutputChain *chain; + + chain = g_new0 (OutputChain, 1); + chain->track = track; + + return chain; +} + +static OutputChain * +get_output_chain_for_track (GESPipeline * self, GESTrack * track) +{ + GList *tmp; + + for (tmp = self->priv->chains; tmp; tmp = tmp->next) { + OutputChain *chain = (OutputChain *) tmp->data; + if (chain->track == track) + return chain; + } + + return NULL; +} + +/* Fetches a compatible pad on the target element which isn't already + * linked */ +static GstPad * +get_compatible_unlinked_pad (GstElement * element, GESTrack * track) +{ + GstPad *res = NULL; + GstIterator *pads; + gboolean done = FALSE; + const GstCaps *srccaps; + GValue paditem = { 0, }; + + if (G_UNLIKELY (track == NULL)) + goto no_track; + + GST_DEBUG_OBJECT (element, " track %" GST_PTR_FORMAT, track); + + pads = gst_element_iterate_sink_pads (element); + srccaps = ges_track_get_caps (track); + + GST_DEBUG_OBJECT (track, "srccaps %" GST_PTR_FORMAT, srccaps); + + while (!done) { + switch (gst_iterator_next (pads, &paditem)) { + case GST_ITERATOR_OK: + { + GstPad *testpad = g_value_get_object (&paditem); + + if (!gst_pad_is_linked (testpad)) { + GstCaps *sinkcaps = gst_pad_query_caps (testpad, NULL); + + GST_DEBUG_OBJECT (track, "sinkccaps %" GST_PTR_FORMAT, sinkcaps); + + if (gst_caps_can_intersect (srccaps, sinkcaps)) { + res = gst_object_ref (testpad); + done = TRUE; + } + gst_caps_unref (sinkcaps); + } + g_value_reset (&paditem); + } + break; + case GST_ITERATOR_DONE: + case GST_ITERATOR_ERROR: + done = TRUE; + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (pads); + break; + } + } + g_value_reset (&paditem); + gst_iterator_free (pads); + + return res; + +no_track: + { + GST_ERROR ("No track to check against"); + return NULL; + } +} + +static void +_link_track (GESPipeline * self, GESTrack * track) +{ + GstPad *pad; + OutputChain *chain; + GstPad *sinkpad; + GstCaps *caps; + GstPadLinkReturn lret; + gboolean reconfigured = FALSE; + gboolean ignore; + + pad = ges_timeline_get_pad_for_track (self->priv->timeline, track); + if (G_UNLIKELY (!pad)) { + GST_ELEMENT_ERROR (self, STREAM, FAILED, (NULL), + ("Trying to link %" GST_PTR_FORMAT + " but no pad is exposed for it.", track)); + return; + } + + caps = gst_pad_query_caps (pad, NULL); + GST_DEBUG_OBJECT (self, "new pad %s:%s , caps:%" GST_PTR_FORMAT, + GST_DEBUG_PAD_NAME (pad), caps); + gst_caps_unref (caps); + + /* FIXME: provide a way for the user to select which audio, video or + * text track to use in preview mode when a timeline has multiple audio, + * video or text tracks. Also provide a way to switch between these. */ + + /* Don't connect track if it's not going to be used */ + ignore = TRUE; + /* only support audio and video. Technically, preview mode could support + * text quite easily, but this isn't yet the case for rendering using + * encodebin */ + if (track->type == GES_TRACK_TYPE_AUDIO || + track->type == GES_TRACK_TYPE_VIDEO) { + if (IN_RENDERING_MODE (self)) + ignore = FALSE; + else if (track->type == GES_TRACK_TYPE_VIDEO && + self->priv->mode & GES_PIPELINE_MODE_PREVIEW_VIDEO) + ignore = FALSE; + else if (track->type == GES_TRACK_TYPE_AUDIO && + self->priv->mode & GES_PIPELINE_MODE_PREVIEW_AUDIO) + ignore = FALSE; + } + + if (ignore) { + gst_object_unref (pad); + GST_DEBUG_OBJECT (self, "Ignoring track (type %u). Not linking", + track->type); + return; + } + + /* Get an existing chain or create it */ + if (!(chain = get_output_chain_for_track (self, track))) + chain = new_output_chain_for_track (self, track); + + if (chain->tee) { + gst_object_unref (pad); + GST_INFO_OBJECT (self, "Chain is already built (%" GST_PTR_FORMAT ")", + chain->encodebinpad ? chain->encodebinpad : chain->playsinkpad); + + return; + } + + chain->srcpad = pad; + + /* Adding tee */ + chain->tee = gst_element_factory_make ("tee", NULL); + gst_bin_add (GST_BIN_CAST (self), chain->tee); + gst_element_sync_state_with_parent (chain->tee); + + /* Linking pad to tee */ + sinkpad = gst_element_get_static_pad (chain->tee, "sink"); + lret = gst_pad_link (pad, sinkpad); + if (lret != GST_PAD_LINK_OK) { + GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, + (NULL), ("Could not link the tee (%s)", gst_pad_link_get_name (lret))); + goto error; + } + + gst_object_unref (sinkpad); + + /* Connect playsink */ + if (self->priv->mode & GES_PIPELINE_MODE_PREVIEW) { + const gchar *sinkpad_name; + GstPad *tmppad; + + GST_DEBUG_OBJECT (self, "Connecting to playsink"); + + switch (track->type) { + case GES_TRACK_TYPE_VIDEO: + sinkpad_name = "video_sink"; + break; + case GES_TRACK_TYPE_AUDIO: + sinkpad_name = "audio_sink"; + break; + case GES_TRACK_TYPE_TEXT: + sinkpad_name = "text_sink"; + break; + default: + GST_WARNING_OBJECT (self, "Can't handle tracks of type %d yet", + track->type); + goto error; + } + + /* Request a sinkpad from playsink */ + if (G_UNLIKELY (!(sinkpad = + gst_element_request_pad_simple (self->priv->playsink, + sinkpad_name)))) { + GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, + (NULL), ("Could not get a pad from playsink for %s", sinkpad_name)); + goto error; + } + + tmppad = gst_element_request_pad_simple (chain->tee, "src_%u"); + lret = gst_pad_link_full (tmppad, sinkpad, GST_PAD_LINK_CHECK_NOTHING); + if (G_UNLIKELY (lret != GST_PAD_LINK_OK)) { + gst_object_unref (tmppad); + GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, + (NULL), + ("Could not link %" GST_PTR_FORMAT " and %" GST_PTR_FORMAT " (%s)", + tmppad, sinkpad, gst_pad_link_get_name (lret))); + goto error; + } + gst_object_unref (tmppad); + + GST_DEBUG ("Reconfiguring playsink"); + + /* reconfigure playsink */ + g_signal_emit_by_name (self->priv->playsink, "reconfigure", &reconfigured); + GST_DEBUG ("'reconfigure' returned %d", reconfigured); + + /* We still hold a reference on the sinkpad */ + chain->playsinkpad = sinkpad; + } + + /* Connect to encodebin */ + if (IN_RENDERING_MODE (self)) { + GstPad *tmppad; + GST_DEBUG_OBJECT (self, "Connecting to encodebin"); + + if (!chain->encodebinpad) { + /* Check for unused static pads */ + sinkpad = get_compatible_unlinked_pad (self->priv->encodebin, track); + + if (sinkpad == NULL) { + GstCaps *caps = gst_pad_query_caps (pad, NULL); + + /* If no compatible static pad is available, request a pad */ + g_signal_emit_by_name (self->priv->encodebin, "request-pad", caps, + &sinkpad); + + if (G_UNLIKELY (sinkpad == NULL)) { + gst_element_set_locked_state (GST_ELEMENT (track), TRUE); + + self->priv->not_rendered_tracks = + g_list_append (self->priv->not_rendered_tracks, track); + + GST_INFO_OBJECT (self, + "Couldn't get a pad from encodebin for: %" GST_PTR_FORMAT, caps); + gst_caps_unref (caps); + goto error; + } + + gst_caps_unref (caps); + } + chain->encodebinpad = sinkpad; + GST_INFO_OBJECT (track, "Linked to %" GST_PTR_FORMAT, sinkpad); + } + + tmppad = gst_element_request_pad_simple (chain->tee, "src_%u"); + if (G_UNLIKELY (gst_pad_link_full (tmppad, chain->encodebinpad, + GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) { + GST_ERROR_OBJECT (self, "Couldn't link track pad to encodebin"); + goto error; + } + gst_object_unref (tmppad); + + } + + /* If chain wasn't already present, insert it in list */ + if (!get_output_chain_for_track (self, track)) + self->priv->chains = g_list_append (self->priv->chains, chain); + + GST_DEBUG ("done"); + gst_object_unref (pad); + return; + +error: + { + gst_object_unref (pad); + if (chain->tee) { + gst_element_set_state (chain->tee, GST_STATE_NULL); + gst_bin_remove (GST_BIN_CAST (self), chain->tee); + } + if (sinkpad) + gst_object_unref (sinkpad); + + g_free (chain); + } +} + +static void +_unlink_track (GESPipeline * self, GESTrack * track) +{ + OutputChain *chain; + + GST_DEBUG_OBJECT (self, "Unlinking removed %" GST_PTR_FORMAT, track); + + if (G_UNLIKELY (!(chain = get_output_chain_for_track (self, track)))) { + GST_DEBUG_OBJECT (self, "Track wasn't used"); + return; + } + + /* Unlink encodebin */ + if (chain->encodebinpad) { + GstPad *peer = gst_pad_get_peer (chain->encodebinpad); + gst_pad_unlink (peer, chain->encodebinpad); + gst_object_unref (peer); + gst_element_release_request_pad (self->priv->encodebin, + chain->encodebinpad); + gst_object_unref (chain->encodebinpad); + } + + /* Unlink playsink */ + if (chain->playsinkpad) { + GstPad *peer = gst_pad_get_peer (chain->playsinkpad); + gst_pad_unlink (peer, chain->playsinkpad); + gst_object_unref (peer); + gst_element_release_request_pad (self->priv->playsink, chain->playsinkpad); + gst_object_unref (chain->playsinkpad); + } + + gst_element_set_state (chain->tee, GST_STATE_NULL); + gst_bin_remove (GST_BIN (self), chain->tee); + + self->priv->chains = g_list_remove (self->priv->chains, chain); + g_free (chain); + + GST_DEBUG ("done"); +} + +/** + * ges_pipeline_set_timeline: + * @pipeline: A #GESPipeline + * @timeline: (transfer full): The timeline to set for @pipeline + * + * Takes the given timeline and sets it as the #GESPipeline:timeline for + * the pipeline. + * + * Note that you should only call this method once on a given pipeline + * because a pipeline can not have its #GESPipeline:timeline changed after + * it has been set. + * + * Returns: %TRUE if @timeline was successfully given to @pipeline. + */ +/* FIXME: allow us to set a new timeline and a NULL timeline */ +/* FIXME 2.0: since the method can fail, (transfer none) would be more + * appropriate for the timeline argument (currently, it is only + * transferred if the method is successful, which makes the memory + * transfer mixed!) */ +gboolean +ges_pipeline_set_timeline (GESPipeline * pipeline, GESTimeline * timeline) +{ + + g_return_val_if_fail (GES_IS_PIPELINE (pipeline), FALSE); + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + g_return_val_if_fail (pipeline->priv->timeline == NULL, FALSE); + CHECK_THREAD (pipeline); + + GST_DEBUG ("pipeline:%p, timeline:%p", timeline, pipeline); + + if (G_UNLIKELY (!gst_bin_add (GST_BIN_CAST (pipeline), + GST_ELEMENT (timeline)))) { + return FALSE; + } + pipeline->priv->timeline = timeline; + + g_signal_connect (timeline, "track-added", + G_CALLBACK (_timeline_track_added_cb), pipeline); + g_signal_connect (timeline, "track-removed", + G_CALLBACK (_timeline_track_removed_cb), pipeline); + /* FIXME Check if we should rollback if we can't sync state */ + gst_element_sync_state_with_parent (GST_ELEMENT (timeline)); + + return TRUE; +} + +/** + * ges_pipeline_set_render_settings: + * @pipeline: A #GESPipeline + * @output_uri: The URI to save the #GESPipeline:timeline rendering + * result to + * @profile: The encoding to use for rendering the #GESPipeline:timeline + * + * Specifies encoding setting to be used by the pipeline to render its + * #GESPipeline:timeline, and where the result should be written to. + * + * This method **must** be called before setting the pipeline mode to + * #GES_PIPELINE_MODE_RENDER. + * + * Returns: %TRUE if the settings were successfully set on @pipeline. + */ +gboolean +ges_pipeline_set_render_settings (GESPipeline * pipeline, + const gchar * output_uri, GstEncodingProfile * profile) +{ + GError *err = NULL; + GstEncodingProfile *set_profile; + guint n_videotracks = 0, n_audiotracks = 0; + + g_return_val_if_fail (GES_IS_PIPELINE (pipeline), FALSE); + CHECK_THREAD (pipeline); + + /* FIXME Properly handle multi track, for now GESPipeline + * only handles single track per type, so we should just set the + * presence to 1. + */ + if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) { + const GList *tmpprofiles = + gst_encoding_container_profile_get_profiles + (GST_ENCODING_CONTAINER_PROFILE (profile)); + GList *tmptrack, *tracks = + ges_timeline_get_tracks (pipeline->priv->timeline); + + for (tmptrack = tracks; tmptrack; tmptrack = tmptrack->next) { + if (GES_IS_AUDIO_TRACK (tmptrack->data)) + n_audiotracks++; + else if (GES_IS_VIDEO_TRACK (tmptrack->data)) + n_videotracks++; + } + g_list_free_full (tracks, gst_object_unref); + + for (; tmpprofiles; tmpprofiles = tmpprofiles->next) { + if (!gst_encoding_profile_is_enabled (tmpprofiles->data)) + continue; + + if (GST_IS_ENCODING_AUDIO_PROFILE (tmpprofiles->data)) { + if (n_audiotracks) { + n_audiotracks--; + } else { + GST_INFO_OBJECT (pipeline, "No audio track but got an audio profile, " + " make it optional: %" GST_PTR_FORMAT, tmpprofiles); + gst_encoding_profile_set_presence (tmpprofiles->data, 0); + + continue; + } + } else if (GST_IS_ENCODING_VIDEO_PROFILE (tmpprofiles->data)) { + if (n_videotracks) { + n_videotracks--; + } else { + GST_INFO_OBJECT (pipeline, "No video track but got a video profile, " + " make it optional: %" GST_PTR_FORMAT, tmpprofiles); + gst_encoding_profile_set_presence (tmpprofiles->data, 0); + + continue; + } + } else { + continue; + } + + GST_DEBUG_OBJECT (pipeline, "Setting presence to 1!"); + gst_encoding_profile_set_single_segment (tmpprofiles->data, TRUE); + if (gst_encoding_profile_get_presence (tmpprofiles->data) == 0) + gst_encoding_profile_set_presence (tmpprofiles->data, 1); + gst_encoding_profile_set_allow_dynamic_output (tmpprofiles->data, FALSE); + } + } + + /* Clear previous URI sink if it existed */ + if (pipeline->priv->urisink) { + GstObject *sink_parent = + gst_object_get_parent (GST_OBJECT (pipeline->priv->urisink)); + if (sink_parent == GST_OBJECT (pipeline)) + gst_bin_remove (GST_BIN (pipeline), pipeline->priv->urisink); + pipeline->priv->urisink = NULL; + gst_clear_object (&sink_parent); + } + + pipeline->priv->urisink = + gst_element_make_from_uri (GST_URI_SINK, output_uri, NULL, &err); + if (G_UNLIKELY (pipeline->priv->urisink == NULL)) { + GST_ERROR_OBJECT (pipeline, "Couldn't not create sink for URI %s: '%s'", + output_uri, ((err + && err->message) ? err->message : "failed to create element")); + g_clear_error (&err); + return FALSE; + } + + if (pipeline->priv->profile) + gst_encoding_profile_unref (pipeline->priv->profile); + g_object_set (pipeline->priv->encodebin, "avoid-reencoding", + !(!(pipeline->priv->mode & GES_PIPELINE_MODE_SMART_RENDER)), NULL); + g_object_set (pipeline->priv->encodebin, "profile", profile, NULL); + g_object_get (pipeline->priv->encodebin, "profile", &set_profile, NULL); + + if (set_profile == NULL) { + GST_ERROR_OBJECT (pipeline, "Profile %" GST_PTR_FORMAT " could no be set", + profile); + + return FALSE; + } + + /* We got a reference when getting back the profile */ + pipeline->priv->profile = profile; + + return TRUE; +} + +/** + * ges_pipeline_get_mode: + * @pipeline: A #GESPipeline + * + * Gets the #GESPipeline:mode of the pipeline. + * + * Returns: The current mode of @pipeline. + **/ +GESPipelineFlags +ges_pipeline_get_mode (GESPipeline * pipeline) +{ + return pipeline->priv->mode; +} + +/** + * ges_pipeline_set_mode: + * @pipeline: A #GESPipeline + * @mode: The mode to set for @pipeline + * + * Sets the #GESPipeline:mode of the pipeline. + * + * Note that the pipeline will be set to #GST_STATE_NULL during this call to + * perform the necessary changes. You will need to set the state again yourself + * after calling this. + * + * > **NOTE**: [Rendering settings](ges_pipeline_set_render_settings) need to be + * > set before setting @mode to #GES_PIPELINE_MODE_RENDER or + * > #GES_PIPELINE_MODE_SMART_RENDER, the call to this method will fail + * > otherwise. + * + * Returns: %TRUE if the mode of @pipeline was successfully set to @mode. + **/ +gboolean +ges_pipeline_set_mode (GESPipeline * pipeline, GESPipelineFlags mode) +{ + + GList *tmp; + g_return_val_if_fail (GES_IS_PIPELINE (pipeline), FALSE); + CHECK_THREAD (pipeline); + + GST_DEBUG_OBJECT (pipeline, "current mode : %d, mode : %d", + pipeline->priv->mode, mode); + + /* fast-path, nothing to change */ + if (mode == pipeline->priv->mode) + return TRUE; + + /* FIXME: It would be nice if we are only (de)activating preview + * modes to not set the whole pipeline to NULL, but instead just + * do the proper (un)linking to playsink. */ + + /* Switch pipeline to NULL since we're changing the configuration */ + gst_element_set_state (GST_ELEMENT_CAST (pipeline), GST_STATE_NULL); + + + if (pipeline->priv->timeline) { + gboolean disabled = + ! !(mode & (GES_PIPELINE_MODE_RENDER | GES_PIPELINE_MODE_SMART_RENDER)); + + for (tmp = pipeline->priv->timeline->tracks; tmp; tmp = tmp->next) + track_disable_last_gap (GES_TRACK (tmp->data), disabled); + } + + /* remove no-longer needed components */ + if (pipeline->priv->mode & GES_PIPELINE_MODE_PREVIEW && + !(mode & GES_PIPELINE_MODE_PREVIEW)) { + /* Disable playsink */ + GST_DEBUG ("Disabling playsink"); + gst_object_ref (pipeline->priv->playsink); + gst_bin_remove (GST_BIN_CAST (pipeline), pipeline->priv->playsink); + } + if ((pipeline->priv->mode & + (GES_PIPELINE_MODE_RENDER | GES_PIPELINE_MODE_SMART_RENDER)) && + !(mode & (GES_PIPELINE_MODE_RENDER | GES_PIPELINE_MODE_SMART_RENDER))) { + + /* Disable render bin */ + GST_DEBUG ("Disabling rendering bin"); + ges_timeline_thaw_commit (pipeline->priv->timeline); + gst_object_ref (pipeline->priv->encodebin); + gst_object_ref (pipeline->priv->urisink); + gst_bin_remove_many (GST_BIN_CAST (pipeline), + pipeline->priv->encodebin, pipeline->priv->urisink, NULL); + } + + /* Add new elements */ + if (!(pipeline->priv->mode & GES_PIPELINE_MODE_PREVIEW) && + (mode & GES_PIPELINE_MODE_PREVIEW)) { + /* Add playsink */ + GST_DEBUG ("Adding playsink"); + if (!gst_bin_add (GST_BIN_CAST (pipeline), pipeline->priv->playsink)) { + GST_ERROR_OBJECT (pipeline, "Couldn't add playsink"); + return FALSE; + } + } + if (!(pipeline->priv->mode & + (GES_PIPELINE_MODE_RENDER | GES_PIPELINE_MODE_SMART_RENDER)) && + (mode & (GES_PIPELINE_MODE_RENDER | GES_PIPELINE_MODE_SMART_RENDER))) { + /* Adding render bin */ + GST_DEBUG ("Adding render bin"); + /* in render mode the commit needs to be locked, see #136 */ + ges_timeline_freeze_commit (pipeline->priv->timeline); + if (G_UNLIKELY (pipeline->priv->urisink == NULL)) { + GST_ERROR_OBJECT (pipeline, "Output URI not set !"); + return FALSE; + } + if (!gst_bin_add (GST_BIN_CAST (pipeline), pipeline->priv->encodebin)) { + GST_ERROR_OBJECT (pipeline, "Couldn't add encodebin"); + return FALSE; + } + if (!gst_bin_add (GST_BIN_CAST (pipeline), pipeline->priv->urisink)) { + GST_ERROR_OBJECT (pipeline, "Couldn't add URI sink"); + return FALSE; + } + g_object_set (pipeline->priv->encodebin, "avoid-reencoding", + !(!(mode & GES_PIPELINE_MODE_SMART_RENDER)), NULL); + + gst_element_link_pads_full (pipeline->priv->encodebin, "src", + pipeline->priv->urisink, "sink", GST_PAD_LINK_CHECK_NOTHING); + } + + if (pipeline->priv->timeline) { + ges_timeline_set_smart_rendering (pipeline->priv->timeline, + (mode & GES_PIPELINE_MODE_SMART_RENDER) != 0); + } + + /* FIXUPS */ + /* FIXME + * If we are rendering, set playsink to sync=False, + * If we are NOT rendering, set playsink to sync=TRUE */ + + pipeline->priv->mode = mode; + + return TRUE; +} + +/** + * ges_pipeline_get_thumbnail: + * @self: A #GESPipeline in #GST_STATE_PLAYING or #GST_STATE_PAUSED + * @caps: (transfer none): Some caps to specifying the desired format, or + * #GST_CAPS_ANY to use the native format + * + * Gets a sample from the pipeline of the currently displayed image in + * preview, in the specified format. + * + * Note that if you use "ANY" caps for @caps, then the current format of + * the image is used. You can retrieve these caps from the returned sample + * with gst_sample_get_caps(). + * + * Returns: (transfer full): A sample of @self's current image preview in + * the format given by @caps, or %NULL if an error prevented fetching the + * sample. + */ + +GstSample * +ges_pipeline_get_thumbnail (GESPipeline * self, GstCaps * caps) +{ + GstElement *sink; + + g_return_val_if_fail (GES_IS_PIPELINE (self), FALSE); + CHECK_THREAD (self); + + sink = self->priv->playsink; + + if (!sink) { + GST_WARNING ("thumbnailing can only be done if we have a playsink"); + return NULL; + } + + return ges_play_sink_convert_frame (sink, caps); +} + +/** + * ges_pipeline_save_thumbnail: + * @self: A #GESPipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED + * @width: The requested pixel width of the image, or -1 to use the native + * size + * @height: The requested pixel height of the image, or -1 to use the + * native size + * @format: The desired mime type (for example, "image/jpeg") + * @location: The path to save the thumbnail to + * @error: (out) (allow-none) (transfer full): An error to be set in case + * something goes wrong, or %NULL to ignore + * + * Saves the currently displayed image of the pipeline in preview to the + * given location, in the specified dimensions and format. + * + * Returns: %TRUE if @self's current image preview was successfully saved + * to @location using the given @format, @height and @width. + */ +gboolean +ges_pipeline_save_thumbnail (GESPipeline * self, int width, int + height, const gchar * format, const gchar * location, GError ** error) +{ + GstMapInfo map_info; + GstBuffer *b; + GstSample *sample; + GstCaps *caps; + gboolean res = TRUE; + + g_return_val_if_fail (GES_IS_PIPELINE (self), FALSE); + CHECK_THREAD (self); + + caps = gst_caps_from_string (format); + + if (width > 1) + gst_caps_set_simple (caps, "width", G_TYPE_INT, width, NULL); + + if (height > 1) + gst_caps_set_simple (caps, "height", G_TYPE_INT, height, NULL); + + if (!(sample = ges_pipeline_get_thumbnail (self, caps))) { + gst_caps_unref (caps); + return FALSE; + } + + b = gst_sample_get_buffer (sample); + if (gst_buffer_map (b, &map_info, GST_MAP_READ)) { + if (!g_file_set_contents (location, (const char *) map_info.data, + map_info.size, error)) { + GST_WARNING ("Could not save thumbnail: %s", + error ? (*error)->message : ""); + res = FALSE; + } + } + + gst_caps_unref (caps); + gst_buffer_unmap (b, &map_info); + gst_sample_unref (sample); + + return res; +} + +/** + * ges_pipeline_get_thumbnail_rgb24: + * @self: A #GESPipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED + * @width: The requested pixel width of the image, or -1 to use the native + * size + * @height: The requested pixel height of the image, or -1 to use the + * native size + * + * Gets a sample from the pipeline of the currently displayed image in + * preview, in the 24-bit "RGB" format and of the desired width and + * height. + * + * See ges_pipeline_get_thumbnail(). + * + * Returns: (transfer full): A sample of @self's current image preview in + * the "RGB" format, scaled to @width and @height, or %NULL if an error + * prevented fetching the sample. + */ + +GstSample * +ges_pipeline_get_thumbnail_rgb24 (GESPipeline * self, gint width, gint height) +{ + GstSample *ret; + GstCaps *caps; + + g_return_val_if_fail (GES_IS_PIPELINE (self), FALSE); + CHECK_THREAD (self); + + caps = gst_caps_new_simple ("video/x-raw", "format", G_TYPE_STRING, + "RGB", NULL); + + if (width != -1) + gst_caps_set_simple (caps, "width", G_TYPE_INT, (gint) width, NULL); + + if (height != -1) + gst_caps_set_simple (caps, "height", G_TYPE_INT, (gint) height, NULL); + + ret = ges_pipeline_get_thumbnail (self, caps); + gst_caps_unref (caps); + return ret; +} + +/** + * ges_pipeline_preview_get_video_sink: + * @self: A #GESPipeline + * + * Gets the #GESPipeline:video-sink of the pipeline. + * + * Returns: (transfer full): The video sink used by @self for preview. + */ +GstElement * +ges_pipeline_preview_get_video_sink (GESPipeline * self) +{ + GstElement *sink = NULL; + + g_return_val_if_fail (GES_IS_PIPELINE (self), FALSE); + CHECK_THREAD (self); + + g_object_get (self->priv->playsink, "video-sink", &sink, NULL); + + return sink; +}; + +/** + * ges_pipeline_preview_set_video_sink: + * @self: A #GESPipeline in #GST_STATE_NULL + * @sink: (transfer none): A video sink for @self to use for preview + * + * Sets the #GESPipeline:video-sink of the pipeline. + */ +void +ges_pipeline_preview_set_video_sink (GESPipeline * self, GstElement * sink) +{ + g_return_if_fail (GES_IS_PIPELINE (self)); + CHECK_THREAD (self); + + g_object_set (self->priv->playsink, "video-sink", sink, NULL); +}; + +/** + * ges_pipeline_preview_get_audio_sink: + * @self: A #GESPipeline + * + * Gets the #GESPipeline:audio-sink of the pipeline. + * + * Returns: (transfer full): The audio sink used by @self for preview. + */ +GstElement * +ges_pipeline_preview_get_audio_sink (GESPipeline * self) +{ + GstElement *sink = NULL; + + g_return_val_if_fail (GES_IS_PIPELINE (self), FALSE); + CHECK_THREAD (self); + + g_object_get (self->priv->playsink, "audio-sink", &sink, NULL); + + return sink; +}; + +/** + * ges_pipeline_preview_set_audio_sink: + * @self: A #GESPipeline in #GST_STATE_NULL + * @sink: (transfer none): A audio sink for @self to use for preview + * + * Sets the #GESPipeline:audio-sink of the pipeline. + */ +void +ges_pipeline_preview_set_audio_sink (GESPipeline * self, GstElement * sink) +{ + g_return_if_fail (GES_IS_PIPELINE (self)); + CHECK_THREAD (self); + + g_object_set (self->priv->playsink, "audio-sink", sink, NULL); +}; diff --git a/ges/ges-pipeline.h b/ges/ges-pipeline.h new file mode 100644 index 0000000000..0d3ff4b371 --- /dev/null +++ b/ges/ges-pipeline.h @@ -0,0 +1,105 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges.h> +#include <gst/pbutils/encoding-profile.h> + +G_BEGIN_DECLS + +#define GES_TYPE_PIPELINE ges_pipeline_get_type() +GES_DECLARE_TYPE(Pipeline, pipeline, PIPELINE); + +/** + * GESPipeline: + * + */ + +struct _GESPipeline { + /*< private >*/ + GstPipeline parent; + + GESPipelinePrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESPipelineClass: + * @parent_class: parent class + * + */ + +struct _GESPipelineClass { + /*< private >*/ + GstPipelineClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API +GESPipeline* ges_pipeline_new (void); + +GES_API +gboolean ges_pipeline_set_timeline (GESPipeline * pipeline, + GESTimeline * timeline); + +GES_API +gboolean ges_pipeline_set_render_settings (GESPipeline *pipeline, + const gchar * output_uri, + GstEncodingProfile *profile); +GES_API +gboolean ges_pipeline_set_mode (GESPipeline *pipeline, + GESPipelineFlags mode); + +GES_API +GESPipelineFlags ges_pipeline_get_mode (GESPipeline *pipeline); + +GES_API GstSample * +ges_pipeline_get_thumbnail(GESPipeline *self, GstCaps *caps); + +GES_API GstSample * +ges_pipeline_get_thumbnail_rgb24(GESPipeline *self, + gint width, gint height); + +GES_API gboolean +ges_pipeline_save_thumbnail(GESPipeline *self, + int width, int height, const gchar *format, const gchar *location, + GError **error); + +GES_API GstElement * +ges_pipeline_preview_get_video_sink (GESPipeline * self); + +GES_API void +ges_pipeline_preview_set_video_sink (GESPipeline * self, + GstElement * sink); + +GES_API GstElement * +ges_pipeline_preview_get_audio_sink (GESPipeline * self); + +GES_API void +ges_pipeline_preview_set_audio_sink (GESPipeline * self, + GstElement * sink); + +G_END_DECLS diff --git a/ges/ges-pitivi-formatter.c b/ges/ges-pitivi-formatter.c new file mode 100644 index 0000000000..5bc6b8d724 --- /dev/null +++ b/ges/ges-pitivi-formatter.c @@ -0,0 +1,779 @@ +/* GStreamer Editing Services Pitivi Formatter + * Copyright (C) 2011-2012 Mathieu Duponchelle <seeed@laposte.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION: gespitiviformatter + * @title: GESPitiviFormatter + * @short_description: A formatter for the obsolete Pitivi xptv project file format + * + * This is a legacy format and you should avoid to use it. The formatter + * is really not in good shape and is deprecated. + * + * Deprecated: 1.0 + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#undef VERSION +#endif + +#include <libxml/xmlreader.h> +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <libxml/xpath.h> +#include <libxml/xpathInternals.h> +#include <libxml/encoding.h> +#include <libxml/xmlwriter.h> + +#include "ges-internal.h" +#include <ges/ges-pitivi-formatter.h> +#include <ges/ges.h> + +/* The Pitivi etree formatter is 0.1 we set GES one to 0.2 */ +//#define VERSION "0.2" +#define DOUBLE_VERSION 0.2 + +#undef GST_CAT_DEFAULT +GST_DEBUG_CATEGORY_STATIC (ges_pitivi_formatter_debug); +#define GST_CAT_DEFAULT ges_pitivi_formatter_debug + +typedef struct SrcMapping +{ + gchar *id; + GESClip *clip; + guint priority; + GList *track_element_ids; +} SrcMapping; + +struct _GESPitiviFormatterPrivate +{ + xmlXPathContextPtr xpathCtx; + + /* {"sourceId" : {"prop": "value"}} */ + GHashTable *sources_table; + + /* Used as a set of the uris */ + GHashTable *source_uris; + + /* {trackId: {"factory_ref": factoryId, ""} + * if effect: + * {"factory_ref": "effect", + * "effect_name": name + * "effect_props": {"propname": value}}} + */ + GHashTable *track_elements_table; + + /* {factory-ref: [track-object-ref-id,...]} */ + GHashTable *clips_table; + + /* {layerPriority: layer} */ + GHashTable *layers_table; + + GESTimeline *timeline; + + GESTrack *tracka, *trackv; + + /* List the Clip that haven't been loaded yet */ + GList *sources_to_load; + + /* Saving context */ + /* {factory_id: uri} */ + GHashTable *saving_source_table; + guint nb_sources; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GESPitiviFormatter, ges_pitivi_formatter, + GES_TYPE_FORMATTER); + + +static void +list_table_destroyer (gpointer key, gpointer value, void *unused) +{ + g_list_foreach (value, (GFunc) g_free, NULL); + g_list_free (value); +} + +static gboolean +pitivi_can_load_uri (GESFormatter * dummy_instance, const gchar * uri, + GError ** error) +{ + xmlDocPtr doc; + gboolean ret = TRUE; + xmlXPathObjectPtr xpathObj; + xmlXPathContextPtr xpathCtx; + gchar *filename = g_filename_from_uri (uri, NULL, NULL); + + if (!filename || !g_file_test (filename, G_FILE_TEST_EXISTS)) { + g_free (filename); + return FALSE; + } + + g_free (filename); + + if (!(doc = xmlParseFile (uri))) { + GST_ERROR ("The xptv file for uri %s was badly formed", uri); + return FALSE; + } + + xpathCtx = xmlXPathNewContext (doc); + xpathObj = xmlXPathEvalExpression ((const xmlChar *) "/pitivi", xpathCtx); + if (!xpathObj || !xpathObj->nodesetval || xpathObj->nodesetval->nodeNr == 0) + ret = FALSE; + + xmlFreeDoc (doc); + xmlXPathFreeObject (xpathObj); + xmlXPathFreeContext (xpathCtx); + + return ret; +} + +/* Project loading functions */ + +/* Return: a GHashTable containing: + * {attr: value} + */ +static GHashTable * +get_nodes_infos (xmlNodePtr node) +{ + xmlAttr *cur_attr; + GHashTable *props_table; + gchar *name, *value; + + props_table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); + + for (cur_attr = node->properties; cur_attr; cur_attr = cur_attr->next) { + name = (gchar *) cur_attr->name; + value = (gchar *) xmlGetProp (node, cur_attr->name); + g_hash_table_insert (props_table, g_strdup (name), g_strdup (value)); + xmlFree (value); + } + + return props_table; +} + +static gboolean +create_tracks (GESFormatter * self) +{ + GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv; + GList *tracks = NULL; + + tracks = ges_timeline_get_tracks (self->timeline); + + GST_DEBUG ("Creating tracks, current number of tracks %d", + g_list_length (tracks)); + + if (tracks) { + GList *tmp = NULL; + GESTrack *track; + for (tmp = tracks; tmp; tmp = tmp->next) { + track = tmp->data; + if (track->type == GES_TRACK_TYPE_AUDIO) { + priv->tracka = track; + } else { + priv->trackv = track; + } + } + g_list_foreach (tracks, (GFunc) gst_object_unref, NULL); + g_list_free (tracks); + return TRUE; + } + + priv->tracka = GES_TRACK (ges_audio_track_new ()); + priv->trackv = GES_TRACK (ges_video_track_new ()); + + if (!ges_timeline_add_track (self->timeline, priv->trackv)) { + return FALSE; + } + + if (!ges_timeline_add_track (self->timeline, priv->tracka)) { + return FALSE; + } + + return TRUE; +} + +static void +parse_metadatas (GESFormatter * self) +{ + guint i, size; + xmlNodePtr node; + xmlAttr *cur_attr; + xmlNodeSetPtr nodes; + xmlXPathObjectPtr xpathObj; + GESMetaContainer *metacontainer = GES_META_CONTAINER (self->project); + + xpathObj = xmlXPathEvalExpression ((const xmlChar *) + "/pitivi/metadata", GES_PITIVI_FORMATTER (self)->priv->xpathCtx); + nodes = xpathObj->nodesetval; + + size = (nodes) ? nodes->nodeNr : 0; + for (i = 0; i < size; i++) { + node = nodes->nodeTab[i]; + for (cur_attr = node->properties; cur_attr; cur_attr = cur_attr->next) { + ges_meta_container_set_string (metacontainer, (gchar *) cur_attr->name, + (gchar *) xmlGetProp (node, cur_attr->name)); + } + } + + xmlXPathFreeObject (xpathObj); +} + +static void +list_sources (GESFormatter * self) +{ + GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv; + xmlXPathObjectPtr xpathObj; + GHashTable *table; + int size, j; + gchar *id, *filename; + xmlNodeSetPtr nodes; + + xpathObj = xmlXPathEvalExpression ((const xmlChar *) + "/pitivi/factories/sources/source", priv->xpathCtx); + nodes = xpathObj->nodesetval; + + size = (nodes) ? nodes->nodeNr : 0; + for (j = 0; j < size; ++j) { + table = get_nodes_infos (nodes->nodeTab[j]); + id = (gchar *) g_hash_table_lookup (table, (gchar *) "id"); + filename = (gchar *) g_hash_table_lookup (table, (gchar *) "filename"); + g_hash_table_insert (priv->sources_table, g_strdup (id), table); + g_hash_table_insert (priv->source_uris, g_strdup (filename), + g_strdup (filename)); + if (self->project) + ges_project_create_asset (self->project, filename, GES_TYPE_URI_CLIP); + } + + xmlXPathFreeObject (xpathObj); +} + +static gboolean +parse_track_elements (GESFormatter * self) +{ + GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv; + xmlXPathObjectPtr xpathObj; + xmlNodeSetPtr nodes; + int size, j; + gchar *id, *fac_ref; + GHashTable *table = NULL, *effect_table = NULL; + xmlNode *first_child; + gchar *media_type; + + /* FIXME Make this whole function cleaner starting from + * "/pitivi/timeline/tracks/track/stream" and descending + * into the children. */ + xpathObj = xmlXPathEvalExpression ((const xmlChar *) + "/pitivi/timeline/tracks/track/track-objects/track-object", + priv->xpathCtx); + + if (xpathObj == NULL) { + GST_DEBUG ("No track object found"); + + return FALSE; + } + + nodes = xpathObj->nodesetval; + size = (nodes) ? nodes->nodeNr : 0; + + for (j = 0; j < size; ++j) { + xmlNodePtr node = nodes->nodeTab[j]; + + table = get_nodes_infos (nodes->nodeTab[j]); + id = (gchar *) g_hash_table_lookup (table, (gchar *) "id"); + first_child = nodes->nodeTab[j]->children->next; + fac_ref = (gchar *) xmlGetProp (first_child, (xmlChar *) "id"); + + /* We check if the first child is "effect" */ + if (!g_strcmp0 ((gchar *) first_child->name, (gchar *) "effect")) { + xmlChar *effect_name; + xmlNodePtr fact_node = first_child->children->next; + + /* We have a node called "text" in between thus ->next->next */ + xmlNodePtr elem_props_node = fact_node->next->next; + + effect_name = xmlGetProp (fact_node, (xmlChar *) "name"); + g_hash_table_insert (table, g_strdup ((gchar *) "effect_name"), + g_strdup ((gchar *) effect_name)); + xmlFree (effect_name); + + /* We put the effects properties in an hacktable (Lapsus is on :) */ + effect_table = get_nodes_infos (elem_props_node); + + g_hash_table_insert (table, g_strdup ((gchar *) "fac_ref"), + g_strdup ("effect")); + + xmlFree (fac_ref); + } else { + + g_hash_table_insert (table, g_strdup ((gchar *) "fac_ref"), + g_strdup (fac_ref)); + xmlFree (fac_ref); + } + + /* Same as before, we got a text node in between, thus the 2 prev + * node->parent is <track-objects>, the one before is <stream> + */ + media_type = (gchar *) xmlGetProp (node->parent->prev->prev, + (const xmlChar *) "type"); + g_hash_table_insert (table, g_strdup ((gchar *) "media_type"), + g_strdup (media_type)); + xmlFree (media_type); + + + if (effect_table) + g_hash_table_insert (table, g_strdup ("effect_props"), effect_table); + + g_hash_table_insert (priv->track_elements_table, g_strdup (id), table); + } + + xmlXPathFreeObject (xpathObj); + return TRUE; +} + +static gboolean +parse_clips (GESFormatter * self) +{ + int size, j; + xmlNodeSetPtr nodes; + xmlXPathObjectPtr xpathObj; + xmlNodePtr clip_nd, tmp_nd, tmp_nd2; + xmlChar *trackelementrefId, *facrefId = NULL; + + GList *reflist = NULL; + GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv; + GHashTable *clips_table = priv->clips_table; + + xpathObj = xmlXPathEvalExpression ((const xmlChar *) + "/pitivi/timeline/timeline-objects/timeline-object", priv->xpathCtx); + + if (xpathObj == NULL) { + xmlXPathFreeObject (xpathObj); + return FALSE; + } + + nodes = xpathObj->nodesetval; + size = (nodes) ? nodes->nodeNr : 0; + + for (j = 0; j < size; j++) { + clip_nd = nodes->nodeTab[j]; + + for (tmp_nd = clip_nd->children; tmp_nd; tmp_nd = tmp_nd->next) { + /* We assume that factory-ref is always before the tckobjs-ref */ + if (!xmlStrcmp (tmp_nd->name, (xmlChar *) "factory-ref")) { + facrefId = xmlGetProp (tmp_nd, (xmlChar *) "id"); + + } else if (!xmlStrcmp (tmp_nd->name, (xmlChar *) "track-object-refs")) { + + for (tmp_nd2 = tmp_nd->children; tmp_nd2; tmp_nd2 = tmp_nd2->next) { + if (!xmlStrcmp (tmp_nd2->name, (xmlChar *) "track-object-ref")) { + /* We add the track object ref ID to the list of the current + * Clip tracks, this way we can merge 2 + * Clip-s into 1 when we have unlinked TrackElement-s */ + reflist = g_hash_table_lookup (clips_table, facrefId); + trackelementrefId = xmlGetProp (tmp_nd2, (xmlChar *) "id"); + reflist = + g_list_append (reflist, g_strdup ((gchar *) trackelementrefId)); + g_hash_table_insert (clips_table, g_strdup ((gchar *) facrefId), + reflist); + + xmlFree (trackelementrefId); + } + } + } + } + } + + xmlXPathFreeObject (xpathObj); + return TRUE; +} + +static void +set_properties (GObject * obj, GHashTable * props_table) +{ + gint i; + gchar **prop_array, *valuestr; + gint64 value; + + gchar props[3][10] = { "duration", "in_point", "start" }; + + for (i = 0; i < 3; i++) { + valuestr = g_hash_table_lookup (props_table, props[i]); + prop_array = g_strsplit (valuestr, ")", 0); + value = g_ascii_strtoll (prop_array[1], NULL, 0); + g_object_set (obj, props[i], value, NULL); + + g_strfreev (prop_array); + } +} + +static void +track_element_added_cb (GESClip * clip, + GESTrackElement * track_element, GHashTable * props_table) +{ + GESPitiviFormatter *formatter; + + formatter = GES_PITIVI_FORMATTER (g_hash_table_lookup (props_table, + "current-formatter")); + if (formatter) { + GESPitiviFormatterPrivate *priv = formatter->priv; + + /* Make sure the hack to get a ref to the formatter + * doesn't break everything */ + g_hash_table_steal (props_table, "current-formatter"); + + priv->sources_to_load = g_list_remove (priv->sources_to_load, clip); + if (!priv->sources_to_load && GES_FORMATTER (formatter)->project) + ges_project_set_loaded (GES_FORMATTER (formatter)->project, + GES_FORMATTER (formatter), NULL); + } + + /* Disconnect the signal */ + g_signal_handlers_disconnect_by_func (clip, track_element_added_cb, + props_table); +} + +static void +make_source (GESFormatter * self, GList * reflist, GHashTable * source_table) +{ + GHashTable *props_table, *effect_table; + gchar **prio_array; + GESLayer *layer; + GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv; + + gchar *fac_ref = NULL, *media_type = NULL, *filename = NULL, *prio_str; + GList *tmp = NULL, *keys, *tmp_key; + GESUriClip *src = NULL; + gint prio; + gboolean a_avail = FALSE, v_avail = FALSE, video; + GHashTable *trackelement_table = priv->track_elements_table; + + for (tmp = reflist; tmp; tmp = tmp->next) { + + /* Get the layer */ + props_table = g_hash_table_lookup (trackelement_table, (gchar *) tmp->data); + prio_str = (gchar *) g_hash_table_lookup (props_table, "priority"); + prio_array = g_strsplit (prio_str, ")", 0); + prio = (gint) g_ascii_strtod (prio_array[1], NULL); + g_strfreev (prio_array); + + /* If we do not have any layer with this priority, create it */ + if (!(layer = g_hash_table_lookup (priv->layers_table, &prio))) { + layer = ges_layer_new (); + g_object_set (layer, "auto-transition", TRUE, "priority", prio, NULL); + ges_timeline_add_layer (self->timeline, layer); + g_hash_table_insert (priv->layers_table, g_memdup2 (&prio, + sizeof (guint64)), layer); + } + + fac_ref = (gchar *) g_hash_table_lookup (props_table, "fac_ref"); + media_type = (gchar *) g_hash_table_lookup (props_table, "media_type"); + + if (!g_strcmp0 (media_type, "pitivi.stream.VideoStream")) + video = TRUE; + else + video = FALSE; + + /* FIXME I am sure we could reimplement this whole part + * in a simpler way */ + + if (g_strcmp0 (fac_ref, (gchar *) "effect")) { + /* FIXME this is a hack to get a ref to the formatter when receiving + * child-added */ + g_hash_table_insert (props_table, (gchar *) "current-formatter", self); + if (a_avail && (!video)) { + a_avail = FALSE; + } else if (v_avail && (video)) { + v_avail = FALSE; + } else { + + /* If we only have audio or only video in the previous source, + * set it has such */ + if (a_avail) { + ges_clip_set_supported_formats (GES_CLIP (src), GES_TRACK_TYPE_VIDEO); + } else if (v_avail) { + ges_clip_set_supported_formats (GES_CLIP (src), GES_TRACK_TYPE_AUDIO); + } + + filename = (gchar *) g_hash_table_lookup (source_table, "filename"); + + src = ges_uri_clip_new (filename); + + if (!video) { + v_avail = TRUE; + a_avail = FALSE; + } else { + a_avail = TRUE; + v_avail = FALSE; + } + + set_properties (G_OBJECT (src), props_table); + ges_layer_add_clip (layer, GES_CLIP (src)); + + g_signal_connect (src, "child-added", + G_CALLBACK (track_element_added_cb), props_table); + + priv->sources_to_load = g_list_prepend (priv->sources_to_load, src); + } + + } else { + GESEffect *effect; + gchar *active = (gchar *) g_hash_table_lookup (props_table, "active"); + + effect = ges_effect_new ((gchar *) + g_hash_table_lookup (props_table, (gchar *) "effect_name")); + ges_track_element_set_track_type (GES_TRACK_ELEMENT (effect), + (video ? GES_TRACK_TYPE_VIDEO : GES_TRACK_TYPE_AUDIO)); + effect_table = + g_hash_table_lookup (props_table, (gchar *) "effect_props"); + + if (!ges_container_add (GES_CONTAINER (src), + GES_TIMELINE_ELEMENT (effect))) { + GST_ERROR ("%p could not add %p while" + " reloading, this should never happen", src, effect); + } + + if (!g_strcmp0 (active, (gchar *) "(bool)False")) + ges_track_element_set_active (GES_TRACK_ELEMENT (effect), FALSE); + + /* Set effect properties */ + keys = g_hash_table_get_keys (effect_table); + for (tmp_key = keys; tmp_key; tmp_key = tmp_key->next) { + GstStructure *structure; + const GValue *value; + GParamSpec *spec; + GstCaps *caps; + gchar *prop_val; + + prop_val = (gchar *) g_hash_table_lookup (effect_table, + (gchar *) tmp_key->data); + + if (g_strstr_len (prop_val, -1, "(GEnum)")) { + gchar **val = g_strsplit (prop_val, ")", 2); + + ges_track_element_set_child_properties (GES_TRACK_ELEMENT (effect), + (gchar *) tmp_key->data, atoi (val[1]), NULL); + g_strfreev (val); + + } else if (ges_track_element_lookup_child (GES_TRACK_ELEMENT (effect), + (gchar *) tmp->data, NULL, &spec)) { + gchar *caps_str = g_strdup_printf ("structure1, property1=%s;", + prop_val); + + caps = gst_caps_from_string (caps_str); + g_free (caps_str); + structure = gst_caps_get_structure (caps, 0); + value = gst_structure_get_value (structure, "property1"); + + ges_track_element_set_child_property_by_pspec (GES_TRACK_ELEMENT + (effect), spec, (GValue *) value); + gst_caps_unref (caps); + } + } + } + } + + if (a_avail) { + ges_clip_set_supported_formats (GES_CLIP (src), GES_TRACK_TYPE_VIDEO); + } else if (v_avail) { + ges_clip_set_supported_formats (GES_CLIP (src), GES_TRACK_TYPE_AUDIO); + } +} + +static gboolean +make_clips (GESFormatter * self) +{ + GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv; + GHashTable *source_table; + + GList *keys = NULL, *tmp = NULL, *reflist = NULL; + + keys = g_hash_table_get_keys (priv->clips_table); + + for (tmp = keys; tmp; tmp = tmp->next) { + gchar *fac_id = (gchar *) tmp->data; + + reflist = g_hash_table_lookup (priv->clips_table, fac_id); + source_table = g_hash_table_lookup (priv->sources_table, fac_id); + make_source (self, reflist, source_table); + } + + g_list_free (keys); + return TRUE; +} + +static gboolean +load_pitivi_file_from_uri (GESFormatter * self, + GESTimeline * timeline, const gchar * uri, GError ** error) +{ + xmlDocPtr doc; + GESLayer *layer; + GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv; + + gboolean ret = TRUE; + gint *prio = malloc (sizeof (gint)); + + *prio = 0; + layer = ges_layer_new (); + g_object_set (layer, "auto-transition", TRUE, NULL); + + g_hash_table_insert (priv->layers_table, prio, layer); + g_object_set (layer, "priority", (gint32) 0, NULL); + + if (!ges_timeline_add_layer (timeline, layer)) { + GST_ERROR ("Couldn't add layer"); + return FALSE; + } + + if (!(doc = xmlParseFile (uri))) { + GST_ERROR ("The xptv file for uri %s was badly formed or did not exist", + uri); + return FALSE; + } + + priv->xpathCtx = xmlXPathNewContext (doc); + + if (self->project) + parse_metadatas (self); + + if (!create_tracks (self)) { + GST_ERROR ("Couldn't create tracks"); + return FALSE; + } + + list_sources (self); + + if (!parse_clips (self)) { + GST_ERROR ("Couldn't find clips markup in the xptv file"); + return FALSE; + } + + if (!parse_track_elements (self)) { + GST_ERROR ("Couldn't find track objects markup in the xptv file"); + return FALSE; + } + + + + /* If there are no clips to load we should emit + * 'project-loaded' signal. + */ + if (!g_hash_table_size (priv->clips_table) && GES_FORMATTER (self)->project) { + ges_project_set_loaded (GES_FORMATTER (self)->project, + GES_FORMATTER (self), NULL); + } else { + if (!make_clips (self)) { + GST_ERROR ("Couldn't deserialise the project properly"); + return FALSE; + } + } + + xmlXPathFreeContext (priv->xpathCtx); + xmlFreeDoc (doc); + return ret; +} + +/* Object functions */ +static void +ges_pitivi_formatter_finalize (GObject * object) +{ + GESPitiviFormatter *self = GES_PITIVI_FORMATTER (object); + GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv; + + g_hash_table_destroy (priv->sources_table); + g_hash_table_destroy (priv->source_uris); + + g_hash_table_destroy (priv->saving_source_table); + g_list_free (priv->sources_to_load); + + if (priv->clips_table != NULL) { + g_hash_table_foreach (priv->clips_table, + (GHFunc) list_table_destroyer, NULL); + g_hash_table_destroy (priv->clips_table); + } + + if (priv->layers_table != NULL) + g_hash_table_destroy (priv->layers_table); + + if (priv->track_elements_table != NULL) { + g_hash_table_destroy (priv->track_elements_table); + } + + G_OBJECT_CLASS (ges_pitivi_formatter_parent_class)->finalize (object); +} + +static void +ges_pitivi_formatter_class_init (GESPitiviFormatterClass * klass) +{ + GESFormatterClass *formatter_klass; + GObjectClass *object_class; + + GST_DEBUG_CATEGORY_INIT (ges_pitivi_formatter_debug, "ges_pitivi_formatter", + GST_DEBUG_FG_YELLOW, "ges pitivi formatter"); + + object_class = G_OBJECT_CLASS (klass); + formatter_klass = GES_FORMATTER_CLASS (klass); + + formatter_klass->can_load_uri = pitivi_can_load_uri; + formatter_klass->save_to_uri = NULL; + formatter_klass->load_from_uri = load_pitivi_file_from_uri; + object_class->finalize = ges_pitivi_formatter_finalize; + + ges_formatter_class_register_metas (formatter_klass, "pitivi", + "Legacy Pitivi project files", "xptv", "text/x-xptv", + DOUBLE_VERSION, GST_RANK_MARGINAL); +} + +static void +ges_pitivi_formatter_init (GESPitiviFormatter * self) +{ + GESPitiviFormatterPrivate *priv; + + self->priv = ges_pitivi_formatter_get_instance_private (self); + + priv = self->priv; + + priv->track_elements_table = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) g_hash_table_destroy); + + priv->clips_table = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + priv->layers_table = + g_hash_table_new_full (g_int_hash, g_str_equal, g_free, gst_object_unref); + + priv->sources_table = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) g_hash_table_destroy); + + priv->source_uris = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + priv->sources_to_load = NULL; + + /* Saving context */ + priv->saving_source_table = + g_hash_table_new_full (g_str_hash, g_int_equal, g_free, g_free); + priv->nb_sources = 1; +} + +GESPitiviFormatter * +ges_pitivi_formatter_new (void) +{ + return g_object_new (GES_TYPE_PITIVI_FORMATTER, NULL); +} diff --git a/ges/ges-pitivi-formatter.h b/ges/ges-pitivi-formatter.h new file mode 100644 index 0000000000..ca25c2b16f --- /dev/null +++ b/ges/ges-pitivi-formatter.h @@ -0,0 +1,58 @@ +/* GStreamer Editing Services Pitivi Formatter + * Copyright (C) 2011-2012 Mathieu Duponchelle <seeed@laposte.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +G_BEGIN_DECLS + +#define GES_TYPE_PITIVI_FORMATTER ges_pitivi_formatter_get_type() +GES_DECLARE_TYPE(PitiviFormatter, pitivi_formatter, PITIVI_FORMATTER); + +/** + * GESPitiviFormatter: (attributes doc.skip=true): + * + * Serializes a #GESTimeline to a file using the xptv Pitivi file format + */ +struct _GESPitiviFormatter { + GESFormatter parent; + + /*< public > */ + /*< private >*/ + GESPitiviFormatterPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESPitiviFormatterClass: (attributes doc.skip=true): + */ +struct _GESPitiviFormatterClass +{ + /*< private >*/ + GESFormatterClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API +GESPitiviFormatter *ges_pitivi_formatter_new (void); + +G_END_DECLS diff --git a/ges/ges-prelude.h b/ges/ges-prelude.h new file mode 100644 index 0000000000..3651c1a567 --- /dev/null +++ b/ges/ges-prelude.h @@ -0,0 +1,39 @@ +/* GStreamer GES Library + * Copyright (C) 2018 GStreamer developers + * + * ges-prelude.h: prelude include header for gst-ges library + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#pragma once + +#include <gst/gst.h> + +#ifndef GES_API +# ifdef BUILDING_GES +# define GES_API GST_API_EXPORT /* from config.h */ +# else +# define GES_API GST_API_IMPORT +# endif +#endif + +#ifndef GST_DISABLE_DEPRECATED +#define GES_DEPRECATED GES_API +#define GES_DEPRECATED_FOR(f) GES_API +#else +#define GES_DEPRECATED G_DEPRECATED GES_API +#define GES_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) GES_API +#endif diff --git a/ges/ges-project.c b/ges/ges-project.c new file mode 100644 index 0000000000..286d3aa687 --- /dev/null +++ b/ges/ges-project.c @@ -0,0 +1,1304 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2012> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION: gesproject + * @title: GESProject + * @short_description: A GESAsset that is used to manage projects + * + * The #GESProject is used to control a set of #GESAsset and is a + * #GESAsset with `GES_TYPE_TIMELINE` as @extractable_type itself. That + * means that you can extract #GESTimeline from a project as followed: + * + * |[ + * GESProject *project; + * GESTimeline *timeline; + * + * project = ges_project_new ("file:///path/to/a/valid/project/uri"); + * + * // Here you can connect to the various signal to get more infos about + * // what is happening and recover from errors if possible + * ... + * + * timeline = ges_asset_extract (GES_ASSET (project)); + * ]| + * + * The #GESProject class offers a higher level API to handle #GESAsset-s. + * It lets you request new asset, and it informs you about new assets through + * a set of signals. Also it handles problem such as missing files/missing + * #GstElement and lets you try to recover from those. + * + * ## Subprojects + * + * In order to add a subproject, the only thing to do is to add the subproject + * to the main project: + * + * ``` c + * ges_project_add_asset (project, GES_ASSET (subproject)); + * ``` + * then the subproject will be serialized in the project files. To use + * the subproject in a timeline, you should use a #GESUriClip with the + * same subproject URI. + * + * When loading a project with subproject, subprojects URIs will be temporary + * writable local files. If you want to edit the subproject timeline, + * you should retrieve the subproject from the parent project asset list and + * extract the timeline with ges_asset_extract() and save it at + * the same temporary location. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges.h" +#include "ges-internal.h" + +static GPtrArray *new_paths = NULL; +static GHashTable *tried_uris = NULL; + +struct _GESProjectPrivate +{ + GHashTable *assets; + /* Set of asset ID being loaded */ + GHashTable *loading_assets; + GHashTable *loaded_with_error; + GESAsset *formatter_asset; + + GList *formatters; + + gchar *uri; + + GList *encoding_profiles; +}; + +typedef struct EmitLoadedInIdle +{ + GESProject *project; + GESTimeline *timeline; +} EmitLoadedInIdle; + +enum +{ + LOADING_SIGNAL, + LOADED_SIGNAL, + ERROR_LOADING, + ERROR_LOADING_ASSET, + ASSET_ADDED_SIGNAL, + ASSET_REMOVED_SIGNAL, + MISSING_URI_SIGNAL, + ASSET_LOADING_SIGNAL, + LAST_SIGNAL +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GESProject, ges_project, GES_TYPE_ASSET); + +static guint _signals[LAST_SIGNAL] = { 0 }; + +static guint nb_projects = 0; + +/* Find the type that implemented the GESExtractable interface */ +static inline const gchar * +_extractable_type_name (GType type) +{ + while (1) { + if (g_type_is_a (g_type_parent (type), GES_TYPE_EXTRACTABLE)) + type = g_type_parent (type); + else + return g_type_name (type); + } +} + +enum +{ + PROP_0, + PROP_URI, + PROP_LAST, +}; + +static GParamSpec *_properties[LAST_SIGNAL] = { 0 }; + +static gboolean +_emit_loaded_in_idle (EmitLoadedInIdle * data) +{ + g_signal_emit (data->project, _signals[LOADED_SIGNAL], 0, data->timeline); + + gst_object_unref (data->project); + gst_object_unref (data->timeline); + g_slice_free (EmitLoadedInIdle, data); + + return FALSE; +} + +/** + * ges_project_add_formatter: + * @project: The project to add a formatter to + * @formatter: A formatter used by @project + * + * Adds a formatter as used to load @project + * + * Since: 1.18 + */ +void +ges_project_add_formatter (GESProject * project, GESFormatter * formatter) +{ + GESProjectPrivate *priv = GES_PROJECT (project)->priv; + + ges_formatter_set_project (formatter, project); + priv->formatters = g_list_append (priv->formatters, formatter); + + gst_object_ref_sink (formatter); +} + +static void +ges_project_remove_formatter (GESProject * project, GESFormatter * formatter) +{ + GList *tmp; + GESProjectPrivate *priv = GES_PROJECT (project)->priv; + + for (tmp = priv->formatters; tmp; tmp = tmp->next) { + if (tmp->data == formatter) { + gst_object_unref (formatter); + priv->formatters = g_list_delete_link (priv->formatters, tmp); + + return; + } + } +} + +static void +ges_project_set_uri (GESProject * project, const gchar * uri) +{ + GESProjectPrivate *priv; + + g_return_if_fail (GES_IS_PROJECT (project)); + + priv = project->priv; + if (priv->uri) { + if (g_strcmp0 (priv->uri, uri)) + GST_WARNING_OBJECT (project, "Trying to reset URI, this is prohibited"); + + return; + } + + if (uri == NULL) { + GST_LOG_OBJECT (project, "Uri should not be NULL"); + return; + } + + priv->uri = g_strdup (uri); + + /* We use that URI as ID */ + ges_asset_set_id (GES_ASSET (project), uri); + + return; +} + +static gboolean +_load_project (GESProject * project, GESTimeline * timeline, GError ** error) +{ + GError *lerr = NULL; + GESProjectPrivate *priv; + GESFormatter *formatter; + + priv = GES_PROJECT (project)->priv; + + g_signal_emit (project, _signals[LOADING_SIGNAL], 0, timeline); + if (priv->uri == NULL) { + const gchar *id = ges_asset_get_id (GES_ASSET (project)); + + if (id && gst_uri_is_valid (id)) { + ges_project_set_uri (project, ges_asset_get_id (GES_ASSET (project))); + GST_INFO_OBJECT (project, "Using asset ID %s as URI.", priv->uri); + } else { + EmitLoadedInIdle *data = g_slice_new (EmitLoadedInIdle); + + GST_INFO_OBJECT (project, "%s, Loading an empty timeline %s" + " as no URI set yet", GST_OBJECT_NAME (timeline), + ges_asset_get_id (GES_ASSET (project))); + + data->timeline = gst_object_ref (timeline); + data->project = gst_object_ref (project); + + /* Make sure the signal is emitted after the functions ends */ + ges_idle_add ((GSourceFunc) _emit_loaded_in_idle, data, NULL); + return TRUE; + } + } + + if (priv->formatter_asset == NULL) + priv->formatter_asset = _find_formatter_asset_for_id (priv->uri); + + if (priv->formatter_asset == NULL) { + lerr = g_error_new (GES_ERROR, 0, "Could not find a suitable formatter"); + goto failed; + } + + formatter = GES_FORMATTER (ges_asset_extract (priv->formatter_asset, &lerr)); + if (lerr) { + GST_WARNING_OBJECT (project, "Could not create the formatter: %s", + lerr->message); + + goto failed; + } + + ges_project_add_formatter (GES_PROJECT (project), formatter); + ges_formatter_load_from_uri (formatter, timeline, priv->uri, &lerr); + if (lerr) { + GST_WARNING_OBJECT (project, "Could not load the timeline," + " returning: %s", lerr->message); + goto failed; + } + + return TRUE; + +failed: + if (lerr) + g_propagate_error (error, lerr); + return FALSE; +} + +static gboolean +_uri_missing_accumulator (GSignalInvocationHint * ihint, GValue * return_accu, + const GValue * handler_return, gpointer data) +{ + const gchar *ret = g_value_get_string (handler_return); + + if (ret) { + if (!gst_uri_is_valid (ret)) { + GST_INFO ("The uri %s was not valid, can not work with it!", ret); + return TRUE; + } + + g_value_set_string (return_accu, ret); + return FALSE; + } + + return TRUE; +} + +/* GESAsset vmethod implementation */ +static GESExtractable * +ges_project_extract (GESAsset * project, GError ** error) +{ + GESTimeline *timeline = g_object_new (GES_TYPE_TIMELINE, NULL); + + ges_extractable_set_asset (GES_EXTRACTABLE (timeline), GES_ASSET (project)); + if (_load_project (GES_PROJECT (project), timeline, error)) + return GES_EXTRACTABLE (timeline); + + gst_object_unref (timeline); + return NULL; +} + +static void +_add_media_new_paths_recursing (const gchar * value) +{ + GFileInfo *info; + GFileEnumerator *fenum; + GFile *file = g_file_new_for_uri (value); + + if (!(fenum = g_file_enumerate_children (file, + "standard::*", G_FILE_QUERY_INFO_NONE, NULL, NULL))) { + GST_INFO ("%s is not a folder", value); + + goto done; + } + + GST_INFO ("Adding folder: %s", value); + g_ptr_array_add (new_paths, g_strdup (value)); + info = g_file_enumerator_next_file (fenum, NULL, NULL); + while (info != NULL) { + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) { + GFile *f = g_file_enumerator_get_child (fenum, info); + gchar *uri = g_file_get_uri (f); + + _add_media_new_paths_recursing (uri); + gst_object_unref (f); + g_free (uri); + } + g_object_unref (info); + info = g_file_enumerator_next_file (fenum, NULL, NULL); + } + +done: + gst_object_unref (file); + if (fenum) + gst_object_unref (fenum); +} + +gboolean +ges_add_missing_uri_relocation_uri (const gchar * uri, gboolean recurse) +{ + g_return_val_if_fail (gst_uri_is_valid (uri), FALSE); + + if (new_paths == NULL) + new_paths = g_ptr_array_new_with_free_func (g_free); + + if (recurse) + _add_media_new_paths_recursing (uri); + else + g_ptr_array_add (new_paths, g_strdup (uri)); + + return TRUE; +} + +static gchar * +ges_missing_uri_default (GESProject * self, GError * error, + GESAsset * wrong_asset) +{ + guint i; + const gchar *old_uri = ges_asset_get_id (wrong_asset); + gchar *new_id = NULL; + + if (ges_asset_request_id_update (wrong_asset, &new_id, error) && new_id) { + GST_INFO_OBJECT (self, "Returned guessed new ID: %s", new_id); + + return new_id; + } + + if (new_paths == NULL) + return NULL; + + if (tried_uris == NULL) + tried_uris = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + for (i = 0; i < new_paths->len; i++) { + gchar *basename, *res; + + basename = g_path_get_basename (old_uri); + res = g_build_filename (new_paths->pdata[i], basename, NULL); + g_free (basename); + + if (g_strcmp0 (old_uri, res) == 0) { + g_hash_table_add (tried_uris, res); + } else if (g_hash_table_lookup (tried_uris, res)) { + GST_DEBUG_OBJECT (self, "File already tried: %s", res); + g_free (res); + } else { + g_hash_table_add (tried_uris, g_strdup (res)); + GST_DEBUG_OBJECT (self, "Trying: %s\n", res); + return res; + } + } + + return NULL; +} + +gchar * +ges_uri_asset_try_update_id (GError * error, GESAsset * wrong_asset) +{ + return ges_missing_uri_default (NULL, error, wrong_asset); +} + +static void +ges_uri_assets_validate_uri (const gchar * nid) +{ + if (tried_uris) + g_hash_table_remove (tried_uris, nid); +} + +/* GObject vmethod implementation */ +static void +_dispose (GObject * object) +{ + GESProjectPrivate *priv = GES_PROJECT (object)->priv; + + if (priv->assets) + g_hash_table_unref (priv->assets); + if (priv->loading_assets) + g_hash_table_unref (priv->loading_assets); + if (priv->loaded_with_error) + g_hash_table_unref (priv->loaded_with_error); + if (priv->formatter_asset) + gst_object_unref (priv->formatter_asset); + + while (priv->formatters) + ges_project_remove_formatter (GES_PROJECT (object), priv->formatters->data); + + G_OBJECT_CLASS (ges_project_parent_class)->dispose (object); +} + +static void +_finalize (GObject * object) +{ + GESProjectPrivate *priv = GES_PROJECT (object)->priv; + + if (priv->uri) + g_free (priv->uri); + g_list_free_full (priv->encoding_profiles, g_object_unref); + + G_OBJECT_CLASS (ges_project_parent_class)->finalize (object); +} + +static void +_get_property (GESProject * project, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESProjectPrivate *priv = project->priv; + + switch (property_id) { + case PROP_URI: + g_value_set_string (value, priv->uri); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (project, property_id, pspec); + } +} + +static void +_set_property (GESProject * project, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + case PROP_URI: + project->priv->uri = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (project, property_id, pspec); + } +} + +static void +ges_project_class_init (GESProjectClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + klass->asset_added = NULL; + klass->missing_uri = ges_missing_uri_default; + klass->loading_error = NULL; + klass->asset_removed = NULL; + object_class->get_property = (GObjectGetPropertyFunc) _get_property; + object_class->set_property = (GObjectSetPropertyFunc) _set_property; + + /** + * GESProject::uri: + * + * The location of the project to use. + */ + _properties[PROP_URI] = g_param_spec_string ("uri", "URI", + "uri of the project", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, PROP_LAST, _properties); + + /** + * GESProject::asset-added: + * @project: the #GESProject + * @asset: The #GESAsset that has been added to @project + */ + _signals[ASSET_ADDED_SIGNAL] = + g_signal_new ("asset-added", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, asset_added), + NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_ASSET); + + /** + * GESProject::asset-loading: + * @project: the #GESProject + * @asset: The #GESAsset that started loading + * + * Since: 1.8 + */ + _signals[ASSET_LOADING_SIGNAL] = + g_signal_new ("asset-loading", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, asset_loading), + NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_ASSET); + + /** + * GESProject::asset-removed: + * @project: the #GESProject + * @asset: The #GESAsset that has been removed from @project + */ + _signals[ASSET_REMOVED_SIGNAL] = + g_signal_new ("asset-removed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, asset_removed), + NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_ASSET); + + /** + * GESProject::loading: + * @project: the #GESProject that is starting to load a timeline + * @timeline: The #GESTimeline that started loading + * + * Since: 1.18 + */ + _signals[LOADING_SIGNAL] = + g_signal_new ("loading", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESProjectClass, loading), + NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_TIMELINE); + + /** + * GESProject::loaded: + * @project: the #GESProject that is done loading a timeline. + * @timeline: The #GESTimeline that completed loading + */ + _signals[LOADED_SIGNAL] = + g_signal_new ("loaded", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESProjectClass, loaded), + NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_TIMELINE); + + /** + * GESProject::missing-uri: + * @project: the #GESProject reporting that a file has moved + * @error: The error that happened + * @wrong_asset: The asset with the wrong ID, you should us it and its content + * only to find out what the new location is. + * + * |[ + * static gchar + * source_moved_cb (GESProject *project, GError *error, GESAsset *asset_with_error) + * { + * return g_strdup ("file:///the/new/uri.ogg"); + * } + * + * static int + * main (int argc, gchar ** argv) + * { + * GESTimeline *timeline; + * GESProject *project = ges_project_new ("file:///some/uri.xges"); + * + * g_signal_connect (project, "missing-uri", source_moved_cb, NULL); + * timeline = ges_asset_extract (GES_ASSET (project)); + * } + * ]| + * + * Returns: (transfer full) (allow-none): The new URI of @wrong_asset + */ + _signals[MISSING_URI_SIGNAL] = + g_signal_new ("missing-uri", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, missing_uri), + _uri_missing_accumulator, NULL, NULL, + G_TYPE_STRING, 2, G_TYPE_ERROR, GES_TYPE_ASSET); + + /** + * GESProject::error-loading-asset: + * @project: the #GESProject on which a problem happend when creted a #GESAsset + * @error: The #GError defining the error that occured, might be %NULL + * @id: The @id of the asset that failed loading + * @extractable_type: The @extractable_type of the asset that + * failed loading + * + * Informs you that a #GESAsset could not be created. In case of + * missing GStreamer plugins, the error will be set to #GST_CORE_ERROR + * #GST_CORE_ERROR_MISSING_PLUGIN + */ + _signals[ERROR_LOADING_ASSET] = + g_signal_new ("error-loading-asset", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, loading_error), + NULL, NULL, NULL, + G_TYPE_NONE, 3, G_TYPE_ERROR, G_TYPE_STRING, G_TYPE_GTYPE); + + /** + * GESProject::error-loading: + * @project: the #GESProject on which a problem happend when creted a #GESAsset + * @timeline: The timeline that failed loading + * @error: The #GError defining the error that occured + * + * Since: 1.18 + */ + _signals[ERROR_LOADING] = + g_signal_new ("error-loading", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 2, GES_TYPE_TIMELINE, G_TYPE_ERROR); + + object_class->dispose = _dispose; + object_class->finalize = _finalize; + + GES_ASSET_CLASS (klass)->extract = ges_project_extract; +} + +static void +ges_project_init (GESProject * project) +{ + GESProjectPrivate *priv = project->priv = + ges_project_get_instance_private (project); + + priv->uri = NULL; + priv->formatters = NULL; + priv->formatter_asset = NULL; + priv->encoding_profiles = NULL; + priv->assets = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, gst_object_unref); + priv->loading_assets = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, gst_object_unref); + priv->loaded_with_error = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); +} + +static gchar * +ges_project_internal_extractable_type_id (GType extractable_type, + const gchar * id) +{ + return g_strdup_printf ("%s:%s", _extractable_type_name (extractable_type), + id); +} + +static gchar * +ges_project_internal_asset_id (GESAsset * asset) +{ + return + ges_project_internal_extractable_type_id (ges_asset_get_extractable_type + (asset), ges_asset_get_id (asset)); +} + +static void +_send_error_loading_asset (GESProject * project, GESAsset * asset, + GError * error) +{ + gchar *internal_id = ges_project_internal_asset_id (asset); + const gchar *id = ges_asset_get_id (asset); + + GST_DEBUG_OBJECT (project, "Sending error loading asset for %s", id); + g_hash_table_remove (project->priv->loading_assets, internal_id); + g_hash_table_add (project->priv->loaded_with_error, internal_id); + g_signal_emit (project, _signals[ERROR_LOADING_ASSET], 0, error, + id, ges_asset_get_extractable_type (asset)); +} + +gchar * +ges_project_try_updating_id (GESProject * project, GESAsset * asset, + GError * error) +{ + gchar *new_id = NULL; + const gchar *id; + gchar *internal_id; + + g_return_val_if_fail (GES_IS_PROJECT (project), NULL); + g_return_val_if_fail (GES_IS_ASSET (asset), NULL); + g_return_val_if_fail (error, NULL); + + id = ges_asset_get_id (asset); + GST_DEBUG_OBJECT (project, "Try to proxy %s", id); + if (ges_asset_request_id_update (asset, &new_id, error) == FALSE) { + GST_DEBUG_OBJECT (project, "Type: %s can not be proxied for id: %s " + "and error: %s", g_type_name (G_OBJECT_TYPE (asset)), id, + error->message); + _send_error_loading_asset (project, asset, error); + + return NULL; + } + + /* Always send the MISSING_URI signal if requesting new ID is possible + * so that subclasses of GESProject are aware of the missing-uri */ + g_signal_emit (project, _signals[MISSING_URI_SIGNAL], 0, error, asset, + &new_id); + + if (new_id) { + GST_DEBUG_OBJECT (project, "new id found: %s", new_id); + if (!ges_asset_try_proxy (asset, new_id)) { + g_free (new_id); + new_id = NULL; + } + } else { + GST_DEBUG_OBJECT (project, "No new id found for %s", id); + } + + internal_id = ges_project_internal_asset_id (asset); + g_hash_table_remove (project->priv->loading_assets, internal_id); + g_free (internal_id); + + if (new_id == NULL) + _send_error_loading_asset (project, asset, error); + + + return new_id; +} + +static void +new_asset_cb (GESAsset * source, GAsyncResult * res, GESProject * project) +{ + GError *error = NULL; + gchar *possible_id = NULL; + GESAsset *asset = ges_asset_request_finish (res, &error); + + if (error) { + possible_id = ges_project_try_updating_id (project, source, error); + g_clear_error (&error); + + if (possible_id == NULL) + return; + + ges_project_create_asset (project, possible_id, + ges_asset_get_extractable_type (source)); + + g_free (possible_id); + return; + } + + if (asset) { + ges_asset_finish_proxy (asset); + ges_project_add_asset (project, asset); + gst_object_unref (asset); + } +} + +/** + * ges_project_set_loaded: + * @project: The #GESProject from which to emit the "project-loaded" signal + * + * Emits the "loaded" signal. This method should be called by sublasses when + * the project is fully loaded. + * + * Returns: %TRUE if the signale could be emitted %FALSE otherwize + */ +gboolean +ges_project_set_loaded (GESProject * project, GESFormatter * formatter, + GError * error) +{ + if (error) { + GST_ERROR_OBJECT (project, "Emit project error-loading %s", error->message); + g_signal_emit (project, _signals[ERROR_LOADING], 0, formatter->timeline, + error); + } + + GST_INFO_OBJECT (project, "Emit project loaded"); + if (GST_STATE (formatter->timeline) < GST_STATE_PAUSED) { + timeline_fill_gaps (formatter->timeline); + } else { + ges_timeline_commit (formatter->timeline); + } + + g_signal_emit (project, _signals[LOADED_SIGNAL], 0, formatter->timeline); + + /* We are now done with that formatter */ + ges_project_remove_formatter (project, formatter); + return TRUE; +} + +void +ges_project_add_loading_asset (GESProject * project, GType extractable_type, + const gchar * id) +{ + GESAsset *asset; + + if ((asset = ges_asset_cache_lookup (extractable_type, id))) { + if (g_hash_table_insert (project->priv->loading_assets, + ges_project_internal_asset_id (asset), gst_object_ref (asset))) + g_signal_emit (project, _signals[ASSET_LOADING_SIGNAL], 0, asset); + } +} + +/************************************** + * * + * API Implementation * + * * + **************************************/ + +/** + * ges_project_create_asset: + * @project: A #GESProject + * @id: (allow-none): The id of the asset to create and add to @project + * @extractable_type: The #GType of the asset to create + * + * Create and add a #GESAsset to @project. You should connect to the + * "asset-added" signal to get the asset when it finally gets added to + * @project + * + * Returns: %TRUE if the asset started to be added %FALSE it was already + * in the project + */ +gboolean +ges_project_create_asset (GESProject * project, const gchar * id, + GType extractable_type) +{ + gchar *internal_id; + g_return_val_if_fail (GES_IS_PROJECT (project), FALSE); + g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE), + FALSE); + + if (id == NULL) + id = g_type_name (extractable_type); + internal_id = ges_project_internal_extractable_type_id (extractable_type, id); + + if (g_hash_table_lookup (project->priv->assets, internal_id) || + g_hash_table_lookup (project->priv->loading_assets, internal_id) || + g_hash_table_lookup (project->priv->loaded_with_error, internal_id)) { + + g_free (internal_id); + return FALSE; + } + g_free (internal_id); + + /* TODO Add a GCancellable somewhere in our API */ + ges_asset_request_async (extractable_type, id, NULL, + (GAsyncReadyCallback) new_asset_cb, project); + ges_project_add_loading_asset (project, extractable_type, id); + + return TRUE; +} + +/** + * ges_project_create_asset_sync: + * @project: A #GESProject + * @id: (allow-none): The id of the asset to create and add to @project + * @extractable_type: The #GType of the asset to create + * @error: A #GError to be set in case of error + * + * Create and add a #GESAsset to @project. You should connect to the + * "asset-added" signal to get the asset when it finally gets added to + * @project + * + * Returns: (transfer full) (nullable): The newly created #GESAsset or %NULL. + */ +GESAsset * +ges_project_create_asset_sync (GESProject * project, const gchar * id, + GType extractable_type, GError ** error) +{ + GESAsset *asset; + gchar *possible_id = NULL, *internal_id; + gboolean retry = TRUE; + + g_return_val_if_fail (GES_IS_PROJECT (project), FALSE); + g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE), + FALSE); + + if (id == NULL) + id = g_type_name (extractable_type); + + internal_id = ges_project_internal_extractable_type_id (extractable_type, id); + if ((asset = g_hash_table_lookup (project->priv->assets, internal_id))) { + g_free (internal_id); + + return gst_object_ref (asset); + } else if (g_hash_table_lookup (project->priv->loading_assets, internal_id) || + g_hash_table_lookup (project->priv->loaded_with_error, internal_id)) { + g_free (internal_id); + + return NULL; + } + g_free (internal_id); + + /* TODO Add a GCancellable somewhere in our API */ + while (retry) { + + if (g_type_is_a (extractable_type, GES_TYPE_URI_CLIP)) { + asset = GES_ASSET (ges_uri_clip_asset_request_sync (id, error)); + } else { + asset = ges_asset_request (extractable_type, id, error); + } + + if (asset) { + retry = FALSE; + internal_id = + ges_project_internal_extractable_type_id (extractable_type, id); + if ((!g_hash_table_lookup (project->priv->assets, internal_id))) + g_signal_emit (project, _signals[ASSET_LOADING_SIGNAL], 0, asset); + g_free (internal_id); + + if (possible_id) { + g_free (possible_id); + ges_uri_assets_validate_uri (id); + } + + break; + } else { + GESAsset *tmpasset; + + tmpasset = ges_asset_cache_lookup (extractable_type, id); + possible_id = ges_project_try_updating_id (project, tmpasset, *error); + + if (possible_id == NULL) { + g_signal_emit (project, _signals[ASSET_LOADING_SIGNAL], 0, tmpasset); + g_signal_emit (project, _signals[ERROR_LOADING_ASSET], 0, *error, id, + extractable_type); + return NULL; + } + + + g_clear_error (error); + + id = possible_id; + } + } + + if (!ges_asset_get_proxy_target (asset)) + ges_asset_finish_proxy (asset); + + ges_project_add_asset (project, asset); + + return asset; +} + +/** + * ges_project_add_asset: + * @project: A #GESProject + * @asset: (transfer none): A #GESAsset to add to @project + * + * Adds a #GESAsset to @project, the project will keep a reference on + * @asset. + * + * Returns: %TRUE if the asset could be added %FALSE it was already + * in the project + */ +gboolean +ges_project_add_asset (GESProject * project, GESAsset * asset) +{ + gchar *internal_id; + g_return_val_if_fail (GES_IS_PROJECT (project), FALSE); + + internal_id = ges_project_internal_asset_id (asset); + if (g_hash_table_lookup (project->priv->assets, internal_id)) { + g_free (internal_id); + return TRUE; + } + + g_hash_table_insert (project->priv->assets, internal_id, + gst_object_ref (asset)); + g_hash_table_remove (project->priv->loading_assets, internal_id); + GST_DEBUG_OBJECT (project, "Asset added: %s", ges_asset_get_id (asset)); + g_signal_emit (project, _signals[ASSET_ADDED_SIGNAL], 0, asset); + + return TRUE; +} + +/** + * ges_project_remove_asset: + * @project: A #GESProject + * @asset: (transfer none): A #GESAsset to remove from @project + * + * remove a @asset to from @project. + * + * Returns: %TRUE if the asset could be removed %FALSE otherwise + */ +gboolean +ges_project_remove_asset (GESProject * project, GESAsset * asset) +{ + gboolean ret; + gchar *internal_id; + + g_return_val_if_fail (GES_IS_PROJECT (project), FALSE); + + internal_id = ges_project_internal_asset_id (asset); + ret = g_hash_table_remove (project->priv->assets, internal_id); + g_free (internal_id); + g_signal_emit (project, _signals[ASSET_REMOVED_SIGNAL], 0, asset); + + return ret; +} + +/** + * ges_project_get_asset: + * @project: A #GESProject + * @id: The id of the asset to retrieve + * @extractable_type: The extractable_type of the asset + * to retrieve from @object + * + * Returns: (transfer full) (allow-none): The #GESAsset with + * @id or %NULL if no asset with @id as an ID + */ +GESAsset * +ges_project_get_asset (GESProject * project, const gchar * id, + GType extractable_type) +{ + GESAsset *asset; + gchar *internal_id; + + g_return_val_if_fail (GES_IS_PROJECT (project), NULL); + g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE), + NULL); + + internal_id = ges_project_internal_extractable_type_id (extractable_type, id); + asset = g_hash_table_lookup (project->priv->assets, internal_id); + g_free (internal_id); + + if (asset) + return gst_object_ref (asset); + + return NULL; +} + +/** + * ges_project_list_assets: + * @project: A #GESProject + * @filter: Type of assets to list, `GES_TYPE_EXTRACTABLE` will list + * all assets + * + * List all @asset contained in @project filtering per extractable_type + * as defined by @filter. It copies the asset and thus will not be updated + * in time. + * + * Returns: (transfer full) (element-type GESAsset): The list of + * #GESAsset the object contains + */ +GList * +ges_project_list_assets (GESProject * project, GType filter) +{ + GList *ret = NULL; + GHashTableIter iter; + gpointer key, value; + + g_return_val_if_fail (GES_IS_PROJECT (project), NULL); + g_return_val_if_fail (filter == G_TYPE_NONE + || g_type_is_a (filter, GES_TYPE_EXTRACTABLE), NULL); + + g_hash_table_iter_init (&iter, project->priv->assets); + while (g_hash_table_iter_next (&iter, &key, &value)) { + if (g_type_is_a (ges_asset_get_extractable_type (GES_ASSET (value)), + filter)) + ret = g_list_append (ret, gst_object_ref (value)); + } + + return ret; +} + +/** + * ges_project_save: + * @project: A #GESProject to save + * @timeline: The #GESTimeline to save, it must have been extracted from @project + * @uri: The uri where to save @project and @timeline + * @formatter_asset: (transfer full) (allow-none): The formatter asset to + * use or %NULL. If %NULL, will try to save in the same format as the one + * from which the timeline as been loaded or default to the best formatter + * as defined in #ges_find_formatter_for_uri + * @overwrite: %TRUE to overwrite file if it exists + * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL + * + * Save the timeline of @project to @uri. You should make sure that @timeline + * is one of the timelines that have been extracted from @project + * (using ges_asset_extract (@project);) + * + * Returns: %TRUE if the project could be save, %FALSE otherwize + */ +gboolean +ges_project_save (GESProject * project, GESTimeline * timeline, + const gchar * uri, GESAsset * formatter_asset, gboolean overwrite, + GError ** error) +{ + GESAsset *tl_asset; + gboolean ret = TRUE; + GESFormatter *formatter = NULL; + + g_return_val_if_fail (GES_IS_PROJECT (project), FALSE); + g_return_val_if_fail (formatter_asset == NULL || + g_type_is_a (ges_asset_get_extractable_type (formatter_asset), + GES_TYPE_FORMATTER), FALSE); + g_return_val_if_fail ((error == NULL || *error == NULL), FALSE); + + tl_asset = ges_extractable_get_asset (GES_EXTRACTABLE (timeline)); + if (tl_asset == NULL && project->priv->uri == NULL) { + GESAsset *asset = ges_asset_cache_lookup (GES_TYPE_PROJECT, uri); + + if (asset) { + GST_WARNING_OBJECT (project, "Trying to save project to %s but we already" + "have %" GST_PTR_FORMAT " for that uri, can not save", uri, asset); + goto out; + } + + GST_DEBUG_OBJECT (project, "Timeline %" GST_PTR_FORMAT " has no asset" + " we have no uri set, so setting ourself as asset", timeline); + + ges_extractable_set_asset (GES_EXTRACTABLE (timeline), GES_ASSET (project)); + } else if (tl_asset != GES_ASSET (project)) { + GST_WARNING_OBJECT (project, "Timeline %" GST_PTR_FORMAT + " not created by this project can not save", timeline); + + ret = FALSE; + goto out; + } + + if (formatter_asset == NULL) { + formatter_asset = gst_object_ref (ges_find_formatter_for_uri (uri)); + } + + formatter = GES_FORMATTER (ges_asset_extract (formatter_asset, error)); + if (formatter == NULL) { + GST_WARNING_OBJECT (project, "Could not create the formatter %p %s: %s", + formatter_asset, ges_asset_get_id (formatter_asset), + (error && *error) ? (*error)->message : "Unknown Error"); + + ret = FALSE; + goto out; + } + + ges_project_add_formatter (project, formatter); + ret = ges_formatter_save_to_uri (formatter, timeline, uri, overwrite, error); + if (ret && project->priv->uri == NULL) + ges_project_set_uri (project, uri); + +out: + if (formatter_asset) + gst_object_unref (formatter_asset); + ges_project_remove_formatter (project, formatter); + + return ret; +} + +/** + * ges_project_new: + * @uri: (allow-none): The uri to be set after creating the project. + * + * Creates a new #GESProject and sets its uri to @uri if provided. Note that + * if @uri is not valid or %NULL, the uri of the project will then be set + * the first time you save the project. If you then save the project to + * other locations, it will never be updated again and the first valid URI is + * the URI it will keep refering to. + * + * Returns: A newly created #GESProject + */ +GESProject * +ges_project_new (const gchar * uri) +{ + gchar *id = (gchar *) uri; + GESProject *project; + + if (uri == NULL) + id = g_strdup_printf ("project-%i", nb_projects++); + + project = GES_PROJECT (ges_asset_request (GES_TYPE_TIMELINE, id, NULL)); + + if (project && uri) + ges_project_set_uri (project, uri); + + if (uri == NULL) + g_free (id); + + return project; +} + +/** + * ges_project_load: + * @project: A #GESProject that has an @uri set already + * @timeline: A blank timeline to load @project into + * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL + * + * Loads @project into @timeline + * + * Returns: %TRUE if the project could be loaded %FALSE otherwize. + */ +gboolean +ges_project_load (GESProject * project, GESTimeline * timeline, GError ** error) +{ + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + g_return_val_if_fail (GES_IS_PROJECT (project), FALSE); + g_return_val_if_fail (project->priv->uri, FALSE); + g_return_val_if_fail (timeline->tracks == NULL, FALSE); + + if (!_load_project (project, timeline, error)) + return FALSE; + + ges_extractable_set_asset (GES_EXTRACTABLE (timeline), GES_ASSET (project)); + + return TRUE; +} + +/** + * ges_project_get_uri: + * @project: A #GESProject + * + * Retrieve the uri that is currently set on @project + * + * Returns: (transfer full) (nullable): a newly allocated string representing uri. + */ +gchar * +ges_project_get_uri (GESProject * project) +{ + GESProjectPrivate *priv; + + g_return_val_if_fail (GES_IS_PROJECT (project), FALSE); + + priv = project->priv; + if (priv->uri) + return g_strdup (priv->uri); + return NULL; +} + +/** + * ges_project_add_encoding_profile: + * @project: A #GESProject + * @profile: A #GstEncodingProfile to add to the project. If a profile with + * the same name already exists, it will be replaced + * + * Adds @profile to the project. It lets you save in what format + * the project has been renders and keep a reference to those formats. + * Also, those formats will be saves to the project file when possible. + * + * Returns: %TRUE if @profile could be added, %FALSE otherwize + */ +gboolean +ges_project_add_encoding_profile (GESProject * project, + GstEncodingProfile * profile) +{ + GList *tmp; + GESProjectPrivate *priv; + + g_return_val_if_fail (GES_IS_PROJECT (project), FALSE); + g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), FALSE); + + priv = project->priv; + for (tmp = priv->encoding_profiles; tmp; tmp = tmp->next) { + GstEncodingProfile *tmpprofile = GST_ENCODING_PROFILE (tmp->data); + + if (g_strcmp0 (gst_encoding_profile_get_name (tmpprofile), + gst_encoding_profile_get_name (profile)) == 0) { + GST_INFO_OBJECT (project, "Already have profile: %s, replacing it", + gst_encoding_profile_get_name (profile)); + + gst_object_unref (tmp->data); + tmp->data = gst_object_ref (profile); + return TRUE; + } + } + + priv->encoding_profiles = g_list_prepend (priv->encoding_profiles, + gst_object_ref (profile)); + + return TRUE; +} + +/** + * ges_project_list_encoding_profiles: + * @project: A #GESProject + * + * Lists the encoding profile that have been set to @project. The first one + * is the latest added. + * + * Returns: (transfer none) (element-type GstPbutils.EncodingProfile) (allow-none): The + * list of #GstEncodingProfile used in @project + */ +const GList * +ges_project_list_encoding_profiles (GESProject * project) +{ + g_return_val_if_fail (GES_IS_PROJECT (project), FALSE); + + return project->priv->encoding_profiles; +} + +/** + * ges_project_get_loading_assets: + * @project: A #GESProject + * + * Get the assets that are being loaded + * + * Returns: (transfer full) (element-type GES.Asset): A set of loading asset + * that will be added to @project. Note that those Asset are *not* loaded yet, + * and thus can not be used + */ +GList * +ges_project_get_loading_assets (GESProject * project) +{ + GHashTableIter iter; + gpointer key, value; + + GList *ret = NULL; + + g_return_val_if_fail (GES_IS_PROJECT (project), NULL); + + g_hash_table_iter_init (&iter, project->priv->loading_assets); + while (g_hash_table_iter_next (&iter, &key, &value)) + ret = g_list_prepend (ret, gst_object_ref (value)); + + return ret; +} diff --git a/ges/ges-project.h b/ges/ges-project.h new file mode 100644 index 0000000000..c3b5b086cd --- /dev/null +++ b/ges/ges-project.h @@ -0,0 +1,129 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2012> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#pragma once + +#include <glib-object.h> +#include <gio/gio.h> +#include <ges/ges-types.h> +#include <ges/ges-asset.h> +#include <gst/pbutils/encoding-profile.h> + +G_BEGIN_DECLS + +#define GES_TYPE_PROJECT ges_project_get_type() +GES_DECLARE_TYPE(Project, project, PROJECT); + +struct _GESProject +{ + GESAsset parent; + + /* <private> */ + GESProjectPrivate *priv; + + /* Padding for API extension */ + gpointer __ges_reserved[GES_PADDING_LARGE]; +}; + +struct _GESProjectClass +{ + GESAssetClass parent_class; + + /* Signals */ + void (*asset_added) (GESProject * self, + GESAsset * asset); + void (*asset_loading) (GESProject * self, + GESAsset * asset); + void (*asset_removed) (GESProject * self, + GESAsset * asset); + gchar * (*missing_uri) (GESProject * self, + GError * error, + GESAsset * wrong_asset); + gboolean (*loading_error) (GESProject * self, + GError * error, + gchar * id, + GType extractable_type); + gboolean (*loaded) (GESProject * self, + GESTimeline * timeline); + /** + * GESProjectClass::loading: + * @self: The self + * @timeline: The loading timeline + * + * Since: 1.18 + */ + void (*loading) (GESProject * self, + GESTimeline * timeline); + + gpointer _ges_reserved[GES_PADDING - 1]; +}; + +GES_API +gboolean ges_project_add_asset (GESProject* project, + GESAsset *asset); +GES_API +gboolean ges_project_remove_asset (GESProject *project, + GESAsset * asset); +GES_API +GList * ges_project_list_assets (GESProject * project, + GType filter); +GES_API +gboolean ges_project_save (GESProject * project, + GESTimeline * timeline, + const gchar *uri, + GESAsset * formatter_asset, + gboolean overwrite, + GError **error); +GES_API +gboolean ges_project_load (GESProject * project, + GESTimeline * timeline, + GError **error); +GES_API +GESProject * ges_project_new (const gchar *uri); +GES_API +gchar * ges_project_get_uri (GESProject *project); +GES_API +GESAsset * ges_project_get_asset (GESProject * project, + const gchar *id, + GType extractable_type); +GES_API +gboolean ges_project_create_asset (GESProject * project, + const gchar *id, + GType extractable_type); + +GES_API +GESAsset * ges_project_create_asset_sync (GESProject * project, + const gchar * id, + GType extractable_type, + GError **error); +GES_API +GList * ges_project_get_loading_assets (GESProject * project); + +GES_API +gboolean ges_project_add_encoding_profile (GESProject *project, + GstEncodingProfile *profile); +GES_API +const GList *ges_project_list_encoding_profiles (GESProject *project); +GES_API +gboolean ges_add_missing_uri_relocation_uri (const gchar * uri, + gboolean recurse); +GES_API +void ges_project_add_formatter (GESProject * project, GESFormatter * formatter); + +G_END_DECLS diff --git a/ges/ges-screenshot.c b/ges/ges-screenshot.c new file mode 100644 index 0000000000..00e58101d9 --- /dev/null +++ b/ges/ges-screenshot.c @@ -0,0 +1,50 @@ +/* Small helper element for format conversion + * Copyright (C) 2005 Tim-Philipp Müller <tim centricular net> + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> +#include <gst/video/video.h> +#include "ges-screenshot.h" +#include "ges-internal.h" + +/** + * ges_play_sink_convert_frame: + * @playsink: The playsink to get last frame from + * @caps: The caps defining the format the return value will have + * + * Get the last buffer @playsink showed + * + * Returns: (transfer full): A #GstSample containing the last frame from + * @playsink in the format defined by the @caps + * + * Deprecated: 1.18: Use the "convert-sample" action signal of + * #playsink instead. + */ +GstSample * +ges_play_sink_convert_frame (GstElement * playsink, GstCaps * caps) +{ + GstSample *sample = NULL; + + g_signal_emit_by_name (playsink, "convert-sample", caps, &sample); + + return sample; +} diff --git a/ges/ges-screenshot.h b/ges/ges-screenshot.h new file mode 100644 index 0000000000..07133aaf73 --- /dev/null +++ b/ges/ges-screenshot.h @@ -0,0 +1,31 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <gst/gst.h> +#include <ges/ges-prelude.h> + +G_BEGIN_DECLS + +GES_DEPRECATED GstSample * +ges_play_sink_convert_frame (GstElement * playsink, GstCaps * caps); + +G_END_DECLS diff --git a/ges/ges-smart-adder.c b/ges/ges-smart-adder.c new file mode 100644 index 0000000000..85ea017114 --- /dev/null +++ b/ges/ges-smart-adder.c @@ -0,0 +1,265 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ +/* + * gst-editing-services + * + * Copyright (C) 2013 Thibault Saunier <tsaunier@gnome.org> + + * gst-editing-services 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 3 of the License, or + * (at your option) any later version. + * + * gst-editing-services 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 program. If not, see <http://www.gnu.org/licenses/>."; + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/audio/audio.h> + +#include "ges-types.h" +#include "ges-internal.h" +#include "ges-smart-adder.h" + +G_DEFINE_TYPE (GESSmartAdder, ges_smart_adder, GST_TYPE_BIN); + +#define GET_LOCK(obj) (&((GESSmartAdder*)(obj))->lock) +#define LOCK(obj) (g_mutex_lock (GET_LOCK(obj))) +#define UNLOCK(obj) (g_mutex_unlock (GET_LOCK(obj))) + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw") + ); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink_%u", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("audio/x-raw") + ); + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN +#define DEFAULT_CAPS "audio/x-raw,format=(string)S32LE;" +#else +#define DEFAULT_CAPS "audio/x-raw,format=(string)S32BE;" +#endif + +typedef struct _PadInfos +{ + GESSmartAdder *self; + GstPad *adder_pad; + GstElement *bin; +} PadInfos; + +static void +destroy_pad (PadInfos * infos) +{ + if (G_LIKELY (infos->bin)) { + gst_element_set_state (infos->bin, GST_STATE_NULL); + gst_element_unlink (infos->bin, infos->self->adder); + gst_bin_remove (GST_BIN (infos->self), infos->bin); + } + + if (infos->adder_pad) { + gst_element_release_request_pad (infos->self->adder, infos->adder_pad); + gst_object_unref (infos->adder_pad); + } + g_slice_free (PadInfos, infos); +} + +/**************************************************** + * GstElement vmetods * + ****************************************************/ +static GstPad * +_request_new_pad (GstElement * element, GstPadTemplate * templ, + const gchar * name, const GstCaps * caps) +{ + GstPad *audioresample_srcpad, *audioconvert_sinkpad, *tmpghost; + GstPad *ghost; + GstElement *audioconvert, *audioresample; + PadInfos *infos = g_slice_new0 (PadInfos); + GESSmartAdder *self = GES_SMART_ADDER (element); + + infos->adder_pad = gst_element_request_pad (self->adder, + gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (self->adder), + "sink_%u"), NULL, caps); + + if (infos->adder_pad == NULL) { + GST_WARNING_OBJECT (element, "Could not get any pad from GstAdder"); + g_slice_free (PadInfos, infos); + + return NULL; + } + + infos->self = self; + + infos->bin = gst_bin_new (NULL); + audioconvert = gst_element_factory_make ("audioconvert", NULL); + audioresample = gst_element_factory_make ("audioresample", NULL); + + gst_bin_add_many (GST_BIN (infos->bin), audioconvert, audioresample, NULL); + gst_element_link_many (audioconvert, audioresample, NULL); + + audioconvert_sinkpad = gst_element_get_static_pad (audioconvert, "sink"); + tmpghost = GST_PAD (gst_ghost_pad_new (NULL, audioconvert_sinkpad)); + gst_object_unref (audioconvert_sinkpad); + gst_pad_set_active (tmpghost, TRUE); + gst_element_add_pad (GST_ELEMENT (infos->bin), tmpghost); + + gst_bin_add (GST_BIN (self), infos->bin); + ghost = gst_ghost_pad_new (NULL, tmpghost); + gst_pad_set_active (ghost, TRUE); + if (!gst_element_add_pad (GST_ELEMENT (self), ghost)) + goto could_not_add; + + audioresample_srcpad = gst_element_get_static_pad (audioresample, "src"); + tmpghost = GST_PAD (gst_ghost_pad_new (NULL, audioresample_srcpad)); + gst_object_unref (audioresample_srcpad); + gst_pad_set_active (tmpghost, TRUE); + gst_element_add_pad (GST_ELEMENT (infos->bin), tmpghost); + gst_pad_link (tmpghost, infos->adder_pad); + + LOCK (self); + g_hash_table_insert (self->pads_infos, ghost, infos); + UNLOCK (self); + + GST_DEBUG_OBJECT (self, "Returning new pad %" GST_PTR_FORMAT, ghost); + return ghost; + +could_not_add: + { + GST_ERROR_OBJECT (self, "could not add pad"); + destroy_pad (infos); + return NULL; + } +} + +static void +_release_pad (GstElement * element, GstPad * pad) +{ + GST_DEBUG_OBJECT (element, "Releasing pad %" GST_PTR_FORMAT, pad); + + LOCK (element); + g_hash_table_remove (GES_SMART_ADDER (element)->pads_infos, pad); + UNLOCK (element); +} + +/**************************************************** + * GObject vmethods * + ****************************************************/ +static void +ges_smart_adder_dispose (GObject * object) +{ + GESSmartAdder *self = GES_SMART_ADDER (object); + + if (self->pads_infos) { + g_hash_table_unref (self->pads_infos); + self->pads_infos = NULL; + } + + G_OBJECT_CLASS (ges_smart_adder_parent_class)->dispose (object); +} + +static void +ges_smart_adder_finalize (GObject * object) +{ + GESSmartAdder *self = GES_SMART_ADDER (object); + + g_mutex_clear (&self->lock); + + G_OBJECT_CLASS (ges_smart_adder_parent_class)->finalize (object); +} + +static void +ges_smart_adder_class_init (GESSmartAdderClass * klass) +{ +/* GstBinClass *parent_class = GST_BIN_CLASS (klass); + */ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + /* FIXME Make sure the AdderClass doesn get destroy before ourself */ + gst_element_class_add_static_pad_template (element_class, &src_template); + gst_element_class_add_static_pad_template (element_class, &sink_template); + gst_element_class_set_static_metadata (element_class, "GES Smart adder", + "Generic/Audio", + "Use adder making use of GES informations", + "Thibault Saunier <thibault.saunier@collabora.com>"); + + element_class->request_new_pad = GST_DEBUG_FUNCPTR (_request_new_pad); + element_class->release_pad = GST_DEBUG_FUNCPTR (_release_pad); + + object_class->dispose = ges_smart_adder_dispose; + object_class->finalize = ges_smart_adder_finalize; +} + +static void +ges_smart_adder_init (GESSmartAdder * self) +{ + GstPad *pad; + + g_mutex_init (&self->lock); + + self->adder = gst_element_factory_make ("audiomixer", "smart-adder-adder"); + gst_bin_add (GST_BIN (self), self->adder); + + self->capsfilter = + gst_element_factory_make ("capsfilter", "smart-adder-capsfilter"); + gst_bin_add (GST_BIN (self), self->capsfilter); + + gst_element_link (self->adder, self->capsfilter); + + pad = gst_element_get_static_pad (self->capsfilter, "src"); + self->srcpad = gst_ghost_pad_new ("src", pad); + gst_pad_set_active (self->srcpad, TRUE); + gst_object_unref (pad); + + gst_element_add_pad (GST_ELEMENT (self), self->srcpad); + + self->pads_infos = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) destroy_pad); +} + +static void +restriction_caps_cb (GESTrack * track, + GParamSpec * arg G_GNUC_UNUSED, GESSmartAdder * self) +{ + GstCaps *caps; + + g_object_get (track, "restriction-caps", &caps, NULL); + + if (!caps) + caps = gst_caps_from_string (DEFAULT_CAPS); + + GST_DEBUG_OBJECT (self, "Setting adder caps to %" GST_PTR_FORMAT, caps); + g_object_set (self->capsfilter, "caps", caps, NULL); + gst_caps_unref (caps); +} + +GstElement * +ges_smart_adder_new (GESTrack * track) +{ + GESSmartAdder *self; + + self = g_object_new (GES_TYPE_SMART_ADDER, NULL); + + self->track = track; + + if (track) { + restriction_caps_cb (track, NULL, self); + + g_signal_connect (track, "notify::restriction-caps", + G_CALLBACK (restriction_caps_cb), self); + } + + /* FIXME Make adder smart and let it properly negotiate caps! */ + + return GST_ELEMENT (self); +} diff --git a/ges/ges-smart-adder.h b/ges/ges-smart-adder.h new file mode 100644 index 0000000000..e1c4a5fbbd --- /dev/null +++ b/ges/ges-smart-adder.h @@ -0,0 +1,69 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ +/* + * gst-editing-services + * Copyright (C) 2013 Thibault Saunier <tsaunier@gnome.org> + * + * gst-editing-services 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 3 of the License, or + * (at your option) any later version. + * + * gst-editing-services 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 program. If not, see <http://www.gnu.org/licenses/>."; + */ + +#pragma once + +#include <glib-object.h> +#include <gst/gst.h> + +#include "ges-track.h" + +G_BEGIN_DECLS + +#define GES_TYPE_SMART_ADDER (ges_smart_adder_get_type ()) +#define GES_SMART_ADDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_SMART_ADDER, GESSmartAdder)) +#define GES_SMART_ADDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_SMART_ADDER, GESSmartAdderClass)) +#define GES_IS_SMART_ADDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_SMART_ADDER)) +#define GES_IS_SMART_ADDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_SMART_ADDER)) +#define GES_SMART_ADDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_SMART_ADDER, GESSmartAdderClass)) + +typedef struct _GESSmartAdderClass GESSmartAdderClass; +typedef struct _GESSmartAdder GESSmartAdder; + +struct _GESSmartAdderClass +{ + GstBinClass parent_class; + + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESSmartAdder +{ + GstBin parent_instance; + + GHashTable *pads_infos; + GstPad *srcpad; + GstElement *adder; + GstElement *capsfilter; + GMutex lock; + + GstCaps *caps; + + GESTrack *track; + + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API +GType ges_smart_adder_get_type (void) G_GNUC_CONST; + +GES_API +GstElement* ges_smart_adder_new (GESTrack *track); + +G_END_DECLS diff --git a/ges/ges-smart-video-mixer.c b/ges/ges-smart-video-mixer.c new file mode 100644 index 0000000000..636f1699b9 --- /dev/null +++ b/ges/ges-smart-video-mixer.c @@ -0,0 +1,484 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ +/* + * gst-editing-services + * + * Copyright (C) 2013 Mathieu Duponchelle <mduponchelle1@gmail.com> + * gst-editing-services 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 3 of the License, or + * (at your option) any later version. + * + * gst-editing-services 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 program. If not, see <http://www.gnu.org/licenses/>."; + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstframepositioner.h" +#include "ges-types.h" +#include "ges-internal.h" +#include "ges-smart-video-mixer.h" +#include <gst/base/base.h> + +#define GES_TYPE_SMART_MIXER_PAD (ges_smart_mixer_pad_get_type ()) +typedef struct _GESSmartMixerPad GESSmartMixerPad; +typedef struct _GESSmartMixerPadClass GESSmartMixerPadClass; +GES_DECLARE_TYPE (SmartMixerPad, smart_mixer_pad, SMART_MIXER_PAD); + +struct _GESSmartMixerPad +{ + GstGhostPad parent; + + gdouble alpha; + GstSegment segment; +}; + +struct _GESSmartMixerPadClass +{ + GstGhostPadClass parent_class; +}; + +enum +{ + PROP_PAD_0, + PROP_PAD_ALPHA, +}; + +G_DEFINE_TYPE (GESSmartMixerPad, ges_smart_mixer_pad, GST_TYPE_GHOST_PAD); + +static void +ges_smart_mixer_pad_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GESSmartMixerPad *pad = GES_SMART_MIXER_PAD (object); + + switch (prop_id) { + case PROP_PAD_ALPHA: + g_value_set_double (value, pad->alpha); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ges_smart_mixer_pad_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GESSmartMixerPad *pad = GES_SMART_MIXER_PAD (object); + + switch (prop_id) { + case PROP_PAD_ALPHA: + pad->alpha = g_value_get_double (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ges_smart_mixer_pad_init (GESSmartMixerPad * self) +{ + gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED); +} + +static void +ges_smart_mixer_pad_class_init (GESSmartMixerPadClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->get_property = ges_smart_mixer_pad_get_property; + gobject_class->set_property = ges_smart_mixer_pad_set_property; + + g_object_class_install_property (gobject_class, PROP_PAD_ALPHA, + g_param_spec_double ("alpha", "Alpha", "Alpha of the picture", 0.0, 1.0, + 1.0, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); +} + +G_DEFINE_TYPE (GESSmartMixer, ges_smart_mixer, GST_TYPE_BIN); + +#define GET_LOCK(obj) (&((GESSmartMixer*)(obj))->lock) +#define LOCK(obj) (g_mutex_lock (GET_LOCK(obj))) +#define UNLOCK(obj) (g_mutex_unlock (GET_LOCK(obj))) + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw") + ); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink_%u", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("video/x-raw") + ); + +typedef struct _PadInfos +{ + gint refcount; + + GESSmartMixer *self; + GstPad *mixer_pad; + GstPad *ghostpad; +} PadInfos; + +static void +pad_infos_unref (PadInfos * infos) +{ + if (g_atomic_int_dec_and_test (&infos->refcount)) { + GST_DEBUG_OBJECT (infos->mixer_pad, "Releasing pad"); + if (infos->mixer_pad) { + gst_element_release_request_pad (infos->self->mixer, infos->mixer_pad); + gst_object_unref (infos->mixer_pad); + } + + g_free (infos); + } +} + +static PadInfos * +pad_infos_new (void) +{ + PadInfos *info = g_new0 (PadInfos, 1); + g_atomic_int_set (&info->refcount, 1); + + return info; +} + +static PadInfos * +pad_infos_ref (PadInfos * info) +{ + g_atomic_int_inc (&info->refcount); + return info; +} + +static gboolean +ges_smart_mixer_sinkpad_event_func (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEGMENT: + { + const GstSegment *seg; + + gst_event_parse_segment (event, &seg); + + GST_OBJECT_LOCK (pad); + ((GESSmartMixerPad *) pad)->segment = *seg; + GST_OBJECT_UNLOCK (pad); + break; + } + default: + break; + } + + return gst_pad_event_default (pad, parent, event); +} + +GstPad * +ges_smart_mixer_get_mixer_pad (GESSmartMixer * self, GstPad ** mixerpad) +{ + PadInfos *info; + GstPad *sinkpad; + + sinkpad = gst_element_request_pad_simple (GST_ELEMENT (self), "sink_%u"); + + if (sinkpad == NULL) + return NULL; + + info = g_hash_table_lookup (self->pads_infos, sinkpad); + *mixerpad = gst_object_ref (info->mixer_pad); + + return sinkpad; +} + +static void +set_pad_properties_from_positioner_meta (GstPad * mixer_pad, GstSample * sample, + GESSmartMixerPad * ghost) +{ + GstFramePositionerMeta *meta; + GstBuffer *buf = gst_sample_get_buffer (sample); + GESSmartMixer *self = GES_SMART_MIXER (GST_OBJECT_PARENT (ghost)); + + meta = + (GstFramePositionerMeta *) gst_buffer_get_meta (buf, + gst_frame_positioner_meta_api_get_type ()); + + if (!meta) { + GST_WARNING ("The current source should use a framepositioner"); + return; + } + + if (!self->is_transition) { + g_object_set (mixer_pad, "alpha", meta->alpha, + "zorder", meta->zorder, NULL); + } else { + gint64 stream_time; + gdouble transalpha; + + stream_time = gst_segment_to_stream_time (gst_sample_get_segment (sample), + GST_FORMAT_TIME, GST_BUFFER_PTS (buf)); + + /* When used in a transition we aggregate the alpha value value if the + * transition pad and the alpha value from upstream frame positioner */ + if (GST_CLOCK_TIME_IS_VALID (stream_time)) + gst_object_sync_values (GST_OBJECT (ghost), stream_time); + + g_object_get (ghost, "alpha", &transalpha, NULL); + g_object_set (mixer_pad, "alpha", meta->alpha * transalpha, NULL); + } + + g_object_set (mixer_pad, "xpos", meta->posx, "ypos", + meta->posy, "width", meta->width, "height", meta->height, + "operator", meta->operator, NULL); +} + +/**************************************************** + * GstElement vmetods * + ****************************************************/ +static GstPad * +_request_new_pad (GstElement * element, GstPadTemplate * templ, + const gchar * name, const GstCaps * caps) +{ + PadInfos *infos = pad_infos_new (); + GESSmartMixer *self = GES_SMART_MIXER (element); + GstPad *ghost; + + infos->mixer_pad = gst_element_request_pad (self->mixer, + gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (self->mixer), + "sink_%u"), NULL, NULL); + + if (infos->mixer_pad == NULL) { + GST_WARNING_OBJECT (element, "Could not get any pad from GstMixer"); + pad_infos_unref (infos); + + return NULL; + } + + infos->self = self; + + ghost = g_object_new (ges_smart_mixer_pad_get_type (), "name", name, + "direction", GST_PAD_DIRECTION (infos->mixer_pad), NULL); + infos->ghostpad = ghost; + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (ghost), infos->mixer_pad); + gst_pad_set_active (ghost, TRUE); + if (!gst_element_add_pad (GST_ELEMENT (self), ghost)) + goto could_not_add; + + gst_pad_set_event_function (GST_PAD (ghost), + ges_smart_mixer_sinkpad_event_func); + + LOCK (self); + g_hash_table_insert (self->pads_infos, ghost, infos); + g_hash_table_insert (self->pads_infos, infos->mixer_pad, + pad_infos_ref (infos)); + UNLOCK (self); + + GST_DEBUG_OBJECT (self, "Returning new pad %" GST_PTR_FORMAT, ghost); + return ghost; + +could_not_add: + { + GST_ERROR_OBJECT (self, "could not add pad"); + pad_infos_unref (infos); + return NULL; + } +} + +static PadInfos * +ges_smart_mixer_find_pad_info (GESSmartMixer * self, GstPad * pad) +{ + PadInfos *info; + + LOCK (self); + info = g_hash_table_lookup (self->pads_infos, pad); + UNLOCK (self); + + if (info) + pad_infos_ref (info); + + return info; +} + +static void +_release_pad (GstElement * element, GstPad * pad) +{ + GstPad *peer; + GESSmartMixer *self = GES_SMART_MIXER (element); + PadInfos *info = ges_smart_mixer_find_pad_info (self, pad); + + GST_DEBUG_OBJECT (element, "Releasing pad %" GST_PTR_FORMAT, pad); + + LOCK (element); + g_hash_table_remove (GES_SMART_MIXER (element)->pads_infos, pad); + g_hash_table_remove (GES_SMART_MIXER (element)->pads_infos, info->mixer_pad); + peer = gst_pad_get_peer (pad); + if (peer) { + gst_pad_unlink (peer, pad); + + gst_object_unref (peer); + } + gst_pad_set_active (pad, FALSE); + gst_element_remove_pad (element, pad); + UNLOCK (element); + + pad_infos_unref (info); +} + +static gboolean +compositor_sync_properties_with_meta (GstElement * compositor, + GstPad * sinkpad, GESSmartMixer * self) +{ + PadInfos *info = ges_smart_mixer_find_pad_info (self, sinkpad); + GstSample *sample; + + if (!info) { + GST_WARNING_OBJECT (self, "Couldn't find pad info?!"); + + return TRUE; + } + + sample = gst_aggregator_peek_next_sample (GST_AGGREGATOR (compositor), + GST_AGGREGATOR_PAD (sinkpad)); + + if (sample) { + set_pad_properties_from_positioner_meta (sinkpad, + sample, GES_SMART_MIXER_PAD (info->ghostpad)); + gst_sample_unref (sample); + } else { + GST_INFO_OBJECT (sinkpad, "No sample set!"); + } + pad_infos_unref (info); + + return TRUE; +} + +static void +ges_smart_mixer_samples_selected_cb (GstElement * compositor, + GstSegment * segment, GstClockTime pts, GstClockTime dts, + GstClockTime duration, GstStructure * info, GESSmartMixer * self) +{ + gst_element_foreach_sink_pad (compositor, + (GstElementForeachPadFunc) compositor_sync_properties_with_meta, self); +} + +/**************************************************** + * GObject vmethods * + ****************************************************/ +static void +ges_smart_mixer_dispose (GObject * object) +{ + GESSmartMixer *self = GES_SMART_MIXER (object); + + if (self->pads_infos != NULL) { + g_hash_table_unref (self->pads_infos); + self->pads_infos = NULL; + } + + G_OBJECT_CLASS (ges_smart_mixer_parent_class)->dispose (object); +} + +static void +ges_smart_mixer_finalize (GObject * object) +{ + GESSmartMixer *self = GES_SMART_MIXER (object); + + g_mutex_clear (&self->lock); + + G_OBJECT_CLASS (ges_smart_mixer_parent_class)->finalize (object); +} + +static void +ges_smart_mixer_constructed (GObject * obj) +{ + GstPad *pad; + GstElement *identity, *videoconvert; + GESSmartMixer *self = GES_SMART_MIXER (obj); + gchar *cname = g_strdup_printf ("%s-compositor", GST_OBJECT_NAME (self)); + GstElement *mixer; + + self->mixer = + gst_element_factory_create (ges_get_compositor_factory (), cname); + g_free (cname); + + if (GST_IS_BIN (self->mixer)) { + g_object_get (self->mixer, "mixer", &mixer, NULL); + g_assert (mixer); + } else { + mixer = gst_object_ref (self->mixer); + } + + g_object_set (mixer, "background", 1, "emit-signals", TRUE, NULL); + g_signal_connect (mixer, "samples-selected", + G_CALLBACK (ges_smart_mixer_samples_selected_cb), self); + gst_object_unref (mixer); + + /* See https://gitlab.freedesktop.org/gstreamer/gstreamer/issues/310 */ + GST_FIXME ("Stop dropping allocation query when it is not required anymore."); + identity = gst_element_factory_make ("identity", NULL); + g_object_set (identity, "drop-allocation", TRUE, NULL); + g_assert (identity); + + videoconvert = gst_element_factory_make ("videoconvert", NULL); + g_assert (videoconvert); + + gst_bin_add_many (GST_BIN (self), self->mixer, identity, videoconvert, NULL); + gst_element_link_many (self->mixer, identity, videoconvert, NULL); + + pad = gst_element_get_static_pad (videoconvert, "src"); + self->srcpad = gst_ghost_pad_new ("src", pad); + gst_pad_set_active (self->srcpad, TRUE); + gst_object_unref (pad); + gst_element_add_pad (GST_ELEMENT (self), self->srcpad); +} + + +static void +ges_smart_mixer_class_init (GESSmartMixerClass * klass) +{ +/* GstBinClass *parent_class = GST_BIN_CLASS (klass); + */ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + /* FIXME Make sure the MixerClass doesn get destroy before ourself */ + gst_element_class_add_static_pad_template (element_class, &src_template); + gst_element_class_add_static_pad_template (element_class, &sink_template); + gst_element_class_set_static_metadata (element_class, "GES Smart mixer", + "Generic/Audio", + "Use mixer making use of GES information", + "Thibault Saunier <thibault.saunier@collabora.com>"); + + element_class->request_new_pad = GST_DEBUG_FUNCPTR (_request_new_pad); + element_class->release_pad = GST_DEBUG_FUNCPTR (_release_pad); + + object_class->dispose = ges_smart_mixer_dispose; + object_class->finalize = ges_smart_mixer_finalize; + object_class->constructed = ges_smart_mixer_constructed; +} + +static void +ges_smart_mixer_init (GESSmartMixer * self) +{ + g_mutex_init (&self->lock); + self->pads_infos = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) pad_infos_unref); +} + +GstElement * +ges_smart_mixer_new (GESTrack * track) +{ + GESSmartMixer *self = g_object_new (GES_TYPE_SMART_MIXER, NULL); + + /* FIXME Make mixer smart and let it properly negotiate caps! */ + return GST_ELEMENT (self); +} diff --git a/ges/ges-smart-video-mixer.h b/ges/ges-smart-video-mixer.h new file mode 100644 index 0000000000..a137c4580e --- /dev/null +++ b/ges/ges-smart-video-mixer.h @@ -0,0 +1,70 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ +/* + * gst-editing-services + * Copyright (C) 2013 Mathieu Duponchelle <mduponchelle1@gmail.com> + * + * gst-editing-services 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 3 of the License, or + * (at your option) any later version. + * + * gst-editing-services 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 program. If not, see <http://www.gnu.org/licenses/>."; + */ + +#pragma once + +#include <glib-object.h> +#include <gst/gst.h> + +#include "ges-track.h" + +G_BEGIN_DECLS + +#define GES_TYPE_SMART_MIXER (ges_smart_mixer_get_type ()) +#define GES_SMART_MIXER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_SMART_MIXER, GESSmartMixer)) +#define GES_SMART_MIXER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_SMART_MIXER, GESSmartMixerClass)) +#define GES_IS_SMART_MIXER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_SMART_MIXER)) +#define GES_IS_SMART_MIXER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_SMART_MIXER)) +#define GES_SMART_MIXER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_SMART_MIXER, GESSmartMixerClass)) + +typedef struct _GESSmartMixerClass GESSmartMixerClass; +typedef struct _GESSmartMixer GESSmartMixer; + +struct _GESSmartMixerClass +{ + GstBinClass parent_class; + + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESSmartMixer +{ + GstBin parent_instance; + + GHashTable *pads_infos; + GstPad *srcpad; + GstElement *mixer; + GMutex lock; + + GstCaps *caps; + gboolean is_transition; + + gpointer _ges_reserved[GES_PADDING]; +}; + +G_GNUC_INTERNAL +GType ges_smart_mixer_get_type (void) G_GNUC_CONST; + +G_GNUC_INTERNAL GstPad * +ges_smart_mixer_get_mixer_pad (GESSmartMixer *self, GstPad **mixerpad); + +G_GNUC_INTERNAL +GstElement* ges_smart_mixer_new (GESTrack *track); + +G_END_DECLS \ No newline at end of file diff --git a/ges/ges-source-clip-asset.c b/ges/ges-source-clip-asset.c new file mode 100644 index 0000000000..f8b7d33fa0 --- /dev/null +++ b/ges/ges-source-clip-asset.c @@ -0,0 +1,54 @@ +/* GStreamer Editing Services + * Copyright (C) 2020 Igalia S.L + * Author: 2020 Thibault Saunier <tsaunier@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION: gessourceclipasset + * @title: GESSourceClipAsset + * @short_description: A GESAsset subclass, baseclass for #GESSourceClip-s extraction + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-source-clip-asset.h" +#include "ges-internal.h" + +G_DEFINE_TYPE (GESSourceClipAsset, ges_source_clip_asset, GES_TYPE_CLIP_ASSET); + +static gboolean +get_natural_framerate (GESClipAsset * self, gint * framerate_n, + gint * framerate_d) +{ + *framerate_n = DEFAULT_FRAMERATE_N; + *framerate_d = DEFAULT_FRAMERATE_D; + return TRUE; +} + +static void +ges_source_clip_asset_class_init (GESSourceClipAssetClass * klass) +{ + GES_CLIP_ASSET_CLASS (klass)->get_natural_framerate = get_natural_framerate; +} + +static void +ges_source_clip_asset_init (GESSourceClipAsset * self) +{ +} diff --git a/ges/ges-source-clip-asset.h b/ges/ges-source-clip-asset.h new file mode 100644 index 0000000000..aeb100dbfd --- /dev/null +++ b/ges/ges-source-clip-asset.h @@ -0,0 +1,47 @@ +/* GStreamer Editing Services + * Copyright (C) 2020 Igalia S.L + * Author: 2020 Thibault Saunier <tsaunier@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <ges/ges-clip-asset.h> + +G_BEGIN_DECLS + +#define GES_TYPE_SOURCE_CLIP_ASSET ges_source_clip_asset_get_type () + +/** + * GESSourceClipAsset: + * + * An asset types from which #GESSourceClip will be extracted + * + * Since: 1.18 + */ +GES_API +G_DECLARE_DERIVABLE_TYPE(GESSourceClipAsset, ges_source_clip_asset, GES, + SOURCE_CLIP_ASSET, GESClipAsset); + +struct _GESSourceClipAssetClass +{ + GESClipAssetClass parent_class; + /* FIXME 2.0: Add some padding, meanwhile that would break ABI */ +}; + + +G_END_DECLS \ No newline at end of file diff --git a/ges/ges-source-clip.c b/ges/ges-source-clip.c new file mode 100644 index 0000000000..6cb02d3e34 --- /dev/null +++ b/ges/ges-source-clip.c @@ -0,0 +1,150 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gessourceclip + * @title: GESSourceClip + * @short_description: Base Class for sources of a #GESLayer + * + * #GESSourceClip-s are clips whose core elements are #GESSource-s. + * + * ## Effects + * + * #GESSourceClip-s can also have #GESBaseEffect-s added as non-core + * elements. These effects are applied to the core sources of the clip + * that they share a #GESTrack with. See #GESClip for how to add and move + * these effects from the clip. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-clip.h" +#include "ges-source-clip.h" +#include "ges-source.h" + + +struct _GESSourceClipPrivate +{ + /* dummy variable */ + void *nothing; +}; + +enum +{ + PROP_0, +}; + +static GESExtractableInterface *parent_extractable_iface = NULL; + +static gchar * +extractable_check_id (GType type, const gchar * id, GError ** error) +{ + if (type == GES_TYPE_SOURCE_CLIP) { + g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID, + "Only `time-overlay` is supported as an ID for type: `GESSourceClip`," + " got: '%s'", id); + return NULL; + } + + return parent_extractable_iface->check_id (type, id, error); +} + +static GType +extractable_get_real_extractable_type (GType wanted_type, const gchar * id) +{ + GstStructure *structure; + + if (!id || (wanted_type != GES_TYPE_SOURCE_CLIP + && wanted_type != GES_TYPE_TEST_CLIP)) + return wanted_type; + + structure = gst_structure_new_from_string (id); + if (!structure) + return wanted_type; + + if (gst_structure_has_name (structure, "time-overlay")) + /* Just reusing TestClip to create a GESTimeOverlayClip + * as it already does the job! */ + wanted_type = GES_TYPE_TEST_CLIP; + + gst_structure_free (structure); + + return wanted_type; +} + +static void +extractable_interface_init (GESExtractableInterface * iface) +{ + iface->asset_type = GES_TYPE_SOURCE_CLIP_ASSET; + iface->get_real_extractable_type = extractable_get_real_extractable_type; + iface->check_id = extractable_check_id; + + parent_extractable_iface = g_type_interface_peek_parent (iface); +} + +G_DEFINE_TYPE_WITH_CODE (GESSourceClip, ges_source_clip, + GES_TYPE_CLIP, G_ADD_PRIVATE (GESSourceClip) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, extractable_interface_init)); + +static void +ges_source_clip_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_source_clip_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_source_clip_finalize (GObject * object) +{ + G_OBJECT_CLASS (ges_source_clip_parent_class)->finalize (object); +} + +static void +ges_source_clip_class_init (GESSourceClipClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = ges_source_clip_get_property; + object_class->set_property = ges_source_clip_set_property; + object_class->finalize = ges_source_clip_finalize; + + GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) = TRUE; +} + +static void +ges_source_clip_init (GESSourceClip * self) +{ + self->priv = ges_source_clip_get_instance_private (self); +} diff --git a/ges/ges-source-clip.h b/ges/ges-source-clip.h new file mode 100644 index 0000000000..d3669dbb97 --- /dev/null +++ b/ges/ges-source-clip.h @@ -0,0 +1,61 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> +#include <ges/ges-clip.h> +#include <ges/ges-enums.h> + +G_BEGIN_DECLS + +#define GES_TYPE_SOURCE_CLIP ges_source_clip_get_type() +GES_DECLARE_TYPE(SourceClip, source_clip, SOURCE_CLIP); + +/** + * GESSourceClip: + * + * Base class for sources of a #GESLayer + */ + +struct _GESSourceClip { + GESClip parent; + + /*< private >*/ + GESSourceClipPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESSourceClipClass: + */ + +struct _GESSourceClipClass { + /*< private >*/ + GESClipClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +G_END_DECLS diff --git a/ges/ges-source.c b/ges/ges-source.c new file mode 100644 index 0000000000..738686011b --- /dev/null +++ b/ges/ges-source.c @@ -0,0 +1,241 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gessource + * @title: GESSource + * @short_description: Base Class for single-media sources + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges/ges-meta-container.h" +#include "ges-track-element.h" +#include "ges-source.h" +#include "ges-layer.h" +#include "gstframepositioner.h" +struct _GESSourcePrivate +{ + GstElement *topbin; + GstElement *first_converter; + GstElement *last_converter; + GstPad *ghostpad; + + gboolean is_rendering_smartly; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GESSource, ges_source, GES_TYPE_TRACK_ELEMENT); + +/****************************** + * Internal helper methods * + ******************************/ +static GstElement * +link_elements (GstElement * bin, GPtrArray * elements) +{ + GstElement *element, *prev = NULL, *first = NULL; + gint i; + + for (i = 0; i < elements->len; i++) { + element = elements->pdata[i]; + if (!element) + continue; + + gst_bin_add (GST_BIN (bin), element); + if (prev) { + if (!gst_element_link_pads_full (prev, "src", element, "sink", + GST_PAD_LINK_CHECK_NOTHING)) { + if (!gst_element_link (prev, element)) { + g_error ("Could not link %s and %s", GST_OBJECT_NAME (prev), + GST_OBJECT_NAME (element)); + } + } + } + prev = element; + if (first == NULL) + first = element; + } + + return prev; +} + +static void +_set_ghost_pad_target (GESSource * self, GstPad * srcpad, GstElement * element) +{ + GstPadLinkReturn link_return; + GESSourcePrivate *priv = self->priv; + GESSourceClass *source_klass = GES_SOURCE_GET_CLASS (self); + gboolean use_converter = ! !priv->first_converter; + + if (source_klass->select_pad && !source_klass->select_pad (self, srcpad)) { + GST_INFO_OBJECT (self, "Ignoring pad %" GST_PTR_FORMAT, srcpad); + return; + } + + + if (use_converter && priv->is_rendering_smartly) { + GstPad *pad = gst_element_get_static_pad (priv->first_converter, "sink"); + use_converter = gst_pad_can_link (srcpad, pad); + gst_object_unref (pad); + } + + if (use_converter) { + GstPad *converter_src, *sinkpad; + + converter_src = gst_element_get_static_pad (priv->last_converter, "src"); + if (!gst_ghost_pad_set_target (GST_GHOST_PAD (priv->ghostpad), + converter_src)) { + GST_ERROR_OBJECT (self, "Could not set ghost target"); + } + + sinkpad = gst_element_get_static_pad (priv->first_converter, "sink"); + link_return = gst_pad_link (srcpad, sinkpad); +#ifndef GST_DISABLE_GST_DEBUG + if (link_return != GST_PAD_LINK_OK) { + GstCaps *srccaps = NULL; + GstCaps *sinkcaps = NULL; + + srccaps = gst_pad_query_caps (srcpad, NULL); + sinkcaps = gst_pad_query_caps (sinkpad, NULL); + + GST_ERROR_OBJECT (element, "Could not link source with " + "conversion bin: %s (srcpad caps %" GST_PTR_FORMAT + " sinkpad caps: %" GST_PTR_FORMAT ")", + gst_pad_link_get_name (link_return), srccaps, sinkcaps); + gst_caps_unref (srccaps); + gst_caps_unref (sinkcaps); + } +#endif + + gst_object_unref (converter_src); + gst_object_unref (sinkpad); + } else { + if (!gst_ghost_pad_set_target (GST_GHOST_PAD (priv->ghostpad), srcpad)) + GST_ERROR_OBJECT (self, "Could not set ghost target"); + } + + gst_element_no_more_pads (element); +} + +/* @elements: (transfer-full) */ +GstElement * +ges_source_create_topbin (GESSource * source, const gchar * bin_name, + GstElement * sub_element, GPtrArray * elements) +{ + GstElement *last; + GstElement *bin; + GstPad *sub_srcpad; + GESSourcePrivate *priv = source->priv; + + bin = gst_bin_new (bin_name); + if (!gst_bin_add (GST_BIN (bin), sub_element)) { + GST_ERROR_OBJECT (source, "Could not add sub element: %" GST_PTR_FORMAT, + sub_element); + gst_object_unref (bin); + return NULL; + } + + priv->ghostpad = gst_object_ref (gst_ghost_pad_new_no_target ("src", + GST_PAD_SRC)); + gst_pad_set_active (priv->ghostpad, TRUE); + gst_element_add_pad (bin, priv->ghostpad); + priv->topbin = gst_object_ref (bin); + last = link_elements (bin, elements); + if (last) { + gint i = 0; + + while (!elements->pdata[i]) + i++; + + priv->first_converter = gst_object_ref (elements->pdata[i]); + priv->last_converter = gst_object_ref (last); + } + + sub_srcpad = gst_element_get_static_pad (sub_element, "src"); + if (sub_srcpad) { + _set_ghost_pad_target (source, sub_srcpad, sub_element); + gst_object_unref (sub_srcpad); + } else { + GST_INFO_OBJECT (source, "Waiting for pad added"); + g_signal_connect_swapped (sub_element, "pad-added", + G_CALLBACK (_set_ghost_pad_target), source); + } + g_ptr_array_free (elements, TRUE); + + return bin; +} + + +void +ges_source_set_rendering_smartly (GESSource * source, + gboolean is_rendering_smartly) +{ + + if (is_rendering_smartly) { + GESTrack *track = ges_track_element_get_track (GES_TRACK_ELEMENT (source)); + + if (track && ges_track_get_mixing (track)) { + GST_DEBUG_OBJECT (source, "Not rendering smartly as track is mixing!"); + + source->priv->is_rendering_smartly = FALSE; + return; + } + } + source->priv->is_rendering_smartly = is_rendering_smartly; +} + +gboolean +ges_source_get_rendering_smartly (GESSource * source) +{ + return source->priv->is_rendering_smartly; +} + +static void +ges_source_dispose (GObject * object) +{ + GESSourcePrivate *priv = GES_SOURCE (object)->priv; + + gst_clear_object (&priv->first_converter); + gst_clear_object (&priv->last_converter); + gst_clear_object (&priv->topbin); + gst_clear_object (&priv->ghostpad); + + G_OBJECT_CLASS (ges_source_parent_class)->dispose (object); +} + +static void +ges_source_class_init (GESSourceClass * klass) +{ + GESTrackElementClass *track_class = GES_TRACK_ELEMENT_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + track_class->nleobject_factorytype = "nlesource"; + track_class->create_element = NULL; + object_class->dispose = ges_source_dispose; + + GES_TRACK_ELEMENT_CLASS_DEFAULT_HAS_INTERNAL_SOURCE (klass) = TRUE; +} + +static void +ges_source_init (GESSource * self) +{ + self->priv = ges_source_get_instance_private (self); +} diff --git a/ges/ges-source.h b/ges/ges-source.h new file mode 100644 index 0000000000..a807667cf4 --- /dev/null +++ b/ges/ges-source.h @@ -0,0 +1,88 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <gst/gst.h> +#include <ges/ges-types.h> +#include <ges/ges-track-element.h> + +G_BEGIN_DECLS + +#define GES_TYPE_SOURCE ges_source_get_type() +GES_DECLARE_TYPE(Source, source, SOURCE); + +/** + * GESSource: + * + * Base class for single-media sources + */ + +struct _GESSource { + /*< private >*/ + GESTrackElement parent; + + GESSourcePrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESSourceClass: + */ + +struct _GESSourceClass { + /*< private >*/ + GESTrackElementClass parent_class; + + /** + * GESSourceClass::select_pad: + * @source: The @source for which to check if @pad should be used or not + * @pad: The pad to check + * + * Check whether @pad should be exposed/used. + * + * Returns: %TRUE if @pad should be used %FALSE otherwise. + * + * Since: 1.20 + */ + gboolean (*select_pad)(GESSource *source, GstPad *pad); + + /** + * GESSourceClass::create_source: + * @source: The #GESAudioSource + * + * Creates the GstElement to put in the source topbin. Other elements will be + * queued, like a volume. In the case of a AudioUriSource, for example, the + * subclass will return a decodebin, and we will append a volume. + * + * Returns: (transfer floating): The source element to use. + * + * Since: 1.20 + */ + GstElement* (*create_source) (GESSource * source); + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING - 2]; +}; + +G_END_DECLS diff --git a/ges/ges-structure-parser.c b/ges/ges-structure-parser.c new file mode 100644 index 0000000000..d84d64b22e --- /dev/null +++ b/ges/ges-structure-parser.c @@ -0,0 +1,210 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2015> Mathieu Duponchelle <mathieu.duponchelle@opencreed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-structure-parser.h" + +#include <ges/ges.h> + +G_DEFINE_TYPE (GESStructureParser, ges_structure_parser, G_TYPE_OBJECT); + +static void +ges_structure_parser_init (GESStructureParser * self) +{ +} + +static void +_finalize (GObject * self) +{ + GESStructureParser *parser = GES_STRUCTURE_PARSER (self); + + g_list_free_full (parser->structures, (GDestroyNotify) gst_structure_free); + g_list_free_full (parser->wrong_strings, (GDestroyNotify) g_free); + + G_OBJECT_CLASS (ges_structure_parser_parent_class)->finalize (self); +} + +static void +ges_structure_parser_class_init (GESStructureParserClass * klass) +{ + G_OBJECT_CLASS (klass)->finalize = _finalize; +} + +void +ges_structure_parser_parse_string (GESStructureParser * self, + const gchar * text, gboolean is_symbol) +{ + gchar *new_string = NULL; + + if (self->current_string) { + new_string = g_strconcat (self->current_string, text, NULL); + } else if (is_symbol) { + new_string = g_strdup (text); + } + + g_free (self->current_string); + self->current_string = new_string; +} + +void +ges_structure_parser_parse_value (GESStructureParser * self, const gchar * text) +{ + /* text starts with '=' */ + gchar *val_string = g_strconcat ("=(string)", text + 1, NULL); + ges_structure_parser_parse_string (self, val_string, FALSE); + g_free (val_string); +} + +void +ges_structure_parser_parse_default (GESStructureParser * self, + const gchar * text) +{ + gchar *new_string = NULL; + + if (self->add_comma && self->current_string) { + new_string = g_strconcat (self->current_string, ",", text, NULL); + g_free (self->current_string); + self->current_string = new_string; + self->add_comma = FALSE; + } else { + ges_structure_parser_parse_string (self, text, FALSE); + } +} + +void +ges_structure_parser_parse_whitespace (GESStructureParser * self) +{ + self->add_comma = TRUE; +} + +static void +_finish_structure (GESStructureParser * self) +{ + GstStructure *structure; + + if (!self->current_string) + return; + + structure = gst_structure_new_from_string (self->current_string); + + if (structure == NULL) { + GST_ERROR ("Could not parse %s", self->current_string); + + self->wrong_strings = g_list_append (self->wrong_strings, + g_strdup (self->current_string)); + + return; + } + + self->structures = g_list_append (self->structures, structure); + g_free (self->current_string); + self->current_string = NULL; +} + + +void +ges_structure_parser_end_of_file (GESStructureParser * self) +{ + _finish_structure (self); +} + +void +ges_structure_parser_parse_symbol (GESStructureParser * self, + const gchar * symbol) +{ + _finish_structure (self); + + while (*symbol == ' ' || *symbol == '+') + symbol++; + + self->add_comma = FALSE; + if (!g_ascii_strncasecmp (symbol, "clip", 4)) + ges_structure_parser_parse_string (self, "clip, uri=(string)", TRUE); + else if (!g_ascii_strncasecmp (symbol, "test-clip", 9)) + ges_structure_parser_parse_string (self, "test-clip, pattern=(string)", + TRUE); + else if (!g_ascii_strncasecmp (symbol, "effect", 6)) + ges_structure_parser_parse_string (self, "effect, bin-description=(string)", + TRUE); + else if (!g_ascii_strncasecmp (symbol, "transition", 10)) + ges_structure_parser_parse_string (self, "transition, type=(string)", TRUE); + else if (!g_ascii_strncasecmp (symbol, "title", 5)) + ges_structure_parser_parse_string (self, "title, text=(string)", TRUE); + else if (!g_ascii_strncasecmp (symbol, "track", 5)) + ges_structure_parser_parse_string (self, "track, type=(string)", TRUE); + else if (!g_ascii_strncasecmp (symbol, "keyframes", 8)) { + ges_structure_parser_parse_string (self, + "keyframes, property-name=(string)", TRUE); + } +} + +void +ges_structure_parser_parse_setter (GESStructureParser * self, + const gchar * setter) +{ + gchar *parsed_setter; + + _finish_structure (self); + + while (*setter == '-' || *setter == ' ') + setter++; + + while (*setter != '-') + setter++; + + setter++; + + parsed_setter = g_strdup_printf ("set-property, property=(string)%s, " + "value=(string)", setter); + self->add_comma = FALSE; + ges_structure_parser_parse_string (self, parsed_setter, TRUE); + g_free (parsed_setter); +} + +GESStructureParser * +ges_structure_parser_new (void) +{ + return (g_object_new (GES_TYPE_STRUCTURE_PARSER, NULL)); +} + +GError * +ges_structure_parser_get_error (GESStructureParser * self) +{ + GList *tmp; + GError *error = NULL; + GString *msg = NULL; + + if (self->wrong_strings == NULL) + return NULL; + + msg = g_string_new ("Could not parse: "); + + + for (tmp = self->wrong_strings; tmp; tmp = tmp->next) { + g_string_append_printf (msg, " %s", (gchar *) tmp->data); + } + + error = g_error_new_literal (GES_ERROR, 0, msg->str); + g_string_free (msg, TRUE); + + return error; +} diff --git a/ges/ges-structure-parser.h b/ges/ges-structure-parser.h new file mode 100644 index 0000000000..1252655608 --- /dev/null +++ b/ges/ges-structure-parser.h @@ -0,0 +1,63 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2015> Mathieu Duponchelle <mathieu.duponchelle@opencreed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#pragma once + +#include <glib.h> +#include <gst/gst.h> + +G_BEGIN_DECLS + +#define GES_TYPE_STRUCTURE_PARSER ges_structure_parser_get_type() +#define GES_STRUCTURE_PARSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_STRUCTURE_PARSER, GESStructureParser)) + +typedef struct _GESStructureParser GESStructureParser; +typedef struct _GESStructureParserClass GESStructureParserClass; + +struct _GESStructureParser +{ + GObject parent; + GList *structures; + + GList *wrong_strings; + + /*< private > */ + gchar *current_string; + gboolean add_comma; +}; + +struct _GESStructureParserClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +G_GNUC_INTERNAL GType ges_structure_parser_get_type (void) G_GNUC_CONST; + +G_GNUC_INTERNAL GError * ges_structure_parser_get_error (GESStructureParser *self); +G_GNUC_INTERNAL void ges_structure_parser_parse_string (GESStructureParser *self, const gchar *string, gboolean is_symbol); +G_GNUC_INTERNAL void ges_structure_parser_parse_value (GESStructureParser *self, const gchar *string); +G_GNUC_INTERNAL void ges_structure_parser_parse_default (GESStructureParser *self, const gchar *text); +G_GNUC_INTERNAL void ges_structure_parser_parse_whitespace (GESStructureParser *self); +G_GNUC_INTERNAL void ges_structure_parser_parse_symbol (GESStructureParser *self, const gchar *symbol); +G_GNUC_INTERNAL void ges_structure_parser_parse_setter (GESStructureParser *self, const gchar *setter); +G_GNUC_INTERNAL void ges_structure_parser_end_of_file (GESStructureParser *self); + +G_GNUC_INTERNAL GESStructureParser *ges_structure_parser_new(void); +G_END_DECLS diff --git a/ges/ges-structured-interface.c b/ges/ges-structured-interface.c new file mode 100644 index 0000000000..0ee9ab12ad --- /dev/null +++ b/ges/ges-structured-interface.c @@ -0,0 +1,1120 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2015> Thibault Saunier <tsaunier@gnome.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-structured-interface.h" +#include "ges-internal.h" + +#include <string.h> + + +#define LAST_CONTAINER_QDATA g_quark_from_string("ges-structured-last-container") +#define LAST_CHILD_QDATA g_quark_from_string("ges-structured-last-child") + +#ifdef G_HAVE_ISO_VARARGS +#define REPORT_UNLESS(condition, errpoint, ...) \ + G_STMT_START { \ + if (!(condition)) { \ + gchar *tmp = gst_info_strdup_printf(__VA_ARGS__); \ + *error = g_error_new_literal (GES_ERROR, 0, tmp); \ + g_free (tmp); \ + goto errpoint; \ + } \ + } \ + G_STMT_END +#else /* G_HAVE_GNUC_VARARGS */ +#ifdef G_HAVE_GNUC_VARARGS +#define REPORT_UNLESS(condition, errpoint, args...) \ + G_STMT_START { \ + if (!(condition)) { \ + gchar *tmp = gst_info_strdup_printf(##args); \ + *error = g_error_new_literal (GES_ERROR, 0, tmp); \ + g_free (tmp); \ + goto errpoint; \ + } \ + } \ + G_STMT_END +#endif /* G_HAVE_ISO_VARARGS */ +#endif /* G_HAVE_GNUC_VARARGS */ + +#define GET_AND_CHECK(name,type,var,label) G_STMT_START {\ + gboolean found = FALSE; \ +\ + if (type == GST_TYPE_CLOCK_TIME) {\ + found = ges_util_structure_get_clocktime (structure,name, (GstClockTime*)var,NULL);\ + }\ + else { \ + found = gst_structure_get (structure, name, type, var, NULL); \ + }\ + if (!found) {\ + gchar *struct_str = gst_structure_to_string (structure); \ + *error = g_error_new (GES_ERROR, 0, \ + "Could not get the mandatory field '%s'" \ + " of type %s - fields in %s", name, g_type_name (type), struct_str); \ + g_free (struct_str); \ + goto label;\ + } \ +} G_STMT_END + +#define TRY_GET_STRING(name,var,def) G_STMT_START {\ + *var = gst_structure_get_string (structure, name); \ + if (*var == NULL) \ + *var = def; \ +} G_STMT_END + +#define TRY_GET_TIME(name, var, var_frames, def) G_STMT_START { \ + if (!ges_util_structure_get_clocktime (structure, name, var, var_frames)) { \ + *var = def; \ + *var_frames = GES_FRAME_NUMBER_NONE; \ + } \ +} G_STMT_END + +static gboolean +_get_structure_value (GstStructure * structure, const gchar * field, GType type, + gpointer v) +{ + gboolean res = TRUE; + const gchar *value_str; + const GValue *value; + GValue nvalue = G_VALUE_INIT; + + if (gst_structure_get (structure, field, type, v, NULL)) + return res; + + g_value_init (&nvalue, type); + value = gst_structure_get_value (structure, field); + if (!value) + goto fail; + + if (g_value_transform (value, &nvalue)) + goto set_and_get_value; + + if (!G_VALUE_HOLDS_STRING (value)) + goto fail; + + value_str = g_value_get_string (value); + if (!gst_value_deserialize (&nvalue, value_str)) + goto done; + +set_and_get_value: + gst_structure_set_value (structure, field, &nvalue); + gst_structure_get (structure, field, type, v, NULL); + +done: + g_value_reset (&nvalue); + return res; + +fail: + res = FALSE; + goto done; +} + +#define TRY_GET(name, type, var, def) G_STMT_START {\ + g_assert (type != GST_TYPE_CLOCK_TIME); \ + if (!_get_structure_value (structure, name, type, var))\ + *var = def; \ +} G_STMT_END + +typedef struct +{ + const gchar **fields; + GList *invalid_fields; +} FieldsError; + +static gboolean +enum_from_str (GType type, const gchar * str_enum, guint * enum_value) +{ + GValue value = G_VALUE_INIT; + g_value_init (&value, type); + + if (!gst_value_deserialize (&value, str_enum)) + return FALSE; + + *enum_value = g_value_get_enum (&value); + g_value_unset (&value); + + return TRUE; +} + +static gboolean +_check_field (GQuark field_id, const GValue * value, FieldsError * fields_error) +{ + guint i; + const gchar *field = g_quark_to_string (field_id); + + for (i = 0; fields_error->fields[i]; i++) { + if (g_strcmp0 (fields_error->fields[i], field) == 0) { + + return TRUE; + } + } + + fields_error->invalid_fields = + g_list_append (fields_error->invalid_fields, (gpointer) field); + + return TRUE; +} + +static gboolean +_check_fields (GstStructure * structure, FieldsError fields_error, + GError ** error) +{ + gst_structure_foreach (structure, + (GstStructureForeachFunc) _check_field, &fields_error); + + if (fields_error.invalid_fields) { + GList *tmp; + const gchar *struct_name = gst_structure_get_name (structure); + GString *msg = g_string_new (NULL); + + g_string_append_printf (msg, "Unknown propert%s in %s%s:", + g_list_length (fields_error.invalid_fields) > 1 ? "ies" : "y", + strlen (struct_name) > 1 ? "--" : "-", + gst_structure_get_name (structure)); + + for (tmp = fields_error.invalid_fields; tmp; tmp = tmp->next) + g_string_append_printf (msg, " %s", (gchar *) tmp->data); + + if (error) + *error = g_error_new_literal (GES_ERROR, 0, msg->str); + + g_string_free (msg, TRUE); + + return FALSE; + } + + return TRUE; +} + +static GESTimelineElement * +find_element_for_property (GESTimeline * timeline, GstStructure * structure, + gchar ** property_name, gboolean require_track_element, GError ** error) +{ + GList *tmp; + const gchar *element_name; + GESTimelineElement *element; + + element_name = gst_structure_get_string (structure, "element-name"); + if (element_name == NULL) { + element = g_object_get_qdata (G_OBJECT (timeline), LAST_CHILD_QDATA); + if (element) + gst_object_ref (element); + } else { + element = ges_timeline_get_element (timeline, element_name); + } + + if (*property_name == NULL) { + gchar *tmpstr = *property_name; + const gchar *name = gst_structure_get_name (structure); + + REPORT_UNLESS (g_str_has_prefix (name, "set-"), err, + "Could not find any property name in %" GST_PTR_FORMAT, structure); + + *property_name = g_strdup (&tmpstr[4]); + + g_free (tmpstr); + } + + if (element) { + if (!ges_timeline_element_lookup_child (element, + *property_name, NULL, NULL)) { + gst_clear_object (&element); + } + } + + if (!element) { + element = g_object_get_qdata (G_OBJECT (timeline), LAST_CONTAINER_QDATA); + if (element) + gst_object_ref (element); + } + + REPORT_UNLESS (GES_IS_TIMELINE_ELEMENT (element), err, + "Could not find child %s from %" GST_PTR_FORMAT, element_name, structure); + + if (!require_track_element || GES_IS_TRACK_ELEMENT (element)) + return element; + + + REPORT_UNLESS (GES_IS_CONTAINER (element), err, + "Could not find child %s from %" GST_PTR_FORMAT, element_name, structure); + + for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { + if (ges_timeline_element_lookup_child (tmp->data, *property_name, NULL, + NULL)) { + gst_object_replace ((GstObject **) & element, tmp->data); + + break; + } + } + + REPORT_UNLESS (GES_IS_TRACK_ELEMENT (element), err, + "Could not find TrackElement from %" GST_PTR_FORMAT, structure); + + return element; + +err: + g_clear_object (&element); + return element; +} + +gboolean +_ges_save_timeline_if_needed (GESTimeline * timeline, GstStructure * structure, + GError ** error) +{ + gboolean res = TRUE; + const gchar *nested_timeline_id = + gst_structure_get_string (structure, "project-uri"); + + if (nested_timeline_id) { + res = ges_timeline_save_to_uri (timeline, nested_timeline_id, NULL, TRUE, + error); + } + + return res; +} + +typedef struct +{ + GstTimedValueControlSource *source; + GstStructure *structure; + GError *error; + const gchar *property_name; + gboolean res; +} SetKeyframesData; + +static gboolean +un_set_keyframes_foreach (GQuark field_id, const GValue * value, + SetKeyframesData * d) +{ + GValue v = G_VALUE_INIT; + GError **error = &d->error; + gchar *tmp; + gint i; + const gchar *valid_fields[] = { + "element-name", "property-name", "value", "timestamp", "project-uri", + "binding-type", "source-type", "interpolation-mode", "interpolation-mode", + NULL + }; + const gchar *field = g_quark_to_string (field_id); + gdouble ts; + + for (i = 0; valid_fields[i]; i++) { + if (g_quark_from_string (valid_fields[i]) == field_id) + return TRUE; + } + + errno = 0; + ts = g_strtod (field, &tmp); + + REPORT_UNLESS (errno == 0 && field != tmp, err, + "Could not convert `%s` to GstClockTime (%s)", field, g_strerror (errno)); + + if (gst_structure_has_name (d->structure, "remove-keyframe")) { + REPORT_UNLESS (gst_timed_value_control_source_unset (d->source, + ts * GST_SECOND), err, "Could not unset keyframe at %f", ts); + + return TRUE; + } + + g_value_init (&v, G_TYPE_DOUBLE); + REPORT_UNLESS (g_value_transform (value, &v), err, + "Could not convert keyframe %f value %s to double", ts, + gst_value_serialize (value)); + + REPORT_UNLESS (gst_timed_value_control_source_set (d->source, ts * GST_SECOND, + g_value_get_double (&v)), err, "Could not set keyframe %f=%f", ts, + g_value_get_double (&v)); + + g_value_reset (&v); + return TRUE; + +err: + if (v.g_type) + g_value_reset (&v); + d->res = FALSE; + return FALSE; +} + + +gboolean +_ges_add_remove_keyframe_from_struct (GESTimeline * timeline, + GstStructure * structure, GError ** error) +{ + GESTimelineElement *element = NULL; + + gboolean absolute; + gdouble value; + GstClockTime timestamp; + GstControlBinding *binding = NULL; + GstTimedValueControlSource *source = NULL; + gchar *property_name = NULL; + + gboolean ret = FALSE; + gboolean setting_value; + + const gchar *valid_fields[] = + { "element-name", "property-name", "value", "timestamp", "project-uri", + NULL + }; + + FieldsError fields_error = { valid_fields, NULL }; + + if (gst_structure_has_field (structure, "value")) { + if (!_check_fields (structure, fields_error, error)) + return FALSE; + GET_AND_CHECK ("timestamp", GST_TYPE_CLOCK_TIME, ×tamp, done); + } else { + REPORT_UNLESS (!gst_structure_has_field (structure, "timestamp"), done, + "Doesn't have a `value` field in %" GST_PTR_FORMAT + " but has a `timestamp`" " that can't work!", structure); + } + + GET_AND_CHECK ("property-name", G_TYPE_STRING, &property_name, done); + if (!(element = + find_element_for_property (timeline, structure, &property_name, TRUE, + error))) + goto done; + + REPORT_UNLESS (binding = + ges_track_element_get_control_binding (GES_TRACK_ELEMENT (element), + property_name), + done, "No control binding found for %" GST_PTR_FORMAT, structure); + + g_object_get (binding, "control-source", &source, NULL); + REPORT_UNLESS (source, done, + "No control source found for '%" GST_PTR_FORMAT + "' you should first set-control-binding on it", structure); + REPORT_UNLESS (GST_IS_TIMED_VALUE_CONTROL_SOURCE (source), done, + "You can use add-keyframe" + " only on GstTimedValueControlSource not %s", + G_OBJECT_TYPE_NAME (source)); + + if (!gst_structure_has_field (structure, "value")) { + SetKeyframesData d = { + source, structure, NULL, property_name, TRUE, + }; + gst_structure_foreach (structure, + (GstStructureForeachFunc) un_set_keyframes_foreach, &d); + if (!d.res) + g_propagate_error (error, d.error); + + return d.res; + } + + g_object_get (binding, "absolute", &absolute, NULL); + if (absolute) { + GParamSpec *pspec; + const GValue *v; + GValue v2 = G_VALUE_INIT; + + if (!ges_timeline_element_lookup_child (element, property_name, NULL, + &pspec)) { + *error = + g_error_new (GES_ERROR, 0, "Could not get property %s for %s", + property_name, GES_TIMELINE_ELEMENT_NAME (element)); + goto done; + } + + v = gst_structure_get_value (structure, "value"); + if (!v) { + gchar *struct_str = gst_structure_to_string (structure); + + *error = g_error_new (GES_ERROR, 0, + "Could not get the mandatory field 'value'" + " of type %s - fields in %s", g_type_name (pspec->value_type), + struct_str); + g_free (struct_str); + goto done; + } + + g_value_init (&v2, G_TYPE_DOUBLE); + if (!g_value_transform (v, &v2)) { + gchar *struct_str = gst_structure_to_string (structure); + + *error = g_error_new (GES_ERROR, 0, + "Could not get the mandatory field 'value'" + " of type %s - fields in %s", g_type_name (pspec->value_type), + struct_str); + g_free (struct_str); + goto done; + } + value = g_value_get_double (&v2); + g_value_reset (&v2); + } else + GET_AND_CHECK ("value", G_TYPE_DOUBLE, &value, done); + + setting_value = + !g_strcmp0 (gst_structure_get_name (structure), "add-keyframe"); + if (setting_value) + ret = gst_timed_value_control_source_set (source, timestamp, value); + else + ret = gst_timed_value_control_source_unset (source, timestamp); + + if (!ret) { + *error = + g_error_new (GES_ERROR, 0, + "Could not %sset value for timestamp: %" GST_TIME_FORMAT, + setting_value ? "" : "un", GST_TIME_ARGS (timestamp)); + } + ret = _ges_save_timeline_if_needed (timeline, structure, error); + +done: + gst_clear_object (&source); + gst_clear_object (&element); + g_free (property_name); + + return ret; + +} + +GESAsset * +_ges_get_asset_from_timeline (GESTimeline * timeline, GType type, + const gchar * id, GError ** error) +{ + GESAsset *asset; + GESProject *project = ges_timeline_get_project (timeline); + GError *err = NULL; + + asset = ges_project_create_asset_sync (project, id, type, &err); + + if (err) + g_propagate_error (error, err); + if (!asset || (error && *error)) { + + if (error && !*error) { + *error = g_error_new (GES_ERROR, 0, + "There was an error requesting the asset with id %s and type %s (%s)", + id, g_type_name (type), "unknown"); + } + + GST_ERROR + ("There was an error requesting the asset with id %s and type %s (%s)", + id, g_type_name (type), error ? (*error)->message : "unknown"); + + return NULL; + } + + return asset; +} + +/* Unref after usage */ +GESLayer * +_ges_get_layer_by_priority (GESTimeline * timeline, gint priority) +{ + GList *layers; + gint nlayers; + GESLayer *layer = NULL; + + priority = MAX (priority, 0); + layers = ges_timeline_get_layers (timeline); + nlayers = (gint) g_list_length (layers); + if (priority >= nlayers) { + gint i = nlayers; + + while (i <= priority) { + layer = ges_timeline_append_layer (timeline); + + i++; + } + + layer = gst_object_ref (layer); + + goto done; + } + + layer = ges_timeline_get_layer (timeline, priority); + +done: + g_list_free_full (layers, gst_object_unref); + + return layer; +} + +static gchar * +ensure_uri (gchar * location) +{ + if (gst_uri_is_valid (location)) + return g_strdup (location); + else + return gst_filename_to_uri (location, NULL); +} + +static gboolean +get_flags_from_string (GType type, const gchar * str_flags, guint * flags) +{ + GValue value = G_VALUE_INIT; + g_value_init (&value, type); + + if (!gst_value_deserialize (&value, str_flags)) { + g_value_unset (&value); + + return FALSE; + } + + *flags = g_value_get_flags (&value); + g_value_unset (&value); + + return TRUE; +} + +gboolean +_ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure, + GError ** error) +{ + GESAsset *asset = NULL; + GESLayer *layer = NULL; + GESClip *clip; + gint layer_priority; + const gchar *name; + const gchar *text; + const gchar *pattern; + const gchar *track_types_str; + const gchar *nested_timeline_id; + gchar *asset_id = NULL; + gchar *check_asset_id = NULL; + const gchar *type_string; + GType type; + gboolean res = FALSE; + GESTrackType track_types = GES_TRACK_TYPE_UNKNOWN; + + GESFrameNumber start_frame = GES_FRAME_NUMBER_NONE, inpoint_frame = + GES_FRAME_NUMBER_NONE, duration_frame = GES_FRAME_NUMBER_NONE; + GstClockTime duration = 1 * GST_SECOND, inpoint = 0, start = + GST_CLOCK_TIME_NONE; + + const gchar *valid_fields[] = + { "asset-id", "pattern", "name", "layer-priority", "layer", "type", + "start", "inpoint", "duration", "text", "track-types", "project-uri", + NULL + }; + + FieldsError fields_error = { valid_fields, NULL }; + + if (!_check_fields (structure, fields_error, error)) + return FALSE; + + GET_AND_CHECK ("asset-id", G_TYPE_STRING, &check_asset_id, beach); + + TRY_GET_STRING ("pattern", &pattern, NULL); + TRY_GET_STRING ("text", &text, NULL); + TRY_GET_STRING ("name", &name, NULL); + TRY_GET ("layer-priority", G_TYPE_INT, &layer_priority, -1); + if (layer_priority == -1) + TRY_GET ("layer", G_TYPE_INT, &layer_priority, -1); + TRY_GET_STRING ("type", &type_string, "GESUriClip"); + TRY_GET_TIME ("start", &start, &start_frame, GST_CLOCK_TIME_NONE); + TRY_GET_TIME ("inpoint", &inpoint, &inpoint_frame, 0); + TRY_GET_TIME ("duration", &duration, &duration_frame, GST_CLOCK_TIME_NONE); + TRY_GET_STRING ("track-types", &track_types_str, NULL); + TRY_GET_STRING ("project-uri", &nested_timeline_id, NULL); + + if (track_types_str) { + if (!get_flags_from_string (GES_TYPE_TRACK_TYPE, track_types_str, + &track_types)) { + *error = + g_error_new (GES_ERROR, 0, "Invalid track types: %s", + track_types_str); + } + + } + + if (!(type = g_type_from_name (type_string))) { + *error = g_error_new (GES_ERROR, 0, "This type doesn't exist : %s", + type_string); + + goto beach; + } + + if (type == GES_TYPE_URI_CLIP) { + asset_id = ensure_uri (check_asset_id); + } else { + asset_id = g_strdup (check_asset_id); + } + + gst_structure_set (structure, "asset-id", G_TYPE_STRING, asset_id, NULL); + asset = _ges_get_asset_from_timeline (timeline, type, asset_id, error); + if (!asset) { + res = FALSE; + + goto beach; + } + + if (layer_priority == -1) { + GESContainer *container; + + container = g_object_get_qdata (G_OBJECT (timeline), LAST_CONTAINER_QDATA); + if (!container || !GES_IS_CLIP (container)) + layer = _ges_get_layer_by_priority (timeline, 0); + else + layer = ges_clip_get_layer (GES_CLIP (container)); + + if (!layer) + layer = _ges_get_layer_by_priority (timeline, 0); + } else { + layer = _ges_get_layer_by_priority (timeline, layer_priority); + } + + if (!layer) { + *error = + g_error_new (GES_ERROR, 0, "No layer with priority %d", layer_priority); + goto beach; + } + + if (GES_FRAME_NUMBER_IS_VALID (start_frame)) + start = ges_timeline_get_frame_time (timeline, start_frame); + + if (GES_FRAME_NUMBER_IS_VALID (inpoint_frame)) { + inpoint = + ges_clip_asset_get_frame_time (GES_CLIP_ASSET (asset), inpoint_frame); + if (!GST_CLOCK_TIME_IS_VALID (inpoint)) { + *error = + g_error_new (GES_ERROR, 0, "Could not get inpoint from frame %" + G_GINT64_FORMAT, inpoint_frame); + goto beach; + } + } + + if (GES_FRAME_NUMBER_IS_VALID (duration_frame)) { + duration = ges_timeline_get_frame_time (timeline, duration_frame); + } + + if (GES_IS_URI_CLIP_ASSET (asset) && !GST_CLOCK_TIME_IS_VALID (duration)) { + duration = GST_CLOCK_DIFF (inpoint, + ges_uri_clip_asset_get_duration (GES_URI_CLIP_ASSET (asset))); + } + + clip = ges_layer_add_asset (layer, asset, start, inpoint, duration, + track_types); + + if (clip) { + res = TRUE; + + if (GES_TIMELINE_ELEMENT_DURATION (clip) == 0) { + *error = g_error_new (GES_ERROR, 0, + "Clip %s has 0 as duration, please provide a proper duration", + asset_id); + res = FALSE; + goto beach; + } + + + if (GES_IS_TEST_CLIP (clip)) { + if (pattern) { + guint v; + + REPORT_UNLESS (enum_from_str (GES_VIDEO_TEST_PATTERN_TYPE, pattern, &v), + beach, "Invalid pattern: %s", pattern); + ges_test_clip_set_vpattern (GES_TEST_CLIP (clip), v); + } + } + + if (GES_IS_TITLE_CLIP (clip) && text) + ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (clip), + "text", text, NULL); + + if (name + && !ges_timeline_element_set_name (GES_TIMELINE_ELEMENT (clip), name)) { + res = FALSE; + *error = + g_error_new (GES_ERROR, 0, "couldn't set name %s on clip with id %s", + name, asset_id); + } + } else { + *error = + g_error_new (GES_ERROR, 0, + "Couldn't add clip with id %s to layer with priority %d", asset_id, + layer_priority); + res = FALSE; + goto beach; + } + + if (res) { + g_object_set_qdata (G_OBJECT (timeline), LAST_CONTAINER_QDATA, clip); + g_object_set_qdata (G_OBJECT (timeline), LAST_CHILD_QDATA, NULL); + } + + res = _ges_save_timeline_if_needed (timeline, structure, error); + +beach: + gst_clear_object (&layer); + gst_clear_object (&asset); + g_free (asset_id); + g_free (check_asset_id); + return res; +} + +gboolean +_ges_add_track_from_struct (GESTimeline * timeline, + GstStructure * structure, GError ** error) +{ + const gchar *ttype; + GESTrack *track; + GstCaps *caps; + + const gchar *valid_fields[] = { "type", "restrictions", NULL }; + + FieldsError fields_error = { valid_fields, NULL }; + + if (!_check_fields (structure, fields_error, error)) + return FALSE; + + ttype = gst_structure_get_string (structure, "type"); + if (!g_strcmp0 (ttype, "video")) { + track = GES_TRACK (ges_video_track_new ()); + } else if (!g_strcmp0 (ttype, "audio")) { + track = GES_TRACK (ges_audio_track_new ()); + } else { + g_set_error (error, GES_ERROR, 0, "Unhandled track type: `%s`", ttype); + + return FALSE; + } + + if (gst_structure_has_field (structure, "restrictions")) { + GstStructure *restriction_struct; + gchar *restriction_str; + + if (gst_structure_get (structure, "restrictions", GST_TYPE_STRUCTURE, + &restriction_struct, NULL)) { + caps = gst_caps_new_full (restriction_struct, NULL); + } else if (gst_structure_get (structure, "restrictions", G_TYPE_STRING, + &restriction_str, NULL)) { + caps = gst_caps_from_string (restriction_str); + + if (!caps) { + g_set_error (error, GES_ERROR, 0, "Invalid restrictions caps: %s", + restriction_str); + + g_free (restriction_str); + return FALSE; + } + g_free (restriction_str); + } else if (!gst_structure_get (structure, "restrictions", GST_TYPE_CAPS, + &caps, NULL)) { + gchar *tmp = gst_structure_to_string (structure); + + g_set_error (error, GES_ERROR, 0, "Can't use restrictions caps from %s", + tmp); + + g_object_unref (track); + return FALSE; + } + + ges_track_set_restriction_caps (track, caps); + gst_caps_unref (caps); + } + + return ges_timeline_add_track (timeline, track); +} + +gboolean +_ges_container_add_child_from_struct (GESTimeline * timeline, + GstStructure * structure, GError ** error) +{ + GESAsset *asset = NULL; + GESContainer *container; + GESTimelineElement *child = NULL; + const gchar *container_name, *child_name, *child_type, *id; + + gboolean res = TRUE; + const gchar *valid_fields[] = { "container-name", "asset-id", "inpoint", + "child-type", "child-name", "project-uri", NULL + }; + + FieldsError fields_error = { valid_fields, NULL }; + + if (!_check_fields (structure, fields_error, error)) + return FALSE; + + container_name = gst_structure_get_string (structure, "container-name"); + + if (container_name == NULL) { + container = g_object_get_qdata (G_OBJECT (timeline), LAST_CONTAINER_QDATA); + } else { + container = + GES_CONTAINER (ges_timeline_get_element (timeline, container_name)); + } + + if (!GES_IS_CONTAINER (container)) { + *error = + g_error_new (GES_ERROR, 0, "Could not find container: %s (%p)", + container_name, container); + + res = FALSE; + goto beach; + } + + id = gst_structure_get_string (structure, "asset-id"); + child_type = gst_structure_get_string (structure, "child-type"); + + if (id && child_type) { + asset = + _ges_get_asset_from_timeline (timeline, g_type_from_name (child_type), + id, error); + + if (asset == NULL) { + res = FALSE; + goto beach; + } + + child = GES_TIMELINE_ELEMENT (ges_asset_extract (asset, NULL)); + if (!GES_IS_TIMELINE_ELEMENT (child)) { + *error = g_error_new (GES_ERROR, 0, "Could not extract child element"); + + goto beach; + } + } + + child_name = gst_structure_get_string (structure, "child-name"); + if (!child && child_name) { + child = ges_timeline_get_element (timeline, child_name); + if (!GES_IS_TIMELINE_ELEMENT (child)) { + *error = g_error_new (GES_ERROR, 0, "Could not find child element"); + + goto beach; + } + } + + if (!child) { + *error = + g_error_new (GES_ERROR, 0, "Wrong parameters, could not get a child"); + + return FALSE; + } + + if (child_name) + ges_timeline_element_set_name (child, child_name); + else + child_name = GES_TIMELINE_ELEMENT_NAME (child); + + if (gst_structure_has_field (structure, "inpoint")) { + GstClockTime inpoint; + GESFrameNumber finpoint; + + if (!GES_IS_TRACK_ELEMENT (child)) { + *error = g_error_new (GES_ERROR, 0, "Child %s is not a trackelement" + ", can't set inpoint.", child_name); + + gst_object_unref (child); + + goto beach; + } + + if (!ges_util_structure_get_clocktime (structure, "inpoint", &inpoint, + &finpoint)) { + *error = g_error_new (GES_ERROR, 0, "Could not use inpoint."); + gst_object_unref (child); + + goto beach; + } + + if (!ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (child), + TRUE)) { + *error = + g_error_new (GES_ERROR, 0, + "Could not set inpoint as %s can't have an internal source", + child_name); + gst_object_unref (child); + + goto beach; + } + + if (GES_FRAME_NUMBER_IS_VALID (finpoint)) + inpoint = ges_timeline_get_frame_time (timeline, finpoint); + + ges_timeline_element_set_inpoint (child, inpoint); + + } + + res = ges_container_add (container, child); + if (res == FALSE) { + g_error_new (GES_ERROR, 0, "Could not add child to container"); + } else { + g_object_set_qdata (G_OBJECT (timeline), LAST_CHILD_QDATA, child); + } + res = _ges_save_timeline_if_needed (timeline, structure, error); + +beach: + gst_clear_object (&asset); + return res; +} + +gboolean +_ges_set_child_property_from_struct (GESTimeline * timeline, + GstStructure * structure, GError ** error) +{ + const GValue *value; + GValue prop_value = G_VALUE_INIT; + gboolean prop_value_set = FALSE; + gchar *property_name; + GESTimelineElement *element; + gchar *serialized; + gboolean res; + + const gchar *valid_fields[] = + { "element-name", "property", "value", "project-uri", NULL }; + + FieldsError fields_error = { valid_fields, NULL }; + + if (!_check_fields (structure, fields_error, error)) + return FALSE; + + GET_AND_CHECK ("property", G_TYPE_STRING, &property_name, err); + + if (!(element = + find_element_for_property (timeline, structure, &property_name, FALSE, + error))) + goto err; + + value = gst_structure_get_value (structure, "value"); + + if (G_VALUE_TYPE (value) == G_TYPE_STRING) { + GParamSpec *pspec; + if (ges_timeline_element_lookup_child (element, property_name, NULL, + &pspec)) { + GType p_type = pspec->value_type; + g_param_spec_unref (pspec); + if (p_type != G_TYPE_STRING) { + const gchar *val_string = g_value_get_string (value); + g_value_init (&prop_value, p_type); + if (!gst_value_deserialize (&prop_value, val_string)) { + *error = g_error_new (GES_ERROR, 0, "Could not set the property %s " + "because the value %s could not be deserialized to the %s type", + property_name, val_string, g_type_name (p_type)); + return FALSE; + } + prop_value_set = TRUE; + } + } + /* else, let the setter fail below */ + } + + if (!prop_value_set) { + g_value_init (&prop_value, G_VALUE_TYPE (value)); + g_value_copy (value, &prop_value); + } + + serialized = gst_value_serialize (&prop_value); + GST_INFO_OBJECT (element, "Setting property %s to %s\n", property_name, + serialized); + g_free (serialized); + + res = ges_timeline_element_set_child_property (element, property_name, + &prop_value); + g_value_unset (&prop_value); + if (!res) { + guint n_specs, i; + GParamSpec **specs = + ges_timeline_element_list_children_properties (element, &n_specs); + GString *errstr = g_string_new (NULL); + + g_string_append_printf (errstr, + "\n Could not set property `%s` on `%s`, valid properties:\n", + property_name, GES_TIMELINE_ELEMENT_NAME (element)); + + for (i = 0; i < n_specs; i++) + g_string_append_printf (errstr, " - %s\n", specs[i]->name); + g_free (specs); + + *error = g_error_new_literal (GES_ERROR, 0, errstr->str); + g_string_free (errstr, TRUE); + + return FALSE; + } + g_free (property_name); + return _ges_save_timeline_if_needed (timeline, structure, error); + +err: + g_free (property_name); + return FALSE; +} + +gboolean +_ges_set_control_source_from_struct (GESTimeline * timeline, + GstStructure * structure, GError ** error) +{ + guint mode; + gboolean res = FALSE; + GESTimelineElement *element = NULL; + + GstControlSource *source = NULL; + gchar *property_name, *binding_type = NULL, + *source_type = NULL, *interpolation_mode = NULL; + + GET_AND_CHECK ("property-name", G_TYPE_STRING, &property_name, beach); + + if (!(element = + find_element_for_property (timeline, structure, &property_name, TRUE, + error))) + goto beach; + + if (GES_IS_CLIP (element)) { + GList *tmp; + for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { + if (ges_timeline_element_lookup_child (tmp->data, property_name, NULL, + NULL)) { + gst_object_replace ((GstObject **) & element, tmp->data); + + break; + } + } + } + + REPORT_UNLESS (GES_IS_TRACK_ELEMENT (element), beach, + "Could not find TrackElement from %" GST_PTR_FORMAT, structure); + + TRY_GET ("binding-type", G_TYPE_STRING, &binding_type, NULL); + TRY_GET ("source-type", G_TYPE_STRING, &source_type, NULL); + TRY_GET ("interpolation-mode", G_TYPE_STRING, &interpolation_mode, NULL); + + if (!binding_type) + binding_type = g_strdup ("direct"); + + REPORT_UNLESS (source_type == NULL + || !g_strcmp0 (source_type, "interpolation"), beach, + "Interpolation type %s not supported", source_type); + source = gst_interpolation_control_source_new (); + + if (interpolation_mode) + REPORT_UNLESS (enum_from_str (GST_TYPE_INTERPOLATION_MODE, + interpolation_mode, &mode), beach, + "Wrong interpolation mode: %s", interpolation_mode); + else + mode = GST_INTERPOLATION_MODE_LINEAR; + + g_object_set (source, "mode", mode, NULL); + + res = ges_track_element_set_control_source (GES_TRACK_ELEMENT (element), + source, property_name, binding_type); + +beach: + gst_clear_object (&element); + gst_clear_object (&source); + g_free (property_name); + g_free (binding_type); + g_free (source_type); + g_free (interpolation_mode); + + return res; +} + +#undef GET_AND_CHECK +#undef TRY_GET diff --git a/ges/ges-structured-interface.h b/ges/ges-structured-interface.h new file mode 100644 index 0000000000..9dd7a69b50 --- /dev/null +++ b/ges/ges-structured-interface.h @@ -0,0 +1,69 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2015> Thibault Saunier <tsaunier@gnome.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#pragma once + +#include <ges/ges.h> + +G_BEGIN_DECLS + +typedef gboolean (*ActionFromStructureFunc) (GESTimeline * timeline, + GstStructure * structure, + GError ** error); + +G_GNUC_INTERNAL gboolean +_ges_add_remove_keyframe_from_struct (GESTimeline * timeline, + GstStructure * structure, + GError ** error); +G_GNUC_INTERNAL gboolean +_ges_add_clip_from_struct (GESTimeline * timeline, + GstStructure * structure, + GError ** error); + +G_GNUC_INTERNAL gboolean +_ges_add_track_from_struct (GESTimeline * timeline, + GstStructure * structure, + GError ** error); + +G_GNUC_INTERNAL gboolean +_ges_container_add_child_from_struct (GESTimeline * timeline, + GstStructure * structure, + GError ** error); + +G_GNUC_INTERNAL gboolean +_ges_set_child_property_from_struct (GESTimeline * timeline, + GstStructure * structure, + GError ** error); + +G_GNUC_INTERNAL GESAsset * +_ges_get_asset_from_timeline (GESTimeline * timeline, + GType type, + const gchar * id, + GError **error); +G_GNUC_INTERNAL GESLayer * +_ges_get_layer_by_priority (GESTimeline * timeline, + gint priority); +G_GNUC_INTERNAL gboolean +_ges_save_timeline_if_needed (GESTimeline* timeline, GstStructure* structure, GError** error); + +G_GNUC_INTERNAL gboolean +_ges_set_control_source_from_struct (GESTimeline * timeline, + GstStructure * structure, GError ** error); + +G_END_DECLS diff --git a/ges/ges-test-clip.c b/ges/ges-test-clip.c new file mode 100644 index 0000000000..0eba35920a --- /dev/null +++ b/ges/ges-test-clip.c @@ -0,0 +1,613 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * Copyright (C) 2020 Igalia S.L + * Author: 2020 Thibault Saunier <tsaunier@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gestestclip + * @title: GESTestClip + * @short_description: Render video and audio test patterns in a GESLayer + * + * Useful for testing purposes. + * + * ## Asset + * + * The default asset ID is GESTestClip, but the framerate and video + * size can be overridden using an ID of the form: + * + * ``` + * framerate=60/1, width=1920, height=1080, max-duration=5.0 + * ``` + * Note: `max-duration` can be provided in seconds as float, or as GstClockTime + * as guint64 or gint. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-test-clip.h" +#include "ges-source-clip.h" +#include "ges-track-element.h" +#include "ges-video-test-source.h" +#include "ges-audio-test-source.h" +#include <string.h> + +#define DEFAULT_VOLUME 1.0 +#define DEFAULT_VPATTERN GES_VIDEO_TEST_PATTERN_SMPTE + +G_DECLARE_FINAL_TYPE (GESTestClipAsset, ges_test_clip_asset, GES, + TEST_CLIP_ASSET, GESSourceClipAsset); + +struct _GESTestClipAsset +{ + GESSourceClipAsset parent; + + gint natural_framerate_n; + gint natural_framerate_d; + gint natural_width; + gint natural_height; + GstClockTime max_duration; +}; + +#define GES_TYPE_TEST_CLIP_ASSET (ges_test_clip_asset_get_type()) +G_DEFINE_TYPE (GESTestClipAsset, ges_test_clip_asset, + GES_TYPE_SOURCE_CLIP_ASSET); + +static gboolean +_get_natural_framerate (GESClipAsset * asset, gint * framerate_n, + gint * framerate_d) +{ + GESTestClipAsset *self = GES_TEST_CLIP_ASSET (asset); + + *framerate_n = self->natural_framerate_n; + *framerate_d = self->natural_framerate_d; + return TRUE; +} + +static GstClockTime +ges_test_clip_asset_get_max_duration (GESAsset * asset) +{ + GESTestClipAsset *self = GES_TEST_CLIP_ASSET (asset); + + return GES_TEST_CLIP_ASSET (self)->max_duration; +} + +gboolean +ges_test_clip_asset_get_natural_size (GESAsset * asset, gint * width, + gint * height) +{ + GESTestClipAsset *self = GES_TEST_CLIP_ASSET (asset); + + *width = self->natural_width; + *height = self->natural_height; + + return TRUE; +} + +static void +ges_test_clip_asset_constructed (GObject * gobject) +{ + GESFrameNumber fmax_dur = GES_FRAME_NUMBER_NONE; + GESTestClipAsset *self = GES_TEST_CLIP_ASSET (gobject); + GstStructure *structure = + gst_structure_from_string (ges_asset_get_id (GES_ASSET (self)), NULL); + + g_assert (structure); + + gst_structure_get_int (structure, "width", &self->natural_width); + gst_structure_get_int (structure, "height", &self->natural_height); + gst_structure_get_fraction (structure, "framerate", + &self->natural_framerate_n, &self->natural_framerate_d); + ges_util_structure_get_clocktime (structure, "max-duration", + &self->max_duration, &fmax_dur); + if (GES_FRAME_NUMBER_IS_VALID (fmax_dur)) + self->max_duration = + gst_util_uint64_scale (fmax_dur, self->natural_framerate_d * GST_SECOND, + self->natural_framerate_n); + gst_structure_free (structure); + + G_OBJECT_CLASS (ges_test_clip_asset_parent_class)->constructed (gobject); +} + +static void +ges_test_clip_asset_class_init (GESTestClipAssetClass * klass) +{ + GESClipAssetClass *clip_asset_class = GES_CLIP_ASSET_CLASS (klass); + + clip_asset_class->get_natural_framerate = _get_natural_framerate; + G_OBJECT_CLASS (klass)->constructed = ges_test_clip_asset_constructed; +} + +static void +ges_test_clip_asset_init (GESTestClipAsset * self) +{ + self->natural_width = DEFAULT_WIDTH; + self->natural_height = DEFAULT_HEIGHT; + self->natural_framerate_n = DEFAULT_FRAMERATE_N; + self->natural_framerate_d = DEFAULT_FRAMERATE_D; + self->max_duration = GST_CLOCK_TIME_NONE; +} + +struct _GESTestClipPrivate +{ + gboolean mute; + GESVideoTestPattern vpattern; + gdouble freq; + gdouble volume; +}; + +enum +{ + PROP_0, + PROP_MUTE, + PROP_VPATTERN, + PROP_FREQ, + PROP_VOLUME, +}; + +typedef struct +{ + const gchar *name; + GType type; +} ValidField; + +gchar * +ges_test_source_asset_check_id (GType type, const gchar * id, GError ** error) +{ + if (id && g_strcmp0 (id, g_type_name (type))) { + gchar *res = NULL; + GstStructure *structure = gst_structure_from_string (id, NULL); + + if (!structure) { + gchar *struct_str = g_strdup_printf ("%s,%s", g_type_name (type), id); + + structure = gst_structure_from_string (struct_str, NULL); + g_free (struct_str); + } + + GST_INFO ("Test source ID: %" GST_PTR_FORMAT, structure); + if (!structure) { + g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID, + "GESTestClipAsset ID should be in the form: `framerate=30/1, " + "width=1920, height=1080, got %s", id); + } else { + static ValidField valid_fields[] = { + {"width", G_TYPE_INT}, + {"height", G_TYPE_INT}, + {"framerate", G_TYPE_NONE}, /* GST_TYPE_FRACTION is not constant */ + {"max-duration", GST_TYPE_CLOCK_TIME}, + {"disable-timecodestamper", G_TYPE_BOOLEAN}, + }; + gint i; + + for (i = 0; i < G_N_ELEMENTS (valid_fields); i++) { + if (gst_structure_has_field (structure, valid_fields[i].name)) { + GstClockTime ts; + GESFrameNumber fn; + ValidField field = valid_fields[i]; + GType type = + field.type == G_TYPE_NONE ? GST_TYPE_FRACTION : field.type; + + if (!(gst_structure_has_field_typed (structure, field.name, + type) || + (type == GST_TYPE_CLOCK_TIME && + ges_util_structure_get_clocktime (structure, field.name, + &ts, &fn)))) { + + g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID, + "Field %s has wrong type, %s, expected %s", field.name, + g_type_name (gst_structure_get_field_type (structure, + field.name)), g_type_name (type)); + + gst_structure_free (structure); + + return FALSE; + } + } + } + res = gst_structure_to_string (structure); + gst_structure_free (structure); + } + + return res; + } + + return g_strdup (g_type_name (type)); +} + +static void +ges_extractable_interface_init (GESExtractableInterface * iface) +{ + iface->asset_type = GES_TYPE_TEST_CLIP_ASSET; + iface->check_id = ges_test_source_asset_check_id; +} + +G_DEFINE_TYPE_WITH_CODE (GESTestClip, ges_test_clip, GES_TYPE_SOURCE_CLIP, + G_ADD_PRIVATE (GESTestClip) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, + ges_extractable_interface_init)); + + +static GESTrackElement + * ges_test_clip_create_track_element (GESClip * clip, GESTrackType type); + +static void +ges_test_clip_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESTestClipPrivate *priv = GES_TEST_CLIP (object)->priv; + + switch (property_id) { + case PROP_MUTE: + g_value_set_boolean (value, priv->mute); + break; + case PROP_VPATTERN: + g_value_set_enum (value, priv->vpattern); + break; + case PROP_FREQ: + g_value_set_double (value, priv->freq); + break; + case PROP_VOLUME: + g_value_set_double (value, priv->volume); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_test_clip_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESTestClip *uriclip = GES_TEST_CLIP (object); + + switch (property_id) { + case PROP_MUTE: + ges_test_clip_set_mute (uriclip, g_value_get_boolean (value)); + break; + case PROP_VPATTERN: + ges_test_clip_set_vpattern (uriclip, g_value_get_enum (value)); + break; + case PROP_FREQ: + ges_test_clip_set_frequency (uriclip, g_value_get_double (value)); + break; + case PROP_VOLUME: + ges_test_clip_set_volume (uriclip, g_value_get_double (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_test_clip_class_init (GESTestClipClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESClipClass *timobj_class = GES_CLIP_CLASS (klass); + + object_class->get_property = ges_test_clip_get_property; + object_class->set_property = ges_test_clip_set_property; + + /** + * GESTestClip:vpattern: + * + * Video pattern to display in video track elements. + */ + g_object_class_install_property (object_class, PROP_VPATTERN, + g_param_spec_enum ("vpattern", "VPattern", + "Which video pattern to display. See videotestsrc element", + GES_VIDEO_TEST_PATTERN_TYPE, + DEFAULT_VPATTERN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + /** + * GESTestClip:freq: + * + * The frequency to generate for audio track elements. + */ + g_object_class_install_property (object_class, PROP_FREQ, + g_param_spec_double ("freq", "Audio Frequency", + "The frequency to generate. See audiotestsrc element", + 0, 20000, 440, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + /** + * GESTestClip:volume: + * + * The volume for the audio track elements. + */ + g_object_class_install_property (object_class, PROP_VOLUME, + g_param_spec_double ("volume", "Audio Volume", + "The volume of the test audio signal.", + 0, 1, DEFAULT_VOLUME, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + + /** + * GESTestClip:mute: + * + * Whether the sound will be played or not. + */ + g_object_class_install_property (object_class, PROP_MUTE, + g_param_spec_boolean ("mute", "Mute", "Mute audio track", + FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + timobj_class->create_track_element = ges_test_clip_create_track_element; +} + +static void +ges_test_clip_init (GESTestClip * self) +{ + SUPRESS_UNUSED_WARNING (GES_IS_TEST_CLIP_ASSET); + self->priv = ges_test_clip_get_instance_private (self); + + self->priv->freq = 0; + self->priv->volume = 0; + GES_TIMELINE_ELEMENT (self)->duration = 0; +} + +/** + * ges_test_clip_set_mute: + * @self: the #GESTestClip on which to mute or unmute the audio track + * @mute: %TRUE to mute the audio track, %FALSE to unmute it + * + * Sets whether the audio track of this clip is muted or not. + * + */ +void +ges_test_clip_set_mute (GESTestClip * self, gboolean mute) +{ + GList *tmp; + + GST_DEBUG ("self:%p, mute:%d", self, mute); + + self->priv->mute = mute; + + /* Go over tracked objects, and update 'active' status on all audio objects */ + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + GESTrackElement *trackelement = (GESTrackElement *) tmp->data; + + if (ges_track_element_get_track (trackelement)->type == + GES_TRACK_TYPE_AUDIO) + ges_track_element_set_active (trackelement, !mute); + } +} + +/** + * ges_test_clip_set_vpattern: + * @self: the #GESTestClip to set the pattern on + * @vpattern: the #GESVideoTestPattern to use on @self + * + * Sets which video pattern to display on @self. + * + */ +void +ges_test_clip_set_vpattern (GESTestClip * self, GESVideoTestPattern vpattern) +{ + GList *tmp; + + self->priv->vpattern = vpattern; + + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + GESTrackElement *trackelement = (GESTrackElement *) tmp->data; + if (GES_IS_VIDEO_TEST_SOURCE (trackelement)) + ges_video_test_source_set_pattern ( + (GESVideoTestSource *) trackelement, vpattern); + } +} + +/** + * ges_test_clip_set_frequency: + * @self: the #GESTestClip to set the frequency on + * @freq: the frequency you want to use on @self + * + * Sets the frequency to generate. See audiotestsrc element. + * + */ +void +ges_test_clip_set_frequency (GESTestClip * self, gdouble freq) +{ + GList *tmp; + + self->priv->freq = freq; + + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + GESTrackElement *trackelement = (GESTrackElement *) tmp->data; + if (GES_IS_AUDIO_TEST_SOURCE (trackelement)) + ges_audio_test_source_set_freq ( + (GESAudioTestSource *) trackelement, freq); + } +} + +/** + * ges_test_clip_set_volume: + * @self: the #GESTestClip to set the volume on + * @volume: the volume of the audio signal you want to use on @self + * + * Sets the volume of the test audio signal. + * + */ +void +ges_test_clip_set_volume (GESTestClip * self, gdouble volume) +{ + GList *tmp; + + self->priv->volume = volume; + + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + GESTrackElement *trackelement = (GESTrackElement *) tmp->data; + if (GES_IS_AUDIO_TEST_SOURCE (trackelement)) + ges_audio_test_source_set_volume ( + (GESAudioTestSource *) trackelement, volume); + } +} + +/** + * ges_test_clip_get_vpattern: + * @self: a #GESTestClip + * + * Get the #GESVideoTestPattern which is applied on @self. + * + * Returns: The #GESVideoTestPattern which is applied on @self. + */ +GESVideoTestPattern +ges_test_clip_get_vpattern (GESTestClip * self) +{ + return self->priv->vpattern; +} + +/** + * ges_test_clip_is_muted: + * @self: a #GESTestClip + * + * Let you know if the audio track of @self is muted or not. + * + * Returns: Whether the audio track of @self is muted or not. + */ +gboolean +ges_test_clip_is_muted (GESTestClip * self) +{ + return self->priv->mute; +} + +/** + * ges_test_clip_get_frequency: + * @self: a #GESTestClip + * + * Get the frequency @self generates. + * + * Returns: The frequency @self generates. See audiotestsrc element. + */ +gdouble +ges_test_clip_get_frequency (GESTestClip * self) +{ + return self->priv->freq; +} + +/** + * ges_test_clip_get_volume: + * @self: a #GESTestClip + * + * Get the volume of the test audio signal applied on @self. + * + * Returns: The volume of the test audio signal applied on @self. + */ +gdouble +ges_test_clip_get_volume (GESTestClip * self) +{ + return self->priv->volume; +} + +static GESTrackElement * +ges_test_clip_create_track_element (GESClip * clip, GESTrackType type) +{ + GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (clip)); + GESTestClipPrivate *priv = GES_TEST_CLIP (clip)->priv; + GESTrackElement *res = NULL; + + GST_DEBUG ("Creating a GESTrackTestSource for type: %s", + ges_track_type_name (type)); + + if (type == GES_TRACK_TYPE_VIDEO) { + gchar *id = NULL; + GESAsset *videoasset; + + if (asset) { + GstStructure *structure = + gst_structure_from_string (ges_asset_get_id (asset), NULL); + + if (structure) { + id = g_strdup (gst_structure_get_name (structure)); + gst_structure_free (structure); + } + } + /* Our asset ID has been verified and thus this should not fail ever */ + videoasset = ges_asset_request (GES_TYPE_VIDEO_TEST_SOURCE, id, NULL); + g_assert (videoasset); + g_free (id); + + res = (GESTrackElement *) ges_asset_extract (videoasset, NULL); + gst_object_unref (videoasset); + ges_video_test_source_set_pattern ( + (GESVideoTestSource *) res, priv->vpattern); + } else if (type == GES_TRACK_TYPE_AUDIO) { + res = (GESTrackElement *) ges_audio_test_source_new (); + + if (priv->mute) + ges_track_element_set_active (res, FALSE); + + ges_audio_test_source_set_freq ((GESAudioTestSource *) res, priv->freq); + ges_audio_test_source_set_volume ((GESAudioTestSource *) res, priv->volume); + } + + if (asset) + ges_timeline_element_set_max_duration (GES_TIMELINE_ELEMENT (res), + ges_test_clip_asset_get_max_duration (asset)); + + return res; +} + +/** + * ges_test_clip_new: + * + * Creates a new #GESTestClip. + * + * Returns: (transfer floating) (nullable): The newly created #GESTestClip, + * or %NULL if there was an error. + */ +GESTestClip * +ges_test_clip_new (void) +{ + GESTestClip *new_clip; + GESAsset *asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + new_clip = GES_TEST_CLIP (ges_asset_extract (asset, NULL)); + gst_object_unref (asset); + + return new_clip; +} + +/** + * ges_test_clip_new_for_nick: + * @nick: the nickname for which to create the #GESTestClip + * + * Creates a new #GESTestClip for the provided @nick. + * + * Returns: (transfer floating) (nullable): The newly created #GESTestClip, + * or %NULL if there was an error. + */ +GESTestClip * +ges_test_clip_new_for_nick (gchar * nick) +{ + GEnumValue *value; + GEnumClass *klass; + GESTestClip *ret = NULL; + + klass = G_ENUM_CLASS (g_type_class_ref (GES_VIDEO_TEST_PATTERN_TYPE)); + if (!klass) + return NULL; + + value = g_enum_get_value_by_nick (klass, nick); + if (value) { + ret = ges_test_clip_new (); + ges_test_clip_set_vpattern (ret, value->value); + } + + g_type_class_unref (klass); + return ret; +} diff --git a/ges/ges-test-clip.h b/ges/ges-test-clip.h new file mode 100644 index 0000000000..6eaae99b4b --- /dev/null +++ b/ges/ges-test-clip.h @@ -0,0 +1,94 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2009 Nokia Corporation + * Copyright (C) 2020 Igalia S.L + * Author: 2020 Thibault Saunier <tsaunier@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + + +#include <glib-object.h> +#include <ges/ges-enums.h> +#include <ges/ges-types.h> +#include <ges/ges-source-clip.h> +#include <ges/ges-track.h> + +G_BEGIN_DECLS + +#define GES_TYPE_TEST_CLIP ges_test_clip_get_type() +GES_DECLARE_TYPE(TestClip, test_clip, TEST_CLIP); + +/** + * GESTestClip: + */ + +struct _GESTestClip { + + GESSourceClip parent; + + /*< private >*/ + GESTestClipPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESTestClipClass: + */ + +struct _GESTestClipClass { + /*< private >*/ + GESSourceClipClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API void +ges_test_clip_set_mute (GESTestClip * self, gboolean mute); + +GES_API void +ges_test_clip_set_vpattern (GESTestClip * self, + GESVideoTestPattern vpattern); + +GES_API void +ges_test_clip_set_frequency (GESTestClip * self, gdouble freq); + +GES_API void +ges_test_clip_set_volume (GESTestClip * self, + gdouble volume); + + +GES_API GESVideoTestPattern +ges_test_clip_get_vpattern (GESTestClip * self); + +GES_API +gboolean ges_test_clip_is_muted (GESTestClip * self); +GES_API +gdouble ges_test_clip_get_frequency (GESTestClip * self); +GES_API +gdouble ges_test_clip_get_volume (GESTestClip * self); + +GES_API +GESTestClip* ges_test_clip_new (void); +GES_API +GESTestClip* ges_test_clip_new_for_nick(gchar * nick); + +G_END_DECLS diff --git a/ges/ges-text-overlay-clip.c b/ges/ges-text-overlay-clip.c new file mode 100644 index 0000000000..72d826509e --- /dev/null +++ b/ges/ges-text-overlay-clip.c @@ -0,0 +1,600 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gestextoverlayclip + * @title: GESTextOverlayClip + * @short_description: Render text onto another stream in a GESLayer + * + * Renders text onto the next lower priority stream using textrender. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-text-overlay-clip.h" +#include "ges-track-element.h" +#include "ges-text-overlay.h" +#include <string.h> + + +#define DEFAULT_PROP_TEXT "" +#define DEFAULT_PROP_FONT_DESC "Serif 36" +#define DEFAULT_PROP_VALIGNMENT GES_TEXT_VALIGN_BASELINE +#define DEFAULT_PROP_HALIGNMENT GES_TEXT_HALIGN_CENTER + +struct _GESTextOverlayClipPrivate +{ + gchar *text; + gchar *font_desc; + GESTextHAlign halign; + GESTextVAlign valign; + guint32 color; + gdouble xpos; + gdouble ypos; +}; + +enum +{ + PROP_0, + PROP_TEXT, + PROP_FONT_DESC, + PROP_HALIGNMENT, + PROP_VALIGNMENT, + PROP_COLOR, + PROP_XPOS, + PROP_YPOS, +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GESTextOverlayClip, ges_text_overlay_clip, + GES_TYPE_OVERLAY_CLIP); + +static GESTrackElement + * ges_text_overlay_clip_create_track_element (GESClip * clip, + GESTrackType type); + +static void +ges_text_overlay_clip_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESTextOverlayClipPrivate *priv = GES_OVERLAY_TEXT_CLIP (object)->priv; + + switch (property_id) { + case PROP_TEXT: + g_value_set_string (value, priv->text); + break; + case PROP_FONT_DESC: + g_value_set_string (value, priv->font_desc); + break; + case PROP_HALIGNMENT: + g_value_set_enum (value, priv->halign); + break; + case PROP_VALIGNMENT: + g_value_set_enum (value, priv->valign); + break; + case PROP_COLOR: + g_value_set_uint (value, priv->color); + break; + case PROP_XPOS: + g_value_set_double (value, priv->xpos); + break; + case PROP_YPOS: + g_value_set_double (value, priv->ypos); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_text_overlay_clip_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESTextOverlayClip *uriclip = GES_OVERLAY_TEXT_CLIP (object); + + switch (property_id) { + case PROP_TEXT: + ges_text_overlay_clip_set_text (uriclip, g_value_get_string (value)); + break; + case PROP_FONT_DESC: + ges_text_overlay_clip_set_font_desc (uriclip, g_value_get_string (value)); + break; + case PROP_HALIGNMENT: + ges_text_overlay_clip_set_halign (uriclip, g_value_get_enum (value)); + break; + case PROP_VALIGNMENT: + ges_text_overlay_clip_set_valign (uriclip, g_value_get_enum (value)); + break; + case PROP_COLOR: + ges_text_overlay_clip_set_color (uriclip, g_value_get_uint (value)); + break; + case PROP_XPOS: + ges_text_overlay_clip_set_xpos (uriclip, g_value_get_double (value)); + break; + case PROP_YPOS: + ges_text_overlay_clip_set_ypos (uriclip, g_value_get_double (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_text_overlay_clip_dispose (GObject * object) +{ + GESTextOverlayClipPrivate *priv = GES_OVERLAY_TEXT_CLIP (object)->priv; + + if (priv->text) + g_free (priv->text); + if (priv->font_desc) + g_free (priv->font_desc); + + G_OBJECT_CLASS (ges_text_overlay_clip_parent_class)->dispose (object); +} + +static void +ges_text_overlay_clip_class_init (GESTextOverlayClipClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESClipClass *timobj_class = GES_CLIP_CLASS (klass); + + object_class->get_property = ges_text_overlay_clip_get_property; + object_class->set_property = ges_text_overlay_clip_set_property; + object_class->dispose = ges_text_overlay_clip_dispose; + + /** + * GESTextOverlayClip:text: + * + * The text to diplay + */ + + g_object_class_install_property (object_class, PROP_TEXT, + g_param_spec_string ("text", "Text", "The text to display", + DEFAULT_PROP_TEXT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + /** + * GESTextOverlayClip:font-desc: + * + * Pango font description string + */ + + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC, + g_param_spec_string ("font-desc", "font description", + "Pango font description of font to be used for rendering. " + "See documentation of pango_font_description_from_string " + "for syntax.", DEFAULT_PROP_FONT_DESC, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + + /** + * GESTextOverlayClip:valignment: + * + * Vertical alignent of the text + */ + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT, + g_param_spec_enum ("valignment", "vertical alignment", + "Vertical alignment of the text", GES_TEXT_VALIGN_TYPE, + DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + /** + * GESTextOverlayClip:halignment: + * + * Horizontal alignment of the text + */ + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT, + g_param_spec_enum ("halignment", "horizontal alignment", + "Horizontal alignment of the text", + GES_TEXT_HALIGN_TYPE, DEFAULT_PROP_HALIGNMENT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + + timobj_class->create_track_element = + ges_text_overlay_clip_create_track_element; + + /** + * GESTextOverlayClip:color: + * + * The color of the text + */ + + g_object_class_install_property (object_class, PROP_COLOR, + g_param_spec_uint ("color", "Color", "The color of the text", + 0, G_MAXUINT32, G_MAXUINT32, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + /** + * GESTextOverlayClip:xpos: + * + * The horizontal position of the text + */ + + g_object_class_install_property (object_class, PROP_XPOS, + g_param_spec_double ("xpos", "Xpos", "The horizontal position", + 0, 1, 0.5, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + /** + * GESTextOverlayClip:ypos: + * + * The vertical position of the text + */ + + g_object_class_install_property (object_class, PROP_YPOS, + g_param_spec_double ("ypos", "Ypos", "The vertical position", + 0, 1, 0.5, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); +} + +static void +ges_text_overlay_clip_init (GESTextOverlayClip * self) +{ + self->priv = ges_text_overlay_clip_get_instance_private (self); + + GES_TIMELINE_ELEMENT (self)->duration = 0; + /* Not 100% needed since gobject contents are memzero'd when created */ + self->priv->text = NULL; + self->priv->font_desc = NULL; + self->priv->halign = DEFAULT_PROP_HALIGNMENT; + self->priv->valign = DEFAULT_PROP_VALIGNMENT; + self->priv->color = G_MAXUINT32; + self->priv->xpos = 0.5; + self->priv->ypos = 0.5; +} + +/** + * ges_text_overlay_clip_set_text: + * @self: the #GESTextOverlayClip* to set text on + * @text: the text to render. an internal copy of this text will be + * made. + * + * Sets the text this clip will render. + * + */ +void +ges_text_overlay_clip_set_text (GESTextOverlayClip * self, const gchar * text) +{ + GList *tmp; + + GST_DEBUG ("self:%p, text:%s", self, text); + + if (self->priv->text) + g_free (self->priv->text); + + self->priv->text = g_strdup (text); + + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + GESTrackElement *trackelement = (GESTrackElement *) tmp->data; + + if (ges_track_element_get_track (trackelement)->type == + GES_TRACK_TYPE_VIDEO) + ges_text_overlay_set_text (GES_TEXT_OVERLAY (trackelement), + self->priv->text); + } +} + +/** + * ges_text_overlay_clip_set_font_desc: + * @self: the #GESTextOverlayClip* + * @font_desc: the pango font description + * + * Sets the pango font description of the text + * + */ +void +ges_text_overlay_clip_set_font_desc (GESTextOverlayClip * self, + const gchar * font_desc) +{ + GList *tmp; + + GST_DEBUG ("self:%p, font_desc:%s", self, font_desc); + + if (self->priv->font_desc) + g_free (self->priv->font_desc); + + self->priv->font_desc = g_strdup (font_desc); + + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + GESTrackElement *trackelement = (GESTrackElement *) tmp->data; + + if (ges_track_element_get_track (trackelement)->type == + GES_TRACK_TYPE_VIDEO) + ges_text_overlay_set_font_desc (GES_TEXT_OVERLAY + (trackelement), self->priv->font_desc); + } + +} + +/** + * ges_text_overlay_clip_set_halign: + * @self: the #GESTextOverlayClip* to set horizontal alignement of text on + * @halign: #GESTextHAlign + * + * Sets the horizontal aligment of the text. + * + */ +void +ges_text_overlay_clip_set_halign (GESTextOverlayClip * self, + GESTextHAlign halign) +{ + GList *tmp; + + GST_DEBUG ("self:%p, halign:%d", self, halign); + + self->priv->halign = halign; + + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + GESTrackElement *trackelement = (GESTrackElement *) tmp->data; + + if (ges_track_element_get_track (trackelement)->type == + GES_TRACK_TYPE_VIDEO) + ges_text_overlay_set_halignment (GES_TEXT_OVERLAY + (trackelement), self->priv->halign); + } + +} + +/** + * ges_text_overlay_clip_set_valign: + * @self: the #GESTextOverlayClip* to set vertical alignement of text on + * @valign: #GESTextVAlign + * + * Sets the vertical aligment of the text. + * + */ +void +ges_text_overlay_clip_set_valign (GESTextOverlayClip * self, + GESTextVAlign valign) +{ + GList *tmp; + + GST_DEBUG ("self:%p, valign:%d", self, valign); + + self->priv->valign = valign; + + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + GESTrackElement *trackelement = (GESTrackElement *) tmp->data; + + if (ges_track_element_get_track (trackelement)->type == + GES_TRACK_TYPE_VIDEO) + ges_text_overlay_set_valignment (GES_TEXT_OVERLAY + (trackelement), self->priv->valign); + } + +} + +/** + * ges_text_overlay_clip_set_color: + * @self: the #GESTextOverlayClip* to set + * @color: The color @self is being set to + * + * Sets the color of the text. + */ +void +ges_text_overlay_clip_set_color (GESTextOverlayClip * self, guint32 color) +{ + GList *tmp; + + GST_DEBUG ("self:%p, color:%d", self, color); + + self->priv->color = color; + + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + GESTrackElement *trackelement = (GESTrackElement *) tmp->data; + + if (ges_track_element_get_track (trackelement)->type == + GES_TRACK_TYPE_VIDEO) + ges_text_overlay_set_color (GES_TEXT_OVERLAY (trackelement), + self->priv->color); + } +} + +/** + * ges_text_overlay_clip_set_xpos: + * @self: the #GESTextOverlayClip* to set + * @position: The horizontal position @self is being set to + * + * Sets the horizontal position of the text. + */ +void +ges_text_overlay_clip_set_xpos (GESTextOverlayClip * self, gdouble position) +{ + GList *tmp; + + GST_DEBUG ("self:%p, xpos:%f", self, position); + + self->priv->xpos = position; + + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + GESTrackElement *trackelement = (GESTrackElement *) tmp->data; + + if (ges_track_element_get_track (trackelement)->type == + GES_TRACK_TYPE_VIDEO) + ges_text_overlay_set_xpos (GES_TEXT_OVERLAY (trackelement), + self->priv->xpos); + } +} + +/** + * ges_text_overlay_clip_set_ypos: + * @self: the #GESTextOverlayClip* to set + * @position: The vertical position @self is being set to + * + * Sets the vertical position of the text. + */ +void +ges_text_overlay_clip_set_ypos (GESTextOverlayClip * self, gdouble position) +{ + GList *tmp; + + GST_DEBUG ("self:%p, ypos:%f", self, position); + + self->priv->ypos = position; + + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + GESTrackElement *trackelement = (GESTrackElement *) tmp->data; + + if (ges_track_element_get_track (trackelement)->type == + GES_TRACK_TYPE_VIDEO) + ges_text_overlay_set_ypos (GES_TEXT_OVERLAY (trackelement), + self->priv->ypos); + } +} + +/** + * ges_text_overlay_clip_get_text: + * @self: a #GESTextOverlayClip + * + * Get the text currently set on @self. + * + * Returns: The text currently set on @self. + * + */ +const gchar * +ges_text_overlay_clip_get_text (GESTextOverlayClip * self) +{ + return self->priv->text; +} + +/** + * ges_text_overlay_clip_get_font_desc: + * @self: a #GESTextOverlayClip + * + * Get the pango font description used by @self. + * + * Returns: The pango font description used by @self. + */ +const char * +ges_text_overlay_clip_get_font_desc (GESTextOverlayClip * self) +{ + return self->priv->font_desc; +} + +/** + * ges_text_overlay_clip_get_halignment: + * @self: a #GESTextOverlayClip + * + * Get the horizontal aligment used by @self. + * + * Returns: The horizontal aligment used by @self. + */ +GESTextHAlign +ges_text_overlay_clip_get_halignment (GESTextOverlayClip * self) +{ + return self->priv->halign; +} + +/** + * ges_text_overlay_clip_get_valignment: + * @self: a #GESTextOverlayClip + * + * Get the vertical aligment used by @self. + * + * Returns: The vertical aligment used by @self. + */ +GESTextVAlign +ges_text_overlay_clip_get_valignment (GESTextOverlayClip * self) +{ + return self->priv->valign; +} + +/** + * ges_text_overlay_clip_get_color: + * @self: a #GESTextOverlayClip + * + * Get the color used by @source. + * + * Returns: The color used by @source. + */ + +const guint32 +ges_text_overlay_clip_get_color (GESTextOverlayClip * self) +{ + return self->priv->color; +} + +/** + * ges_text_overlay_clip_get_xpos: + * @self: a #GESTextOverlayClip + * + * Get the horizontal position used by @source. + * + * Returns: The horizontal position used by @source. + */ + +const gdouble +ges_text_overlay_clip_get_xpos (GESTextOverlayClip * self) +{ + return self->priv->xpos; +} + +/** + * ges_text_overlay_clip_get_ypos: + * @self: a #GESTextOverlayClip + * + * Get the vertical position used by @source. + * + * Returns: The vertical position used by @source. + */ + +const gdouble +ges_text_overlay_clip_get_ypos (GESTextOverlayClip * self) +{ + return self->priv->ypos; +} + +static GESTrackElement * +ges_text_overlay_clip_create_track_element (GESClip * clip, GESTrackType type) +{ + + GESTextOverlayClipPrivate *priv = GES_OVERLAY_TEXT_CLIP (clip)->priv; + GESTrackElement *res = NULL; + + GST_DEBUG ("Creating a GESTrackOverlay"); + + if (type == GES_TRACK_TYPE_VIDEO) { + res = (GESTrackElement *) ges_text_overlay_new (); + GST_DEBUG ("Setting text property"); + ges_text_overlay_set_text ((GESTextOverlay *) res, priv->text); + ges_text_overlay_set_font_desc ((GESTextOverlay *) res, priv->font_desc); + ges_text_overlay_set_halignment ((GESTextOverlay *) res, priv->halign); + ges_text_overlay_set_valignment ((GESTextOverlay *) res, priv->valign); + ges_text_overlay_set_color ((GESTextOverlay *) res, priv->color); + ges_text_overlay_set_xpos ((GESTextOverlay *) res, priv->xpos); + ges_text_overlay_set_ypos ((GESTextOverlay *) res, priv->ypos); + } + + return res; +} + +/** + * ges_text_overlay_clip_new: + * + * Creates a new #GESTextOverlayClip + * + * Returns: (transfer floating) (nullable): The newly created + * #GESTextOverlayClip, or %NULL if there was an error. + */ +GESTextOverlayClip * +ges_text_overlay_clip_new (void) +{ + GESTextOverlayClip *new_clip; + GESAsset *asset = ges_asset_request (GES_TYPE_OVERLAY_TEXT_CLIP, NULL, NULL); + + new_clip = GES_OVERLAY_TEXT_CLIP (ges_asset_extract (asset, NULL)); + gst_object_unref (asset); + + return new_clip; +} diff --git a/ges/ges-text-overlay-clip.h b/ges/ges-text-overlay-clip.h new file mode 100644 index 0000000000..d12c165c2d --- /dev/null +++ b/ges/ges-text-overlay-clip.h @@ -0,0 +1,114 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> +#include <ges/ges-overlay-clip.h> +#include <ges/ges-track.h> + +G_BEGIN_DECLS +#define GES_TYPE_OVERLAY_TEXT_CLIP ges_text_overlay_clip_get_type() +GES_DECLARE_TYPE(TextOverlayClip, text_overlay_clip, OVERLAY_TEXT_CLIP); + +/** + * GESTextOverlayClip: + */ + +struct _GESTextOverlayClip +{ + GESOverlayClip parent; + + /*< private > */ + GESTextOverlayClipPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESTextOverlayClipClass: + */ + +struct _GESTextOverlayClipClass +{ + /*< private > */ + + GESOverlayClipClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API void +ges_text_overlay_clip_set_text (GESTextOverlayClip * self, + const gchar * text); + +GES_API void +ges_text_overlay_clip_set_font_desc (GESTextOverlayClip * self, + const gchar * font_desc); + +GES_API void +ges_text_overlay_clip_set_valign (GESTextOverlayClip * self, + GESTextVAlign valign); + +GES_API void +ges_text_overlay_clip_set_halign (GESTextOverlayClip * self, + GESTextHAlign halign); + +GES_API void +ges_text_overlay_clip_set_color (GESTextOverlayClip * self, + guint32 color); + +GES_API void +ges_text_overlay_clip_set_xpos (GESTextOverlayClip * self, + gdouble position); + +GES_API void +ges_text_overlay_clip_set_ypos (GESTextOverlayClip * self, + gdouble position); + +GES_API +const gchar *ges_text_overlay_clip_get_text (GESTextOverlayClip * self); + +GES_API +const gchar *ges_text_overlay_clip_get_font_desc (GESTextOverlayClip * + self); + +GES_API GESTextVAlign +ges_text_overlay_clip_get_valignment (GESTextOverlayClip * self); + +GES_API const guint32 +ges_text_overlay_clip_get_color (GESTextOverlayClip * self); + +GES_API const gdouble +ges_text_overlay_clip_get_xpos (GESTextOverlayClip * self); + +GES_API const gdouble +ges_text_overlay_clip_get_ypos (GESTextOverlayClip * self); + +GES_API GESTextHAlign +ges_text_overlay_clip_get_halignment (GESTextOverlayClip * self); + +GES_API +GESTextOverlayClip *ges_text_overlay_clip_new (void); + +G_END_DECLS diff --git a/ges/ges-text-overlay.c b/ges/ges-text-overlay.c new file mode 100644 index 0000000000..2c95fa358f --- /dev/null +++ b/ges/ges-text-overlay.c @@ -0,0 +1,447 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gestextoverlay + * @title: GESTextOverlay + * @short_description: render text onto another video stream in a GESLayer + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-track-element.h" +#include "ges-title-source.h" +#include "ges-text-overlay.h" + +struct _GESTextOverlayPrivate +{ + gchar *text; + gchar *font_desc; + GESTextHAlign halign; + GESTextVAlign valign; + guint32 color; + gdouble xpos; + gdouble ypos; + GstElement *text_el; +}; + +enum +{ + PROP_0, +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GESTextOverlay, ges_text_overlay, + GES_TYPE_OPERATION); + +static void ges_text_overlay_dispose (GObject * object); + +static void ges_text_overlay_finalize (GObject * object); + +static void ges_text_overlay_get_property (GObject * object, guint + property_id, GValue * value, GParamSpec * pspec); + +static void ges_text_overlay_set_property (GObject * object, guint + property_id, const GValue * value, GParamSpec * pspec); + +static GstElement *ges_text_overlay_create_element (GESTrackElement * self); + +static void +ges_text_overlay_class_init (GESTextOverlayClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESTrackElementClass *track_element_class = GES_TRACK_ELEMENT_CLASS (klass); + + object_class->get_property = ges_text_overlay_get_property; + object_class->set_property = ges_text_overlay_set_property; + object_class->dispose = ges_text_overlay_dispose; + object_class->finalize = ges_text_overlay_finalize; + + track_element_class->create_element = ges_text_overlay_create_element; + track_element_class->ABI.abi.default_track_type = GES_TRACK_TYPE_VIDEO; +} + +static void +ges_text_overlay_init (GESTextOverlay * self) +{ + self->priv = ges_text_overlay_get_instance_private (self); + + self->priv->text = NULL; + self->priv->font_desc = NULL; + self->priv->text_el = NULL; + self->priv->halign = DEFAULT_HALIGNMENT; + self->priv->valign = DEFAULT_VALIGNMENT; + self->priv->color = G_MAXUINT32; + self->priv->xpos = 0.5; + self->priv->ypos = 0.5; +} + +static void +ges_text_overlay_dispose (GObject * object) +{ + GESTextOverlay *self = GES_TEXT_OVERLAY (object); + if (self->priv->text) { + g_free (self->priv->text); + } + + if (self->priv->font_desc) { + g_free (self->priv->font_desc); + } + + if (self->priv->text_el) { + gst_object_unref (self->priv->text_el); + self->priv->text_el = NULL; + } + + G_OBJECT_CLASS (ges_text_overlay_parent_class)->dispose (object); +} + +static void +ges_text_overlay_finalize (GObject * object) +{ + G_OBJECT_CLASS (ges_text_overlay_parent_class)->finalize (object); +} + +static void +ges_text_overlay_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_text_overlay_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static GstElement * +ges_text_overlay_create_element (GESTrackElement * track_element) +{ + GstElement *ret, *text, *iconv, *oconv; + GstPad *src_target, *sink_target; + GstPad *src, *sink; + GESTextOverlay *self = GES_TEXT_OVERLAY (track_element); + const gchar *child_props[] = + { "xpos", "ypos", "deltax", "deltay", "auto-resize", "outline-color", + NULL + }; + + text = gst_element_factory_make ("textoverlay", NULL); + iconv = gst_element_factory_make ("videoconvert", NULL); + oconv = gst_element_factory_make ("videoconvert", NULL); + self->priv->text_el = text; + gst_object_ref (text); + + if (self->priv->text) + g_object_set (text, "text", (gchar *) self->priv->text, NULL); + if (self->priv->font_desc) + g_object_set (text, "font-desc", (gchar *) self->priv->font_desc, NULL); + + g_object_set (text, "halignment", (gint) self->priv->halign, "valignment", + (gint) self->priv->valign, NULL); + g_object_set (text, "color", (guint) self->priv->color, NULL); + g_object_set (text, "xpos", (gdouble) self->priv->xpos, NULL); + g_object_set (text, "ypos", (gdouble) self->priv->ypos, NULL); + + ges_track_element_add_children_props (track_element, text, NULL, NULL, + child_props); + + ret = gst_bin_new ("overlay-bin"); + gst_bin_add_many (GST_BIN (ret), text, iconv, oconv, NULL); + gst_element_link_many (iconv, text, oconv, NULL); + + src_target = gst_element_get_static_pad (oconv, "src"); + sink_target = gst_element_get_static_pad (iconv, "sink"); + + src = gst_ghost_pad_new ("src", src_target); + sink = gst_ghost_pad_new ("video_sink", sink_target); + gst_object_unref (src_target); + gst_object_unref (sink_target); + + gst_element_add_pad (ret, src); + gst_element_add_pad (ret, sink); + + return ret; +} + +/** + * ges_text_overlay_set_text: + * @self: the #GESTextOverlay* to set text on + * @text: the text to render. an internal copy of this text will be + * made. + * + * Sets the text this track element will render. + * + */ +void +ges_text_overlay_set_text (GESTextOverlay * self, const gchar * text) +{ + GST_DEBUG ("self:%p, text:%s", self, text); + + if (self->priv->text) + g_free (self->priv->text); + + self->priv->text = g_strdup (text); + if (self->priv->text_el) + g_object_set (self->priv->text_el, "text", text, NULL); +} + +/** + * ges_text_overlay_set_font_desc: + * @self: the #GESTextOverlay + * @font_desc: the pango font description + * + * Sets the pango font description of the text this track element + * will render. + * + */ +void +ges_text_overlay_set_font_desc (GESTextOverlay * self, const gchar * font_desc) +{ + GST_DEBUG ("self:%p, font_desc:%s", self, font_desc); + + if (self->priv->font_desc) + g_free (self->priv->font_desc); + + self->priv->font_desc = g_strdup (font_desc); + GST_LOG ("setting font-desc to '%s'", font_desc); + if (self->priv->text_el) + g_object_set (self->priv->text_el, "font-desc", font_desc, NULL); +} + +/** + * ges_text_overlay_set_valignment: + * @self: the #GESTextOverlay* to set text on + * @valign: The #GESTextVAlign defining the vertical alignment + * of the text render by @self. + * + * Sets the vertical aligment of the text. + * + */ +void +ges_text_overlay_set_valignment (GESTextOverlay * self, GESTextVAlign valign) +{ + GST_DEBUG ("self:%p, halign:%d", self, valign); + + self->priv->valign = valign; + if (self->priv->text_el) + g_object_set (self->priv->text_el, "valignment", valign, NULL); +} + +/** + * ges_text_overlay_set_halignment: + * @self: the #GESTextOverlay* to set text on + * @halign: The #GESTextHAlign defining the horizontal alignment + * of the text render by @self. + * + * Sets the horizontal aligment of the text. + * + */ +void +ges_text_overlay_set_halignment (GESTextOverlay * self, GESTextHAlign halign) +{ + GST_DEBUG ("self:%p, halign:%d", self, halign); + + self->priv->halign = halign; + if (self->priv->text_el) + g_object_set (self->priv->text_el, "halignment", halign, NULL); +} + +/** + * ges_text_overlay_set_color: + * @self: the #GESTextOverlay* to set + * @color: The color @self is being set to + * + * Sets the color of the text. + */ +void +ges_text_overlay_set_color (GESTextOverlay * self, guint32 color) +{ + GST_DEBUG ("self:%p, color:%d", self, color); + + self->priv->color = color; + if (self->priv->text_el) + g_object_set (self->priv->text_el, "color", color, NULL); +} + +/** + * ges_text_overlay_set_xpos: + * @self: the #GESTextOverlay* to set + * @position: The horizontal position @self is being set to + * + * Sets the horizontal position of the text. + */ +void +ges_text_overlay_set_xpos (GESTextOverlay * self, gdouble position) +{ + GST_DEBUG ("self:%p, xpos:%f", self, position); + + self->priv->xpos = position; + if (self->priv->text_el) + g_object_set (self->priv->text_el, "xpos", position, NULL); +} + +/** + * ges_text_overlay_set_ypos: + * @self: the #GESTextOverlay* to set + * @position: The vertical position @self is being set to + * + * Sets the vertical position of the text. + */ +void +ges_text_overlay_set_ypos (GESTextOverlay * self, gdouble position) +{ + GST_DEBUG ("self:%p, ypos:%f", self, position); + + self->priv->ypos = position; + if (self->priv->text_el) + g_object_set (self->priv->text_el, "ypos", position, NULL); +} + +/** + * ges_text_overlay_get_text: + * @self: a GESTextOverlay + * + * Get the text currently set on @source. + * + * Returns: The text currently set on @source. + */ +const gchar * +ges_text_overlay_get_text (GESTextOverlay * self) +{ + return self->priv->text; +} + +/** + * ges_text_overlay_get_font_desc: + * @self: a GESTextOverlay + * + * Get the pango font description currently set on @source. + * + * Returns: The pango font description currently set on @source. + */ +const char * +ges_text_overlay_get_font_desc (GESTextOverlay * self) +{ + return self->priv->font_desc; +} + +/** + * ges_text_overlay_get_halignment: + * @self: a GESTextOverlay + * + * Get the horizontal aligment used by @source. + * + * Returns: The horizontal aligment used by @source. + */ +GESTextHAlign +ges_text_overlay_get_halignment (GESTextOverlay * self) +{ + return self->priv->halign; +} + +/** + * ges_text_overlay_get_valignment: + * @self: a GESTextOverlay + * + * Get the vertical aligment used by @source. + * + * Returns: The vertical aligment used by @source. + */ +GESTextVAlign +ges_text_overlay_get_valignment (GESTextOverlay * self) +{ + return self->priv->valign; +} + +/** + * ges_text_overlay_get_color: + * @self: a GESTextOverlay + * + * Get the color used by @source. + * + * Returns: The color used by @source. + */ +const guint32 +ges_text_overlay_get_color (GESTextOverlay * self) +{ + return self->priv->color; +} + +/** + * ges_text_overlay_get_xpos: + * @self: a GESTextOverlay + * + * Get the horizontal position used by @source. + * + * Returns: The horizontal position used by @source. + */ +const gdouble +ges_text_overlay_get_xpos (GESTextOverlay * self) +{ + return self->priv->xpos; +} + +/** + * ges_text_overlay_get_ypos: + * @self: a GESTextOverlay + * + * Get the vertical position used by @source. + * + * Returns: The vertical position used by @source. + */ +const gdouble +ges_text_overlay_get_ypos (GESTextOverlay * self) +{ + return self->priv->ypos; +} + +/** + * ges_text_overlay_new: + * + * Creates a new #GESTextOverlay. + * + * Returns: (transfer floating) (nullable): The newly created #GESTextOverlay or + * %NULL if something went wrong. + * + * Deprecated: 1.18: This should never be called by applications as this will + * be created by clips. + */ +GESTextOverlay * +ges_text_overlay_new (void) +{ + GESTextOverlay *res; + GESAsset *asset = ges_asset_request (GES_TYPE_TEXT_OVERLAY, NULL, NULL); + + res = GES_TEXT_OVERLAY (ges_asset_extract (asset, NULL)); + gst_object_unref (asset); + + return res; +} diff --git a/ges/ges-text-overlay.h b/ges/ges-text-overlay.h new file mode 100644 index 0000000000..559c53da87 --- /dev/null +++ b/ges/ges-text-overlay.h @@ -0,0 +1,100 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> +#include <ges/ges-title-source.h> +#include <ges/ges-operation.h> + +G_BEGIN_DECLS +#define GES_TYPE_TEXT_OVERLAY ges_text_overlay_get_type() +GES_DECLARE_TYPE(TextOverlay, text_overlay, TEXT_OVERLAY); + +/** + * GESTextOverlay: + */ +struct _GESTextOverlay +{ + GESOperation parent; + + /*< private > */ + GESTextOverlayPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESTextOverlayClass +{ + GESOperationClass parent_class; + + /*< private > */ + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API +void ges_text_overlay_set_text (GESTextOverlay * self, + const gchar * text); +GES_API +void ges_text_overlay_set_font_desc (GESTextOverlay * self, + const gchar * font_desc); + +GES_API +void ges_text_overlay_set_halignment (GESTextOverlay * self, + GESTextHAlign halign); + +GES_API +void ges_text_overlay_set_valignment (GESTextOverlay * self, + GESTextVAlign valign); +GES_API +void ges_text_overlay_set_color (GESTextOverlay * self, + guint32 color); +GES_API +void ges_text_overlay_set_xpos (GESTextOverlay * self, + gdouble position); +GES_API +void ges_text_overlay_set_ypos (GESTextOverlay * self, + gdouble position); + +GES_DEPRECATED +GESTextOverlay *ges_text_overlay_new (void); + +GES_API +const gchar *ges_text_overlay_get_text (GESTextOverlay * self); +GES_API +const char *ges_text_overlay_get_font_desc (GESTextOverlay * self); +GES_API +GESTextHAlign ges_text_overlay_get_halignment (GESTextOverlay * + self); +GES_API +GESTextVAlign ges_text_overlay_get_valignment (GESTextOverlay * + self); +GES_API +const guint32 ges_text_overlay_get_color (GESTextOverlay * self); +GES_API +const gdouble ges_text_overlay_get_xpos (GESTextOverlay * self); +GES_API +const gdouble ges_text_overlay_get_ypos (GESTextOverlay * self); + +G_END_DECLS diff --git a/ges/ges-time-overlay-clip.c b/ges/ges-time-overlay-clip.c new file mode 100644 index 0000000000..aad769c120 --- /dev/null +++ b/ges/ges-time-overlay-clip.c @@ -0,0 +1,54 @@ +/** + * SECTION:gestimeoverlayclip + * @title: GESTimeOverlayClip + * @short_description: Source with a time overlay on top + * @symbols: + * - ges_source_clip_new_time_overlay + * + * A #GESSourceClip that overlays timing information on top. + * + * ## Asset + * + * The default asset ID is "time-overlay" (of type #GES_TYPE_SOURCE_CLIP), + * but the framerate and video size can be overridden using an ID of the form: + * + * ``` + * time-overlay, framerate=60/1, width=1920, height=1080, max-duration=5.0 + * ``` + * + * ## Children properties + * + * {{ libs/GESTimeOverlayClip-children-props.md }} + * + * ## Symbols + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-asset.h" +#include "ges-time-overlay-clip.h" + + +/** + * ges_source_clip_new_time_overlay: + * + * Creates a new #GESSourceClip that renders a time overlay on top + * + * Returns: (transfer floating) (nullable): The newly created #GESSourceClip, + * or %NULL if there was an error. + * Since: 1.18 + */ +GESSourceClip * +ges_source_clip_new_time_overlay (void) +{ + GESSourceClip *new_clip; + GESAsset *asset = ges_asset_request (GES_TYPE_SOURCE_CLIP, + "time-overlay", NULL); + + new_clip = GES_SOURCE_CLIP (ges_asset_extract (asset, NULL)); + gst_object_unref (asset); + + return new_clip; +} diff --git a/ges/ges-time-overlay-clip.h b/ges/ges-time-overlay-clip.h new file mode 100644 index 0000000000..7f0de147f2 --- /dev/null +++ b/ges/ges-time-overlay-clip.h @@ -0,0 +1,30 @@ +/* GStreamer Editing Services + * Copyright (C) 2020 Igalia S.L + * Author: 2020 Thibault Saunier <tsaunier@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "ges-source-clip.h" + +G_BEGIN_DECLS + +GES_API +GESSourceClip* ges_source_clip_new_time_overlay (void); + +G_END_DECLS \ No newline at end of file diff --git a/ges/ges-timeline-element.c b/ges/ges-timeline-element.c new file mode 100644 index 0000000000..9341d850fe --- /dev/null +++ b/ges/ges-timeline-element.c @@ -0,0 +1,2500 @@ +/* gst-editing-services + * Copyright (C) <2013> Thibault Saunier <thibault.saunier@collabora.com> + * <2013> Collabora Ltd. + * + * gst-editing-services is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * gst-editing-services is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * SECTION:gestimelineelement + * @title: GESTimelineElement + * @short_description: Base Class for all elements with some temporal extent + * within a #GESTimeline. + * + * A #GESTimelineElement will have some temporal extent in its + * corresponding #GESTimelineElement:timeline, controlled by its + * #GESTimelineElement:start and #GESTimelineElement:duration. This + * determines when its content will be displayed, or its effect applied, + * in the timeline. Several objects may overlap within a given + * #GESTimeline, in which case their #GESTimelineElement:priority is used + * to determine their ordering in the timeline. Priority is mostly handled + * internally by #GESLayer-s and #GESClip-s. + * + * A timeline element can have a #GESTimelineElement:parent, + * such as a #GESClip, which is responsible for controlling its timing. + * + * ## Editing + * + * Elements can be moved around in their #GESTimelineElement:timeline by + * setting their #GESTimelineElement:start and + * #GESTimelineElement:duration using ges_timeline_element_set_start() + * and ges_timeline_element_set_duration(). Additionally, which parts of + * the underlying content are played in the timeline can be adjusted by + * setting the #GESTimelineElement:in-point using + * ges_timeline_element_set_inpoint(). The library also provides + * ges_timeline_element_edit(), with various #GESEditMode-s, which can + * adjust these properties in a convenient way, as well as introduce + * similar changes in neighbouring or later elements in the timeline. + * + * However, a timeline may refuse a change in these properties if they + * would place the timeline in an unsupported configuration. See + * #GESTimeline for its overlap rules. + * + * Additionally, an edit may be refused if it would place one of the + * timing properties out of bounds (such as a negative time value for + * #GESTimelineElement:start, or having insufficient internal + * content to last for the desired #GESTimelineElement:duration). + * + * ## Time Coordinates + * + * There are three main sets of time coordinates to consider when using + * timeline elements: + * + * + Timeline coordinates: these are the time coordinates used in the + * output of the timeline in its #GESTrack-s. Each track share the same + * coordinates, so there is only one set of coordinates for the + * timeline. These extend indefinitely from 0. The times used for + * editing (including setting #GESTimelineElement:start and + * #GESTimelineElement:duration) use these coordinates, since these + * define when an element is present and for how long the element lasts + * for in the timeline. + * + Internal source coordinates: these are the time coordinates used + * internally at the element's output. This is only really defined for + * #GESTrackElement-s, where it refers to time coordinates used at the + * final source pad of the wrapped #GstElement-s. However, these + * coordinates may also be used in a #GESClip in reference to its + * children. In particular, these are the coordinates used for + * #GESTimelineElement:in-point and #GESTimelineElement:max-duration. + * + Internal sink coordinates: these are the time coordinates used + * internally at the element's input. A #GESSource has no input, so + * these would be undefined. Otherwise, for most #GESTrackElement-s + * these will be the same set of coordinates as the internal source + * coordinates because the element does not change the timing + * internally. Only #GESBaseEffect can support elements where these + * are different. See #GESBaseEffect for more information. + * + * You can determine the timeline time for a given internal source time + * in a #GESTrack in a #GESClip using + * ges_clip_get_timeline_time_from_internal_time(), and vice versa using + * ges_clip_get_internal_time_from_timeline_time(), for the purposes of + * editing and setting timings properties. + * + * ## Children Properties + * + * If a timeline element owns another #GstObject and wishes to expose + * some of its properties, it can do so by registering the property as one + * of the timeline element's children properties using + * ges_timeline_element_add_child_property(). The registered property of + * the child can then be read and set using the + * ges_timeline_element_get_child_property() and + * ges_timeline_element_set_child_property() methods, respectively. Some + * sub-classed objects will be created with pre-registered children + * properties; for example, to expose part of an underlying #GstElement + * that is used internally. The registered properties can be listed with + * ges_timeline_element_list_children_properties(). + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-utils.h" +#include "ges-timeline-element.h" +#include "ges-extractable.h" +#include "ges-meta-container.h" +#include "ges-internal.h" +#include "ges-effect.h" + +#include <string.h> +#include <gobject/gvaluecollector.h> + +/* maps type name quark => count */ +static GData *object_name_counts = NULL; + +static void +extractable_set_asset (GESExtractable * extractable, GESAsset * asset) +{ + GES_TIMELINE_ELEMENT (extractable)->asset = asset; +} + +static void +ges_extractable_interface_init (GESExtractableInterface * iface) +{ + iface->set_asset = extractable_set_asset; +} + +enum +{ + PROP_0, + PROP_PARENT, + PROP_TIMELINE, + PROP_START, + PROP_INPOINT, + PROP_DURATION, + PROP_MAX_DURATION, + PROP_PRIORITY, + PROP_NAME, + PROP_SERIALIZE, + PROP_LAST +}; + +enum +{ + DEEP_NOTIFY, + CHILD_PROPERTY_ADDED, + CHILD_PROPERTY_REMOVED, + LAST_SIGNAL +}; + +static guint ges_timeline_element_signals[LAST_SIGNAL] = { 0 }; + +static GParamSpec *properties[PROP_LAST] = { NULL, }; + +typedef struct +{ + GObject *child; + GESTimelineElement *owner; + gulong handler_id; + GESTimelineElement *self; +} ChildPropHandler; + +struct _GESTimelineElementPrivate +{ + gboolean serialize; + + /* We keep a link between properties name and elements internally + * The hashtable should look like + * {GParamaSpec ---> child}*/ + GHashTable *children_props; + + GESTimelineElement *copied_from; + + GESTimelineElementFlags flags; +}; + +typedef struct +{ + GObject *child; + GParamSpec *arg; + GESTimelineElement *self; +} EmitDeepNotifyInIdleData; + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESTimelineElement, ges_timeline_element, + G_TYPE_INITIALLY_UNOWNED, G_ADD_PRIVATE (GESTimelineElement) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, ges_extractable_interface_init) + G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, NULL)); + +/********************************************* + * Virtual methods implementation * + *********************************************/ +static void +_set_child_property (GESTimelineElement * self G_GNUC_UNUSED, GObject * child, + GParamSpec * pspec, GValue * value) +{ + if (G_VALUE_TYPE (value) != pspec->value_type + && G_VALUE_TYPE (value) == G_TYPE_STRING) + gst_util_set_object_arg (child, pspec->name, g_value_get_string (value)); + else + g_object_set_property (child, pspec->name, value); +} + +static gboolean +_set_child_property_full (GESTimelineElement * self, GObject * child, + GParamSpec * pspec, const GValue * value, GError ** error) +{ + GES_TIMELINE_ELEMENT_GET_CLASS (self)->set_child_property (self, child, + pspec, (GValue *) value); + return TRUE; +} + +static gboolean +_lookup_child (GESTimelineElement * self, const gchar * prop_name, + GObject ** child, GParamSpec ** pspec) +{ + GHashTableIter iter; + gpointer key, value; + gchar **names, *name, *classename; + gboolean res; + + classename = NULL; + res = FALSE; + + names = g_strsplit (prop_name, "::", 2); + if (names[1] != NULL) { + classename = names[0]; + name = names[1]; + } else + name = names[0]; + + g_hash_table_iter_init (&iter, self->priv->children_props); + while (g_hash_table_iter_next (&iter, &key, &value)) { + if (g_strcmp0 (G_PARAM_SPEC (key)->name, name) == 0) { + ChildPropHandler *handler = (ChildPropHandler *) value; + if (classename == NULL || + g_strcmp0 (G_OBJECT_TYPE_NAME (G_OBJECT (handler->child)), + classename) == 0 || + g_strcmp0 (g_type_name (G_PARAM_SPEC (key)->owner_type), + classename) == 0) { + GST_DEBUG_OBJECT (self, "The %s property from %s has been found", name, + classename); + if (child) + *child = gst_object_ref (handler->child); + + if (pspec) + *pspec = g_param_spec_ref (key); + res = TRUE; + break; + } + } + } + g_strfreev (names); + + return res; +} + +GParamSpec ** +ges_timeline_element_get_children_properties (GESTimelineElement * self, + guint * n_properties) +{ + GParamSpec **pspec, *spec; + GHashTableIter iter; + gpointer key, value; + + guint i = 0; + + *n_properties = g_hash_table_size (self->priv->children_props); + pspec = g_new (GParamSpec *, *n_properties); + + g_hash_table_iter_init (&iter, self->priv->children_props); + while (g_hash_table_iter_next (&iter, &key, &value)) { + spec = G_PARAM_SPEC (key); + pspec[i] = g_param_spec_ref (spec); + i++; + } + + return pspec; +} + +static void +_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESTimelineElement *self = GES_TIMELINE_ELEMENT (object); + + switch (property_id) { + case PROP_PARENT: + g_value_take_object (value, self->parent); + break; + case PROP_TIMELINE: + g_value_take_object (value, self->timeline); + break; + case PROP_START: + g_value_set_uint64 (value, self->start); + break; + case PROP_INPOINT: + g_value_set_uint64 (value, self->inpoint); + break; + case PROP_DURATION: + g_value_set_uint64 (value, self->duration); + break; + case PROP_MAX_DURATION: + g_value_set_uint64 (value, self->maxduration); + break; + case PROP_PRIORITY: + g_value_set_uint (value, self->priority); + break; + case PROP_NAME: + g_value_take_string (value, ges_timeline_element_get_name (self)); + break; + case PROP_SERIALIZE: + g_value_set_boolean (value, self->priv->serialize); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec); + } +} + +static void +_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESTimelineElement *self = GES_TIMELINE_ELEMENT (object); + + switch (property_id) { + case PROP_PARENT: + ges_timeline_element_set_parent (self, g_value_get_object (value)); + break; + case PROP_TIMELINE: + ges_timeline_element_set_timeline (self, g_value_get_object (value)); + break; + case PROP_START: + ges_timeline_element_set_start (self, g_value_get_uint64 (value)); + break; + case PROP_INPOINT: + ges_timeline_element_set_inpoint (self, g_value_get_uint64 (value)); + break; + case PROP_DURATION: + ges_timeline_element_set_duration (self, g_value_get_uint64 (value)); + break; + case PROP_PRIORITY: + ges_timeline_element_set_priority (self, g_value_get_uint (value)); + break; + case PROP_MAX_DURATION: + ges_timeline_element_set_max_duration (self, g_value_get_uint64 (value)); + break; + case PROP_NAME: + ges_timeline_element_set_name (self, g_value_get_string (value)); + break; + case PROP_SERIALIZE: + self->priv->serialize = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec); + } +} + +static void +ges_timeline_element_dispose (GObject * object) +{ + GESTimelineElement *self = GES_TIMELINE_ELEMENT (object); + + if (self->priv->children_props) { + g_hash_table_unref (self->priv->children_props); + self->priv->children_props = NULL; + } + + g_clear_object (&self->priv->copied_from); + + G_OBJECT_CLASS (ges_timeline_element_parent_class)->dispose (object); +} + +static void +ges_timeline_element_finalize (GObject * self) +{ + GESTimelineElement *tle = GES_TIMELINE_ELEMENT (self); + + g_free (tle->name); + + G_OBJECT_CLASS (ges_timeline_element_parent_class)->finalize (self); +} + +static void +_child_prop_handler_free (ChildPropHandler * handler) +{ + g_object_freeze_notify (handler->child); + if (handler->handler_id) + g_signal_handler_disconnect (handler->child, handler->handler_id); + g_object_thaw_notify (handler->child); + + if (handler->child != (GObject *) handler->self && + handler->child != (GObject *) handler->owner) + gst_object_unref (handler->child); + g_slice_free (ChildPropHandler, handler); +} + +static gboolean +_get_natural_framerate (GESTimelineElement * self, gint * framerate_n, + gint * framerate_d) +{ + GST_INFO_OBJECT (self, "No natural framerate"); + + return FALSE; +} + +static void +ges_timeline_element_init (GESTimelineElement * self) +{ + self->priv = ges_timeline_element_get_instance_private (self); + + self->priv->serialize = TRUE; + + self->priv->children_props = + g_hash_table_new_full ((GHashFunc) ges_pspec_hash, ges_pspec_equal, + (GDestroyNotify) g_param_spec_unref, + (GDestroyNotify) _child_prop_handler_free); +} + +static void +ges_timeline_element_class_init (GESTimelineElementClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = _get_property; + object_class->set_property = _set_property; + + /** + * GESTimelineElement:parent: + * + * The parent container of the element. + */ + properties[PROP_PARENT] = + g_param_spec_object ("parent", "Parent", + "The parent container of the object", GES_TYPE_TIMELINE_ELEMENT, + G_PARAM_READWRITE); + + /** + * GESTimelineElement:timeline: + * + * The timeline that the element lies within. + */ + properties[PROP_TIMELINE] = + g_param_spec_object ("timeline", "Timeline", + "The timeline the object is in", GES_TYPE_TIMELINE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GESTimelineElement:start: + * + * The starting position of the element in the timeline (in nanoseconds + * and in the time coordinates of the timeline). For example, for a + * source element, this would determine the time at which it should + * start outputting its internal content. For an operation element, this + * would determine the time at which it should start applying its effect + * to any source content. + */ + properties[PROP_START] = g_param_spec_uint64 ("start", "Start", + "The position in the timeline", 0, G_MAXUINT64, 0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GESTimelineElement:in-point: + * + * The initial offset to use internally when outputting content (in + * nanoseconds, but in the time coordinates of the internal content). + * + * For example, for a #GESVideoUriSource that references some media + * file, the "internal content" is the media file data, and the + * in-point would correspond to some timestamp in the media file. + * When playing the timeline, and when the element is first reached at + * timeline-time #GESTimelineElement:start, it will begin outputting the + * data from the timestamp in-point **onwards**, until it reaches the + * end of its #GESTimelineElement:duration in the timeline. + * + * For elements that have no internal content, this should be kept + * as 0. + */ + properties[PROP_INPOINT] = + g_param_spec_uint64 ("in-point", "In-point", "The in-point", 0, + G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GESTimelineElement:duration: + * + * The duration that the element is in effect for in the timeline (a + * time difference in nanoseconds using the time coordinates of the + * timeline). For example, for a source element, this would determine + * for how long it should output its internal content for. For an + * operation element, this would determine for how long its effect + * should be applied to any source content. + */ + properties[PROP_DURATION] = + g_param_spec_uint64 ("duration", "Duration", "The play duration", 0, + G_MAXUINT64, GST_CLOCK_TIME_NONE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GESTimelineElement:max-duration: + * + * The full duration of internal content that is available (a time + * difference in nanoseconds using the time coordinates of the internal + * content). + * + * This will act as a cap on the #GESTimelineElement:in-point of the + * element (which is in the same time coordinates), and will sometimes + * be used to limit the #GESTimelineElement:duration of the element in + * the timeline. + * + * For example, for a #GESVideoUriSource that references some media + * file, this would be the length of the media file. + * + * For elements that have no internal content, or whose content is + * indefinite, this should be kept as #GST_CLOCK_TIME_NONE. + */ + properties[PROP_MAX_DURATION] = + g_param_spec_uint64 ("max-duration", "Maximum duration", + "The maximum duration of the object", 0, G_MAXUINT64, GST_CLOCK_TIME_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GESTimelineElement:priority: + * + * The priority of the element. + * + * Deprecated: 1.10: Priority management is now done by GES itself. + */ + properties[PROP_PRIORITY] = g_param_spec_uint ("priority", "Priority", + "The priority of the object", 0, G_MAXUINT, 0, G_PARAM_READWRITE); + + /** + * GESTimelineElement:name: + * + * The name of the element. This should be unique within its timeline. + */ + properties[PROP_NAME] = + g_param_spec_string ("name", "Name", "The name of the timeline object", + NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + /** + * GESTimelineElement:serialize: + * + * Whether the element should be serialized. + */ + properties[PROP_SERIALIZE] = g_param_spec_boolean ("serialize", "Serialize", + "Whether the element should be serialized", TRUE, + G_PARAM_READWRITE | GES_PARAM_NO_SERIALIZATION); + + g_object_class_install_properties (object_class, PROP_LAST, properties); + + /** + * GESTimelineElement::deep-notify: + * @timeline_element: A #GESTtimelineElement + * @prop_object: The child whose property has been set + * @prop: The specification for the property that been set + * + * Emitted when a child of the element has one of its registered + * properties set. See ges_timeline_element_add_child_property(). + * Note that unlike #GObject::notify, a child property name can not be + * used as a signal detail. + */ + ges_timeline_element_signals[DEEP_NOTIFY] = + g_signal_new ("deep-notify", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED | + G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_PARAM); + + /** + * GESTimelineElement::child-property-added: + * @timeline_element: A #GESTtimelineElement + * @prop_object: The child whose property has been registered + * @prop: The specification for the property that has been registered + * + * Emitted when the element has a new child property registered. See + * ges_timeline_element_add_child_property(). + * + * Note that some GES elements will be automatically created with + * pre-registered children properties. You can use + * ges_timeline_element_list_children_properties() to list these. + * + * Since: 1.18 + */ + ges_timeline_element_signals[CHILD_PROPERTY_ADDED] = + g_signal_new ("child-property-added", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, + G_TYPE_OBJECT, G_TYPE_PARAM); + + /** + * GESTimelineElement::child-property-removed: + * @timeline_element: A #GESTimelineElement + * @prop_object: The child whose property has been unregistered + * @prop: The specification for the property that has been unregistered + * + * Emitted when the element has a child property unregistered. See + * ges_timeline_element_remove_child_property(). + * + * Since: 1.18 + */ + ges_timeline_element_signals[CHILD_PROPERTY_REMOVED] = + g_signal_new ("child-property-removed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, + G_TYPE_OBJECT, G_TYPE_PARAM); + + + object_class->dispose = ges_timeline_element_dispose; + object_class->finalize = ges_timeline_element_finalize; + + klass->set_parent = NULL; + klass->set_start = NULL; + klass->set_inpoint = NULL; + klass->set_duration = NULL; + klass->set_max_duration = NULL; + klass->set_priority = NULL; + + klass->ripple = NULL; + klass->ripple_end = NULL; + klass->roll_start = NULL; + klass->roll_end = NULL; + klass->trim = NULL; + + klass->list_children_properties = + ges_timeline_element_get_children_properties; + klass->lookup_child = _lookup_child; + klass->set_child_property = _set_child_property; + klass->set_child_property_full = _set_child_property_full; + klass->get_natural_framerate = _get_natural_framerate; +} + +static void +_set_name (GESTimelineElement * self, const gchar * wanted_name) +{ + const gchar *type_name; + gchar *lowcase_type; + gint count; + GQuark q; + guint i, l; + gchar *name = NULL; + + if (!object_name_counts) { + g_datalist_init (&object_name_counts); + } + + q = g_type_qname (G_OBJECT_TYPE (self)); + count = GPOINTER_TO_INT (g_datalist_id_get_data (&object_name_counts, q)); + + /* GstFooSink -> foosink<N> */ + type_name = g_quark_to_string (q); + if (strncmp (type_name, "GES", 3) == 0) + type_name += 3; + + lowcase_type = g_strdup (type_name); + l = strlen (lowcase_type); + for (i = 0; i < l; i++) + lowcase_type[i] = g_ascii_tolower (lowcase_type[i]); + + if (wanted_name == NULL) { + /* give the 20th "uriclip" element and the first "uriclip2" (if needed in the future) + * different names */ + l = strlen (type_name); + if (l > 0 && g_ascii_isdigit (type_name[l - 1])) { + name = g_strdup_printf ("%s-%d", lowcase_type, count++); + } else { + name = g_strdup_printf ("%s%d", lowcase_type, count++); + } + } else { + /* If the wanted name uses the same 'namespace' as default, make + * sure it does not badly interfere with our counting system */ + + /* FIXME: should we really be allowing a user to set the name + * "uriclip1" for, say, a GESTransition? The below code *does not* + * capture this case (because the prefix does not match "transition"). + * If the user subsequently calls _set_name with name == NULL, on a + * GESClip *for the first time*, then the GES library will + * automatically choose the *same* name "uriclip1", but this is not + * unique! */ + if (g_str_has_prefix (wanted_name, lowcase_type)) { + guint64 tmpcount = + g_ascii_strtoull (&wanted_name[strlen (lowcase_type)], NULL, 10); + + if (tmpcount > count) { + count = tmpcount + 1; + GST_DEBUG_OBJECT (self, "Using same naming %s but updated count to %i", + wanted_name, count); + } else if (tmpcount < count) { + /* FIXME: this can unexpectedly change names given by the user + * E.g. if "transition2" already exists, and a user then wants to + * set a GESTransition to have the name "transition-custom" or + * "transition 1 too many" then tmpcount would in fact be 0 or 1, + * and the name would then be changed to "transition3"! */ + name = g_strdup_printf ("%s%d", lowcase_type, count); + count++; + GST_DEBUG_OBJECT (self, "Name %s already allocated, giving: %s instead" + " New count is %i", wanted_name, name, count); + } else { + count++; + GST_DEBUG_OBJECT (self, "Perfect name, just bumping object count"); + } + } + + if (name == NULL) + name = g_strdup (wanted_name); + } + + g_free (lowcase_type); + g_datalist_id_set_data (&object_name_counts, q, GINT_TO_POINTER (count)); + + g_free (self->name); + self->name = name; +} + +/********************************************* + * Internal and private helpers * + *********************************************/ + +GESTimelineElement * +ges_timeline_element_peak_toplevel (GESTimelineElement * self) +{ + GESTimelineElement *toplevel = self; + + while (toplevel->parent) + toplevel = toplevel->parent; + + return toplevel; +} + +GESTimelineElement * +ges_timeline_element_get_copied_from (GESTimelineElement * self) +{ + GESTimelineElement *copied_from = self->priv->copied_from; + self->priv->copied_from = NULL; + return copied_from; +} + +GESTimelineElementFlags +ges_timeline_element_flags (GESTimelineElement * self) +{ + return self->priv->flags; +} + +void +ges_timeline_element_set_flags (GESTimelineElement * self, + GESTimelineElementFlags flags) +{ + self->priv->flags = flags; + +} + +static gboolean +emit_deep_notify_in_idle (EmitDeepNotifyInIdleData * data) +{ + g_signal_emit (data->self, ges_timeline_element_signals[DEEP_NOTIFY], 0, + data->child, data->arg); + + gst_object_unref (data->child); + g_param_spec_unref (data->arg); + gst_object_unref (data->self); + g_slice_free (EmitDeepNotifyInIdleData, data); + + return FALSE; +} + +static void +child_prop_changed_cb (GObject * child, GParamSpec * arg, + GESTimelineElement * self) +{ + EmitDeepNotifyInIdleData *data; + + /* Emit "deep-notify" right away if in main thread */ + if (g_main_context_acquire (g_main_context_default ())) { + g_main_context_release (g_main_context_default ()); + g_signal_emit (self, ges_timeline_element_signals[DEEP_NOTIFY], 0, + child, arg); + return; + } + + data = g_slice_new (EmitDeepNotifyInIdleData); + + data->child = gst_object_ref (child); + data->arg = g_param_spec_ref (arg); + data->self = gst_object_ref (self); + + ges_idle_add ((GSourceFunc) emit_deep_notify_in_idle, data, NULL); +} + +static gboolean +set_child_property_by_pspec (GESTimelineElement * self, + GParamSpec * pspec, const GValue * value, GError ** error) +{ + GESTimelineElementClass *klass; + GESTimelineElement *setter = self; + ChildPropHandler *handler = + g_hash_table_lookup (self->priv->children_props, pspec); + + if (!handler) { + GST_ERROR_OBJECT (self, "The %s property doesn't exist", pspec->name); + return FALSE; + } + + if (handler->owner) { + klass = GES_TIMELINE_ELEMENT_GET_CLASS (handler->owner); + setter = handler->owner; + } else { + klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); + } + + if (klass->set_child_property_full) + return klass->set_child_property_full (setter, handler->child, pspec, + value, error); + + g_assert (klass->set_child_property); + klass->set_child_property (setter, handler->child, pspec, (GValue *) value); + + return TRUE; +} + +gboolean +ges_timeline_element_add_child_property_full (GESTimelineElement * self, + GESTimelineElement * owner, GParamSpec * pspec, GObject * child) +{ + gchar *signame; + ChildPropHandler *handler; + + /* FIXME: allow the same pspec, provided the child is different. This + * is important for containers that may have duplicate children + * If this is changed, _remove_childs_child_property in ges-container.c + * should be changed to reflect this. + * We could hack around this by copying the pspec into a new instance + * of GParamSpec, but there is no such GLib method, and it would break + * the usage of get_..._from_pspec and set_..._from_pspec */ + if (g_hash_table_contains (self->priv->children_props, pspec)) { + GST_INFO_OBJECT (self, "Child property already exists: %s", pspec->name); + return FALSE; + } + + GST_DEBUG_OBJECT (self, "Adding child property: %" GST_PTR_FORMAT "::%s", + child, pspec->name); + + signame = g_strconcat ("notify::", pspec->name, NULL); + handler = (ChildPropHandler *) g_slice_new0 (ChildPropHandler); + handler->self = self; + if (child == G_OBJECT (self) || child == G_OBJECT (owner)) + handler->child = child; + else + handler->child = gst_object_ref (child); + handler->owner = owner; + handler->handler_id = + g_signal_connect (child, signame, G_CALLBACK (child_prop_changed_cb), + self); + g_hash_table_insert (self->priv->children_props, g_param_spec_ref (pspec), + handler); + + g_signal_emit (self, ges_timeline_element_signals[CHILD_PROPERTY_ADDED], 0, + child, pspec); + + g_free (signame); + return TRUE; +} + +GObject * +ges_timeline_element_get_child_from_child_property (GESTimelineElement * self, + GParamSpec * pspec) +{ + ChildPropHandler *handler = + g_hash_table_lookup (self->priv->children_props, pspec); + if (handler) + return handler->child; + return NULL; +} + + +/********************************************* + * API implementation * + *********************************************/ + +/** + * ges_timeline_element_set_parent: + * @self: A #GESTimelineElement + * @parent (nullable): New parent of @self + * + * Sets the #GESTimelineElement:parent for the element. + * + * This is used internally and you should normally not call this. A + * #GESContainer will set the #GESTimelineElement:parent of its children + * in ges_container_add() and ges_container_remove(). + * + * Note, if @parent is not %NULL, @self must not already have a parent + * set. Therefore, if you wish to switch parents, you will need to call + * this function twice: first to set the parent to %NULL, and then to the + * new parent. + * + * If @parent is not %NULL, you must ensure it already has a + * (non-floating) reference to @self before calling this. + * + * Returns: %TRUE if @parent could be set for @self. + */ +gboolean +ges_timeline_element_set_parent (GESTimelineElement * self, + GESTimelineElement * parent) +{ + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + g_return_val_if_fail (parent == NULL + || GES_IS_TIMELINE_ELEMENT (parent), FALSE); + + if (self == parent) { + GST_INFO_OBJECT (self, "Trying to add %p in itself, not a good idea!", + self); + /* FIXME: why are we sinking and then unreffing self when we do not + * own it? */ + gst_object_ref_sink (self); + gst_object_unref (self); + return FALSE; + } + + GST_DEBUG_OBJECT (self, "set parent to %" GST_PTR_FORMAT, parent); + + if (self->parent != NULL && parent != NULL) + goto had_parent; + + if (GES_TIMELINE_ELEMENT_GET_CLASS (self)->set_parent) { + if (!GES_TIMELINE_ELEMENT_GET_CLASS (self)->set_parent (self, parent)) + return FALSE; + } + + self->parent = parent; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PARENT]); + return TRUE; + + /* ERROR handling */ +had_parent: + { + GST_WARNING_OBJECT (self, "set parent failed, object already had a parent"); + /* FIXME: why are we sinking and then unreffing self when we do not + * own it? */ + gst_object_ref_sink (self); + gst_object_unref (self); + return FALSE; + } +} + +/** + * ges_timeline_element_get_parent: + * @self: A #GESTimelineElement + * + * Gets the #GESTimelineElement:parent for the element. + * + * Returns: (transfer full) (nullable): The parent of @self, or %NULL if + * @self has no parent. + */ +GESTimelineElement * +ges_timeline_element_get_parent (GESTimelineElement * self) +{ + GESTimelineElement *result = NULL; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL); + + result = self->parent; + if (G_LIKELY (result)) + gst_object_ref (result); + + return result; +} + +/** + * ges_timeline_element_set_timeline: + * @self: A #GESTimelineElement + * @timeline (nullable): The #GESTimeline @self should be in + * + * Sets the #GESTimelineElement:timeline of the element. + * + * This is used internally and you should normally not call this. A + * #GESClip will have its #GESTimelineElement:timeline set through its + * #GESLayer. A #GESTrack will similarly take care of setting the + * #GESTimelineElement:timeline of its #GESTrackElement-s. A #GESGroup + * will adopt the same #GESTimelineElement:timeline as its children. + * + * If @timeline is %NULL, this will stop its current + * #GESTimelineElement:timeline from tracking it, otherwise @timeline will + * start tracking @self. Note, in the latter case, @self must not already + * have a timeline set. Therefore, if you wish to switch timelines, you + * will need to call this function twice: first to set the timeline to + * %NULL, and then to the new timeline. + * + * Returns: %TRUE if @timeline could be set for @self. + */ +gboolean +ges_timeline_element_set_timeline (GESTimelineElement * self, + GESTimeline * timeline) +{ + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + g_return_val_if_fail (timeline == NULL || GES_IS_TIMELINE (timeline), FALSE); + + GST_DEBUG_OBJECT (self, "set timeline to %" GST_PTR_FORMAT, timeline); + + if (self->timeline == timeline) + return TRUE; + + if (timeline != NULL && G_UNLIKELY (self->timeline != NULL)) + goto had_timeline; + + if (timeline == NULL) { + if (self->timeline) { + if (!timeline_remove_element (self->timeline, self)) { + GST_INFO_OBJECT (self, "Could not remove from" + " currently set timeline %" GST_PTR_FORMAT, self->timeline); + return FALSE; + } + } + } else { + if (!timeline_add_element (timeline, self)) { + GST_INFO_OBJECT (self, "Could not add to timeline %" GST_PTR_FORMAT, + self); + return FALSE; + } + } + + self->timeline = timeline; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIMELINE]); + return TRUE; + + /* ERROR handling */ +had_timeline: + { + GST_DEBUG_OBJECT (self, "set timeline failed, object already had a " + "timeline"); + return FALSE; + } +} + +/** + * ges_timeline_element_get_timeline: + * @self: A #GESTimelineElement + * + * Gets the #GESTimelineElement:timeline for the element. + * + * Returns: (transfer full) (nullable): The timeline of @self, or %NULL + * if @self has no timeline. + */ +GESTimeline * +ges_timeline_element_get_timeline (GESTimelineElement * self) +{ + GESTimeline *result = NULL; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL); + + result = self->timeline; + if (G_LIKELY (result)) + gst_object_ref (result); + + return result; +} + +/** + * ges_timeline_element_set_start: + * @self: A #GESTimelineElement + * @start: The desired start position of the element in its timeline + * + * Sets #GESTimelineElement:start for the element. If the element has a + * parent, this will also move its siblings with the same shift. + * + * Whilst the element is part of a #GESTimeline, this is the same as + * editing the element with ges_timeline_element_edit() under + * #GES_EDIT_MODE_NORMAL with #GES_EDGE_NONE. In particular, the + * #GESTimelineElement:start of the element may be snapped to a different + * timeline time from the one given. In addition, setting may fail if it + * would place the timeline in an unsupported configuration. + * + * Returns: %TRUE if @start could be set for @self. + */ +gboolean +ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start) +{ + GESTimelineElementClass *klass; + GESTimelineElement *toplevel_container, *parent; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start), FALSE); + + if (self->start == start) + return TRUE; + + GST_DEBUG_OBJECT (self, "current start: %" GST_TIME_FORMAT + " new start: %" GST_TIME_FORMAT, + GST_TIME_ARGS (GES_TIMELINE_ELEMENT_START (self)), GST_TIME_ARGS (start)); + + if (self->timeline && !GES_TIMELINE_ELEMENT_BEING_EDITED (self)) + return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, start); + + toplevel_container = ges_timeline_element_peak_toplevel (self); + parent = self->parent; + + /* FIXME This should not belong to GESTimelineElement */ + /* only check if no timeline, otherwise the timeline-tree will handle this + * check */ + if (!self->timeline && toplevel_container && + ((gint64) (_START (toplevel_container) + start - _START (self))) < 0 && + parent + && GES_CONTAINER (parent)->children_control_mode == GES_CHILDREN_UPDATE) { + GST_INFO_OBJECT (self, + "Can not move the object as it would imply its " + "container to have a negative start value"); + + return FALSE; + } + + klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); + if (klass->set_start) { + gint res = klass->set_start (self, start); + if (res == FALSE) + return FALSE; + if (res == TRUE) { + self->start = start; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_START]); + } + + GST_DEBUG_OBJECT (self, "New start: %" GST_TIME_FORMAT, + GST_TIME_ARGS (GES_TIMELINE_ELEMENT_START (self))); + + return TRUE; + } + + GST_WARNING_OBJECT (self, "No set_start virtual method implementation" + " on class %s. Can not set start %" GST_TIME_FORMAT, + G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (start)); + return FALSE; +} + +/** + * ges_timeline_element_set_inpoint: + * @self: A #GESTimelineElement + * @inpoint: The in-point, in internal time coordinates + * + * Sets #GESTimelineElement:in-point for the element. If the new in-point + * is above the current #GESTimelineElement:max-duration of the element, + * this method will fail. + * + * Returns: %TRUE if @inpoint could be set for @self. + */ +gboolean +ges_timeline_element_set_inpoint (GESTimelineElement * self, + GstClockTime inpoint) +{ + GESTimelineElementClass *klass; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + + GST_DEBUG_OBJECT (self, "current inpoint: %" GST_TIME_FORMAT + " new inpoint: %" GST_TIME_FORMAT, GST_TIME_ARGS (self->inpoint), + GST_TIME_ARGS (inpoint)); + + if (G_UNLIKELY (inpoint == self->inpoint)) + return TRUE; + + if (GES_CLOCK_TIME_IS_LESS (self->maxduration, inpoint)) { + GST_WARNING_OBJECT (self, "Can not set an in-point of %" GST_TIME_FORMAT + " because it exceeds the element's max-duration: %" GST_TIME_FORMAT, + GST_TIME_ARGS (inpoint), GST_TIME_ARGS (self->maxduration)); + return FALSE; + } + + klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); + + if (klass->set_inpoint) { + /* FIXME: Could we instead use g_object_freeze_notify() to prevent + * duplicate notify signals? Rather than relying on the return value + * being -1 for setting that succeeds but does not want a notify + * signal because it will call this method on itself a second time. */ + if (!klass->set_inpoint (self, inpoint)) + return FALSE; + + self->inpoint = inpoint; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INPOINT]); + + return TRUE; + } + + GST_DEBUG_OBJECT (self, "No set_inpoint virtual method implementation" + " on class %s. Can not set inpoint %" GST_TIME_FORMAT, + G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (inpoint)); + + return FALSE; +} + +/** + * ges_timeline_element_set_max_duration: + * @self: A #GESTimelineElement + * @maxduration: The maximum duration, in internal time coordinates + * + * Sets #GESTimelineElement:max-duration for the element. If the new + * maximum duration is below the current #GESTimelineElement:in-point of + * the element, this method will fail. + * + * Returns: %TRUE if @maxduration could be set for @self. + */ +gboolean +ges_timeline_element_set_max_duration (GESTimelineElement * self, + GstClockTime maxduration) +{ + GESTimelineElementClass *klass; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + + GST_DEBUG_OBJECT (self, "current max-duration: %" GST_TIME_FORMAT + " new max-duration: %" GST_TIME_FORMAT, + GST_TIME_ARGS (self->maxduration), GST_TIME_ARGS (maxduration)); + + if (G_UNLIKELY (maxduration == self->maxduration)) + return TRUE; + + if (GES_CLOCK_TIME_IS_LESS (maxduration, self->inpoint)) { + GST_WARNING_OBJECT (self, "Can not set a max-duration of %" + GST_TIME_FORMAT " because it lies below the element's in-point: %" + GST_TIME_FORMAT, GST_TIME_ARGS (maxduration), + GST_TIME_ARGS (self->inpoint)); + return FALSE; + } + + klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); + + if (klass->set_max_duration) { + if (!klass->set_max_duration (self, maxduration)) + return FALSE; + self->maxduration = maxduration; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_DURATION]); + + return TRUE; + } + + GST_DEBUG_OBJECT (self, "No set_max_duration virtual method implementation" + " on class %s. Can not set max-duration %" GST_TIME_FORMAT, + G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (maxduration)); + + return FALSE; +} + +/** + * ges_timeline_element_set_duration: + * @self: A #GESTimelineElement + * @duration: The desired duration in its timeline + * + * Sets #GESTimelineElement:duration for the element. + * + * Whilst the element is part of a #GESTimeline, this is the same as + * editing the element with ges_timeline_element_edit() under + * #GES_EDIT_MODE_TRIM with #GES_EDGE_END. In particular, the + * #GESTimelineElement:duration of the element may be snapped to a + * different timeline time difference from the one given. In addition, + * setting may fail if it would place the timeline in an unsupported + * configuration, or the element does not have enough internal content to + * last the desired duration. + * + * Returns: %TRUE if @duration could be set for @self. + */ +gboolean +ges_timeline_element_set_duration (GESTimelineElement * self, + GstClockTime duration) +{ + GESTimelineElementClass *klass; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + + if (duration == self->duration) + return TRUE; + + if (self->timeline && !GES_TIMELINE_ELEMENT_BEING_EDITED (self)) + return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_END, self->start + duration); + + GST_DEBUG_OBJECT (self, "current duration: %" GST_TIME_FORMAT + " new duration: %" GST_TIME_FORMAT, + GST_TIME_ARGS (GES_TIMELINE_ELEMENT_DURATION (self)), + GST_TIME_ARGS (duration)); + + klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); + if (klass->set_duration) { + gint res = klass->set_duration (self, duration); + if (res == FALSE) + return FALSE; + if (res == TRUE) { + self->duration = duration; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]); + } + + return TRUE; + } + + GST_WARNING_OBJECT (self, "No set_duration virtual method implementation" + " on class %s. Can not set duration %" GST_TIME_FORMAT, + G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (duration)); + return FALSE; +} + +/** + * ges_timeline_element_get_start: + * @self: A #GESTimelineElement + * + * Gets the #GESTimelineElement:start for the element. + * + * Returns: The start of @self (in nanoseconds). + */ +GstClockTime +ges_timeline_element_get_start (GESTimelineElement * self) +{ + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), GST_CLOCK_TIME_NONE); + + return self->start; +} + +/** + * ges_timeline_element_get_inpoint: + * @self: A #GESTimelineElement + * + * Gets the #GESTimelineElement:in-point for the element. + * + * Returns: The in-point of @self (in nanoseconds). + */ +GstClockTime +ges_timeline_element_get_inpoint (GESTimelineElement * self) +{ + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), GST_CLOCK_TIME_NONE); + + return self->inpoint; +} + +/** + * ges_timeline_element_get_duration: + * @self: A #GESTimelineElement + * + * Gets the #GESTimelineElement:duration for the element. + * + * Returns: The duration of @self (in nanoseconds). + */ +GstClockTime +ges_timeline_element_get_duration (GESTimelineElement * self) +{ + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), GST_CLOCK_TIME_NONE); + + return self->duration; +} + +/** + * ges_timeline_element_get_max_duration: + * @self: A #GESTimelineElement + * + * Gets the #GESTimelineElement:max-duration for the element. + * + * Returns: The max-duration of @self (in nanoseconds). + */ +GstClockTime +ges_timeline_element_get_max_duration (GESTimelineElement * self) +{ + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), GST_CLOCK_TIME_NONE); + + return self->maxduration; +} + +/** + * ges_timeline_element_get_priority: + * @self: A #GESTimelineElement + * + * Gets the #GESTimelineElement:priority for the element. + * + * Returns: The priority of @self. + */ +guint32 +ges_timeline_element_get_priority (GESTimelineElement * self) +{ + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), 0); + + return self->priority; +} + +/** + * ges_timeline_element_set_priority: + * @self: A #GESTimelineElement + * @priority: The priority + * + * Sets the priority of the element within the containing layer. + * + * Deprecated:1.10: All priority management is done by GES itself now. + * To set #GESEffect priorities #ges_clip_set_top_effect_index should + * be used. + * + * Returns: %TRUE if @priority could be set for @self. + */ +gboolean +ges_timeline_element_set_priority (GESTimelineElement * self, guint32 priority) +{ + GESTimelineElementClass *klass; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + + klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); + + GST_DEBUG_OBJECT (self, "current priority: %d new priority: %d", + self->priority, priority); + + if (klass->set_priority) { + gboolean res = klass->set_priority (self, priority); + if (res) { + self->priority = priority; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PRIORITY]); + } + + return res; + } + + GST_WARNING_OBJECT (self, "No set_priority virtual method implementation" + " on class %s. Can not set priority %d", G_OBJECT_CLASS_NAME (klass), + priority); + return FALSE; +} + +/** + * ges_timeline_element_ripple: + * @self: The #GESTimelineElement to ripple + * @start: The new start time of @self in ripple mode + * + * Edits the start time of an element within its timeline in ripple mode. + * See ges_timeline_element_edit() with #GES_EDIT_MODE_RIPPLE and + * #GES_EDGE_NONE. + * + * Returns: %TRUE if the ripple edit of @self completed, %FALSE on + * failure. + */ +gboolean +ges_timeline_element_ripple (GESTimelineElement * self, GstClockTime start) +{ + GESTimelineElementClass *klass; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start), FALSE); + + klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); + + if (klass->ripple) + return klass->ripple (self, start); + + return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_NONE, start); + + return FALSE; +} + +/** + * ges_timeline_element_ripple_end: + * @self: The #GESTimelineElement to ripple + * @end: The new end time of @self in ripple mode + * + * Edits the end time of an element within its timeline in ripple mode. + * See ges_timeline_element_edit() with #GES_EDIT_MODE_RIPPLE and + * #GES_EDGE_END. + * + * Returns: %TRUE if the ripple edit of @self completed, %FALSE on + * failure. + */ +gboolean +ges_timeline_element_ripple_end (GESTimelineElement * self, GstClockTime end) +{ + GESTimelineElementClass *klass; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (end), FALSE); + + klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); + + if (klass->ripple_end) + return klass->ripple_end (self, end); + + return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_END, end); +} + +/** + * ges_timeline_element_roll_start: + * @self: The #GESTimelineElement to roll + * @start: The new start time of @self in roll mode + * + * Edits the start time of an element within its timeline in roll mode. + * See ges_timeline_element_edit() with #GES_EDIT_MODE_ROLL and + * #GES_EDGE_START. + * + * Returns: %TRUE if the roll edit of @self completed, %FALSE on failure. + */ +gboolean +ges_timeline_element_roll_start (GESTimelineElement * self, GstClockTime start) +{ + GESTimelineElementClass *klass; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start), FALSE); + + klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); + + if (klass->roll_start) + return klass->roll_start (self, start); + + return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_START, start); +} + +/** + * ges_timeline_element_roll_end: + * @self: The #GESTimelineElement to roll + * @end: The new end time of @self in roll mode + * + * Edits the end time of an element within its timeline in roll mode. + * See ges_timeline_element_edit() with #GES_EDIT_MODE_ROLL and + * #GES_EDGE_END. + * + * Returns: %TRUE if the roll edit of @self completed, %FALSE on failure. + */ +gboolean +ges_timeline_element_roll_end (GESTimelineElement * self, GstClockTime end) +{ + GESTimelineElementClass *klass; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (end), FALSE); + + klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); + + if (klass->roll_end) + return klass->roll_end (self, end); + + return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_END, end); +} + +/** + * ges_timeline_element_trim: + * @self: The #GESTimelineElement to trim + * @start: The new start time of @self in trim mode + * + * Edits the start time of an element within its timeline in trim mode. + * See ges_timeline_element_edit() with #GES_EDIT_MODE_TRIM and + * #GES_EDGE_START. + * + * Returns: %TRUE if the trim edit of @self completed, %FALSE on failure. + */ +gboolean +ges_timeline_element_trim (GESTimelineElement * self, GstClockTime start) +{ + GESTimelineElementClass *klass; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start), FALSE); + + klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); + + if (klass->trim) + return klass->trim (self, start); + + return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_START, start); +} + +/** + * ges_timeline_element_copy: + * @self: The #GESTimelineElement to copy + * @deep: Whether the copy is needed for pasting + * + * Create a copy of @self. All the properties of @self are copied into + * a new element, with the exception of #GESTimelineElement:parent, + * #GESTimelineElement:timeline and #GESTimelineElement:name. Other data, + * such the list of a #GESContainer's children, is **not** copied. + * + * If @deep is %TRUE, then the new element is prepared so that it can be + * used in ges_timeline_element_paste() or ges_timeline_paste_element(). + * In the case of copying a #GESContainer, this ensures that the children + * of @self will also be pasted. The new element should not be used for + * anything else and can only be used **once** in a pasting operation. In + * particular, the new element itself is not an actual 'deep' copy of + * @self, but should be thought of as an intermediate object used for a + * single paste operation. + * + * Returns: (transfer floating): The newly create element, + * copied from @self. + */ +G_GNUC_BEGIN_IGNORE_DEPRECATIONS; /* Start ignoring GParameter deprecation */ +GESTimelineElement * +ges_timeline_element_copy (GESTimelineElement * self, gboolean deep) +{ + GESAsset *asset; + GParamSpec **specs; + GESTimelineElementClass *klass; + guint n, n_specs; + + GESTimelineElement *ret = NULL; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL); + + klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); + + specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (self), &n_specs); + + asset = ges_extractable_get_asset (GES_EXTRACTABLE (self)); + g_assert (asset); + ret = GES_TIMELINE_ELEMENT (ges_asset_extract (asset, NULL)); + for (n = 0; n < n_specs; ++n) { + /* We do not want the timeline or the name to be copied */ + if (g_strcmp0 (specs[n]->name, "parent") && + g_strcmp0 (specs[n]->name, "timeline") && + g_strcmp0 (specs[n]->name, "name") && + (specs[n]->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE && + (specs[n]->flags & G_PARAM_CONSTRUCT_ONLY) == 0) { + GValue v = G_VALUE_INIT; + g_value_init (&v, specs[n]->value_type); + g_object_get_property (G_OBJECT (self), specs[n]->name, &v); + + g_object_set_property (G_OBJECT (ret), specs[n]->name, &v); + g_value_reset (&v); + } + } + + g_free (specs); + if (deep) { + if (klass->deep_copy) + klass->deep_copy (self, ret); + else + GST_WARNING_OBJECT (self, "No deep_copy virtual method implementation" + " on class %s. Can not finish the copy", G_OBJECT_CLASS_NAME (klass)); + } + + if (deep) { + ret->priv->copied_from = gst_object_ref (self); + } + + return ret; +} + +G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */ + +/** + * ges_timeline_element_get_toplevel_parent: + * @self: The #GESTimelineElement to get the toplevel parent from + * + * Gets the toplevel #GESTimelineElement:parent of the element. + * + * Returns: (transfer full): The toplevel parent of @self. + */ +GESTimelineElement * +ges_timeline_element_get_toplevel_parent (GESTimelineElement * self) +{ + GESTimelineElement *toplevel; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL); + + toplevel = ges_timeline_element_peak_toplevel (self); + + return gst_object_ref (toplevel); +} + +/** + * ges_timeline_element_get_name: + * @self: A #GESTimelineElement + * + * Gets the #GESTimelineElement:name for the element. + * + * Returns: (transfer full): The name of @self. + */ +gchar * +ges_timeline_element_get_name (GESTimelineElement * self) +{ + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL); + + return g_strdup (self->name); +} + +/** + * ges_timeline_element_set_name: + * @self: A #GESTimelineElement + * @name: (allow-none): The name @self should take + * + * Sets the #GESTimelineElement:name for the element. If %NULL is given + * for @name, then the library will instead generate a new name based on + * the type name of the element, such as the name "uriclip3" for a + * #GESUriClip, and will set that name instead. + * + * If @self already has a #GESTimelineElement:timeline, you should not + * call this function with @name set to %NULL. + * + * You should ensure that, within each #GESTimeline, every element has a + * unique name. If you call this function with @name as %NULL, then + * the library should ensure that the set generated name is unique from + * previously **generated** names. However, if you choose a @name that + * interferes with the naming conventions of the library, the library will + * attempt to ensure that the generated names will not conflict with the + * chosen name, which may lead to a different name being set instead, but + * the uniqueness between generated and user-chosen names is not + * guaranteed. + * + * Returns: %TRUE if @name or a generated name for @self could be set. + */ +gboolean +ges_timeline_element_set_name (GESTimelineElement * self, const gchar * name) +{ + gboolean result = TRUE, readd_to_timeline = FALSE; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + + if (name != NULL && !g_strcmp0 (name, self->name)) { + GST_DEBUG_OBJECT (self, "Same name!"); + return TRUE; + } + + /* parented objects cannot be renamed */ + if (self->timeline != NULL && name) { + GESTimelineElement *tmp = ges_timeline_get_element (self->timeline, name); + + /* FIXME: if tmp == self then this means that we setting the name of + * self to its existing name. There is no need to throw an error */ + if (tmp) { + gst_object_unref (tmp); + goto had_timeline; + } + + timeline_remove_element (self->timeline, self); + readd_to_timeline = TRUE; + } + /* FIXME: if self already has a timeline and name is NULL, then it also + * needs to be re-added to the timeline (or, at least its entry in + * timeline->priv->all_elements needs its key to be updated) using the + * new generated name */ + + _set_name (self, name); + + /* FIXME: the set name may not always be unique in a given timeline, see + * _set_name(). This can cause timeline_add_element to fail! */ + if (readd_to_timeline) + timeline_add_element (self->timeline, self); + + return result; + + /* error */ +had_timeline: + { + /* FIXME: message is misleading. We are here if some other object in + * the timeline was added under @name (see above) */ + GST_WARNING ("Object %s already in a timeline can't be renamed to %s", + self->name, name); + return FALSE; + } +} + +/** + * ges_timeline_element_add_child_property: + * @self: A #GESTimelineElement + * @pspec: The specification for the property to add + * @child: The #GstObject who the property belongs to + * + * Register a property of a child of the element to allow it to be + * written with ges_timeline_element_set_child_property() and read with + * ges_timeline_element_get_child_property(). A change in the property + * will also appear in the #GESTimelineElement::deep-notify signal. + * + * @pspec should be unique from other children properties that have been + * registered on @self. + * + * Returns: %TRUE if the property was successfully registered. + */ +gboolean +ges_timeline_element_add_child_property (GESTimelineElement * self, + GParamSpec * pspec, GObject * child) +{ + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), FALSE); + g_return_val_if_fail (G_IS_OBJECT (child), FALSE); + + return ges_timeline_element_add_child_property_full (self, NULL, pspec, + child); +} + +/** + * ges_timeline_element_get_child_property_by_pspec: + * @self: A #GESTimelineElement + * @pspec: The specification of a registered child property to get + * @value: (out): The return location for the value + * + * Gets the property of a child of the element. Specifically, the property + * corresponding to the @pspec used in + * ges_timeline_element_add_child_property() is copied into @value. + */ +void +ges_timeline_element_get_child_property_by_pspec (GESTimelineElement * self, + GParamSpec * pspec, GValue * value) +{ + ChildPropHandler *handler; + + g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self)); + g_return_if_fail (G_IS_PARAM_SPEC (pspec)); + + handler = g_hash_table_lookup (self->priv->children_props, pspec); + if (!handler) + goto not_found; + + g_object_get_property (G_OBJECT (handler->child), pspec->name, value); + + return; + +not_found: + { + GST_ERROR_OBJECT (self, "The %s property doesn't exist", pspec->name); + return; + } +} + +/** + * ges_timeline_element_set_child_property_by_pspec: + * @self: A #GESTimelineElement + * @pspec: The specification of a registered child property to set + * @value: The value to set the property to + * + * Sets the property of a child of the element. Specifically, the property + * corresponding to the @pspec used in + * ges_timeline_element_add_child_property() is set to @value. + */ +void +ges_timeline_element_set_child_property_by_pspec (GESTimelineElement * self, + GParamSpec * pspec, const GValue * value) +{ + g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self)); + g_return_if_fail (G_IS_PARAM_SPEC (pspec)); + + set_child_property_by_pspec (self, pspec, value, NULL); +} + +/** + * ges_timeline_element_set_child_property_full: + * @self: A #GESTimelineElement + * @property_name: The name of the child property to set + * @value: The value to set the property to + * @error: (nullable): Return location for an error + * + * Sets the property of a child of the element. + * + * @property_name can either be in the format "prop-name" or + * "TypeName::prop-name", where "prop-name" is the name of the property + * to set (as used in g_object_set()), and "TypeName" is the type name of + * the child (as returned by G_OBJECT_TYPE_NAME()). The latter format is + * useful when two children of different types share the same property + * name. + * + * The first child found with the given "prop-name" property that was + * registered with ges_timeline_element_add_child_property() (and of the + * type "TypeName", if it was given) will have the corresponding + * property set to @value. Other children that may have also matched the + * property name (and type name) are left unchanged! + * + * Returns: %TRUE if the property was found and set. + * Since: 1.18 + */ +gboolean +ges_timeline_element_set_child_property_full (GESTimelineElement * self, + const gchar * property_name, const GValue * value, GError ** error) +{ + GParamSpec *pspec; + GObject *child; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + if (!ges_timeline_element_lookup_child (self, property_name, &child, &pspec)) + goto not_found; + + return set_child_property_by_pspec (self, pspec, value, error); + +not_found: + { + GST_WARNING_OBJECT (self, "The %s property doesn't exist", property_name); + + return FALSE; + } +} + +/** + * ges_timeline_element_set_child_property: + * @self: A #GESTimelineElement + * @property_name: The name of the child property to set + * @value: The value to set the property to + * + * See ges_timeline_element_set_child_property_full(), which also gives an + * error. + * + * Note that ges_timeline_element_set_child_properties() may be more + * convenient for C programming. + * + * Returns: %TRUE if the property was found and set. + */ +gboolean +ges_timeline_element_set_child_property (GESTimelineElement * self, + const gchar * property_name, const GValue * value) +{ + return ges_timeline_element_set_child_property_full (self, property_name, + value, NULL); +} + +/** + * ges_timeline_element_get_child_property: + * @self: A #GESTimelineElement + * @property_name: The name of the child property to get + * @value: (out): The return location for the value + * + * Gets the property of a child of the element. + * + * @property_name can either be in the format "prop-name" or + * "TypeName::prop-name", where "prop-name" is the name of the property + * to get (as used in g_object_get()), and "TypeName" is the type name of + * the child (as returned by G_OBJECT_TYPE_NAME()). The latter format is + * useful when two children of different types share the same property + * name. + * + * The first child found with the given "prop-name" property that was + * registered with ges_timeline_element_add_child_property() (and of the + * type "TypeName", if it was given) will have the corresponding + * property copied into @value. + * + * Note that ges_timeline_element_get_child_properties() may be more + * convenient for C programming. + * + * Returns: %TRUE if the property was found and copied to @value. + */ +gboolean +ges_timeline_element_get_child_property (GESTimelineElement * self, + const gchar * property_name, GValue * value) +{ + GParamSpec *pspec; + GObject *child; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + + if (!ges_timeline_element_lookup_child (self, property_name, &child, &pspec)) + goto not_found; + + /* FIXME: since GLib 2.60, g_object_get_property() will automatically + * initialize the type */ + if (G_VALUE_TYPE (value) == G_TYPE_INVALID) + g_value_init (value, pspec->value_type); + + g_object_get_property (child, pspec->name, value); + + gst_object_unref (child); + g_param_spec_unref (pspec); + + return TRUE; + +not_found: + { + GST_WARNING_OBJECT (self, "The %s property doesn't exist", property_name); + + return FALSE; + } +} + +/** + * ges_timeline_element_lookup_child: + * @self: A #GESTimelineElement + * @prop_name: The name of a child property + * @child: (out) (optional) (transfer full): The return location for the + * found child + * @pspec: (out) (optional) (transfer full): The return location for the + * specification of the child property + * + * Looks up a child property of the element. + * + * @prop_name can either be in the format "prop-name" or + * "TypeName::prop-name", where "prop-name" is the name of the property + * to look up (as used in g_object_get()), and "TypeName" is the type name + * of the child (as returned by G_OBJECT_TYPE_NAME()). The latter format is + * useful when two children of different types share the same property + * name. + * + * The first child found with the given "prop-name" property that was + * registered with ges_timeline_element_add_child_property() (and of the + * type "TypeName", if it was given) will be passed to @child, and the + * registered specification of this property will be passed to @pspec. + * + * Returns: %TRUE if a child corresponding to the property was found, in + * which case @child and @pspec are set. + */ +gboolean +ges_timeline_element_lookup_child (GESTimelineElement * self, + const gchar * prop_name, GObject ** child, GParamSpec ** pspec) +{ + GESTimelineElementClass *class; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + class = GES_TIMELINE_ELEMENT_GET_CLASS (self); + g_return_val_if_fail (class->lookup_child, FALSE); + + return class->lookup_child (self, prop_name, child, pspec); +} + +/** + * ges_timeline_element_set_child_property_valist: + * @self: A #GESTimelineElement + * @first_property_name: The name of the first child property to set + * @var_args: The value for the first property, followed optionally by more + * name/value pairs, followed by %NULL + * + * Sets several of the children properties of the element. See + * ges_timeline_element_set_child_property(). + */ +void +ges_timeline_element_set_child_property_valist (GESTimelineElement * self, + const gchar * first_property_name, va_list var_args) +{ + const gchar *name; + GParamSpec *pspec; + + gchar *error = NULL; + GValue value = { 0, }; + + g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self)); + + name = first_property_name; + + /* Note: This part is in big part copied from the gst_child_object_set_valist + * method. */ + + /* iterate over pairs */ + while (name) { + if (!ges_timeline_element_lookup_child (self, name, NULL, &pspec)) + goto not_found; + + G_VALUE_COLLECT_INIT (&value, pspec->value_type, var_args, + G_VALUE_NOCOPY_CONTENTS, &error); + + if (error) + goto cant_copy; + + set_child_property_by_pspec (self, pspec, &value, NULL); + + g_param_spec_unref (pspec); + g_value_unset (&value); + + name = va_arg (var_args, gchar *); + } + return; + +not_found: + { + GST_WARNING_OBJECT (self, "No property %s in OBJECT\n", name); + return; + } +cant_copy: + { + GST_WARNING_OBJECT (self, "error copying value %s in %p: %s", pspec->name, + self, error); + + g_param_spec_unref (pspec); + g_value_unset (&value); + return; + } +} + +/** + * ges_timeline_element_set_child_properties: + * @self: A #GESTimelineElement + * @first_property_name: The name of the first child property to set + * @...: The value for the first property, followed optionally by more + * name/value pairs, followed by %NULL + * + * Sets several of the children properties of the element. See + * ges_timeline_element_set_child_property(). + */ +void +ges_timeline_element_set_child_properties (GESTimelineElement * self, + const gchar * first_property_name, ...) +{ + va_list var_args; + + g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self)); + + va_start (var_args, first_property_name); + ges_timeline_element_set_child_property_valist (self, first_property_name, + var_args); + va_end (var_args); +} + +/** + * ges_timeline_element_get_child_property_valist: + * @self: A #GESTimelineElement + * @first_property_name: The name of the first child property to get + * @var_args: The return location for the first property, followed + * optionally by more name/return location pairs, followed by %NULL + * + * Gets several of the children properties of the element. See + * ges_timeline_element_get_child_property(). + */ +void +ges_timeline_element_get_child_property_valist (GESTimelineElement * self, + const gchar * first_property_name, va_list var_args) +{ + const gchar *name; + gchar *error = NULL; + GValue value = { 0, }; + GParamSpec *pspec; + GObject *child; + + g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self)); + + name = first_property_name; + + /* This part is in big part copied from the gst_child_object_get_valist method */ + while (name) { + if (!ges_timeline_element_lookup_child (self, name, &child, &pspec)) + goto not_found; + + g_value_init (&value, pspec->value_type); + g_object_get_property (child, pspec->name, &value); + gst_object_unref (child); + g_param_spec_unref (pspec); + + G_VALUE_LCOPY (&value, var_args, 0, &error); + if (error) + goto cant_copy; + g_value_unset (&value); + name = va_arg (var_args, gchar *); + } + return; + +not_found: + { + GST_WARNING_OBJECT (self, "no child property %s", name); + return; + } +cant_copy: + { + GST_WARNING_OBJECT (self, "error copying value %s in %s", pspec->name, + error); + + g_value_unset (&value); + return; + } +} + +static gint +compare_gparamspec (GParamSpec ** a, GParamSpec ** b, gpointer udata) +{ + return g_strcmp0 ((*a)->name, (*b)->name); +} + + +/** + * ges_timeline_element_list_children_properties: + * @self: A #GESTimelineElement + * @n_properties: (out): The return location for the length of the + * returned array + * + * Get a list of children properties of the element, which is a list of + * all the specifications passed to + * ges_timeline_element_add_child_property(). + * + * Returns: (transfer full) (array length=n_properties): An array of + * #GParamSpec corresponding to the child properties of @self, or %NULL if + * something went wrong. + */ +GParamSpec ** +ges_timeline_element_list_children_properties (GESTimelineElement * self, + guint * n_properties) +{ + GParamSpec **ret; + GESTimelineElementClass *class; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL); + + class = GES_TIMELINE_ELEMENT_GET_CLASS (self); + + if (!class->list_children_properties) { + GST_INFO_OBJECT (self, "No %s->list_children_properties implementation", + G_OBJECT_TYPE_NAME (self)); + + *n_properties = 0; + return NULL; + } + + ret = class->list_children_properties (self, n_properties); + g_qsort_with_data (ret, *n_properties, sizeof (GParamSpec *), + (GCompareDataFunc) compare_gparamspec, NULL); + + return ret; +} + +/** + * ges_timeline_element_get_child_properties: + * @self: A #GESTimelineElement + * @first_property_name: The name of the first child property to get + * @...: The return location for the first property, followed + * optionally by more name/return location pairs, followed by %NULL + * + * Gets several of the children properties of the element. See + * ges_timeline_element_get_child_property(). + */ +void +ges_timeline_element_get_child_properties (GESTimelineElement * self, + const gchar * first_property_name, ...) +{ + va_list var_args; + + g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self)); + + va_start (var_args, first_property_name); + ges_timeline_element_get_child_property_valist (self, first_property_name, + var_args); + va_end (var_args); +} + +/** + * ges_timeline_element_remove_child_property: + * @self: A #GESTimelineElement + * @pspec: The specification for the property to remove + * + * Remove a child property from the element. @pspec should be a + * specification that was passed to + * ges_timeline_element_add_child_property(). The corresponding property + * will no longer be registered as a child property for the element. + * + * Returns: %TRUE if the property was successfully un-registered for @self. + */ +gboolean +ges_timeline_element_remove_child_property (GESTimelineElement * self, + GParamSpec * pspec) +{ + gpointer key, value; + GParamSpec *found_pspec; + ChildPropHandler *handler; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), FALSE); + + if (!g_hash_table_lookup_extended (self->priv->children_props, pspec, + &key, &value)) { + GST_WARNING_OBJECT (self, "No child property with pspec %p (%s) found", + pspec, pspec->name); + return FALSE; + } + g_hash_table_steal (self->priv->children_props, pspec); + found_pspec = G_PARAM_SPEC (key); + handler = (ChildPropHandler *) value; + + g_signal_emit (self, ges_timeline_element_signals[CHILD_PROPERTY_REMOVED], 0, + handler->child, found_pspec); + + g_param_spec_unref (found_pspec); + _child_prop_handler_free (handler); + + return TRUE; +} + +/** + * ges_timeline_element_get_track_types: + * @self: A #GESTimelineElement + * + * Gets the track types that the element can interact with, i.e. the type + * of #GESTrack it can exist in, or will create #GESTrackElement-s for. + * + * Returns: The track types that @self supports. + * + * Since: 1.6.0 + */ +GESTrackType +ges_timeline_element_get_track_types (GESTimelineElement * self) +{ + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), 0); + g_return_val_if_fail (GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_track_types, + 0); + + return GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_track_types (self); +} + +/** + * ges_timeline_element_paste: + * @self: The #GESTimelineElement to paste + * @paste_position: The position in the timeline @element should be pasted + * to, i.e. the #GESTimelineElement:start value for the pasted element. + * + * Paste an element inside the same timeline and layer as @self. @self + * **must** be the return of ges_timeline_element_copy() with `deep=TRUE`, + * and it should not be changed before pasting. + * @self is not placed in the timeline, instead a new element is created, + * alike to the originally copied element. Note that the originally + * copied element must stay within the same timeline and layer, at both + * the point of copying and pasting. + * + * Pasting may fail if it would place the timeline in an unsupported + * configuration. + * + * After calling this function @element should not be used. In particular, + * @element can **not** be pasted again. Instead, you can copy the + * returned element and paste that copy (although, this is only possible + * if the paste was successful). + * + * See also ges_timeline_paste_element(). + * + * Returns: (transfer full) (nullable): The newly created element, or + * %NULL if pasting fails. + * + * Since: 1.6.0 + */ +GESTimelineElement * +ges_timeline_element_paste (GESTimelineElement * self, + GstClockTime paste_position) +{ + GESTimelineElement *res; + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (paste_position), FALSE); + + if (!self->priv->copied_from) { + GST_ERROR_OBJECT (self, "Is not being 'deeply' copied!"); + + return NULL; + } + + if (!GES_TIMELINE_ELEMENT_GET_CLASS (self)->paste) { + GST_ERROR_OBJECT (self, "No paste vmethod implemented"); + + return NULL; + } + + res = GES_TIMELINE_ELEMENT_GET_CLASS (self)->paste (self, + self->priv->copied_from, paste_position); + + g_clear_object (&self->priv->copied_from); + + return res ? g_object_ref_sink (res) : res; +} + +/** + * ges_timeline_element_get_layer_priority: + * @self: A #GESTimelineElement + * + * Gets the priority of the layer the element is in. A #GESGroup may span + * several layers, so this would return the highest priority (numerically, + * the smallest) amongst them. + * + * Returns: The priority of the layer @self is in, or + * #GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY if @self does not exist in a + * layer. + * + * Since: 1.16 + */ +guint32 +ges_timeline_element_get_layer_priority (GESTimelineElement * self) +{ + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), + GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY); + + if (!GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_layer_priority) + return self->priority; + + return GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_layer_priority (self); +} + +/** + * ges_timeline_element_edit_full: + * @self: The #GESTimelineElement to edit + * @new_layer_priority: The priority/index of the layer @self should be + * moved to. -1 means no move + * @mode: The edit mode + * @edge: The edge of @self where the edit should occur + * @position: The edit position: a new location for the edge of @self + * (in nanoseconds) in the timeline coordinates + * @error: (nullable): Return location for an error + * + * Edits the element within its timeline by adjusting its + * #GESTimelineElement:start, #GESTimelineElement:duration or + * #GESTimelineElement:in-point, and potentially doing the same for + * other elements in the timeline. See #GESEditMode for details about each + * edit mode. An edit may fail if it would place one of these properties + * out of bounds, or if it would place the timeline in an unsupported + * configuration. + * + * Note that if you act on a #GESTrackElement, this will edit its parent + * #GESClip instead. Moreover, for any #GESTimelineElement, if you select + * #GES_EDGE_NONE for #GES_EDIT_MODE_NORMAL or #GES_EDIT_MODE_RIPPLE, this + * will edit the toplevel instead, but still in such a way as to make the + * #GESTimelineElement:start of @self reach the edit @position. + * + * Note that if the element's timeline has a + * #GESTimeline:snapping-distance set, then the edit position may be + * snapped to the edge of some element under the edited element. + * + * @new_layer_priority can be used to switch @self, and other elements + * moved by the edit, to a new layer. New layers may be be created if the + * the corresponding layer priority/index does not yet exist for the + * timeline. + * + * Returns: %TRUE if the edit of @self completed, %FALSE on failure. + * Since: 1.18 + */ + +gboolean +ges_timeline_element_edit_full (GESTimelineElement * self, + gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position, + GError ** error) +{ + GESTimeline *timeline; + guint32 layer_prio; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + timeline = GES_TIMELINE_ELEMENT_TIMELINE (self); + g_return_val_if_fail (timeline, FALSE); + + layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self); + + if (new_layer_priority < 0) + new_layer_priority = layer_prio; + + GST_DEBUG_OBJECT (self, "Editing %s at edge %s to position %" + GST_TIME_FORMAT " under %s mode, and to layer %" G_GINT64_FORMAT, + self->name, ges_edge_name (edge), GST_TIME_ARGS (position), + ges_edit_mode_name (mode), new_layer_priority); + + return ges_timeline_edit (timeline, self, new_layer_priority, mode, + edge, position, error); +} + +/** + * ges_timeline_element_edit: + * @self: The #GESTimelineElement to edit + * @layers: (element-type GESLayer) (nullable): A whitelist of layers + * where the edit can be performed, %NULL allows all layers in the + * timeline. + * @new_layer_priority: The priority/index of the layer @self should be + * moved to. -1 means no move + * @mode: The edit mode + * @edge: The edge of @self where the edit should occur + * @position: The edit position: a new location for the edge of @self + * (in nanoseconds) in the timeline coordinates + * + * See ges_timeline_element_edit_full(), which also gives an error. + * + * Note that the @layers argument is currently ignored, so you should + * just pass %NULL. + * + * Returns: %TRUE if the edit of @self completed, %FALSE on failure. + * + * Since: 1.18 + */ + +/* FIXME: handle the layers argument. Currently we always treat it as if + * it is NULL in the ges-timeline code */ +gboolean +ges_timeline_element_edit (GESTimelineElement * self, GList * layers, + gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position) +{ + return ges_timeline_element_edit_full (self, new_layer_priority, mode, edge, + position, NULL); +} + +/** + * ges_timeline_element_get_natural_framerate: + * @self: The #GESTimelineElement to get "natural" framerate from + * @framerate_n: (out): The framerate numerator + * @framerate_d: (out): The framerate denominator + * + * Get the "natural" framerate of @self. This is to say, for example + * for a #GESVideoUriSource the framerate of the source. + * + * Note that a #GESAudioSource may also have a natural framerate if it derives + * from the same #GESSourceClip asset as a #GESVideoSource, and its value will + * be that of the video source. For example, if the uri of a #GESUriClip points + * to a file that contains both a video and audio stream, then the corresponding + * #GESAudioUriSource will share the natural framerate of the corresponding + * #GESVideoUriSource. + * + * Returns: Whether @self has a natural framerate or not, @framerate_n + * and @framerate_d will be set to, respectively, 0 and -1 if it is + * not the case. + * + * Since: 1.18 + */ +gboolean +ges_timeline_element_get_natural_framerate (GESTimelineElement * self, + gint * framerate_n, gint * framerate_d) +{ + GESTimelineElementClass *klass; + + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + g_return_val_if_fail (framerate_n && framerate_d, FALSE); + + klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); + + *framerate_n = 0; + *framerate_d = -1; + return klass->get_natural_framerate (self, framerate_n, framerate_d); +} diff --git a/ges/ges-timeline-element.h b/ges/ges-timeline-element.h new file mode 100644 index 0000000000..80fbd6ff74 --- /dev/null +++ b/ges/ges-timeline-element.h @@ -0,0 +1,456 @@ +/* + * gst-editing-services + * Copyright (C) <2013> Thibault Saunier <thibault.saunier@collabora.com> + * <2013> Collabora Ltd. + * + * gst-editing-services is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * gst-editing-services is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <glib-object.h> +#include <gst/gst.h> +#include "ges-enums.h" +#include "ges-types.h" + +G_BEGIN_DECLS + +#define GES_TYPE_TIMELINE_ELEMENT (ges_timeline_element_get_type ()) +GES_DECLARE_TYPE(TimelineElement, timeline_element, TIMELINE_ELEMENT); + +/** + * GES_TIMELINE_ELEMENT_START: + * @obj: A #GESTimelineElement + * + * The #GESTimelineElement:start of @obj. + */ +#define GES_TIMELINE_ELEMENT_START(obj) (((GESTimelineElement*)obj)->start) + +/** + * GES_TIMELINE_ELEMENT_END: + * @obj: A #GESTimelineElement + * + * The end position of @obj: #GESTimelineElement:start + + * #GESTimelineElement:duration. + */ +#define GES_TIMELINE_ELEMENT_END(obj) ((((GESTimelineElement*)obj)->start) + (((GESTimelineElement*)obj)->duration)) + +/** + * GES_TIMELINE_ELEMENT_INPOINT: + * @obj: A #GESTimelineElement + * + * The #GESTimelineElement:in-point of @obj. + */ +#define GES_TIMELINE_ELEMENT_INPOINT(obj) (((GESTimelineElement*)obj)->inpoint) + +/** + * GES_TIMELINE_ELEMENT_DURATION: + * @obj: A #GESTimelineElement + * + * The #GESTimelineElement:duration of @obj. + */ +#define GES_TIMELINE_ELEMENT_DURATION(obj) (((GESTimelineElement*)obj)->duration) + +/** + * GES_TIMELINE_ELEMENT_MAX_DURATION: + * @obj: A #GESTimelineElement + * + * The #GESTimelineElement:max-duration of @obj. + */ +#define GES_TIMELINE_ELEMENT_MAX_DURATION(obj) (((GESTimelineElement*)obj)->maxduration) + +/** + * GES_TIMELINE_ELEMENT_PRIORITY: + * @obj: A #GESTimelineElement + * + * The #GESTimelineElement:priority of @obj. + */ +#define GES_TIMELINE_ELEMENT_PRIORITY(obj) (((GESTimelineElement*)obj)->priority) + +/** + * GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY: + * + * Layer priority when a timeline element is not in any layer. + */ +#define GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY ((guint32) -1) + +/** + * GES_TIMELINE_ELEMENT_LAYER_PRIORITY: + * @obj: The object to retrieve the layer priority from + * + * See #ges_timeline_element_get_layer_priority. + */ +#define GES_TIMELINE_ELEMENT_LAYER_PRIORITY(obj) (ges_timeline_element_get_layer_priority(((GESTimelineElement*)obj))) + +/** + * GES_TIMELINE_ELEMENT_PARENT: + * @obj: A #GESTimelineElement + * + * The #GESTimelineElement:parent of @obj. + */ +#define GES_TIMELINE_ELEMENT_PARENT(obj) (((GESTimelineElement*)obj)->parent) + +/** + * GES_TIMELINE_ELEMENT_TIMELINE: + * @obj: A #GESTimelineElement + * + * The #GESTimelineElement:timeline of @obj. + */ +#define GES_TIMELINE_ELEMENT_TIMELINE(obj) (((GESTimelineElement*)obj)->timeline) + +/** + * GES_TIMELINE_ELEMENT_NAME: + * @obj: A #GESTimelineElement + * + * The #GESTimelineElement:name of @obj. + */ +#define GES_TIMELINE_ELEMENT_NAME(obj) (((GESTimelineElement*)obj)->name) + +/** + * GESTimelineElement: + * @parent: The #GESTimelineElement:parent of the element + * @asset: The #GESAsset from which the object has been extracted + * @start: The #GESTimelineElement:start of the element + * @inpoint: The #GESTimelineElement:in-point of the element + * @duration: The #GESTimelineElement:duration of the element + * @maxduration: The #GESTimelineElement:max-duration of the element + * @priority: The #GESTimelineElement:priority of the element + * @name: The #GESTimelineElement:name of the element + * @timeline: The #GESTimelineElement:timeline of the element + * + * All members can be read freely, but should usually not be written to. + * Subclasses may write to them, but should make sure to properly call + * g_object_notify(). + */ +struct _GESTimelineElement +{ + GInitiallyUnowned parent_instance; + + /*< public > */ + /*< read only >*/ + GESTimelineElement *parent; + GESAsset *asset; + GstClockTime start; + GstClockTime inpoint; + GstClockTime duration; + GstClockTime maxduration; + guint32 priority; + GESTimeline *timeline; + gchar *name; + + /*< private >*/ + GESTimelineElementPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING_LARGE]; +}; + +/** + * GESTimelineElementClass: + * @set_parent: Method called just before the #GESTimelineElement:parent + * is set. + * @set_start: Method called just before the #GESTimelineElement:start is + * set. This method should check whether the #GESTimelineElement:start can + * be changed to the new value and to otherwise prepare the element in + * response to what the new value will be. A return of %FALSE means that + * the property should not be set. A return of %TRUE means that the + * property should be set to the value given to the setter and a notify + * emitted. A return of -1 means that the property should not be set but + * the setter should still return %TRUE (normally because the method + * already handled setting the value, potentially to a snapped value, and + * emitted the notify signal). + * #GESTimelineElement:duration is set. This method should check + * whether the #GESTimelineElement:duration can be changed to the new + * value and to otherwise prepare the element in response to what the new + * value will be. A return of %FALSE means that the property should not be + * set. A return of %TRUE means that the property should be set to the + * value given to the setter and a notify emitted. A return of -1 means + * that the property should not be set but the setter should still return + * %TRUE (normally because the method already handled setting the value, + * potentially to a snapped value, and emitted the notify signal). + * @set_inpoint: Method called just before the + * #GESTimelineElement:in-point is set to a new value. This method should + * not set the #GESTimelineElement:in-point itself, but should check + * whether it can be changed to the new value and to otherwise prepare the + * element in response to what the new value will be. A return of %FALSE + * means that the property should not be set. + * @set_max_duration: Method called just before the + * #GESTimelineElement:max-duration is set. This method should + * not set the #GESTimelineElement:max-duration itself, but should check + * whether it can be changed to the new value and to otherwise prepare the + * element in response to what the new value will be. A return of %FALSE + * means that the property should not be set. + * @set_priority: Method called just before the + * #GESTimelineElement:priority is set. + * @ripple: Set this method to overwrite a redirect to + * ges_timeline_element_edit() in ges_timeline_element_ripple(). + * @ripple_end: Set this method to overwrite a redirect to + * ges_timeline_element_edit() in ges_timeline_element_ripple_end(). + * @roll: Set this method to overwrite a redirect to + * ges_timeline_element_edit() in ges_timeline_element_roll(). + * @roll_end: Set this method to overwrite a redirect to + * ges_timeline_element_edit() in ges_timeline_element_roll_end(). + * @trim: Set this method to overwrite a redirect to + * ges_timeline_element_edit() in ges_timeline_element_trim(). + * @deep_copy: Prepare @copy for pasting as a copy of @self. At least by + * copying the children properties of @self into @copy. + * @paste: Paste @self, which is the @copy prepared by @deep_copy, into + * the timeline at the given @paste_position, with @ref_element as a + * reference, which is the @self that was passed to @deep_copy. + * @lookup-child: Method to find a child with the child property. + * @prop_name. The default vmethod will return the first child that + * matches. Overwrite this with a call to the parent vmethod if you wish + * to redirect property names. Or overwrite to change which child is + * returned if multiple children share the same child property name. + * @get_track_types: Return a the track types for the element. + * @list_children_properties: List the children properties that have been + * registered for the element. The default implementation is able to fetch + * all of these, so should be sufficient. If you overwrite this, you + * should still call the default implementation to get the full list, and + * then edit its content. + * @lookup_child: Find @child, and its registered child property @pspec, + * corresponding to the child property specified by @prop_name. The + * default implementation will search for the first child that matches. If + * you overwrite this, you will likely still want to call the default + * vmethod, which has access to the registered parameter specifications. + * + * The #GESTimelineElement base class. Subclasses should override at least + * @set_start @set_inpoint @set_duration @ripple @ripple_end @roll_start + * @roll_end and @trim. + * + * Vmethods in subclasses should apply all the operation they need to but + * the real method implementation is in charge of setting the proper field, + * and emitting the notify signal. + */ +struct _GESTimelineElementClass +{ + GInitiallyUnownedClass parent_class; + + /*< public > */ + gboolean (*set_parent) (GESTimelineElement * self, GESTimelineElement *parent); + gboolean (*set_start) (GESTimelineElement * self, GstClockTime start); + gboolean (*set_inpoint) (GESTimelineElement * self, GstClockTime inpoint); + gboolean (*set_duration) (GESTimelineElement * self, GstClockTime duration); + gboolean (*set_max_duration) (GESTimelineElement * self, GstClockTime maxduration); + gboolean (*set_priority) (GESTimelineElement * self, guint32 priority); /* set_priority is now protected */ + + gboolean (*ripple) (GESTimelineElement *self, guint64 start); + gboolean (*ripple_end) (GESTimelineElement *self, guint64 end); + gboolean (*roll_start) (GESTimelineElement *self, guint64 start); + gboolean (*roll_end) (GESTimelineElement *self, guint64 end); + gboolean (*trim) (GESTimelineElement *self, guint64 start); + void (*deep_copy) (GESTimelineElement *self, GESTimelineElement *copy); + + GESTimelineElement * (*paste) (GESTimelineElement *self, + GESTimelineElement *ref_element, + GstClockTime paste_position); + + GParamSpec** (*list_children_properties) (GESTimelineElement * self, guint *n_properties); + gboolean (*lookup_child) (GESTimelineElement *self, const gchar *prop_name, + GObject **child, GParamSpec **pspec); + GESTrackType (*get_track_types) (GESTimelineElement * self); + + /** + * GESTimelineElementClass::set_child_property: + * + * Method for setting the child property given by + * @pspec on @child to @value. Default implementation will use + * g_object_set_property(). + * + * Since: 1.16 + */ + void (*set_child_property) (GESTimelineElement * self, + GObject *child, + GParamSpec *pspec, + GValue *value); + + /** + * GESTimelineElementClass::get_layer_priority: + * + * Get the #GESLayer:priority of the layer that this + * element is part of. + * + * Since: 1.16 + */ + guint32 (*get_layer_priority) (GESTimelineElement *self); + + /** + * GESTimelineElementClass::get_natural_framerate: + * @self: A #GESTimelineElement + * @framerate_n: The framerate numerator to retrieve + * @framerate_d: The framerate denominator to retrieve + * + * Returns: %TRUE if @self has a natural framerate @FALSE otherwise. + * + * Since: 1.18 + */ + gboolean (*get_natural_framerate) (GESTimelineElement * self, gint *framerate_n, gint *framerate_d); + + /** + * GESTimelineElementClass::set_child_property_full: + * + * Similar to @set_child_property, except setting can fail, with the @error + * being optionally set. Default implementation will call @set_child_property + * and return %TRUE. + * + * Since: 1.18 + */ + gboolean (*set_child_property_full) (GESTimelineElement * self, + GObject *child, + GParamSpec *pspec, + const GValue *value, + GError ** error); + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING_LARGE - 6]; +}; + +GES_API +GESTimelineElement * ges_timeline_element_get_toplevel_parent (GESTimelineElement *self); +GES_API +GESTimelineElement * ges_timeline_element_get_parent (GESTimelineElement * self); +GES_API +gboolean ges_timeline_element_set_parent (GESTimelineElement *self, + GESTimelineElement *parent); +GES_API +gboolean ges_timeline_element_set_timeline (GESTimelineElement *self, + GESTimeline *timeline); +GES_API +gboolean ges_timeline_element_set_start (GESTimelineElement *self, + GstClockTime start); +GES_API +gboolean ges_timeline_element_set_inpoint (GESTimelineElement *self, + GstClockTime inpoint); +GES_API +gboolean ges_timeline_element_set_duration (GESTimelineElement *self, + GstClockTime duration); +GES_API +gboolean ges_timeline_element_set_max_duration (GESTimelineElement *self, + GstClockTime maxduration); +GES_DEPRECATED +gboolean ges_timeline_element_set_priority (GESTimelineElement *self, + guint32 priority); +GES_API +GstClockTime ges_timeline_element_get_start (GESTimelineElement *self); +GES_API +GstClockTime ges_timeline_element_get_inpoint (GESTimelineElement *self); +GES_API +GstClockTime ges_timeline_element_get_duration (GESTimelineElement *self); +GES_API +GstClockTime ges_timeline_element_get_max_duration (GESTimelineElement *self); +GES_API +GESTimeline * ges_timeline_element_get_timeline (GESTimelineElement *self); +GES_API +guint32 ges_timeline_element_get_priority (GESTimelineElement *self); +GES_API +gboolean ges_timeline_element_ripple (GESTimelineElement *self, + GstClockTime start); +GES_API +gboolean ges_timeline_element_ripple_end (GESTimelineElement *self, + GstClockTime end); +GES_API +gboolean ges_timeline_element_roll_start (GESTimelineElement *self, + GstClockTime start); +GES_API +gboolean ges_timeline_element_roll_end (GESTimelineElement *self, + GstClockTime end); +GES_API +gboolean ges_timeline_element_trim (GESTimelineElement *self, + GstClockTime start); +GES_API +GESTimelineElement * ges_timeline_element_copy (GESTimelineElement *self, + gboolean deep); +GES_API +gchar * ges_timeline_element_get_name (GESTimelineElement *self); +GES_API +gboolean ges_timeline_element_set_name (GESTimelineElement *self, + const gchar *name); +GES_API +GParamSpec ** ges_timeline_element_list_children_properties (GESTimelineElement *self, + guint *n_properties); +GES_API +gboolean ges_timeline_element_lookup_child (GESTimelineElement *self, + const gchar *prop_name, + GObject **child, + GParamSpec **pspec); +GES_API +void ges_timeline_element_get_child_property_by_pspec (GESTimelineElement * self, + GParamSpec * pspec, GValue * value); +GES_API +void ges_timeline_element_get_child_property_valist (GESTimelineElement * self, + const gchar * first_property_name, + va_list var_args); +GES_API +void ges_timeline_element_get_child_properties (GESTimelineElement *self, + const gchar * first_property_name, ...) G_GNUC_NULL_TERMINATED; +GES_API +void ges_timeline_element_set_child_property_valist (GESTimelineElement * self, + const gchar * first_property_name, + va_list var_args); +GES_API +void ges_timeline_element_set_child_property_by_pspec (GESTimelineElement * self, + GParamSpec * pspec, + const GValue * value); +GES_API +void ges_timeline_element_set_child_properties (GESTimelineElement * self, + const gchar * first_property_name, + ...) G_GNUC_NULL_TERMINATED; +GES_API +gboolean ges_timeline_element_set_child_property (GESTimelineElement *self, + const gchar *property_name, + const GValue * value); +GES_API +gboolean ges_timeline_element_set_child_property_full (GESTimelineElement *self, + const gchar *property_name, + const GValue * value, + GError ** error); +GES_API +gboolean ges_timeline_element_get_child_property (GESTimelineElement *self, + const gchar *property_name, + GValue * value); +GES_API +gboolean ges_timeline_element_add_child_property (GESTimelineElement * self, + GParamSpec *pspec, + GObject *child); +GES_API +gboolean ges_timeline_element_remove_child_property (GESTimelineElement * self, + GParamSpec *pspec); +GES_API +GESTimelineElement * ges_timeline_element_paste (GESTimelineElement * self, + GstClockTime paste_position); +GES_API +GESTrackType ges_timeline_element_get_track_types (GESTimelineElement * self); +GES_API +gboolean ges_timeline_element_get_natural_framerate (GESTimelineElement *self, + gint *framerate_n, + gint *framerate_d); + +GES_API +guint32 ges_timeline_element_get_layer_priority (GESTimelineElement * self); + +GES_API +gboolean ges_timeline_element_edit (GESTimelineElement * self, + GList * layers, + gint64 new_layer_priority, + GESEditMode mode, + GESEdge edge, + guint64 position); + +GES_API +gboolean ges_timeline_element_edit_full (GESTimelineElement * self, + gint64 new_layer_priority, + GESEditMode mode, + GESEdge edge, + guint64 position, + GError ** error); +G_END_DECLS diff --git a/ges/ges-timeline-tree.c b/ges/ges-timeline-tree.c new file mode 100644 index 0000000000..ed44884ed2 --- /dev/null +++ b/ges/ges-timeline-tree.c @@ -0,0 +1,2575 @@ +/* GStreamer Editing Services + * Copyright (C) 2019 Igalia S.L + * Author: 2019 Thibault Saunier <tsaunier@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-timeline-tree.h" +#include "ges-internal.h" +#include "ges-marker-list.h" + +GST_DEBUG_CATEGORY_STATIC (tree_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT tree_debug + +#define ELEMENT_EDGE_VALUE(e, edge) ((edge == GES_EDGE_END) ? _END (e) : _START (e)) + +typedef struct _SnappedPosition +{ + /* the element that was being snapped */ + GESTrackElement *element; + /* the position of element, and whether it is a negative position */ + gboolean negative; + GstClockTime position; + /* the element that was snapped to */ + GESTrackElement *snapped_to; + /* the snapped positioned */ + GstClockTime snapped; + /* the distance below which two elements can snap */ + GstClockTime distance; +} SnappedPosition; + +typedef enum +{ + EDIT_MOVE, + EDIT_TRIM_START, + EDIT_TRIM_END, + EDIT_TRIM_INPOINT_ONLY, +} ElementEditMode; + +typedef struct _EditData +{ + /* offsets to use */ + GstClockTime offset; + gint64 layer_offset; + /* actual values */ + GstClockTime duration; + GstClockTime start; + GstClockTime inpoint; + guint32 layer_priority; + /* mode */ + ElementEditMode mode; +} EditData; + +typedef struct _PositionData +{ + guint32 layer_priority; + GstClockTime start; + GstClockTime end; +} PositionData; + +/* *INDENT-OFF* */ +struct _TreeIterationData +{ + GNode *root; + gboolean res; + /* an error to set */ + GError **error; + + /* The element we are visiting */ + GESTimelineElement *element; + /* the position data of the visited element */ + PositionData *pos_data; + + /* All the TrackElement currently moving: owned by data */ + GHashTable *moving; + + /* Elements overlaping on the start/end of @element */ + GESTimelineElement *overlaping_on_start; + GESTimelineElement *overlaping_on_end; + GstClockTime overlap_start_final_time; + GstClockTime overlap_end_first_time; + + SnappedPosition *snap; + GList *sources; + GstClockTime position; + GstClockTime negative; + + GESEdge edge; + GList *neighbours; +} tree_iteration_data_init = { + .root = NULL, + .res = TRUE, + .element = NULL, + .pos_data = NULL, + .moving = NULL, + .overlaping_on_start = NULL, + .overlaping_on_end = NULL, + .overlap_start_final_time = GST_CLOCK_TIME_NONE, + .overlap_end_first_time = GST_CLOCK_TIME_NONE, + .snap = NULL, + .sources = NULL, + .position = GST_CLOCK_TIME_NONE, + .negative = FALSE, + .edge = GES_EDGE_NONE, + .neighbours = NULL, +}; +/* *INDENT-ON* */ + +typedef struct _TreeIterationData TreeIterationData; + +static EditData * +new_edit_data (ElementEditMode mode, GstClockTimeDiff offset, + gint64 layer_offset) +{ + EditData *data = g_new (EditData, 1); + + data->start = GST_CLOCK_TIME_NONE; + data->duration = GST_CLOCK_TIME_NONE; + data->inpoint = GST_CLOCK_TIME_NONE; + data->layer_priority = GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY; + + data->mode = mode; + data->offset = offset; + data->layer_offset = layer_offset; + + return data; +} + +static SnappedPosition * +new_snapped_position (GstClockTime distance) +{ + SnappedPosition *snap; + + if (distance == 0) + return NULL; + + snap = g_new0 (SnappedPosition, 1); + snap->position = GST_CLOCK_TIME_NONE; + snap->snapped = GST_CLOCK_TIME_NONE; + snap->distance = distance; + + return snap; +} + +static GHashTable * +new_edit_table () +{ + return g_hash_table_new_full (NULL, NULL, NULL, g_free); +} + +static GHashTable * +new_position_table () +{ + return g_hash_table_new_full (NULL, NULL, NULL, g_free); +} + +void +timeline_tree_init_debug (void) +{ + GST_DEBUG_CATEGORY_INIT (tree_debug, "gestree", + GST_DEBUG_FG_YELLOW, "timeline tree"); +} + + +static gboolean +print_node (GNode * node, gpointer unused_data) +{ + if (G_NODE_IS_ROOT (node)) { + gst_print ("Timeline: %p\n", node->data); + return FALSE; + } + + gst_print ("%*c- %" GES_FORMAT " - layer %" G_GINT32_FORMAT "\n", + 2 * g_node_depth (node), ' ', GES_ARGS (node->data), + ges_timeline_element_get_layer_priority (node->data)); + + return FALSE; +} + +void +timeline_tree_debug (GNode * root) +{ + g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + (GNodeTraverseFunc) print_node, NULL); +} + +static GNode * +find_node (GNode * root, gpointer element) +{ + return g_node_find (root, G_IN_ORDER, G_TRAVERSE_ALL, element); +} + +static void +timeline_element_parent_cb (GESTimelineElement * child, GParamSpec * arg + G_GNUC_UNUSED, GNode * root) +{ + GNode *new_parent_node = NULL, *node = find_node (root, child); + + if (child->parent) + new_parent_node = find_node (root, child->parent); + + if (!new_parent_node) + new_parent_node = root; + + g_node_unlink (node); + g_node_prepend (new_parent_node, node); +} + +void +timeline_tree_track_element (GNode * root, GESTimelineElement * element) +{ + GNode *node; + GNode *parent; + GESTimelineElement *toplevel; + + if (find_node (root, element)) { + return; + } + + g_signal_connect (element, "notify::parent", + G_CALLBACK (timeline_element_parent_cb), root); + + toplevel = ges_timeline_element_peak_toplevel (element); + if (toplevel == element) { + GST_DEBUG ("Tracking toplevel element %" GES_FORMAT, GES_ARGS (element)); + + node = g_node_prepend_data (root, element); + } else { + parent = find_node (root, element->parent); + GST_LOG ("%" GES_FORMAT "parent is %" GES_FORMAT, GES_ARGS (element), + GES_ARGS (element->parent)); + g_assert (parent); + node = g_node_prepend_data (parent, element); + } + + if (GES_IS_CONTAINER (element)) { + GList *tmp; + + for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { + GNode *child_node = find_node (root, tmp->data); + + if (child_node) { + g_node_unlink (child_node); + g_node_prepend (node, child_node); + } else { + timeline_tree_track_element (root, tmp->data); + } + } + } + + timeline_update_duration (root->data); +} + +void +timeline_tree_stop_tracking_element (GNode * root, GESTimelineElement * element) +{ + GNode *node = find_node (root, element); + + node = find_node (root, element); + + /* Move children to the parent */ + while (node->children) { + GNode *tmp = node->children; + g_node_unlink (tmp); + g_node_prepend (node->parent, tmp); + } + + g_assert (node); + GST_DEBUG ("Stop tracking %" GES_FORMAT, GES_ARGS (element)); + g_signal_handlers_disconnect_by_func (element, timeline_element_parent_cb, + root); + + g_node_destroy (node); + timeline_update_duration (root->data); +} + +/**************************************************** + * GstClockTime with over/underflow checking * + ****************************************************/ + +static GstClockTime +_clock_time_plus (GstClockTime time, GstClockTime add) +{ + if (!GST_CLOCK_TIME_IS_VALID (time) || !GST_CLOCK_TIME_IS_VALID (add)) + return GST_CLOCK_TIME_NONE; + + if (time >= (G_MAXUINT64 - add)) { + GST_ERROR ("The time %" G_GUINT64_FORMAT " would overflow when " + "adding %" G_GUINT64_FORMAT, time, add); + return GST_CLOCK_TIME_NONE; + } + return time + add; +} + +static GstClockTime +_clock_time_minus (GstClockTime time, GstClockTime minus, gboolean * negative) +{ + if (negative) + *negative = FALSE; + + if (!GST_CLOCK_TIME_IS_VALID (time) || !GST_CLOCK_TIME_IS_VALID (minus)) + return GST_CLOCK_TIME_NONE; + + if (time < minus) { + if (negative) { + *negative = TRUE; + return minus - time; + } + /* otherwise don't allow negative */ + GST_INFO ("The time %" G_GUINT64_FORMAT " would underflow when " + "subtracting %" G_GUINT64_FORMAT, time, minus); + return GST_CLOCK_TIME_NONE; + } + return time - minus; +} + +static GstClockTime +_clock_time_minus_diff (GstClockTime time, GstClockTimeDiff diff, + gboolean * negative) +{ + if (negative) + *negative = FALSE; + + if (!GST_CLOCK_TIME_IS_VALID (time)) + return GST_CLOCK_TIME_NONE; + + if (diff < 0) + return _clock_time_plus (time, -diff); + else + return _clock_time_minus (time, diff, negative); +} + +static GstClockTime +_abs_clock_time_distance (GstClockTime time1, GstClockTime time2) +{ + if (!GST_CLOCK_TIME_IS_VALID (time1) || !GST_CLOCK_TIME_IS_VALID (time2)) + return GST_CLOCK_TIME_NONE; + if (time1 > time2) + return time1 - time2; + else + return time2 - time1; +} + +static void +get_start_end_from_offset (GESTimelineElement * element, ElementEditMode mode, + GstClockTimeDiff offset, GstClockTime * start, gboolean * negative_start, + GstClockTime * end, gboolean * negative_end) +{ + GstClockTime current_end = + _clock_time_plus (element->start, element->duration); + GstClockTime new_start = GST_CLOCK_TIME_NONE, new_end = GST_CLOCK_TIME_NONE; + + switch (mode) { + case EDIT_MOVE: + new_start = + _clock_time_minus_diff (element->start, offset, negative_start); + new_end = _clock_time_minus_diff (current_end, offset, negative_end); + break; + case EDIT_TRIM_START: + new_start = + _clock_time_minus_diff (element->start, offset, negative_start); + new_end = current_end; + if (negative_end) + *negative_end = FALSE; + break; + case EDIT_TRIM_END: + new_start = element->start; + if (negative_start) + *negative_start = FALSE; + new_end = _clock_time_minus_diff (current_end, offset, negative_end); + break; + case EDIT_TRIM_INPOINT_ONLY: + GST_ERROR_OBJECT (element, "Trim in-point only not handled"); + break; + } + if (start) + *start = new_start; + if (end) + *end = new_end; +} + +/**************************************************** + * Snapping * + ****************************************************/ + +static void +snap_to_marker (GESTrackElement * element, GstClockTime position, + gboolean negative, GstClockTime marker_timestamp, + GESTrackElement * marker_parent, SnappedPosition * snap) +{ + GstClockTime distance; + + if (negative) + distance = _clock_time_plus (position, marker_timestamp); + else + distance = _abs_clock_time_distance (position, marker_timestamp); + + if (GST_CLOCK_TIME_IS_VALID (distance) && distance <= snap->distance) { + snap->negative = negative; + snap->position = position; + snap->distance = distance; + snap->snapped = marker_timestamp; + snap->element = element; + snap->snapped_to = marker_parent; + } +} + +static void +snap_to_edge (GESTrackElement * element, GstClockTime position, + gboolean negative, GESTrackElement * snap_to, GESEdge edge, + SnappedPosition * snap) +{ + GstClockTime edge_pos = ELEMENT_EDGE_VALUE (snap_to, edge); + GstClockTime distance; + + if (negative) + distance = _clock_time_plus (position, edge_pos); + else + distance = _abs_clock_time_distance (position, edge_pos); + + if (GST_CLOCK_TIME_IS_VALID (distance) && distance <= snap->distance) { + GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (element); + GESTimelineElement *snap_parent = GES_TIMELINE_ELEMENT_PARENT (snap_to); + GST_LOG_OBJECT (element, "%s (under %s) snapped with %" GES_FORMAT + "(under %s) from position %s%" GST_TIME_FORMAT " to %" + GST_TIME_FORMAT, GES_TIMELINE_ELEMENT_NAME (element), + parent ? parent->name : NULL, GES_ARGS (snap_to), + snap_parent ? snap_parent->name : NULL, negative ? "-" : "", + GST_TIME_ARGS (position), GST_TIME_ARGS (edge_pos)); + snap->negative = negative; + snap->position = position; + snap->distance = distance; + snap->snapped = edge_pos; + snap->element = element; + snap->snapped_to = snap_to; + } +} + +static void +find_marker_snap (const GESMetaContainer * container, const gchar * key, + const GValue * value, TreeIterationData * data) +{ + GESTrackElement *marker_parent, *moving; + GESClip *parent_clip; + GstClockTime timestamp; + GESMarkerList *marker_list; + GESMarker *marker; + GESMarkerFlags flags; + GObject *obj; + + if (!G_VALUE_HOLDS_OBJECT (value)) + return; + + obj = g_value_get_object (value); + if (!GES_IS_MARKER_LIST (obj)) + return; + + marker_list = GES_MARKER_LIST (obj); + + g_object_get (marker_list, "flags", &flags, NULL); + if (!(flags & GES_MARKER_FLAG_SNAPPABLE)) + return; + + marker_parent = GES_TRACK_ELEMENT ((gpointer) container); + moving = GES_TRACK_ELEMENT (data->element); + parent_clip = (GESClip *) GES_TIMELINE_ELEMENT_PARENT (marker_parent); + + /* Translate current position into the target clip's time domain */ + timestamp = + ges_clip_get_internal_time_from_timeline_time (parent_clip, marker_parent, + data->position, NULL); + marker = ges_marker_list_get_closest (marker_list, timestamp); + + if (marker == NULL) + return; + + /* Make timestamp timeline-relative again */ + g_object_get (marker, "position", ×tamp, NULL); + timestamp = + ges_clip_get_timeline_time_from_internal_time (parent_clip, marker_parent, + timestamp, NULL); + snap_to_marker (moving, data->position, data->negative, timestamp, + marker_parent, data->snap); + + g_object_unref (marker); +} + +static gboolean +find_snap (GNode * node, TreeIterationData * data) +{ + GESTimelineElement *element = node->data; + GESTrackElement *track_el, *moving; + + /* Only snap to sources */ + /* Maybe we should allow snapping to anything that isn't an + * auto-transition? */ + if (!GES_IS_SOURCE (element)) + return FALSE; + + /* don't snap to anything we are moving */ + if (g_hash_table_contains (data->moving, element)) + return FALSE; + + track_el = GES_TRACK_ELEMENT (element); + moving = GES_TRACK_ELEMENT (data->element); + snap_to_edge (moving, data->position, data->negative, track_el, + GES_EDGE_END, data->snap); + snap_to_edge (moving, data->position, data->negative, track_el, + GES_EDGE_START, data->snap); + + ges_meta_container_foreach (GES_META_CONTAINER (element), + (GESMetaForeachFunc) find_marker_snap, data); + + return FALSE; +} + +static void +find_snap_for_element (GESTrackElement * element, GstClockTime position, + gboolean negative, TreeIterationData * data) +{ + data->element = GES_TIMELINE_ELEMENT (element); + data->position = position; + data->negative = negative; + g_node_traverse (data->root, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, + (GNodeTraverseFunc) find_snap, data); +} + +/* find up to one source at the edge */ +static gboolean +find_source_at_edge (GNode * node, TreeIterationData * data) +{ + GESEdge edge = data->edge; + GESTimelineElement *element = node->data; + GESTimelineElement *ancestor = data->element; + + if (!GES_IS_SOURCE (element)) + return FALSE; + + if (ELEMENT_EDGE_VALUE (element, edge) == ELEMENT_EDGE_VALUE (ancestor, edge)) { + data->sources = g_list_append (data->sources, element); + return TRUE; + } + return FALSE; +} + +static gboolean +find_sources (GNode * node, TreeIterationData * data) +{ + GESTimelineElement *element = node->data; + if (GES_IS_SOURCE (element)) + data->sources = g_list_append (data->sources, element); + return FALSE; +} + +/* Tries to find a new snap to the start or end edge of one of the + * descendant sources of @element, depending on @mode, and updates @offset + * by the size of the jump. + * Any elements in @moving are not snapped to. + */ +static gboolean +timeline_tree_snap (GNode * root, GESTimelineElement * element, + ElementEditMode mode, GstClockTimeDiff * offset, GHashTable * moving, + SnappedPosition * snap) +{ + gboolean ret = FALSE; + TreeIterationData data = tree_iteration_data_init; + GList *tmp; + GNode *node; + + if (!snap) + return TRUE; + + /* get the sources we can snap to */ + data.root = root; + data.moving = moving; + data.sources = NULL; + data.snap = snap; + data.element = element; + + node = find_node (root, element); + + if (!node) { + GST_ERROR_OBJECT (element, "Not being tracked"); + goto done; + } + + switch (mode) { + case EDIT_MOVE: + /* can snap with any source below the element, if any */ + g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, + (GNodeTraverseFunc) find_sources, &data); + break; + case EDIT_TRIM_START: + /* can only snap with sources at the start of the element. + * only need one such source since all will share the same start. + * if there is no source at the start edge, then snapping is not + * possible */ + data.edge = GES_EDGE_START; + g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, + (GNodeTraverseFunc) find_source_at_edge, &data); + break; + case EDIT_TRIM_END: + /* can only snap with sources at the end of the element. + * only need one such source since all will share the same end. + * if there is no source at the end edge, then snapping is not + * possible */ + data.edge = GES_EDGE_END; + g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, + (GNodeTraverseFunc) find_source_at_edge, &data); + break; + case EDIT_TRIM_INPOINT_ONLY: + GST_ERROR_OBJECT (element, "Trim in-point only not handled"); + goto done; + } + + for (tmp = data.sources; tmp; tmp = tmp->next) { + GESTrackElement *source = tmp->data; + GstClockTime end, start; + gboolean negative_end, negative_start; + + /* Allow negative start/end positions in case a snap makes them valid! + * But we can still only snap to an existing edge in the timeline, + * which should be a valid time */ + get_start_end_from_offset (GES_TIMELINE_ELEMENT (source), mode, *offset, + &start, &negative_start, &end, &negative_end); + + if (!GST_CLOCK_TIME_IS_VALID (start)) { + GST_INFO_OBJECT (element, "Cannot edit element %" GES_FORMAT + " with offset %" G_GINT64_FORMAT " because it would result in " + "an invalid start", GES_ARGS (element), *offset); + goto done; + } + + if (!GST_CLOCK_TIME_IS_VALID (end)) { + GST_INFO_OBJECT (element, "Cannot edit element %" GES_FORMAT + " with offset %" G_GINT64_FORMAT " because it would result in " + "an invalid end", GES_ARGS (element), *offset); + goto done; + } + + switch (mode) { + case EDIT_MOVE: + /* try snap start and end */ + find_snap_for_element (source, end, negative_end, &data); + find_snap_for_element (source, start, negative_start, &data); + break; + case EDIT_TRIM_START: + /* only snap the start of the source */ + find_snap_for_element (source, start, negative_start, &data); + break; + case EDIT_TRIM_END: + /* only snap the start of the source */ + find_snap_for_element (source, end, negative_end, &data); + break; + case EDIT_TRIM_INPOINT_ONLY: + GST_ERROR_OBJECT (element, "Trim in-point only not handled"); + goto done; + } + } + + if (GST_CLOCK_TIME_IS_VALID (snap->snapped)) { + if (snap->negative) + *offset -= (snap->position + snap->snapped); + else + *offset += (snap->position - snap->snapped); + GST_INFO_OBJECT (element, "Element %s under %s snapped with %" GES_FORMAT + " from %s%" GST_TIME_FORMAT " to %" GST_TIME_FORMAT, + GES_TIMELINE_ELEMENT_NAME (snap->element), element->name, + GES_ARGS (snap->snapped_to), snap->negative ? "-" : "", + GST_TIME_ARGS (snap->position), GST_TIME_ARGS (snap->snapped)); + } else { + GST_INFO_OBJECT (element, "Nothing within snapping distance of %s", + element->name); + } + + ret = TRUE; + +done: + g_list_free (data.sources); + + return ret; +} + +/**************************************************** + * Check Overlaps * + ****************************************************/ + +#define _SOURCE_FORMAT "\"%s\"%s%s%s" +#define _SOURCE_ARGS(element) \ + element->name, element->parent ? " (parent: \"" : "", \ + element->parent ? element->parent->name : "", \ + element->parent ? "\")" : "" + +static void +set_full_overlap_error (GError ** error, GESTimelineElement * super, + GESTimelineElement * sub, GESTrack * track) +{ + if (error) { + gchar *track_name = gst_object_get_name (GST_OBJECT (track)); + g_set_error (error, GES_ERROR, GES_ERROR_INVALID_OVERLAP_IN_TRACK, + "The source " _SOURCE_FORMAT " would totally overlap the " + "source " _SOURCE_FORMAT " in the track \"%s\"", _SOURCE_ARGS (super), + _SOURCE_ARGS (sub), track_name); + g_free (track_name); + } +} + +static void +set_triple_overlap_error (GError ** error, GESTimelineElement * first, + GESTimelineElement * second, GESTimelineElement * third, GESTrack * track) +{ + if (error) { + gchar *track_name = gst_object_get_name (GST_OBJECT (track)); + g_set_error (error, GES_ERROR, GES_ERROR_INVALID_OVERLAP_IN_TRACK, + "The sources " _SOURCE_FORMAT ", " _SOURCE_FORMAT " and " + _SOURCE_FORMAT " would all overlap at the same point in the " + "track \"%s\"", _SOURCE_ARGS (first), _SOURCE_ARGS (second), + _SOURCE_ARGS (third), track_name); + g_free (track_name); + } +} + +#define _ELEMENT_FORMAT \ + "%s (under %s) [%" GST_TIME_FORMAT " - %" GST_TIME_FORMAT "] " \ + "(layer: %" G_GUINT32_FORMAT ") (track :%" GST_PTR_FORMAT ")" +#define _E_ARGS e->name, e->parent ? e->parent->name : NULL, \ + GST_TIME_ARGS (start), GST_TIME_ARGS (end), layer_prio, track +#define _CMP_ARGS cmp->name, cmp->parent ? cmp->parent->name : NULL, \ + GST_TIME_ARGS (cmp_start), GST_TIME_ARGS (cmp_end), cmp_layer_prio, \ + cmp_track + +static gboolean +check_overlap_with_element (GNode * node, TreeIterationData * data) +{ + GESTimelineElement *e = node->data, *cmp = data->element; + GstClockTime start, end, cmp_start, cmp_end; + guint32 layer_prio, cmp_layer_prio; + GESTrack *track, *cmp_track; + PositionData *pos_data; + + if (e == cmp) + return FALSE; + + if (!GES_IS_SOURCE (e) || !GES_IS_SOURCE (cmp)) + return FALSE; + + /* get position of compared element */ + pos_data = data->pos_data; + if (pos_data) { + cmp_start = pos_data->start; + cmp_end = pos_data->end; + cmp_layer_prio = pos_data->layer_priority; + } else { + cmp_start = cmp->start; + cmp_end = cmp_start + cmp->duration; + cmp_layer_prio = ges_timeline_element_get_layer_priority (cmp); + } + + /* get position of the node */ + if (data->moving) + pos_data = g_hash_table_lookup (data->moving, e); + else + pos_data = NULL; + + if (pos_data) { + start = pos_data->start; + end = pos_data->end; + layer_prio = pos_data->layer_priority; + } else { + start = e->start; + end = start + e->duration; + layer_prio = ges_timeline_element_get_layer_priority (e); + } + + track = ges_track_element_get_track (GES_TRACK_ELEMENT (e)); + cmp_track = ges_track_element_get_track (GES_TRACK_ELEMENT (cmp)); + GST_LOG ("Checking overlap between " _ELEMENT_FORMAT " and " + _ELEMENT_FORMAT, _CMP_ARGS, _E_ARGS); + + if (track != cmp_track || track == NULL || cmp_track == NULL) { + GST_LOG (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " are not in the " + "same track", _CMP_ARGS, _E_ARGS); + return FALSE; + } + + if (layer_prio != cmp_layer_prio) { + GST_LOG (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " are not in the " + "same layer", _CMP_ARGS, _E_ARGS); + return FALSE; + } + + if (start >= cmp_end || cmp_start >= end) { + /* They do not overlap at all */ + GST_LOG (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " do not overlap", + _CMP_ARGS, _E_ARGS); + return FALSE; + } + + if (cmp_start <= start && cmp_end >= end) { + /* cmp fully overlaps e */ + GST_INFO (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " fully overlap", + _CMP_ARGS, _E_ARGS); + set_full_overlap_error (data->error, cmp, e, track); + goto error; + } + + if (cmp_start >= start && cmp_end <= end) { + /* e fully overlaps cmp */ + GST_INFO (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " fully overlap", + _CMP_ARGS, _E_ARGS); + set_full_overlap_error (data->error, e, cmp, track); + goto error; + } + + if (cmp_start < end && cmp_start > start) { + /* cmp_start is between the start and end of the node */ + GST_LOG (_ELEMENT_FORMAT " is overlapped at its start by " + _ELEMENT_FORMAT ". Overlap ends at %" GST_TIME_FORMAT, + _CMP_ARGS, _E_ARGS, GST_TIME_ARGS (end)); + if (data->overlaping_on_start) { + GST_INFO (_ELEMENT_FORMAT " is overlapped by %s and %s on its start", + _CMP_ARGS, data->overlaping_on_start->name, e->name); + set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_start, + track); + goto error; + } + if (GST_CLOCK_TIME_IS_VALID (data->overlap_end_first_time) && + end > data->overlap_end_first_time) { + GST_INFO (_ELEMENT_FORMAT " overlaps %s on its start and %s on its " + "end, but they already overlap each other", _CMP_ARGS, e->name, + data->overlaping_on_end->name); + set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_end, + track); + goto error; + } + /* record the time at which the overlapped ends */ + data->overlap_start_final_time = end; + data->overlaping_on_start = e; + } + + if (cmp_end < end && cmp_end > start) { + /* cmp_end is between the start and end of the node */ + GST_LOG (_ELEMENT_FORMAT " is overlapped at its end by " + _ELEMENT_FORMAT ". Overlap starts at %" GST_TIME_FORMAT, + _CMP_ARGS, _E_ARGS, GST_TIME_ARGS (start)); + + if (data->overlaping_on_end) { + GST_INFO (_ELEMENT_FORMAT " is overlapped by %s and %s on its end", + _CMP_ARGS, data->overlaping_on_end->name, e->name); + set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_end, + track); + goto error; + } + if (GST_CLOCK_TIME_IS_VALID (data->overlap_start_final_time) && + start < data->overlap_start_final_time) { + GST_INFO (_ELEMENT_FORMAT " overlaps %s on its end and %s on its " + "start, but they already overlap each other", _CMP_ARGS, e->name, + data->overlaping_on_start->name); + set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_start, + track); + goto error; + } + /* record the time at which the overlapped starts */ + data->overlap_end_first_time = start; + data->overlaping_on_end = e; + } + + return FALSE; + +error: + data->res = FALSE; + return TRUE; +} + +/* check and find the overlaps with the element at node */ +static gboolean +check_all_overlaps_with_element (GNode * node, TreeIterationData * data) +{ + GESTimelineElement *element = node->data; + if (GES_IS_SOURCE (element)) { + data->element = element; + data->overlaping_on_start = NULL; + data->overlaping_on_end = NULL; + data->overlap_start_final_time = GST_CLOCK_TIME_NONE; + data->overlap_end_first_time = GST_CLOCK_TIME_NONE; + if (data->moving) + data->pos_data = g_hash_table_lookup (data->moving, element); + else + data->pos_data = NULL; + + g_node_traverse (data->root, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, + (GNodeTraverseFunc) check_overlap_with_element, data); + + return !data->res; + } + return FALSE; +} + +static gboolean +check_moving_overlaps (GNode * node, TreeIterationData * data) +{ + if (g_hash_table_contains (data->moving, node->data)) + return check_all_overlaps_with_element (node, data); + return FALSE; +} + +/* whether the elements in moving can be moved to their corresponding + * PositionData */ +static gboolean +timeline_tree_can_move_elements (GNode * root, GHashTable * moving, + GError ** error) +{ + TreeIterationData data = tree_iteration_data_init; + data.moving = moving; + data.root = root; + data.res = TRUE; + data.error = error; + /* sufficient to check the leaves, which is all the track elements or + * empty clips + * should also be sufficient to only check the moving elements */ + g_node_traverse (root, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, + (GNodeTraverseFunc) check_moving_overlaps, &data); + + return data.res; +} + +/**************************************************** + * Setting Edit Data * + ****************************************************/ + +static void +set_negative_start_error (GError ** error, GESTimelineElement * element, + GstClockTime neg_start) +{ + g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME, + "The element \"%s\" would have a negative start of -%" + GST_TIME_FORMAT, element->name, GST_TIME_ARGS (neg_start)); +} + +static void +set_negative_duration_error (GError ** error, GESTimelineElement * element, + GstClockTime neg_duration) +{ + g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME, + "The element \"%s\" would have a negative duration of -%" + GST_TIME_FORMAT, element->name, GST_TIME_ARGS (neg_duration)); +} + +static void +set_negative_inpoint_error (GError ** error, GESTimelineElement * element, + GstClockTime neg_inpoint) +{ + g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME, + "The element \"%s\" would have a negative in-point of -%" + GST_TIME_FORMAT, element->name, GST_TIME_ARGS (neg_inpoint)); +} + +static void +set_negative_layer_error (GError ** error, GESTimelineElement * element, + gint64 neg_layer) +{ + g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_LAYER, + "The element \"%s\" would have a negative layer priority of -%" + G_GINT64_FORMAT, element->name, neg_layer); +} + +static void +set_breaks_duration_limit_error (GError ** error, GESClip * clip, + GstClockTime duration, GstClockTime duration_limit) +{ + g_set_error (error, GES_ERROR, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT, + "The clip \"%s\" would have a duration of %" GST_TIME_FORMAT + " that would break its duration-limit of %" GST_TIME_FORMAT, + GES_TIMELINE_ELEMENT_NAME (clip), GST_TIME_ARGS (duration), + GST_TIME_ARGS (duration_limit)); +} + +static void +set_inpoint_breaks_max_duration_error (GError ** error, + GESTimelineElement * element, GstClockTime inpoint, + GstClockTime max_duration) +{ + g_set_error (error, GES_ERROR, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT, + "The element \"%s\" would have an in-point of %" GST_TIME_FORMAT + " that would break its max-duration of %" GST_TIME_FORMAT, + GES_TIMELINE_ELEMENT_NAME (element), GST_TIME_ARGS (inpoint), + GST_TIME_ARGS (max_duration)); +} + +static gboolean +set_layer_priority (GESTimelineElement * element, EditData * data, + GError ** error) +{ + gint64 layer_offset = data->layer_offset; + guint32 layer_prio = ges_timeline_element_get_layer_priority (element); + + if (!layer_offset) + return TRUE; + + if (layer_prio == GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY) { + GST_INFO_OBJECT (element, "Cannot shift %s to a new layer because it " + "has no layer priority", element->name); + return FALSE; + } + + if (layer_offset > (gint64) layer_prio) { + GST_INFO_OBJECT (element, "%s would have a negative layer priority (%" + G_GUINT32_FORMAT " - %" G_GINT64_FORMAT ")", element->name, + layer_prio, layer_offset); + set_negative_layer_error (error, element, + layer_offset - (gint64) layer_prio); + return FALSE; + } + if ((layer_prio - (gint64) layer_offset) >= G_MAXUINT32) { + GST_ERROR_OBJECT (element, "%s would have an overflowing layer priority", + element->name); + return FALSE; + } + + data->layer_priority = (guint32) (layer_prio - (gint64) layer_offset); + + if (ges_timeline_layer_priority_in_gap (element->timeline, + data->layer_priority)) { + GST_ERROR_OBJECT (element, "Edit layer %" G_GUINT32_FORMAT " would " + "be within a gap in the timeline layers", data->layer_priority); + return FALSE; + } + + GST_INFO_OBJECT (element, "%s will move to layer %" G_GUINT32_FORMAT, + element->name, data->layer_priority); + + return TRUE; +} + +#define _CHECK_END(element, start, duration) \ + if (!GST_CLOCK_TIME_IS_VALID (_clock_time_plus (start, duration))) { \ + GST_INFO_OBJECT (element, "Cannot edit %s because it would result in " \ + "an invalid end", element->name); \ + return FALSE; \ + } + +static gboolean +set_edit_move_values (GESTimelineElement * element, EditData * data, + GError ** error) +{ + gboolean negative = FALSE; + GstClockTime new_start = + _clock_time_minus_diff (element->start, data->offset, &negative); + if (negative || !GST_CLOCK_TIME_IS_VALID (new_start)) { + GST_INFO_OBJECT (element, "Cannot move %" GES_FORMAT " with offset %" + G_GINT64_FORMAT " because it would result in an invalid start", + GES_ARGS (element), data->offset); + if (negative) + set_negative_start_error (error, element, new_start); + return FALSE; + } + _CHECK_END (element, new_start, element->duration); + data->start = new_start; + + if (GES_IS_GROUP (element)) + return TRUE; + + GST_INFO_OBJECT (element, "%s will move by setting start to %" + GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->start)); + + return set_layer_priority (element, data, error); +} + +static gboolean +set_edit_trim_start_clip_inpoints (GESClip * clip, EditData * clip_data, + GHashTable * edit_table, GError ** error) +{ + gboolean ret = FALSE; + GList *tmp; + GstClockTime duration_limit; + GstClockTime clip_inpoint; + GstClockTime new_start = clip_data->start; + gboolean no_core = FALSE; + GHashTable *child_inpoints; + + child_inpoints = g_hash_table_new_full (NULL, NULL, gst_object_unref, g_free); + + clip_inpoint = ges_clip_get_core_internal_time_from_timeline_time (clip, + new_start, &no_core, error); + + if (no_core) { + GST_INFO_OBJECT (clip, "Clip %" GES_FORMAT " has no active core " + "children with an internal source. Not setting in-point during " + "trim to start", GES_ARGS (clip)); + clip_inpoint = GES_TIMELINE_ELEMENT_INPOINT (clip); + } else if (!GST_CLOCK_TIME_IS_VALID (clip_inpoint)) { + GST_INFO_OBJECT (clip, "Cannot trim start of %" GES_FORMAT + " with offset %" G_GINT64_FORMAT " because it would result in an " + "invalid in-point for its core children", GES_ARGS (clip), + clip_data->offset); + goto done; + } else { + GST_INFO_OBJECT (clip, "Clip %" GES_FORMAT " will have its in-point " + " set to %" GST_TIME_FORMAT " because its start is being trimmed " + "to %" GST_TIME_FORMAT, GES_ARGS (clip), + GST_TIME_ARGS (clip_inpoint), GST_TIME_ARGS (new_start)); + clip_data->inpoint = clip_inpoint; + } + + /* need to set in-point of active non-core children to keep their + * internal content at the same timeline position */ + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + GESTrackElement *el = tmp->data; + GstClockTime new_inpoint = child->inpoint; + GstClockTime *inpoint_p; + + if (ges_track_element_has_internal_source (el)) { + if (ges_track_element_is_core (el)) { + new_inpoint = clip_inpoint; + } else if (ges_track_element_is_active (el)) { + EditData *data; + + if (g_hash_table_contains (edit_table, child)) { + GST_ERROR_OBJECT (child, "Already set to be edited"); + goto done; + } + + new_inpoint = ges_clip_get_internal_time_from_timeline_time (clip, el, + new_start, error); + + if (!GST_CLOCK_TIME_IS_VALID (new_inpoint)) { + GST_INFO_OBJECT (clip, "Cannot trim start of %" GES_FORMAT + " to %" GST_TIME_FORMAT " because it would result in an " + "invalid in-point for the non-core child %" GES_FORMAT, + GES_ARGS (clip), GST_TIME_ARGS (new_start), GES_ARGS (child)); + goto done; + } + + GST_INFO_OBJECT (child, "Setting track element %s to trim " + "in-point to %" GST_TIME_FORMAT " since the parent clip %" + GES_FORMAT " is being trimmed to start %" GST_TIME_FORMAT, + child->name, GST_TIME_ARGS (new_inpoint), GES_ARGS (clip), + GST_TIME_ARGS (new_start)); + + data = new_edit_data (EDIT_TRIM_INPOINT_ONLY, 0, 0); + data->inpoint = new_inpoint; + g_hash_table_insert (edit_table, child, data); + } + } + + if (GES_CLOCK_TIME_IS_LESS (child->maxduration, new_inpoint)) { + GST_INFO_OBJECT (clip, "Cannot trim start of %" GES_FORMAT + " to %" GST_TIME_FORMAT " because it would result in an " + "in-point of %" GST_TIME_FORMAT " for the child %" GES_FORMAT + ", which breaks its max-duration", GES_ARGS (clip), + GST_TIME_ARGS (new_start), GST_TIME_ARGS (new_inpoint), + GES_ARGS (child)); + + set_inpoint_breaks_max_duration_error (error, child, new_inpoint, + child->maxduration); + goto done; + } + + inpoint_p = g_new (GstClockTime, 1); + *inpoint_p = new_inpoint; + g_hash_table_insert (child_inpoints, gst_object_ref (child), inpoint_p); + } + + duration_limit = + ges_clip_duration_limit_with_new_children_inpoints (clip, child_inpoints); + + if (GES_CLOCK_TIME_IS_LESS (duration_limit, clip_data->duration)) { + GST_INFO_OBJECT (clip, "Cannot trim start of %" GES_FORMAT + " to %" GST_TIME_FORMAT " because it would result in a " + "duration of %" GST_TIME_FORMAT " that breaks its new " + "duration-limit of %" GST_TIME_FORMAT, GES_ARGS (clip), + GST_TIME_ARGS (new_start), GST_TIME_ARGS (clip_data->duration), + GST_TIME_ARGS (duration_limit)); + + set_breaks_duration_limit_error (error, clip, clip_data->duration, + duration_limit); + goto done; + } + + ret = TRUE; + +done: + g_hash_table_unref (child_inpoints); + + return ret; +} + +/* trim the start of a clip or a track element */ +static gboolean +set_edit_trim_start_values (GESTimelineElement * element, EditData * data, + GHashTable * edit_table, GError ** error) +{ + gboolean negative = FALSE; + GstClockTime new_duration; + GstClockTime new_start = + _clock_time_minus_diff (element->start, data->offset, &negative); + + if (negative || !GST_CLOCK_TIME_IS_VALID (new_start)) { + GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT + " with offset %" G_GINT64_FORMAT " because it would result in an " + "invalid start", GES_ARGS (element), data->offset); + if (negative) + set_negative_start_error (error, element, new_start); + return FALSE; + } + + new_duration = + _clock_time_minus_diff (element->duration, -data->offset, &negative); + + if (negative || !GST_CLOCK_TIME_IS_VALID (new_duration)) { + GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT + " with offset %" G_GINT64_FORMAT " because it would result in an " + "invalid duration", GES_ARGS (element), data->offset); + if (negative) + set_negative_duration_error (error, element, new_duration); + return FALSE; + } + _CHECK_END (element, new_start, new_duration); + + data->start = new_start; + data->duration = new_duration; + + if (GES_IS_GROUP (element)) + return TRUE; + + if (GES_IS_CLIP (element)) { + if (!set_edit_trim_start_clip_inpoints (GES_CLIP (element), data, + edit_table, error)) + return FALSE; + } else if (GES_IS_TRACK_ELEMENT (element) + && ges_track_element_has_internal_source (GES_TRACK_ELEMENT (element))) { + GstClockTime new_inpoint = + _clock_time_minus_diff (element->inpoint, data->offset, &negative); + + if (negative || !GST_CLOCK_TIME_IS_VALID (new_inpoint)) { + GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT + " with offset %" G_GINT64_FORMAT " because it would result in " + "an invalid in-point", GES_ARGS (element), data->offset); + if (negative) + set_negative_inpoint_error (error, element, new_inpoint); + return FALSE; + } + } + + GST_INFO_OBJECT (element, "%s will trim start by setting start to %" + GST_TIME_FORMAT ", in-point to %" GST_TIME_FORMAT " and duration " + "to %" GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->start), + GST_TIME_ARGS (data->inpoint), GST_TIME_ARGS (data->duration)); + + return set_layer_priority (element, data, error); +} + +/* trim the end of a clip or a track element */ +static gboolean +set_edit_trim_end_values (GESTimelineElement * element, EditData * data, + GError ** error) +{ + gboolean negative = FALSE; + GstClockTime new_duration = + _clock_time_minus_diff (element->duration, data->offset, &negative); + if (negative || !GST_CLOCK_TIME_IS_VALID (new_duration)) { + GST_INFO_OBJECT (element, "Cannot trim end of %" GES_FORMAT + " with offset %" G_GINT64_FORMAT " because it would result in an " + "invalid duration", GES_ARGS (element), data->offset); + if (negative) + set_negative_duration_error (error, element, new_duration); + return FALSE; + } + _CHECK_END (element, element->start, new_duration); + + if (GES_IS_CLIP (element)) { + GESClip *clip = GES_CLIP (element); + GstClockTime limit = ges_clip_get_duration_limit (clip); + + if (GES_CLOCK_TIME_IS_LESS (limit, new_duration)) { + GST_INFO_OBJECT (element, "Cannot trim end of %" GES_FORMAT + " with offset %" G_GINT64_FORMAT " because the duration would " + "exceed the clip's duration-limit %" G_GINT64_FORMAT, + GES_ARGS (element), data->offset, limit); + + set_breaks_duration_limit_error (error, clip, new_duration, limit); + return FALSE; + } + } + + data->duration = new_duration; + + if (GES_IS_GROUP (element)) + return TRUE; + + GST_INFO_OBJECT (element, "%s will trim end by setting duration to %" + GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->duration)); + + return set_layer_priority (element, data, error); +} + +static gboolean +set_edit_values (GESTimelineElement * element, EditData * data, + GHashTable * edit_table, GError ** error) +{ + switch (data->mode) { + case EDIT_MOVE: + return set_edit_move_values (element, data, error); + case EDIT_TRIM_START: + return set_edit_trim_start_values (element, data, edit_table, error); + case EDIT_TRIM_END: + return set_edit_trim_end_values (element, data, error); + case EDIT_TRIM_INPOINT_ONLY: + GST_ERROR_OBJECT (element, "Trim in-point only not handled"); + return FALSE; + } + return FALSE; +} + +static gboolean +add_clips_to_list (GNode * node, GList ** list) +{ + GESTimelineElement *element = node->data; + GESTimelineElement *clip = NULL; + + if (GES_IS_CLIP (element)) + clip = element; + else if (GES_IS_CLIP (element->parent)) + clip = element->parent; + + if (clip && !g_list_find (*list, clip)) + *list = g_list_append (*list, clip); + + return FALSE; +} + +static gboolean +replace_group_with_clip_edits (GNode * root, GESTimelineElement * group, + GHashTable * edit_table, GError ** err) +{ + gboolean ret = TRUE; + GList *tmp, *clips = NULL; + GNode *node = find_node (root, group); + GstClockTime new_end, new_start; + ElementEditMode mode; + gint64 layer_offset; + + if (!node) { + GST_ERROR_OBJECT (group, "Not being tracked"); + goto error; + } + + /* new context for the lifespan of group_data */ + { + EditData *group_edit = g_hash_table_lookup (edit_table, group); + + if (!group_edit) { + GST_ERROR_OBJECT (group, "Edit data for group was missing"); + goto error; + } + + group_edit->start = group->start; + group_edit->duration = group->duration; + + /* should only set the start and duration fields, table should not be + * needed, so we pass NULL */ + if (!set_edit_values (group, group_edit, NULL, err)) + goto error; + + new_start = group_edit->start; + new_end = _clock_time_plus (group_edit->start, group_edit->duration); + + if (!GST_CLOCK_TIME_IS_VALID (new_start) + || !GST_CLOCK_TIME_IS_VALID (new_end)) { + GST_ERROR_OBJECT (group, "Edit data gave an invalid start or end"); + goto error; + } + + layer_offset = group_edit->layer_offset; + mode = group_edit->mode; + + /* can traverse leaves to find all the clips since they are at _most_ + * one step above the track elements */ + g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, + (GNodeTraverseFunc) add_clips_to_list, &clips); + + if (!clips) { + GST_INFO_OBJECT (group, "Contains no clips, so cannot be edited"); + goto error; + } + + if (!g_hash_table_remove (edit_table, group)) { + GST_ERROR_OBJECT (group, "Could not replace the group in the edit list"); + goto error; + } + /* removing the group from the table frees group_edit */ + } + + for (tmp = clips; tmp; tmp = tmp->next) { + GESTimelineElement *clip = tmp->data; + gboolean edit = FALSE; + GstClockTimeDiff offset = G_MAXINT64; + ElementEditMode clip_mode = mode; + + /* if at the edge of the group and being trimmed forward or backward */ + if (mode == EDIT_MOVE) { + /* same offset as the group */ + edit = TRUE; + offset = group->start - new_start; + + GST_INFO_OBJECT (clip, "Setting clip %s to moving with offset %" + G_GINT64_FORMAT " since an ancestor group %" GES_FORMAT + " is moving to %" GST_TIME_FORMAT, clip->name, offset, + GES_ARGS (group), GST_TIME_ARGS (new_start)); + + } else if ((mode == EDIT_TRIM_START) + && (clip->start <= new_start || clip->start == group->start)) { + /* trim to same start */ + edit = TRUE; + offset = clip->start - new_start; + + GST_INFO_OBJECT (clip, "Setting clip %s to trim start with offset %" + G_GINT64_FORMAT " since an ancestor group %" GES_FORMAT " is " + "being trimmed to start %" GST_TIME_FORMAT, clip->name, offset, + GES_ARGS (group), GST_TIME_ARGS (new_start)); + + } else if (mode == EDIT_TRIM_END + && (_END (clip) >= new_end || _END (clip) == _END (group))) { + /* trim to same end */ + edit = TRUE; + offset = _END (clip) - new_end; + + GST_INFO_OBJECT (clip, "Setting clip %s to trim end with offset %" + G_GINT64_FORMAT " since an ancestor group %" GES_FORMAT " is " + "being trimmed to end %" GST_TIME_FORMAT, clip->name, offset, + GES_ARGS (group), GST_TIME_ARGS (new_end)); + + } else if (layer_offset) { + /* still need to move layer */ + edit = TRUE; + clip_mode = EDIT_MOVE; + offset = 0; + } + if (edit) { + EditData *clip_data; + + if (layer_offset) + GST_INFO_OBJECT (clip, "Setting clip %s to move to new layer with " + "offset %" G_GINT64_FORMAT " since an ancestor group %" + GES_FORMAT " is being moved with the same offset", clip->name, + layer_offset, GES_ARGS (group)); + + if (g_hash_table_contains (edit_table, clip)) { + GST_ERROR_OBJECT (clip, "Already set to be edited"); + goto error; + } + clip_data = new_edit_data (clip_mode, offset, layer_offset); + g_hash_table_insert (edit_table, clip, clip_data); + if (!set_edit_values (clip, clip_data, edit_table, err)) + goto error; + } + } + +done: + g_list_free (clips); + return ret; + +error: + ret = FALSE; + goto done; +} + +/* set the edit values for the entries in @edits + * any groups in @edits will be replaced by their clip children */ +static gboolean +timeline_tree_set_element_edit_values (GNode * root, GHashTable * edits, + GError ** err) +{ + gboolean ret = TRUE; + GESTimelineElement *element; + EditData *edit_data; + /* content of edit table may change when group edits are replaced by + * clip edits and clip edits introduce edits for non-core children */ + GList *tmp, *elements = g_hash_table_get_keys (edits); + + for (tmp = elements; tmp; tmp = tmp->next) { + gboolean res; + element = tmp->data; + edit_data = g_hash_table_lookup (edits, element); + if (!edit_data) { + GST_ERROR_OBJECT (element, "No edit data for the element"); + goto error; + } + if (GES_IS_GROUP (element)) + res = replace_group_with_clip_edits (root, element, edits, err); + else + res = set_edit_values (element, edit_data, edits, err); + if (!res) + goto error; + } + +done: + g_list_free (elements); + + return ret; + +error: + ret = FALSE; + goto done; +} + +/* set the moving PositionData by using their parent clips. + * @edit_table should already have had its values set, and any group edits + * replaced by clip edits. */ +static void +set_moving_positions_from_edits (GHashTable * moving, GHashTable * edit_table) +{ + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, moving); + while (g_hash_table_iter_next (&iter, &key, &value)) { + GESTimelineElement *element = key; + PositionData *pos = value; + GESTimelineElement *parent; + EditData *edit; + + /* a track element will end up with the same start and end as its clip */ + /* if no parent, act as own parent */ + parent = element->parent ? element->parent : element; + edit = g_hash_table_lookup (edit_table, parent); + + if (edit && GST_CLOCK_TIME_IS_VALID (edit->start)) + pos->start = edit->start; + else + pos->start = element->start; + + if (edit && GST_CLOCK_TIME_IS_VALID (edit->duration)) + pos->end = pos->start + edit->duration; + else + pos->end = pos->start + element->duration; + + if (edit && edit->layer_priority != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY) + pos->layer_priority = edit->layer_priority; + else + pos->layer_priority = ges_timeline_element_get_layer_priority (element); + } +} + +static void +give_edits_same_offset (GHashTable * edits, GstClockTimeDiff offset, + gint64 layer_offset) +{ + GHashTableIter iter; + gpointer value; + + g_hash_table_iter_init (&iter, edits); + while (g_hash_table_iter_next (&iter, NULL, &value)) { + EditData *edit_data = value; + edit_data->offset = offset; + edit_data->layer_offset = layer_offset; + } +} + +/**************************************************** + * Initialise Edit Data and Moving * + ****************************************************/ + +static gboolean +add_track_elements_to_moving (GNode * node, GHashTable * track_elements) +{ + GESTimelineElement *element = node->data; + if (GES_IS_TRACK_ELEMENT (element)) { + GST_LOG_OBJECT (element, "%s set as moving", element->name); + g_hash_table_insert (track_elements, element, g_new0 (PositionData, 1)); + } + return FALSE; +} + +/* add all the track elements found under the elements in @edits to @moving, + * but does not set their position data */ +static gboolean +timeline_tree_add_edited_to_moving (GNode * root, GHashTable * edits, + GHashTable * moving) +{ + GHashTableIter iter; + gpointer key; + + g_hash_table_iter_init (&iter, edits); + while (g_hash_table_iter_next (&iter, &key, NULL)) { + GESTimelineElement *element = key; + GNode *node = find_node (root, element); + if (!node) { + GST_ERROR_OBJECT (element, "Not being tracked"); + return FALSE; + } + g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, + (GNodeTraverseFunc) add_track_elements_to_moving, moving); + } + + return TRUE; +} + +/* check we can handle the top and all of its children */ +static gboolean +check_types (GESTimelineElement * element, gboolean is_top) +{ + if (!GES_IS_CLIP (element) && !GES_IS_GROUP (element) + && !GES_IS_TRACK_ELEMENT (element)) { + GST_ERROR_OBJECT (element, "Cannot handle a GESTimelineElement of the " + "type %s", G_OBJECT_TYPE_NAME (element)); + return FALSE; + } + if (!is_top && element->parent) { + if ((GES_IS_CLIP (element) && !GES_IS_GROUP (element->parent)) + || (GES_IS_GROUP (element) && !GES_IS_GROUP (element->parent)) + || (GES_IS_TRACK_ELEMENT (element) && !GES_IS_CLIP (element->parent))) { + GST_ERROR_OBJECT (element, "A parent of type %s is not handled", + G_OBJECT_TYPE_NAME (element->parent)); + return FALSE; + } + } + if (GES_IS_CONTAINER (element)) { + GList *tmp; + for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { + if (!check_types (tmp->data, FALSE)) + return FALSE; + } + } + + return TRUE; +} + +/* @edits: The table to add the edit to + * @element: The element to edit + * @mode: The mode for editing @element + * + * Adds an edit for @element it to the table with its EditData only set + * with @mode. + * + * The offsets for the edit will have to be set later. + */ +static gboolean +add_element_edit (GHashTable * edits, GESTimelineElement * element, + ElementEditMode mode) +{ + if (!check_types (element, TRUE)) + return FALSE; + + if (g_hash_table_contains (edits, element)) { + GST_ERROR_OBJECT (element, "Already set to be edited"); + return FALSE; + } + + switch (mode) { + case EDIT_MOVE: + GST_LOG_OBJECT (element, "%s set to move", element->name); + break; + case EDIT_TRIM_START: + GST_LOG_OBJECT (element, "%s set to trim start", element->name); + break; + case EDIT_TRIM_END: + GST_LOG_OBJECT (element, "%s set to trim end", element->name); + break; + case EDIT_TRIM_INPOINT_ONLY: + GST_ERROR_OBJECT (element, "%s set to trim in-point only", element->name); + return FALSE; + } + + g_hash_table_insert (edits, element, new_edit_data (mode, 0, 0)); + + return TRUE; +} + +/******************************************** + * Check against current configuration * + ********************************************/ + +/* can move with no snapping or change in parent! */ +gboolean +timeline_tree_can_move_element (GNode * root, + GESTimelineElement * element, guint32 priority, GstClockTime start, + GstClockTime duration, GError ** error) +{ + gboolean ret = FALSE; + guint32 layer_prio = ges_timeline_element_get_layer_priority (element); + GstClockTime distance, new_end; + GHashTable *move_edits, *trim_edits, *moving; + GHashTableIter iter; + gpointer key, value; + + if (layer_prio == GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY + && priority != layer_prio) { + GST_INFO_OBJECT (element, "Cannot move to a layer when no layer " + "priority to begin with"); + return FALSE; + } + + distance = _abs_clock_time_distance (start, element->start); + if ((GstClockTimeDiff) distance >= G_MAXINT64) { + GST_WARNING_OBJECT (element, "Move in start from %" GST_TIME_FORMAT + " to %" GST_TIME_FORMAT " is too large to perform", + GST_TIME_ARGS (element->start), GST_TIME_ARGS (start)); + return FALSE; + } + + distance = _abs_clock_time_distance (duration, element->duration); + if ((GstClockTimeDiff) distance >= G_MAXINT64) { + GST_WARNING_OBJECT (element, "Move in duration from %" GST_TIME_FORMAT + " to %" GST_TIME_FORMAT " is too large to perform", + GST_TIME_ARGS (element->duration), GST_TIME_ARGS (duration)); + return FALSE; + } + + new_end = _clock_time_plus (start, duration); + if (!GST_CLOCK_TIME_IS_VALID (new_end)) { + GST_WARNING_OBJECT (element, "Move in start and duration to %" + GST_TIME_FORMAT " and %" GST_TIME_FORMAT " would produce an " + "invalid end", GST_TIME_ARGS (start), GST_TIME_ARGS (duration)); + return FALSE; + } + + /* treat as an EDIT_MOVE to the new priority, except on the element + * rather than the toplevel, followed by an EDIT_TRIM_END */ + move_edits = new_edit_table (); + trim_edits = new_edit_table (); + moving = new_position_table (); + + if (!add_element_edit (move_edits, element, EDIT_MOVE)) + goto done; + /* moving should remain the same */ + if (!add_element_edit (trim_edits, element, EDIT_TRIM_END)) + goto done; + + if (!timeline_tree_add_edited_to_moving (root, move_edits, moving) + || !timeline_tree_add_edited_to_moving (root, trim_edits, moving)) + goto done; + + /* no snapping */ + give_edits_same_offset (move_edits, element->start - start, + (gint64) layer_prio - (gint64) priority); + give_edits_same_offset (trim_edits, element->duration - duration, 0); + + /* assume both edits can be performed if each could occur individually */ + /* should not effect duration or in-point */ + if (!timeline_tree_set_element_edit_values (root, move_edits, error)) + goto done; + /* should not effect start or in-point or layer */ + if (!timeline_tree_set_element_edit_values (root, trim_edits, error)) + goto done; + + /* merge the two edits into moving positions */ + g_hash_table_iter_init (&iter, moving); + while (g_hash_table_iter_next (&iter, &key, &value)) { + GESTimelineElement *el = key; + PositionData *pos_data = value; + EditData *move = NULL; + EditData *trim = NULL; + + if (el->parent) { + move = g_hash_table_lookup (move_edits, el->parent); + trim = g_hash_table_lookup (trim_edits, el->parent); + } + + if (!move) + move = g_hash_table_lookup (move_edits, el); + if (!trim) + trim = g_hash_table_lookup (trim_edits, el); + + /* should always have move with a valid start */ + if (!move || !GST_CLOCK_TIME_IS_VALID (move->start)) { + GST_ERROR_OBJECT (el, "Element set to moving but neither it nor its " + "parent are being edited"); + goto done; + } + /* may not have trim if element is a group and the child is away + * from the edit position, but if we do it should have a valid duration */ + if (trim && !GST_CLOCK_TIME_IS_VALID (trim->duration)) { + GST_ERROR_OBJECT (el, "Element set to trim end but neither it nor its " + "parent is being trimmed"); + goto done; + } + + pos_data->start = move->start; + + if (move->layer_priority != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY) + pos_data->layer_priority = move->layer_priority; + else + pos_data->layer_priority = ges_timeline_element_get_layer_priority (el); + + if (trim) + pos_data->end = pos_data->start + trim->duration; + else + pos_data->end = pos_data->start + el->duration; + } + + /* check overlaps */ + if (!timeline_tree_can_move_elements (root, moving, error)) + goto done; + + ret = TRUE; + +done: + g_hash_table_unref (trim_edits); + g_hash_table_unref (move_edits); + g_hash_table_unref (moving); + + return ret; +} + +/******************************************** + * Perform Element Edit * + ********************************************/ + +static gboolean +perform_element_edit (GESTimelineElement * element, EditData * edit) +{ + gboolean ret = FALSE; + guint32 layer_prio = ges_timeline_element_get_layer_priority (element); + + switch (edit->mode) { + case EDIT_MOVE: + GST_INFO_OBJECT (element, "Moving %s from %" GST_TIME_FORMAT " to %" + GST_TIME_FORMAT, element->name, GST_TIME_ARGS (element->start), + GST_TIME_ARGS (edit->start)); + break; + case EDIT_TRIM_START: + GST_INFO_OBJECT (element, "Trimming %s start from %" GST_TIME_FORMAT + " to %" GST_TIME_FORMAT, element->name, + GST_TIME_ARGS (element->start), GST_TIME_ARGS (edit->start)); + break; + case EDIT_TRIM_END: + GST_INFO_OBJECT (element, "Trimming %s end from %" GST_TIME_FORMAT + " to %" GST_TIME_FORMAT, element->name, + GST_TIME_ARGS (_END (element)), + GST_TIME_ARGS (element->start + edit->duration)); + break; + case EDIT_TRIM_INPOINT_ONLY: + GST_INFO_OBJECT (element, "Trimming %s in-point from %" + GST_TIME_FORMAT " to %" GST_TIME_FORMAT, element->name, + GST_TIME_ARGS (element->inpoint), GST_TIME_ARGS (edit->inpoint)); + break; + } + + if (!GES_IS_CLIP (element) && !GES_IS_TRACK_ELEMENT (element)) { + GST_ERROR_OBJECT (element, "Cannot perform edit on group"); + return FALSE; + } + + if (!GES_IS_CLIP (element) + && edit->layer_priority != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY) { + GST_ERROR_OBJECT (element, "Cannot move an element that is not a " + "clip to a new layer"); + return FALSE; + } + + GES_TIMELINE_ELEMENT_SET_BEING_EDITED (element); + if (GST_CLOCK_TIME_IS_VALID (edit->start)) { + if (!ges_timeline_element_set_start (element, edit->start)) { + GST_ERROR_OBJECT (element, "Failed to set the start"); + goto done; + } + } + if (GST_CLOCK_TIME_IS_VALID (edit->inpoint)) { + if (!ges_timeline_element_set_inpoint (element, edit->inpoint)) { + GST_ERROR_OBJECT (element, "Failed to set the in-point"); + goto done; + } + } + if (GST_CLOCK_TIME_IS_VALID (edit->duration)) { + if (!ges_timeline_element_set_duration (element, edit->duration)) { + GST_ERROR_OBJECT (element, "Failed to set the duration"); + goto done; + } + } + if (edit->layer_priority != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY) { + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (element); + GESLayer *layer = ges_timeline_get_layer (timeline, edit->layer_priority); + + GST_INFO_OBJECT (element, "Moving %s from layer %" G_GUINT32_FORMAT + " to layer %" G_GUINT32_FORMAT, element->name, layer_prio, + edit->layer_priority); + + if (layer == NULL) { + /* make sure we won't loop forever */ + if (ges_timeline_layer_priority_in_gap (timeline, edit->layer_priority)) { + GST_ERROR_OBJECT (element, "Requested layer %" G_GUINT32_FORMAT + " is within a gap in the timeline layers", edit->layer_priority); + goto done; + } + + do { + layer = ges_timeline_append_layer (timeline); + } while (ges_layer_get_priority (layer) < edit->layer_priority); + } else { + gst_object_unref (layer); + } + + if (!ges_clip_move_to_layer (GES_CLIP (element), layer)) { + GST_ERROR_OBJECT (element, "Failed to move layers"); + goto done; + } + } + + ret = TRUE; + +done: + GES_TIMELINE_ELEMENT_UNSET_BEING_EDITED (element); + + return ret; +} + +/* perform all the element edits found in @edits. + * These should only be clips of track elements. */ +static gboolean +timeline_tree_perform_edits (GNode * root, GHashTable * edits) +{ + gboolean no_errors = TRUE; + GHashTableIter iter; + gpointer key, value; + + /* freeze the auto-transitions whilst we edit */ + ges_timeline_freeze_auto_transitions (root->data, TRUE); + + g_hash_table_iter_init (&iter, edits); + while (g_hash_table_iter_next (&iter, &key, &value)) { + if (GES_IS_TRACK_ELEMENT (key)) + ges_track_element_freeze_control_sources (GES_TRACK_ELEMENT (key), TRUE); + } + + g_hash_table_iter_init (&iter, edits); + while (g_hash_table_iter_next (&iter, &key, &value)) { + GESTimelineElement *element = key; + EditData *edit_data = value; + if (!perform_element_edit (element, edit_data)) + no_errors = FALSE; + } + + g_hash_table_iter_init (&iter, edits); + while (g_hash_table_iter_next (&iter, &key, &value)) { + if (GES_IS_TRACK_ELEMENT (key)) + ges_track_element_freeze_control_sources (GES_TRACK_ELEMENT (key), FALSE); + } + + /* allow the transitions to update if they can */ + ges_timeline_freeze_auto_transitions (root->data, FALSE); + + timeline_tree_create_transitions (root, ges_timeline_find_auto_transition); + timeline_update_duration (root->data); + + return no_errors; +} + +#define _REPLACE_TRACK_ELEMENT_WITH_PARENT(element) \ + element = (GES_IS_TRACK_ELEMENT (element) && element->parent) ? element->parent : element + +/******************************************** + * Ripple * + ********************************************/ + +gboolean +timeline_tree_ripple (GNode * root, GESTimelineElement * element, + gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge, + GstClockTime snapping_distance, GError ** error) +{ + gboolean res = TRUE; + GNode *node; + GESTimelineElement *ripple_toplevel; + GstClockTime ripple_time; + GHashTable *edits = new_edit_table (); + GHashTable *moving = new_position_table (); + ElementEditMode mode; + SnappedPosition *snap = new_snapped_position (snapping_distance); + + _REPLACE_TRACK_ELEMENT_WITH_PARENT (element); + + ripple_toplevel = ges_timeline_element_peak_toplevel (element); + + /* if EDGE_END: + * TRIM_END the element, and MOVE all toplevels whose start is after + * the current end of the element by the same amount + * otherwise: + * MOVE the topevel of the element, and all other toplevel elements + * whose start is after the current start of the element */ + + switch (edge) { + case GES_EDGE_END: + GST_INFO_OBJECT (element, "Rippling end with offset %" + G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset, + layer_priority_offset); + mode = EDIT_TRIM_END; + break; + case GES_EDGE_START: + GST_INFO_OBJECT (element, "Rippling start with offset %" + G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset, + layer_priority_offset); + mode = EDIT_MOVE; + break; + case GES_EDGE_NONE: + GST_INFO_OBJECT (element, "Rippling with toplevel with offset %" + G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset, + layer_priority_offset); + element = ripple_toplevel; + mode = EDIT_MOVE; + break; + default: + GST_WARNING_OBJECT (element, "Edge not supported"); + goto done; + } + + ripple_time = ELEMENT_EDGE_VALUE (element, edge); + + /* add edits */ + if (!add_element_edit (edits, element, mode)) + goto error; + + for (node = root->children; node; node = node->next) { + GESTimelineElement *toplevel = node->data; + if (toplevel == ripple_toplevel) + continue; + + if (toplevel->start >= ripple_time) { + if (!add_element_edit (edits, toplevel, EDIT_MOVE)) + goto error; + } + } + + if (!timeline_tree_add_edited_to_moving (root, edits, moving)) + goto error; + + /* snap */ + if (!timeline_tree_snap (root, element, mode, &offset, moving, snap)) + goto error; + + /* check and set edits using snapped values */ + give_edits_same_offset (edits, offset, layer_priority_offset); + if (!timeline_tree_set_element_edit_values (root, edits, error)) + goto error; + + /* check overlaps */ + set_moving_positions_from_edits (moving, edits); + if (!timeline_tree_can_move_elements (root, moving, error)) + goto error; + + /* emit snapping now. Edits should only fail if a programming error + * occured */ + if (snap) + ges_timeline_emit_snapping (root->data, snap->element, snap->snapped_to, + snap->snapped); + + res = timeline_tree_perform_edits (root, edits); + +done: + g_hash_table_unref (edits); + g_hash_table_unref (moving); + g_free (snap); + return res; + +error: + res = FALSE; + goto done; +} + +/******************************************** + * Trim * + ********************************************/ + +gboolean +timeline_tree_trim (GNode * root, GESTimelineElement * element, + gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge, + GstClockTime snapping_distance, GError ** error) +{ + gboolean res = TRUE; + GHashTable *edits = new_edit_table (); + GHashTable *moving = new_position_table (); + ElementEditMode mode; + SnappedPosition *snap = new_snapped_position (snapping_distance); + + _REPLACE_TRACK_ELEMENT_WITH_PARENT (element); + + /* TODO: 2.0 remove this warning and simply fail if no edge is specified */ + if (edge == GES_EDGE_NONE) { + g_warning ("No edge specified for trimming. Defaulting to GES_EDGE_START"); + edge = GES_EDGE_START; + } + + switch (edge) { + case GES_EDGE_END: + GST_INFO_OBJECT (element, "Trimming end with offset %" + G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset, + layer_priority_offset); + mode = EDIT_TRIM_END; + break; + case GES_EDGE_START: + GST_INFO_OBJECT (element, "Trimming start with offset %" + G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset, + layer_priority_offset); + mode = EDIT_TRIM_START; + break; + default: + GST_WARNING_OBJECT (element, "Edge not supported"); + goto done; + } + + /* add edits */ + if (!add_element_edit (edits, element, mode)) + goto error; + + if (!timeline_tree_add_edited_to_moving (root, edits, moving)) + goto error; + + /* snap */ + if (!timeline_tree_snap (root, element, mode, &offset, moving, snap)) + goto error; + + /* check and set edits using snapped values */ + give_edits_same_offset (edits, offset, layer_priority_offset); + if (!timeline_tree_set_element_edit_values (root, edits, error)) + goto error; + + /* check overlaps */ + set_moving_positions_from_edits (moving, edits); + if (!timeline_tree_can_move_elements (root, moving, error)) { + goto error; + } + + /* emit snapping now. Edits should only fail if a programming error + * occured */ + if (snap) + ges_timeline_emit_snapping (root->data, snap->element, snap->snapped_to, + snap->snapped); + + res = timeline_tree_perform_edits (root, edits); + +done: + g_hash_table_unref (edits); + g_hash_table_unref (moving); + g_free (snap); + return res; + +error: + res = FALSE; + goto done; +} + +/******************************************** + * Move * + ********************************************/ + +gboolean +timeline_tree_move (GNode * root, GESTimelineElement * element, + gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge, + GstClockTime snapping_distance, GError ** error) +{ + gboolean res = TRUE; + GHashTable *edits = new_edit_table (); + GHashTable *moving = new_position_table (); + ElementEditMode mode; + SnappedPosition *snap = new_snapped_position (snapping_distance); + + _REPLACE_TRACK_ELEMENT_WITH_PARENT (element); + + switch (edge) { + case GES_EDGE_END: + GST_INFO_OBJECT (element, "Moving end with offset %" + G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset, + layer_priority_offset); + mode = EDIT_TRIM_END; + break; + case GES_EDGE_START: + GST_INFO_OBJECT (element, "Moving start with offset %" + G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset, + layer_priority_offset); + mode = EDIT_MOVE; + break; + case GES_EDGE_NONE: + GST_INFO_OBJECT (element, "Moving with toplevel with offset %" + G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset, + layer_priority_offset); + element = ges_timeline_element_peak_toplevel (element); + mode = EDIT_MOVE; + break; + default: + GST_WARNING_OBJECT (element, "Edge not supported"); + goto done; + } + + /* add edits */ + if (!add_element_edit (edits, element, mode)) + goto error; + + if (!timeline_tree_add_edited_to_moving (root, edits, moving)) + goto error; + + /* snap */ + if (!timeline_tree_snap (root, element, mode, &offset, moving, snap)) + goto error; + + /* check and set edits using snapped values */ + give_edits_same_offset (edits, offset, layer_priority_offset); + if (!timeline_tree_set_element_edit_values (root, edits, error)) + goto error; + + /* check overlaps */ + set_moving_positions_from_edits (moving, edits); + if (!timeline_tree_can_move_elements (root, moving, error)) { + goto error; + } + + /* emit snapping now. Edits should only fail if a programming error + * occured */ + if (snap) + ges_timeline_emit_snapping (root->data, snap->element, snap->snapped_to, + snap->snapped); + + res = timeline_tree_perform_edits (root, edits); + +done: + g_hash_table_unref (edits); + g_hash_table_unref (moving); + g_free (snap); + return res; + +error: + res = FALSE; + goto done; +} + +/******************************************** + * Roll * + ********************************************/ + +static gboolean +is_descendant (GESTimelineElement * element, GESTimelineElement * ancestor) +{ + GESTimelineElement *parent = element; + while ((parent = parent->parent)) { + if (parent == ancestor) + return TRUE; + } + return FALSE; +} + +static gboolean +find_neighbour (GNode * node, TreeIterationData * data) +{ + GList *tmp; + gboolean in_same_track = FALSE; + GESTimelineElement *edge_element, *element = node->data; + + if (!GES_IS_SOURCE (element)) + return FALSE; + + /* if the element is controlled by the trimmed element (a group or a + * clip) it is not a neighbour */ + if (is_descendant (element, data->element)) + return FALSE; + + /* test if we share a track with one of the sources at the edge */ + for (tmp = data->sources; tmp; tmp = tmp->next) { + if (ges_track_element_get_track (GES_TRACK_ELEMENT (element)) == + ges_track_element_get_track (tmp->data)) { + in_same_track = TRUE; + break; + } + } + + if (!in_same_track) + return FALSE; + + /* get the most toplevel element whose edge touches the position */ + edge_element = NULL; + while (element && ELEMENT_EDGE_VALUE (element, data->edge) == data->position) { + edge_element = element; + element = element->parent; + } + + if (edge_element && !g_list_find (data->neighbours, edge_element)) + data->neighbours = g_list_prepend (data->neighbours, edge_element); + + return FALSE; +} + +static gboolean +find_sources_at_position (GNode * node, TreeIterationData * data) +{ + GESTimelineElement *element = node->data; + + if (!GES_IS_SOURCE (element)) + return FALSE; + + if (ELEMENT_EDGE_VALUE (element, data->edge) == data->position) + data->sources = g_list_append (data->sources, element); + + return FALSE; +} + +gboolean +timeline_tree_roll (GNode * root, GESTimelineElement * element, + GstClockTimeDiff offset, GESEdge edge, GstClockTime snapping_distance, + GError ** error) +{ + gboolean res = TRUE; + GList *tmp; + GNode *node; + TreeIterationData data = tree_iteration_data_init; + GHashTable *edits = new_edit_table (); + GHashTable *moving = new_position_table (); + ElementEditMode mode; + SnappedPosition *snap = new_snapped_position (snapping_distance); + + _REPLACE_TRACK_ELEMENT_WITH_PARENT (element); + + /* if EDGE_END: + * TRIM_END the element, and TRIM_START the neighbouring clips to the + * end edge + * otherwise: + * TRIM_START the element, and TRIM_END the neighbouring clips to the + * start edge */ + + switch (edge) { + case GES_EDGE_END: + GST_INFO_OBJECT (element, "Rolling end with offset %" + G_GINT64_FORMAT, offset); + mode = EDIT_TRIM_END; + break; + case GES_EDGE_START: + GST_INFO_OBJECT (element, "Rolling start with offset %" + G_GINT64_FORMAT, offset); + mode = EDIT_TRIM_START; + break; + case GES_EDGE_NONE: + GST_WARNING_OBJECT (element, "Need to select an edge when rolling."); + goto done; + default: + GST_WARNING_OBJECT (element, "Edge not supported"); + goto done; + } + + /* add edits */ + if (!add_element_edit (edits, element, mode)) + goto error; + + /* first, find all the sources at the edge */ + node = find_node (root, element); + if (!node) { + GST_ERROR_OBJECT (element, "Not being tracked"); + goto error; + } + + data.element = element; + data.edge = (edge == GES_EDGE_END) ? GES_EDGE_END : GES_EDGE_START; + data.position = ELEMENT_EDGE_VALUE (element, data.edge); + data.sources = NULL; + + g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, + (GNodeTraverseFunc) find_sources_at_position, &data); + + /* find elements that whose opposite edge touches the edge of the + * element and shares a track with one of the found sources */ + data.edge = (edge == GES_EDGE_END) ? GES_EDGE_START : GES_EDGE_END; + data.neighbours = NULL; + + g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAVES, -1, + (GNodeTraverseFunc) find_neighbour, &data); + + for (tmp = data.neighbours; tmp; tmp = tmp->next) { + GESTimelineElement *clip = tmp->data; + ElementEditMode opposite = + (mode == EDIT_TRIM_END) ? EDIT_TRIM_START : EDIT_TRIM_END; + if (!add_element_edit (edits, clip, opposite)) + goto error; + } + + if (!timeline_tree_add_edited_to_moving (root, edits, moving)) + goto error; + + /* snap */ + if (!timeline_tree_snap (root, element, mode, &offset, moving, snap)) + goto error; + + /* check and set edits using snapped values */ + give_edits_same_offset (edits, offset, 0); + if (!timeline_tree_set_element_edit_values (root, edits, error)) + goto error; + + /* check overlaps */ + set_moving_positions_from_edits (moving, edits); + if (!timeline_tree_can_move_elements (root, moving, error)) { + goto error; + } + + /* emit snapping now. Edits should only fail if a programming error + * occured */ + if (snap) + ges_timeline_emit_snapping (root->data, snap->element, snap->snapped_to, + snap->snapped); + + res = timeline_tree_perform_edits (root, edits); + +done: + g_hash_table_unref (edits); + g_hash_table_unref (moving); + g_list_free (data.neighbours); + g_list_free (data.sources); + g_free (snap); + return res; + +error: + res = FALSE; + goto done; +} + +static void +create_transition_if_needed (GESTimeline * timeline, GESTrackElement * prev, + GESTrackElement * next, GESTreeGetAutoTransitionFunc get_auto_transition) +{ + GstClockTime duration = _END (prev) - _START (next); + GESAutoTransition *trans = + get_auto_transition (timeline, prev, next, duration); + + if (!trans) { + GESLayer *layer = ges_timeline_get_layer (timeline, + GES_TIMELINE_ELEMENT_LAYER_PRIORITY (prev)); + gst_object_unref (layer); + + GST_INFO ("Creating transition [%" G_GINT64_FORMAT " - %" G_GINT64_FORMAT + "]", _START (next), duration); + ges_timeline_create_transition (timeline, prev, next, NULL, layer, + _START (next), duration); + } else { + GST_INFO ("Already have transition %" GST_PTR_FORMAT " between %" GES_FORMAT + " and %" GES_FORMAT, trans, GES_ARGS (prev), GES_ARGS (next)); + } +} + +static gboolean +create_transitions (GNode * node, + GESTreeGetAutoTransitionFunc get_auto_transition) +{ + TreeIterationData data = tree_iteration_data_init; + GESTimeline *timeline; + GESLayer *layer; + + if (!GES_IS_SOURCE (node->data)) + return FALSE; + + timeline = GES_TIMELINE_ELEMENT_TIMELINE (node->data); + + if (!timeline) { + GST_INFO ("%" GES_FORMAT " not in timeline yet", GES_ARGS (node->data)); + + return FALSE; + } + + layer = + ges_timeline_get_layer (timeline, + GES_TIMELINE_ELEMENT_LAYER_PRIORITY (node->data)); + gst_object_unref (layer); + + if (!ges_layer_get_auto_transition (layer)) + return FALSE; + + GST_LOG (node->data, "Checking for overlaps"); + data.root = g_node_get_root (node); + check_all_overlaps_with_element (node, &data); + + if (data.overlaping_on_start) + create_transition_if_needed (timeline, + GES_TRACK_ELEMENT (data.overlaping_on_start), node->data, + get_auto_transition); + + if (data.overlaping_on_end) + create_transition_if_needed (timeline, node->data, + GES_TRACK_ELEMENT (data.overlaping_on_end), get_auto_transition); + + return FALSE; +} + +void +timeline_tree_create_transitions_for_track_element (GNode * root, + GESTrackElement * element, GESTreeGetAutoTransitionFunc get_auto_transition) +{ + GNode *node = find_node (root, element); + g_assert (node); + + create_transitions (node, get_auto_transition); +} + +void +timeline_tree_create_transitions (GNode * root, + GESTreeGetAutoTransitionFunc get_auto_transition) +{ + g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1, + (GNodeTraverseFunc) create_transitions, get_auto_transition); +} + +static gboolean +compute_duration (GNode * node, GstClockTime * duration) +{ + *duration = MAX (_END (node->data), *duration); + + return FALSE; +} + +GstClockTime +timeline_tree_get_duration (GNode * root) +{ + GstClockTime duration = 0; + + if (root->children) + g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1, + (GNodeTraverseFunc) compute_duration, &duration); + + return duration; +} + +static gboolean +reset_layer_activness (GNode * node, GESLayer * layer) +{ + GESTrack *track; + + + if (!GES_IS_TRACK_ELEMENT (node->data)) + return FALSE; + + track = ges_track_element_get_track (node->data); + if (!track || (ges_timeline_element_get_layer_priority (node->data) != + ges_layer_get_priority (layer))) + return FALSE; + + ges_track_element_set_layer_active (node->data, + ges_layer_get_active_for_track (layer, track)); + + return FALSE; +} + +void +timeline_tree_reset_layer_active (GNode * root, GESLayer * layer) +{ + g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1, + (GNodeTraverseFunc) reset_layer_activness, layer); +} + +static gboolean +set_is_smart_rendering (GNode * node, gboolean * is_rendering_smartly) +{ + if (!GES_IS_SOURCE (node->data)) + return FALSE; + + ges_source_set_rendering_smartly (GES_SOURCE (node->data), + *is_rendering_smartly); + return FALSE; +} + +void +timeline_tree_set_smart_rendering (GNode * root, gboolean rendering_smartly) +{ + g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1, + (GNodeTraverseFunc) set_is_smart_rendering, &rendering_smartly); +} diff --git a/ges/ges-timeline-tree.h b/ges/ges-timeline-tree.h new file mode 100644 index 0000000000..2ab4703c49 --- /dev/null +++ b/ges/ges-timeline-tree.h @@ -0,0 +1,87 @@ +#pragma once + +#include <ges/ges.h> +#include "ges-auto-transition.h" + +void timeline_tree_track_element (GNode *root, + GESTimelineElement *element); + +void timeline_tree_stop_tracking_element (GNode *root, + GESTimelineElement *element); + +gboolean timeline_tree_can_move_element (GNode *root, + GESTimelineElement *element, + guint32 priority, + GstClockTime start, + GstClockTime duration, + GError ** error); + +gboolean timeline_tree_ripple (GNode *root, + GESTimelineElement *element, + gint64 layer_priority_offset, + GstClockTimeDiff offset, + GESEdge edge, + GstClockTime snapping_distance, + GError ** error); + +void ges_timeline_emit_snapping (GESTimeline * timeline, + GESTrackElement * elem1, + GESTrackElement * elem2, + GstClockTime snap_time); + +gboolean timeline_tree_trim (GNode *root, + GESTimelineElement *element, + gint64 layer_priority_offset, + GstClockTimeDiff offset, + GESEdge edge, + GstClockTime snapping_distance, + GError ** error); + + +gboolean timeline_tree_move (GNode *root, + GESTimelineElement *element, + gint64 layer_priority_offset, + GstClockTimeDiff offset, + GESEdge edge, + GstClockTime snapping_distance, + GError ** error); + +gboolean timeline_tree_roll (GNode * root, + GESTimelineElement * element, + GstClockTimeDiff offset, + GESEdge edge, + GstClockTime snapping_distance, + GError ** error); + +typedef GESAutoTransition * +(*GESTreeGetAutoTransitionFunc) (GESTimeline * timeline, + GESTrackElement * previous, + GESTrackElement * next, + GstClockTime transition_duration); + +void +timeline_tree_create_transitions_for_track_element (GNode * root, + GESTrackElement * element, + GESTreeGetAutoTransitionFunc get_auto_transition); +void timeline_tree_create_transitions (GNode *root, + GESTreeGetAutoTransitionFunc get_auto_transition); + +GstClockTime timeline_tree_get_duration (GNode *root); + +void timeline_tree_debug (GNode * root); + +GESAutoTransition * +ges_timeline_create_transition (GESTimeline * timeline, GESTrackElement * previous, + GESTrackElement * next, GESClip * transition, + GESLayer * layer, guint64 start, guint64 duration); +GESAutoTransition * +ges_timeline_find_auto_transition (GESTimeline * timeline, GESTrackElement * prev, + GESTrackElement * next, GstClockTime transition_duration); + +void +timeline_update_duration (GESTimeline * timeline); + +void timeline_tree_reset_layer_active (GNode *root, GESLayer *layer); +void timeline_tree_set_smart_rendering (GNode * root, gboolean rendering_smartly); + +void timeline_tree_init_debug (void); diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c new file mode 100644 index 0000000000..6fe3f21af9 --- /dev/null +++ b/ges/ges-timeline.c @@ -0,0 +1,3389 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * 2012 Thibault Saunier <tsaunier@gnome.org> + * 2012 Collabora Ltd. + * Author: Sebastian Dröge <sebastian.droege@collabora.co.uk> + * 2019 Igalia S.L + * Author: Thibault Saunier <tsaunier@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gestimeline + * @title: GESTimeline + * @short_description: Multimedia timeline + * + * #GESTimeline is the central object for any multimedia timeline. + * + * A timeline is composed of a set of #GESTrack-s and a set of + * #GESLayer-s, which are added to the timeline using + * ges_timeline_add_track() and ges_timeline_append_layer(), respectively. + * + * The contained tracks define the supported types of the timeline + * and provide the media output. Essentially, each track provides an + * additional source #GstPad. + * + * Most usage of a timeline will likely only need a single #GESAudioTrack + * and/or a single #GESVideoTrack. You can create such a timeline with + * ges_timeline_new_audio_video(). After this, you are unlikely to need to + * work with the tracks directly. + * + * A timeline's layers contain #GESClip-s, which in turn control the + * creation of #GESTrackElement-s, which are added to the timeline's + * tracks. See #GESTimeline::select-tracks-for-object if you wish to have + * more control over which track a clip's elements are added to. + * + * The layers are ordered, with higher priority layers having their + * content prioritised in the tracks. This ordering can be changed using + * ges_timeline_move_layer(). + * + * ## Editing + * + * See #GESTimelineElement for the various ways the elements of a timeline + * can be edited. + * + * If you change the timing or ordering of a timeline's + * #GESTimelineElement-s, then these changes will not actually be taken + * into account in the output of the timeline's tracks until the + * ges_timeline_commit() method is called. This allows you to move its + * elements around, say, in response to an end user's mouse dragging, with + * little expense before finalising their effect on the produced data. + * + * ## Overlaps and Auto-Transitions + * + * There are certain restrictions placed on how #GESSource-s may overlap + * in a #GESTrack that belongs to a timeline. These will be enforced by + * GES, so the user will not need to keep track of them, but they should + * be aware that certain edits will be refused as a result if the overlap + * rules would be broken. + * + * Consider two #GESSource-s, `A` and `B`, with start times `startA` and + * `startB`, and end times `endA` and `endB`, respectively. The start + * time refers to their #GESTimelineElement:start, and the end time is + * their #GESTimelineElement:start + #GESTimelineElement:duration. These + * two sources *overlap* if: + * + * + they share the same #GESTrackElement:track (non %NULL), which belongs + * to the timeline; + * + they share the same #GES_TIMELINE_ELEMENT_LAYER_PRIORITY; and + * + `startA < endB` and `startB < endA `. + * + * Note that when `startA = endB` or `startB = endA` then the two sources + * will *touch* at their edges, but are not considered overlapping. + * + * If, in addition, `startA < startB < endA`, then we can say that the + * end of `A` overlaps the start of `B`. + * + * If, instead, `startA <= startB` and `endA >= endB`, then we can say + * that `A` fully overlaps `B`. + * + * The overlap rules for a timeline are that: + * + * 1. One source cannot fully overlap another source. + * 2. A source can only overlap the end of up to one other source at its + * start. + * 3. A source can only overlap the start of up to one other source at its + * end. + * + * The last two rules combined essentially mean that at any given timeline + * position, only up to two #GESSource-s may overlap at that position. So + * triple or more overlaps are not allowed. + * + * If you switch on #GESTimeline:auto-transition, then at any moment when + * the end of one source (the first source) overlaps the start of another + * (the second source), a #GESTransitionClip will be automatically created + * for the pair in the same layer and it will cover their overlap. If the + * two elements are edited in a way such that the end of the first source + * no longer overlaps the start of the second, the transition will be + * automatically removed from the timeline. However, if the two sources + * still overlap at the same edges after the edit, then the same + * transition object will be kept, but with its timing and layer adjusted + * accordingly. + * + * ## Saving + * + * To save/load a timeline, you can use the ges_timeline_load_from_uri() + * and ges_timeline_save_to_uri() methods that use the default format. + * + * ## Playing + * + * A timeline is a #GstBin with a source #GstPad for each of its + * tracks, which you can fetch with ges_timeline_get_pad_for_track(). You + * will likely want to link these to some compatible sink #GstElement-s to + * be able to play or capture the content of the timeline. + * + * You can use a #GESPipeline to easily preview/play the timeline's + * content, or render it to a file. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-project.h" +#include "ges-container.h" +#include "ges-timeline.h" +#include "ges-timeline-tree.h" +#include "ges-track.h" +#include "ges-layer.h" +#include "ges-auto-transition.h" +#include "ges.h" + + +static GPtrArray *select_tracks_for_object_default (GESTimeline * timeline, + GESClip * clip, GESTrackElement * tr_obj, gpointer user_data); +static void ges_extractable_interface_init (GESExtractableInterface * iface); +static void ges_meta_container_interface_init + (GESMetaContainerInterface * iface); + +GST_DEBUG_CATEGORY_STATIC (ges_timeline_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT ges_timeline_debug + +/* lock to protect dynamic callbacks, like pad-added */ +#define DYN_LOCK(timeline) (&GES_TIMELINE (timeline)->priv->dyn_mutex) +#define LOCK_DYN(timeline) G_STMT_START { \ + GST_LOG_OBJECT (timeline, "Getting dynamic lock from %p", \ + g_thread_self()); \ + g_rec_mutex_lock (DYN_LOCK (timeline)); \ + GST_LOG_OBJECT (timeline, "Got Dynamic lock from %p", \ + g_thread_self()); \ + } G_STMT_END + +#define UNLOCK_DYN(timeline) G_STMT_START { \ + GST_LOG_OBJECT (timeline, "Unlocking dynamic lock from %p", \ + g_thread_self()); \ + g_rec_mutex_unlock (DYN_LOCK (timeline)); \ + GST_LOG_OBJECT (timeline, "Unlocked Dynamic lock from %p", \ + g_thread_self()); \ + } G_STMT_END + +#define CHECK_THREAD(timeline) g_assert(timeline->priv->valid_thread == g_thread_self()) + +struct _GESTimelinePrivate +{ + GNode *tree; + + /* The duration of the timeline */ + gint64 duration; + + /* The auto-transition of the timeline */ + gboolean auto_transition; + + /* Timeline edition modes and snapping management */ + guint64 snapping_distance; + + GRecMutex dyn_mutex; + GList *priv_tracks; + + /* Avoid sorting layers when we are actually resyncing them ourself */ + gboolean resyncing_layers; + GList *auto_transitions; + + /* Last snapping properties */ + GstClockTime last_snap_ts; + GESTrackElement *last_snaped1; + GESTrackElement *last_snaped2; + + GESTrack *auto_transition_track; + GESTrack *new_track; + + /* While we are creating and adding the TrackElements for a clip, we need to + * ignore the child-added signal */ + gboolean track_elements_moving; + /* whether any error occurred during track selection, including + * programming or usage errors */ + gboolean has_any_track_selection_error; + /* error set for non-programming/usage errors */ + GError *track_selection_error; + GList *groups; + + guint stream_start_group_id; + + GHashTable *all_elements; + + /* With GST_OBJECT_LOCK */ + guint expected_async_done; + /* With GST_OBJECT_LOCK */ + guint expected_commited; + + /* For ges_timeline_commit_sync */ + GMutex commited_lock; + GCond commited_cond; + gboolean commit_frozen; + gboolean commit_delayed; + + GThread *valid_thread; + gboolean disposed; + + GstStreamCollection *stream_collection; + + gboolean rendering_smartly; +}; + +/* private structure to contain our track-related information */ + +typedef struct +{ + GESTimeline *timeline; + GESTrack *track; + GstPad *pad; /* Pad from the track */ + GstPad *ghostpad; + gulong track_element_added_sigid; + + gulong probe_id; + GstStream *stream; +} TrackPrivate; + +enum +{ + PROP_0, + PROP_DURATION, + PROP_AUTO_TRANSITION, + PROP_SNAPPING_DISTANCE, + PROP_UPDATE, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +enum +{ + TRACK_ADDED, + TRACK_REMOVED, + LAYER_ADDED, + LAYER_REMOVED, + GROUP_ADDED, + GROUP_REMOVED, + SNAPING_STARTED, + SNAPING_ENDED, + SELECT_TRACKS_FOR_OBJECT, + COMMITED, + SELECT_ELEMENT_TRACK, + LAST_SIGNAL +}; + +G_DEFINE_TYPE_WITH_CODE (GESTimeline, ges_timeline, GST_TYPE_BIN, + G_ADD_PRIVATE (GESTimeline) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, ges_extractable_interface_init) + G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, + ges_meta_container_interface_init)); + +static GstBinClass *parent_class; + +static guint ges_timeline_signals[LAST_SIGNAL] = { 0 }; + +static gint custom_find_track (TrackPrivate * tr_priv, GESTrack * track); + +static guint nb_assets = 0; + +/* GESExtractable implementation */ +static gchar * +extractable_check_id (GType type, const gchar * id) +{ + gchar *res; + + if (id == NULL) + res = g_strdup_printf ("%s-%i", "project", nb_assets); + else + res = g_strdup (id); + + nb_assets++; + + return res; +} + +static gchar * +extractable_get_id (GESExtractable * self) +{ + GESAsset *asset; + + if (!(asset = ges_extractable_get_asset (self))) + return NULL; + + return g_strdup (ges_asset_get_id (asset)); +} + +static void +ges_extractable_interface_init (GESExtractableInterface * iface) +{ + iface->asset_type = GES_TYPE_PROJECT; + iface->check_id = (GESExtractableCheckId) extractable_check_id; + iface->get_id = extractable_get_id; +} + +static void +ges_meta_container_interface_init (GESMetaContainerInterface * iface) +{ +} + +/* GObject Standard vmethods*/ +static void +ges_timeline_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESTimeline *timeline = GES_TIMELINE (object); + + switch (property_id) { + case PROP_DURATION: + g_value_set_uint64 (value, timeline->priv->duration); + break; + case PROP_AUTO_TRANSITION: + g_value_set_boolean (value, timeline->priv->auto_transition); + break; + case PROP_SNAPPING_DISTANCE: + g_value_set_uint64 (value, timeline->priv->snapping_distance); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_timeline_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESTimeline *timeline = GES_TIMELINE (object); + + switch (property_id) { + case PROP_AUTO_TRANSITION: + ges_timeline_set_auto_transition (timeline, g_value_get_boolean (value)); + break; + case PROP_SNAPPING_DISTANCE: + timeline->priv->snapping_distance = g_value_get_uint64 (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +gboolean +ges_timeline_is_disposed (GESTimeline * timeline) +{ + return timeline->priv->disposed; +} + +static void +ges_timeline_dispose (GObject * object) +{ + GESTimeline *tl = GES_TIMELINE (object); + GESTimelinePrivate *priv = tl->priv; + GList *tmp, *groups; + + priv->disposed = TRUE; + while (tl->layers) { + GESLayer *layer = (GESLayer *) tl->layers->data; + ges_timeline_remove_layer (GES_TIMELINE (object), layer); + } + + /* FIXME: it should be possible to remove tracks before removing + * layers, but at the moment this creates a problem because the track + * objects aren't notified that their nleobjects have been destroyed. + */ + + LOCK_DYN (tl); + while (tl->tracks) + ges_timeline_remove_track (GES_TIMELINE (object), tl->tracks->data); + UNLOCK_DYN (tl); + + /* NOTE: the timeline should not contain empty groups */ + groups = g_list_copy_deep (priv->groups, (GCopyFunc) gst_object_ref, NULL); + for (tmp = groups; tmp; tmp = tmp->next) { + GList *elems = ges_container_ungroup (tmp->data, FALSE); + + g_list_free_full (elems, gst_object_unref); + } + g_list_free_full (groups, gst_object_unref); + g_list_free_full (priv->groups, gst_object_unref); + + g_list_free_full (priv->auto_transitions, gst_object_unref); + + g_hash_table_unref (priv->all_elements); + gst_object_unref (priv->stream_collection); + + gst_clear_object (&priv->auto_transition_track); + gst_clear_object (&priv->new_track); + g_clear_error (&priv->track_selection_error); + priv->track_selection_error = NULL; + + G_OBJECT_CLASS (ges_timeline_parent_class)->dispose (object); +} + +static void +ges_timeline_finalize (GObject * object) +{ + GESTimeline *tl = GES_TIMELINE (object); + + g_rec_mutex_clear (&tl->priv->dyn_mutex); + g_node_destroy (tl->priv->tree); + + G_OBJECT_CLASS (ges_timeline_parent_class)->finalize (object); +} + +static void +ges_timeline_handle_message (GstBin * bin, GstMessage * message) +{ + GESTimeline *timeline = GES_TIMELINE (bin); + + if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ASYNC_START) { + GST_INFO_OBJECT (timeline, "Dropping %" GST_PTR_FORMAT, message); + return; + } + + if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ASYNC_DONE) { + GST_INFO_OBJECT (timeline, "Dropping %" GST_PTR_FORMAT, message); + + return; + } + + if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT) { + GstMessage *amessage = NULL; + const GstStructure *mstructure = gst_message_get_structure (message); + + if (gst_structure_has_name (mstructure, "NleCompositionStartUpdate")) { + if (g_strcmp0 (gst_structure_get_string (mstructure, "reason"), "Seek")) { + GST_INFO_OBJECT (timeline, + "A composition is starting an update because of %s" + " not considering async", gst_structure_get_string (mstructure, + "reason")); + + goto forward; + } + + GST_OBJECT_LOCK (timeline); + if (timeline->priv->expected_async_done == 0) { + amessage = gst_message_new_async_start (GST_OBJECT_CAST (bin)); + LOCK_DYN (timeline); + timeline->priv->expected_async_done = g_list_length (timeline->tracks); + UNLOCK_DYN (timeline); + GST_INFO_OBJECT (timeline, "Posting ASYNC_START %s", + gst_structure_get_string (mstructure, "reason")); + } + GST_OBJECT_UNLOCK (timeline); + + } else if (gst_structure_has_name (mstructure, "NleCompositionUpdateDone")) { + if (g_strcmp0 (gst_structure_get_string (mstructure, "reason"), "Seek")) { + GST_INFO_OBJECT (timeline, + "A composition is done updating because of %s" + " not considering async", gst_structure_get_string (mstructure, + "reason")); + + goto forward; + } + + GST_OBJECT_LOCK (timeline); + timeline->priv->expected_async_done -= 1; + if (timeline->priv->expected_async_done == 0) { + amessage = gst_message_new_async_done (GST_OBJECT_CAST (bin), + GST_CLOCK_TIME_NONE); + GST_INFO_OBJECT (timeline, "Posting ASYNC_DONE %s", + gst_structure_get_string (mstructure, "reason")); + } + GST_OBJECT_UNLOCK (timeline); + } + + if (amessage) { + gst_message_unref (message); + gst_element_post_message (GST_ELEMENT_CAST (bin), amessage); + return; + } + } + +forward: + gst_element_post_message (GST_ELEMENT_CAST (bin), message); +} + +static GstStateChangeReturn +ges_timeline_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn res; + GESTimeline *timeline = GES_TIMELINE (element); + + res = GST_ELEMENT_CLASS (ges_timeline_parent_class)->change_state (element, + transition); + + if (transition == GST_STATE_CHANGE_READY_TO_PAUSED) + gst_element_post_message ((GstElement *) timeline, + gst_message_new_stream_collection ((GstObject *) timeline, + timeline->priv->stream_collection)); + return res; +} + +static gboolean +ges_timeline_send_event (GstElement * element, GstEvent * event) +{ + GESTimeline *timeline = GES_TIMELINE (element); + + if (GST_EVENT_TYPE (event) == GST_EVENT_SELECT_STREAMS) { + GList *stream_ids = NULL, *tmp, *to_remove = + ges_timeline_get_tracks (timeline); + + gst_event_parse_select_streams (event, &stream_ids); + for (tmp = stream_ids; tmp; tmp = tmp->next) { + GList *trackit; + gchar *stream_id = tmp->data; + + LOCK_DYN (timeline); + for (trackit = timeline->priv->priv_tracks; trackit; + trackit = trackit->next) { + TrackPrivate *tr_priv = trackit->data; + + if (!g_strcmp0 (gst_stream_get_stream_id (tr_priv->stream), stream_id)) { + to_remove = g_list_remove (to_remove, tr_priv->track); + } + } + UNLOCK_DYN (timeline); + } + for (tmp = to_remove; tmp; tmp = tmp->next) { + GST_INFO_OBJECT (timeline, "Removed unselected track: %" GST_PTR_FORMAT, + tmp->data); + ges_timeline_remove_track (timeline, tmp->data); + } + + g_list_free_full (stream_ids, g_free); + g_list_free (to_remove); + + return TRUE; + } + + return GST_ELEMENT_CLASS (ges_timeline_parent_class)->send_event (element, + event); +} + +/* we collect the first result */ +static gboolean +_gst_array_accumulator (GSignalInvocationHint * ihint, + GValue * return_accu, const GValue * handler_return, gpointer dummy) +{ + gpointer array; + + array = g_value_get_boxed (handler_return); + if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP)) + g_value_set_boxed (return_accu, array); + + return FALSE; +} + +static void +ges_timeline_class_init (GESTimelineClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = (GstElementClass *) klass; + GstBinClass *bin_class = GST_BIN_CLASS (klass); + + GST_DEBUG_CATEGORY_INIT (ges_timeline_debug, "gestimeline", + GST_DEBUG_FG_YELLOW, "ges timeline"); + timeline_tree_init_debug (); + + parent_class = g_type_class_peek_parent (klass); + + object_class->get_property = ges_timeline_get_property; + object_class->set_property = ges_timeline_set_property; + object_class->dispose = ges_timeline_dispose; + object_class->finalize = ges_timeline_finalize; + + element_class->change_state = GST_DEBUG_FUNCPTR (ges_timeline_change_state); + element_class->send_event = GST_DEBUG_FUNCPTR (ges_timeline_send_event); + + bin_class->handle_message = GST_DEBUG_FUNCPTR (ges_timeline_handle_message); + + /** + * GESTimeline:duration: + * + * The current duration (in nanoseconds) of the timeline. A timeline + * 'starts' at time 0, so this is the maximum end time of all of its + * #GESTimelineElement-s. + */ + properties[PROP_DURATION] = + g_param_spec_uint64 ("duration", "Duration", + "The duration of the timeline", 0, G_MAXUINT64, + GST_CLOCK_TIME_NONE, G_PARAM_READABLE); + g_object_class_install_property (object_class, PROP_DURATION, + properties[PROP_DURATION]); + + /** + * GESTimeline:auto-transition: + * + * Whether to automatically create a transition whenever two + * #GESSource-s overlap in a track of the timeline. See + * #GESLayer:auto-transition if you want this to only happen in some + * layers. + */ + g_object_class_install_property (object_class, PROP_AUTO_TRANSITION, + g_param_spec_boolean ("auto-transition", "Auto-Transition", + "whether the transitions are added", FALSE, G_PARAM_READWRITE)); + + /** + * GESTimeline:snapping-distance: + * + * The distance (in nanoseconds) at which a #GESTimelineElement being + * moved within the timeline should snap one of its #GESSource-s with + * another #GESSource-s edge. See #GESEditMode for which edges can + * snap during an edit. 0 means no snapping. + */ + properties[PROP_SNAPPING_DISTANCE] = + g_param_spec_uint64 ("snapping-distance", "Snapping distance", + "Distance from which moving an object will snap with neighbours", 0, + G_MAXUINT64, 0, G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_SNAPPING_DISTANCE, + properties[PROP_SNAPPING_DISTANCE]); + + /** + * GESTimeline::track-added: + * @timeline: The #GESTimeline + * @track: The track that was added to @timeline + * + * Will be emitted after the track is added to the timeline. + * + * Note that this should not be emitted whilst a timeline is being + * loaded from its #GESProject asset. You should connect to the + * project's #GESProject::loaded signal if you want to know which + * tracks were created for the timeline. + */ + ges_timeline_signals[TRACK_ADDED] = + g_signal_new ("track-added", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, track_added), NULL, + NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_TRACK); + + /** + * GESTimeline::track-removed: + * @timeline: The #GESTimeline + * @track: The track that was removed from @timeline + * + * Will be emitted after the track is removed from the timeline. + */ + ges_timeline_signals[TRACK_REMOVED] = + g_signal_new ("track-removed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, track_removed), + NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_TRACK); + + /** + * GESTimeline::layer-added: + * @timeline: The #GESTimeline + * @layer: The layer that was added to @timeline + * + * Will be emitted after the layer is added to the timeline. + * + * Note that this should not be emitted whilst a timeline is being + * loaded from its #GESProject asset. You should connect to the + * project's #GESProject::loaded signal if you want to know which + * layers were created for the timeline. + */ + ges_timeline_signals[LAYER_ADDED] = + g_signal_new ("layer-added", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, layer_added), NULL, + NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_LAYER); + + /** + * GESTimeline::layer-removed: + * @timeline: The #GESTimeline + * @layer: The layer that was removed from @timeline + * + * Will be emitted after the layer is removed from the timeline. + */ + ges_timeline_signals[LAYER_REMOVED] = + g_signal_new ("layer-removed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, layer_removed), + NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_LAYER); + + /** + * GESTimeline::group-added + * @timeline: The #GESTimeline + * @group: The group that was added to @timeline + * + * Will be emitted after the group is added to to the timeline. This can + * happen when grouping with `ges_container_group`, or by adding + * containers to a newly created group. + * + * Note that this should not be emitted whilst a timeline is being + * loaded from its #GESProject asset. You should connect to the + * project's #GESProject::loaded signal if you want to know which groups + * were created for the timeline. + */ + ges_timeline_signals[GROUP_ADDED] = + g_signal_new ("group-added", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, group_added), NULL, + NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_GROUP); + + /** + * GESTimeline::group-removed + * @timeline: The #GESTimeline + * @group: The group that was removed from @timeline + * @children: (element-type GESContainer) (transfer none): A list + * of #GESContainer-s that _were_ the children of the removed @group + * + * Will be emitted after the group is removed from the timeline through + * `ges_container_ungroup`. Note that @group will no longer contain its + * former children, these are held in @children. + * + * Note that if a group is emptied, then it will no longer belong to the + * timeline, but this signal will **not** be emitted in such a case. + */ + ges_timeline_signals[GROUP_REMOVED] = + g_signal_new ("group-removed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, group_removed), + NULL, NULL, NULL, G_TYPE_NONE, 2, GES_TYPE_GROUP, G_TYPE_PTR_ARRAY); + + /** + * GESTimeline::snapping-started: + * @timeline: The #GESTimeline + * @obj1: The first element that is snapping + * @obj2: The second element that is snapping + * @position: The position where the two objects will snap to + * + * Will be emitted whenever an element's movement invokes a snapping + * event during an edit (usually of one of its ancestors) because its + * start or end point lies within the #GESTimeline:snapping-distance of + * another element's start or end point. + * + * See #GESEditMode to see what can snap during an edit. + * + * Note that only up to one snapping-started signal will be emitted per + * element edit within a timeline. + */ + ges_timeline_signals[SNAPING_STARTED] = + g_signal_new ("snapping-started", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 3, GES_TYPE_TRACK_ELEMENT, GES_TYPE_TRACK_ELEMENT, + G_TYPE_UINT64); + + /** + * GESTimeline::snapping-ended: + * @timeline: The #GESTimeline + * @obj1: The first element that was snapping + * @obj2: The second element that was snapping + * @position: The position where the two objects were to be snapped to + * + * Will be emitted whenever a snapping event ends. After a snap event + * has started (see #GESTimeline::snapping-started), it can later end + * because either another timeline edit has occurred (which may or may + * not have created a new snapping event), or because the timeline has + * been committed. + */ + ges_timeline_signals[SNAPING_ENDED] = + g_signal_new ("snapping-ended", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 3, GES_TYPE_TRACK_ELEMENT, GES_TYPE_TRACK_ELEMENT, + G_TYPE_UINT64); + + /** + * GESTimeline::select-tracks-for-object: + * @timeline: The #GESTimeline + * @clip: The clip that @track_element is being added to + * @track_element: The element being added + * + * This will be emitted whenever the timeline needs to determine which + * tracks a clip's children should be added to. The track element will + * be added to each of the tracks given in the return. If a track + * element is selected to go into multiple tracks, it will be copied + * into the additional tracks, under the same clip. Note that the copy + * will *not* keep its properties or state in sync with the original. + * + * Connect to this signal once if you wish to control which element + * should be added to which track. Doing so will overwrite the default + * behaviour, which adds @track_element to all tracks whose + * #GESTrack:track-type includes the @track_element's + * #GESTrackElement:track-type. + * + * Note that under the default track selection, if a clip would produce + * multiple core children of the same #GESTrackType, it will choose + * one of the core children arbitrarily to place in the corresponding + * tracks, with a warning for the other core children that are not + * placed in the track. For example, this would happen for a #GESUriClip + * that points to a file that contains multiple audio streams. If you + * wish to choose the stream, you could connect to this signal, and use, + * say, ges_uri_source_asset_get_stream_info() to choose which core + * source to add. + * + * When a clip is first added to a timeline, its core elements will + * be created for the current tracks in the timeline if they have not + * already been created. Then this will be emitted for each of these + * core children to select which tracks, if any, they should be added + * to. It will then be called for any non-core children in the clip. + * + * In addition, if a new track element is ever added to a clip in a + * timeline (and it is not already part of a track) this will be emitted + * to select which tracks the element should be added to. + * + * Finally, as a special case, if a track is added to the timeline + * *after* it already contains clips, then it will request the creation + * of the clips' core elements of the corresponding type, if they have + * not already been created, and this signal will be emitted for each of + * these newly created elements. In addition, this will also be released + * for all other track elements in the timeline's clips that have not + * yet been assigned a track. However, in this final case, the timeline + * will only check whether the newly added track appears in the track + * list. If it does appear, the track element will be added to the newly + * added track. All other tracks in the returned track list are ignored. + * + * In this latter case, track elements that are already part of a track + * will not be asked if they want to be copied into the new track. If + * you wish to do this, you can use ges_clip_add_child_to_track(). + * + * Note that the returned #GPtrArray should own a new reference to each + * of its contained #GESTrack. The timeline will set the #GDestroyNotify + * free function on the #GPtrArray to dereference the elements. + * + * Returns: (transfer full) (element-type GESTrack): An array of + * #GESTrack-s that @track_element should be added to, or %NULL to + * not add the element to any track. + */ + ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT] = + g_signal_new ("select-tracks-for-object", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, _gst_array_accumulator, NULL, NULL, + G_TYPE_PTR_ARRAY, 2, GES_TYPE_CLIP, GES_TYPE_TRACK_ELEMENT); + + /** + * GESTimeline::select-element-track: + * @timeline: The #GESTimeline + * @clip: The clip that @track_element is being added to + * @track_element: The element being added + * + * Simplified version of #GESTimeline::select-tracks-for-object which only + * allows @track_element to be added to a single #GESTrack. + * + * Returns: (transfer full): A track to put @track_element into, or %NULL if + * it should be discarded. + * + * Since: 1.18 + */ + ges_timeline_signals[SELECT_ELEMENT_TRACK] = + g_signal_new ("select-element-track", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + GES_TYPE_TRACK, 2, GES_TYPE_CLIP, GES_TYPE_TRACK_ELEMENT); + + /** + * GESTimeline::commited: + * @timeline: The #GESTimeline + * + * This signal will be emitted once the changes initiated by + * ges_timeline_commit() have been executed in the backend. Use + * ges_timeline_commit_sync() if you do not want to have to connect + * to this signal. + */ + ges_timeline_signals[COMMITED] = + g_signal_new ("commited", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +static void +ges_timeline_init (GESTimeline * self) +{ + GESTimelinePrivate *priv = self->priv; + + self->priv = ges_timeline_get_instance_private (self); + self->priv->tree = g_node_new (self); + + priv = self->priv; + self->layers = NULL; + self->tracks = NULL; + self->priv->duration = 0; + self->priv->auto_transition = FALSE; + priv->snapping_distance = 0; + priv->expected_async_done = 0; + priv->expected_commited = 0; + + self->priv->last_snap_ts = GST_CLOCK_TIME_NONE; + + priv->priv_tracks = NULL; + + priv->all_elements = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, gst_object_unref); + + priv->stream_start_group_id = -1; + priv->stream_collection = gst_stream_collection_new (NULL); + + g_signal_connect_after (self, "select-tracks-for-object", + G_CALLBACK (select_tracks_for_object_default), NULL); + + g_rec_mutex_init (&priv->dyn_mutex); + g_mutex_init (&priv->commited_lock); + priv->valid_thread = g_thread_self (); +} + +/* Private methods */ + +/* Sorting utils*/ +static gint +sort_layers (gpointer a, gpointer b) +{ + GESLayer *layer_a, *layer_b; + guint prio_a, prio_b; + + layer_a = GES_LAYER (a); + layer_b = GES_LAYER (b); + + prio_a = ges_layer_get_priority (layer_a); + prio_b = ges_layer_get_priority (layer_b); + + if (prio_a > prio_b) + return 1; + if (prio_a < prio_b) + return -1; + + return 0; +} + +static void +_resync_layers (GESTimeline * timeline) +{ + GList *tmp; + gint i = 0; + + timeline->priv->resyncing_layers = TRUE; + for (tmp = timeline->layers; tmp; tmp = tmp->next) { + layer_set_priority (tmp->data, i, TRUE); + i++; + } + timeline->priv->resyncing_layers = FALSE; +} + +void +timeline_update_duration (GESTimeline * timeline) +{ + GstClockTime duration = timeline_tree_get_duration (timeline->priv->tree); + + if (timeline->priv->duration != duration) { + GST_DEBUG ("track duration : %" GST_TIME_FORMAT " current : %" + GST_TIME_FORMAT, GST_TIME_ARGS (duration), + GST_TIME_ARGS (timeline->priv->duration)); + + timeline->priv->duration = duration; + + g_object_notify_by_pspec (G_OBJECT (timeline), properties[PROP_DURATION]); + } +} + +static gint +custom_find_track (TrackPrivate * tr_priv, GESTrack * track) +{ + if (tr_priv->track == track) + return 0; + return -1; +} + +static void +_destroy_auto_transition_cb (GESAutoTransition * auto_transition, + GESTimeline * timeline) +{ + GESTimelinePrivate *priv = timeline->priv; + GESClip *transition = auto_transition->transition_clip; + GESLayer *layer = ges_clip_get_layer (transition); + + ges_layer_remove_clip (layer, transition); + g_signal_handlers_disconnect_by_func (auto_transition, + _destroy_auto_transition_cb, timeline); + + priv->auto_transitions = + g_list_remove (priv->auto_transitions, auto_transition); + gst_object_unref (auto_transition); +} + +GESAutoTransition * +ges_timeline_create_transition (GESTimeline * timeline, + GESTrackElement * previous, GESTrackElement * next, GESClip * transition, + GESLayer * layer, guint64 start, guint64 duration) +{ + GESAutoTransition *auto_transition; + GESTrackElement *child; + /* track should not be NULL */ + GESTrack *track = ges_track_element_get_track (next); + + if (transition == NULL) { + GESAsset *asset; + + LOCK_DYN (timeline); + timeline->priv->auto_transition_track = gst_object_ref (track); + UNLOCK_DYN (timeline); + + asset = ges_asset_request (GES_TYPE_TRANSITION_CLIP, "crossfade", NULL); + transition = ges_layer_add_asset (layer, asset, start, 0, duration, + ges_track_element_get_track_type (next)); + gst_object_unref (asset); + + LOCK_DYN (timeline); + /* should have been set to NULL, but clear just in case */ + gst_clear_object (&timeline->priv->auto_transition_track); + UNLOCK_DYN (timeline); + } else { + GST_DEBUG_OBJECT (timeline, + "Reusing already existing transition: %" GST_PTR_FORMAT, transition); + } + + g_return_val_if_fail (transition, NULL); + g_return_val_if_fail (g_list_length (GES_CONTAINER_CHILDREN (transition)) == + 1, NULL); + child = GES_CONTAINER_CHILDREN (transition)->data; + if (ges_track_element_get_track (child) != track) { + GST_ERROR_OBJECT (timeline, "The auto transition element %" + GES_FORMAT " for elements %" GES_FORMAT " and %" GES_FORMAT + " is not in the same track %" GST_PTR_FORMAT, + GES_ARGS (child), GES_ARGS (previous), GES_ARGS (next), track); + return NULL; + } + + /* We know there is only 1 TrackElement */ + auto_transition = ges_auto_transition_new (child, previous, next); + + g_signal_connect (auto_transition, "destroy-me", + G_CALLBACK (_destroy_auto_transition_cb), timeline); + + timeline->priv->auto_transitions = + g_list_prepend (timeline->priv->auto_transitions, auto_transition); + + return auto_transition; +} + +GESAutoTransition * +ges_timeline_find_auto_transition (GESTimeline * timeline, + GESTrackElement * prev, GESTrackElement * next, + GstClockTime transition_duration) +{ + GList *tmp; + + for (tmp = timeline->priv->auto_transitions; tmp; tmp = tmp->next) { + GESAutoTransition *auto_trans = (GESAutoTransition *) tmp->data; + + /* We already have a transition linked to one of the elements we want to + * find a transition for */ + if (auto_trans->previous_source == prev || auto_trans->next_source == next) { + if (auto_trans->previous_source != prev + || auto_trans->next_source != next) { + GST_ERROR_OBJECT (timeline, "Failed creating auto transition, " + " trying to have 3 clips overlapping, rolling back"); + } + + return auto_trans; + } + } + + return NULL; +} + +GESAutoTransition * +ges_timeline_get_auto_transition_at_edge (GESTimeline * timeline, + GESTrackElement * source, GESEdge edge) +{ + GList *tmp, *auto_transitions; + GESAutoTransition *ret = NULL; + + LOCK_DYN (timeline); + auto_transitions = g_list_copy_deep (timeline->priv->auto_transitions, + (GCopyFunc) gst_object_ref, NULL); + UNLOCK_DYN (timeline); + + for (tmp = auto_transitions; tmp; tmp = tmp->next) { + GESAutoTransition *auto_trans = (GESAutoTransition *) tmp->data; + + /* We already have a transition linked to one of the elements we want to + * find a transition for */ + if (edge == GES_EDGE_END && auto_trans->previous_source == source) { + ret = gst_object_ref (auto_trans); + break; + } else if (edge == GES_EDGE_START && auto_trans->next_source == source) { + ret = gst_object_ref (auto_trans); + break; + } + } + + g_list_free_full (auto_transitions, gst_object_unref); + + return ret; +} + +static GESAutoTransition * +_create_auto_transition_from_transitions (GESTimeline * timeline, + GESTrackElement * prev, GESTrackElement * next, + GstClockTime transition_duration) +{ + GList *tmp, *elements; + GESLayer *layer; + guint32 layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (prev); + GESTrack *track; + GESAutoTransition *auto_transition = + ges_timeline_find_auto_transition (timeline, prev, next, + transition_duration); + + if (auto_transition) + return auto_transition; + + layer = ges_timeline_get_layer (timeline, layer_prio); + track = ges_track_element_get_track (prev); + elements = ges_track_get_elements (track); + for (tmp = elements; tmp; tmp = tmp->next) { + GESTrackElement *maybe_transition = tmp->data; + + if (ges_timeline_element_get_layer_priority (tmp->data) != layer_prio) + continue; + + if (_START (maybe_transition) > _START (next)) + break; + else if (_START (maybe_transition) != _START (next) || + _DURATION (maybe_transition) != transition_duration) + continue; + else if (GES_IS_TRANSITION (maybe_transition)) { + /* Use that transition */ + /* TODO We should make sure that the transition contains only + * TrackElement-s in @track and if it is not the case properly unlink the + * object to use it */ + auto_transition = ges_timeline_create_transition (timeline, prev, next, + GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (maybe_transition)), layer, + _START (next), transition_duration); + + break; + } + } + gst_object_unref (layer); + g_list_free_full (elements, gst_object_unref); + + return auto_transition; +} + +void +ges_timeline_emit_snapping (GESTimeline * timeline, GESTrackElement * elem1, + GESTrackElement * elem2, GstClockTime snap_time) +{ + GESTimelinePrivate *priv = timeline->priv; + GstClockTime last_snap_ts = timeline->priv->last_snap_ts; + + if (!GST_CLOCK_TIME_IS_VALID (snap_time)) { + if (priv->last_snaped1 != NULL && priv->last_snaped2 != NULL) { + g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0, + priv->last_snaped1, priv->last_snaped2, last_snap_ts); + priv->last_snaped1 = NULL; + priv->last_snaped2 = NULL; + priv->last_snap_ts = GST_CLOCK_TIME_NONE; + } + + return; + } + + g_assert (elem1 != elem2); + + if (GST_CLOCK_TIME_IS_VALID (last_snap_ts)) + g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0, + priv->last_snaped1, priv->last_snaped2, (last_snap_ts)); + + priv->last_snaped1 = elem1; + priv->last_snaped2 = elem2; + timeline->priv->last_snap_ts = snap_time; + g_signal_emit (timeline, ges_timeline_signals[SNAPING_STARTED], 0, + elem1, elem2, snap_time); +} + +/* Accept @self == NULL, making it use default framerate */ +void +timeline_get_framerate (GESTimeline * self, gint * fps_n, gint * fps_d) +{ + GList *tmp; + + *fps_n = *fps_d = -1; + if (!self) + goto done; + + LOCK_DYN (self); + for (tmp = self->tracks; tmp; tmp = tmp->next) { + if (GES_IS_VIDEO_TRACK (tmp->data)) { + GstCaps *restriction = ges_track_get_restriction_caps (tmp->data); + gint i; + + if (!restriction) + continue; + + for (i = 0; i < gst_caps_get_size (restriction); i++) { + gint n, d; + + if (!gst_structure_get_fraction (gst_caps_get_structure (restriction, + i), "framerate", &n, &d)) + continue; + + if (*fps_n != -1 && *fps_d != -1 && !(n == *fps_n && d == *fps_d)) { + GST_WARNING_OBJECT (self, + "Various framerates specified, this is not supported" + " First one will be used."); + continue; + } + + *fps_n = n; + *fps_d = d; + } + gst_caps_unref (restriction); + } + } + UNLOCK_DYN (self); + +done: + if (*fps_n == -1 && *fps_d == -1) { + GST_INFO_OBJECT (self, + "No framerate found, using default " G_STRINGIFY (FRAMERATE_N) "/ " + G_STRINGIFY (FRAMERATE_D)); + *fps_n = DEFAULT_FRAMERATE_N; + *fps_d = DEFAULT_FRAMERATE_D; + } +} + +void +ges_timeline_freeze_auto_transitions (GESTimeline * timeline, gboolean freeze) +{ + GList *tmp, *trans = g_list_copy (timeline->priv->auto_transitions); + for (tmp = trans; tmp; tmp = tmp->next) { + GESAutoTransition *auto_transition = tmp->data; + auto_transition->frozen = freeze; + if (freeze == FALSE) { + GST_LOG_OBJECT (timeline, "Un-Freezing %" GES_FORMAT, + GES_ARGS (auto_transition->transition_clip)); + ges_auto_transition_update (auto_transition); + } else { + GST_LOG_OBJECT (timeline, "Freezing %" GES_FORMAT, + GES_ARGS (auto_transition->transition_clip)); + } + } + g_list_free (trans); +} + +static gint +_edit_auto_transition (GESTimeline * timeline, GESTimelineElement * element, + gint64 new_layer_priority, GESEditMode mode, GESEdge edge, + GstClockTime position, GError ** error) +{ + GList *tmp; + guint32 layer_prio = ges_timeline_element_get_layer_priority (element); + GESLayer *layer = ges_timeline_get_layer (timeline, layer_prio); + + if (!ges_layer_get_auto_transition (layer)) { + gst_object_unref (layer); + return -1; + } + + gst_object_unref (layer); + for (tmp = timeline->priv->auto_transitions; tmp; tmp = tmp->next) { + GESTimelineElement *replace; + GESAutoTransition *auto_transition = tmp->data; + + if (GES_TIMELINE_ELEMENT (auto_transition->transition) == element || + GES_TIMELINE_ELEMENT (auto_transition->transition_clip) == element) { + if (auto_transition->positioning) { + GST_ERROR_OBJECT (element, "Trying to edit an auto-transition " + "whilst it is being positioned"); + return FALSE; + } + if (new_layer_priority != layer_prio) { + GST_WARNING_OBJECT (element, "Cannot edit an auto-transition to a " + "new layer"); + return FALSE; + } + if (mode != GES_EDIT_MODE_TRIM) { + GST_WARNING_OBJECT (element, "Cannot edit an auto-transition " + "under the edit mode %i", mode); + return FALSE; + } + + if (edge == GES_EDGE_END) + replace = GES_TIMELINE_ELEMENT (auto_transition->previous_source); + else + replace = GES_TIMELINE_ELEMENT (auto_transition->next_source); + + GST_INFO_OBJECT (element, "Trimming %" GES_FORMAT " in place of " + "trimming the corresponding auto-transition", GES_ARGS (replace)); + return ges_timeline_element_edit_full (replace, -1, mode, edge, + position, error); + } + } + + return -1; +} + +gboolean +ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element, + gint64 new_layer_priority, GESEditMode mode, GESEdge edge, + guint64 position, GError ** error) +{ + GstClockTimeDiff edge_diff = (edge == GES_EDGE_END ? + GST_CLOCK_DIFF (position, element->start + element->duration) : + GST_CLOCK_DIFF (position, element->start)); + gint64 prio_diff = (gint64) ges_timeline_element_get_layer_priority (element) + - new_layer_priority; + gint res = -1; + + if ((GES_IS_TRANSITION (element) || GES_IS_TRANSITION_CLIP (element))) + res = _edit_auto_transition (timeline, element, new_layer_priority, mode, + edge, position, error); + + if (res != -1) + return res; + + switch (mode) { + case GES_EDIT_MODE_RIPPLE: + return timeline_tree_ripple (timeline->priv->tree, element, prio_diff, + edge_diff, edge, timeline->priv->snapping_distance, error); + case GES_EDIT_MODE_TRIM: + return timeline_tree_trim (timeline->priv->tree, element, prio_diff, + edge_diff, edge, timeline->priv->snapping_distance, error); + case GES_EDIT_MODE_NORMAL: + return timeline_tree_move (timeline->priv->tree, element, prio_diff, + edge_diff, edge, timeline->priv->snapping_distance, error); + case GES_EDIT_MODE_ROLL: + if (prio_diff != 0) { + GST_WARNING_OBJECT (element, "Cannot roll an element to a new layer"); + return FALSE; + } + return timeline_tree_roll (timeline->priv->tree, element, + edge_diff, edge, timeline->priv->snapping_distance, error); + case GES_EDIT_MODE_SLIDE: + GST_ERROR_OBJECT (element, "Sliding not implemented."); + return FALSE; + } + return FALSE; +} + +void +timeline_add_group (GESTimeline * timeline, GESGroup * group) +{ + GST_DEBUG_OBJECT (timeline, "Adding group %" GST_PTR_FORMAT, group); + + timeline->priv->groups = g_list_prepend (timeline->priv->groups, + gst_object_ref_sink (group)); + + ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (group), timeline); +} + +/** + * timeline_emit_group_added: + * @timeline: The #GESTimeline + * @group: group that was added + * + * Emit group-added signal. + */ +void +timeline_emit_group_added (GESTimeline * timeline, GESGroup * group) +{ + g_signal_emit (timeline, ges_timeline_signals[GROUP_ADDED], 0, group); +} + +/** + * timeline_emit_group_removed: + * @timeline: The #GESTimeline + * @group: group that was removed + * + * Emit group-removed signal. + */ +void +timeline_emit_group_removed (GESTimeline * timeline, GESGroup * group, + GPtrArray * array) +{ + g_signal_emit (timeline, ges_timeline_signals[GROUP_REMOVED], 0, group, + array); +} + +void +timeline_remove_group (GESTimeline * timeline, GESGroup * group) +{ + GST_DEBUG_OBJECT (timeline, "Removing group %" GST_PTR_FORMAT, group); + + timeline->priv->groups = g_list_remove (timeline->priv->groups, group); + + ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (group), NULL); + gst_object_unref (group); +} + +static GESTrackElement * +_core_in_track (GESTrack * track, GESClip * clip) +{ + GList *tmp; + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTrackElement *el = tmp->data; + if (ges_track_element_is_core (el) + && ges_track_element_get_track (el) == track) { + return tmp->data; + } + } + return NULL; +} + +static GPtrArray * +select_tracks_for_object_default (GESTimeline * timeline, + GESClip * clip, GESTrackElement * tr_object, gpointer user_data) +{ + GPtrArray *result; + GList *tmp; + GESTrackElement *core; + + result = g_ptr_array_new (); + + LOCK_DYN (timeline); + for (tmp = timeline->tracks; tmp; tmp = tmp->next) { + GESTrack *track = GES_TRACK (tmp->data); + + if ((track->type & ges_track_element_get_track_type (tr_object))) { + if (ges_track_element_is_core (tr_object)) { + core = _core_in_track (track, clip); + if (core) { + GST_WARNING_OBJECT (timeline, "The clip '%s' contains multiple " + "core elements of the same %s track type. The core child " + "'%s' has already been chosen arbitrarily for the track %" + GST_PTR_FORMAT ", which means that the other core child " + "'%s' of the same type can not be added to the track. " + "Consider connecting to " + "GESTimeline::select-tracks-for-objects to be able to " + "specify which core element should land in the track", + GES_TIMELINE_ELEMENT_NAME (clip), + ges_track_type_name (track->type), + GES_TIMELINE_ELEMENT_NAME (core), track, + GES_TIMELINE_ELEMENT_NAME (tr_object)); + continue; + } + } + gst_object_ref (track); + g_ptr_array_add (result, track); + } + } + UNLOCK_DYN (timeline); + + return result; +} + +static GPtrArray * +_get_selected_tracks (GESTimeline * timeline, GESClip * clip, + GESTrackElement * track_element) +{ + guint i, j; + GPtrArray *tracks = NULL; + GESTrack *track = NULL; + + g_signal_emit (G_OBJECT (timeline), + ges_timeline_signals[SELECT_ELEMENT_TRACK], 0, clip, track_element, + &track); + + if (track) { + tracks = g_ptr_array_new (); + + g_ptr_array_add (tracks, track); + } else { + g_signal_emit (G_OBJECT (timeline), + ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT], 0, clip, track_element, + &tracks); + } + + if (tracks == NULL) + tracks = g_ptr_array_new (); + + g_ptr_array_set_free_func (tracks, gst_object_unref); + + /* make sure unique */ + for (i = 0; i < tracks->len;) { + GESTrack *track = GES_TRACK (g_ptr_array_index (tracks, i)); + + for (j = i + 1; j < tracks->len;) { + if (track == g_ptr_array_index (tracks, j)) { + GST_WARNING_OBJECT (timeline, "Found the track %" GST_PTR_FORMAT + " more than once in the return for select-tracks-for-object " + "signal for track element %" GES_FORMAT " in clip %" + GES_FORMAT ". Ignoring the extra track", track, + GES_ARGS (track_element), GES_ARGS (clip)); + g_ptr_array_remove_index (tracks, j); + /* don't increase index since the next track is in its place */ + continue; + } + j++; + } + + if (ges_track_get_timeline (track) != timeline) { + GST_WARNING_OBJECT (timeline, "The track %" GST_PTR_FORMAT + " found in the return for select-tracks-for-object belongs " + "to a different timeline %" GST_PTR_FORMAT ". Ignoring this " + "track", track, ges_track_get_timeline (track)); + g_ptr_array_remove_index (tracks, i); + /* don't increase index since the next track is in its place */ + continue; + } + i++; + } + + return tracks; +} + +/* returns TRUE if track element was successfully added to all the + * selected tracks */ +static gboolean +_add_track_element_to_tracks (GESTimeline * timeline, GESClip * clip, + GESTrackElement * track_element, GError ** error) +{ + guint i; + gboolean ret = TRUE; + GPtrArray *tracks = _get_selected_tracks (timeline, clip, track_element); + + for (i = 0; i < tracks->len; i++) { + GESTrack *track = GES_TRACK (g_ptr_array_index (tracks, i)); + if (!ges_clip_add_child_to_track (clip, track_element, track, error)) { + ret = FALSE; + if (error) + break; + } + } + + g_ptr_array_unref (tracks); + + return ret; +} + +static gboolean +_try_add_track_element_to_track (GESTimeline * timeline, GESClip * clip, + GESTrackElement * track_element, GESTrack * track, GError ** error) +{ + gboolean no_error = TRUE; + GPtrArray *tracks = _get_selected_tracks (timeline, clip, track_element); + + /* if we are trying to add the element to a newly added track, then + * we only check whether the track list contains the newly added track, + * if it does we add the track element to the track, or add a copy if + * the track element is already in a track */ + if (g_ptr_array_find (tracks, track, NULL)) { + if (!ges_clip_add_child_to_track (clip, track_element, track, error)) + no_error = FALSE; + } + + g_ptr_array_unref (tracks); + return no_error; +} + +/* accepts NULL */ +void +ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving) +{ + if (timeline) { + LOCK_DYN (timeline); + timeline->priv->track_elements_moving = moving; + UNLOCK_DYN (timeline); + } +} + +void +ges_timeline_set_track_selection_error (GESTimeline * timeline, + gboolean was_error, GError * error) +{ + GESTimelinePrivate *priv; + + LOCK_DYN (timeline); + + priv = timeline->priv; + g_clear_error (&priv->track_selection_error); + priv->track_selection_error = error; + priv->has_any_track_selection_error = was_error; + + UNLOCK_DYN (timeline); +} + +gboolean +ges_timeline_take_track_selection_error (GESTimeline * timeline, + GError ** error) +{ + gboolean ret; + GESTimelinePrivate *priv; + + LOCK_DYN (timeline); + + priv = timeline->priv; + if (error) { + if (*error) { + GST_ERROR_OBJECT (timeline, "Error not handled %s", (*error)->message); + g_error_free (*error); + } + *error = priv->track_selection_error; + } else if (priv->track_selection_error) { + GST_WARNING_OBJECT (timeline, "Got track selection error: %s", + priv->track_selection_error->message); + g_error_free (priv->track_selection_error); + } + priv->track_selection_error = NULL; + ret = priv->has_any_track_selection_error; + priv->has_any_track_selection_error = FALSE; + + UNLOCK_DYN (timeline); + + return ret; +} + +static void +clip_track_element_added_cb (GESClip * clip, + GESTrackElement * track_element, GESTimeline * timeline) +{ + GESTrack *auto_trans_track, *new_track; + GError *error = NULL; + gboolean success = FALSE; + + if (timeline->priv->track_elements_moving) { + GST_DEBUG_OBJECT (timeline, "Ignoring element added: %" GES_FORMAT + " in %" GES_FORMAT, GES_ARGS (track_element), GES_ARGS (clip)); + return; + } + + if (ges_track_element_get_track (track_element) != NULL) { + GST_DEBUG_OBJECT (timeline, "Not selecting tracks for %" GES_FORMAT + " in %" GES_FORMAT " because it already part of the track %" + GST_PTR_FORMAT, GES_ARGS (track_element), GES_ARGS (clip), + ges_track_element_get_track (track_element)); + return; + } + + LOCK_DYN (timeline); + /* take ownership of auto_transition_track. For auto-transitions, this + * should be used exactly once! */ + auto_trans_track = timeline->priv->auto_transition_track; + timeline->priv->auto_transition_track = NULL; + /* don't take ownership of new_track */ + new_track = timeline->priv->new_track; + UNLOCK_DYN (timeline); + + if (auto_trans_track) { + /* don't use track-selection */ + success = ! !ges_clip_add_child_to_track (clip, track_element, + auto_trans_track, &error); + gst_object_unref (auto_trans_track); + } else { + if (new_track) + success = _try_add_track_element_to_track (timeline, clip, track_element, + new_track, &error); + else + success = _add_track_element_to_tracks (timeline, clip, track_element, + &error); + } + + if (error || !success) { + if (!error) + GST_WARNING_OBJECT (timeline, "Track selection failed for %" GES_FORMAT, + GES_ARGS (track_element)); + ges_timeline_set_track_selection_error (timeline, TRUE, error); + } +} + +static void +clip_track_element_removed_cb (GESClip * clip, + GESTrackElement * track_element, GESTimeline * timeline) +{ + GESTrack *track = ges_track_element_get_track (track_element); + + if (timeline->priv->track_elements_moving) { + GST_DEBUG_OBJECT (timeline, "Ignoring element removed (%" GST_PTR_FORMAT + " in %" GST_PTR_FORMAT, track_element, clip); + + return; + } + + if (track) { + /* if we have non-core elements in the same track, they should be + * removed from them to preserve the rule that a non-core can only be + * in the same track as a core element from the same clip */ + if (ges_track_element_is_core (track_element)) + ges_clip_empty_from_track (clip, track); + ges_track_remove_element (track, track_element); + } +} + +static void +track_element_added_cb (GESTrack * track, GESTrackElement * element, + GESTimeline * timeline) +{ + if (GES_IS_SOURCE (element)) + timeline_tree_create_transitions_for_track_element (timeline->priv->tree, + element, ges_timeline_find_auto_transition); +} + +/* returns TRUE if no errors in adding to tracks */ +static gboolean +_add_clip_children_to_tracks (GESTimeline * timeline, GESClip * clip, + gboolean add_core, GESTrack * new_track, GList * blacklist, GError ** error) +{ + GList *tmp, *children; + gboolean no_errors = TRUE; + + /* list of children may change if some are copied into tracks */ + children = ges_container_get_children (GES_CONTAINER (clip), FALSE); + for (tmp = children; tmp; tmp = tmp->next) { + GESTrackElement *el = tmp->data; + if (ges_track_element_is_core (el) != add_core) + continue; + if (g_list_find (blacklist, el)) + continue; + if (ges_track_element_get_track (el) == NULL) { + gboolean res; + if (new_track) + res = _try_add_track_element_to_track (timeline, clip, el, new_track, + error); + else + res = _add_track_element_to_tracks (timeline, clip, el, error); + if (!res) { + no_errors = FALSE; + if (error) + goto done; + } + } + } + +done: + g_list_free_full (children, gst_object_unref); + + return no_errors; +} + +/* returns TRUE if no errors in adding to tracks */ +static gboolean +add_object_to_tracks (GESTimeline * timeline, GESClip * clip, + GESTrack * new_track, GError ** error) +{ + GList *tracks, *tmp, *list, *created, *just_added = NULL; + gboolean no_errors = TRUE; + + GST_DEBUG_OBJECT (timeline, "Creating %" GST_PTR_FORMAT + " trackelements and adding them to our tracks", clip); + + LOCK_DYN (timeline); + tracks = + g_list_copy_deep (timeline->tracks, (GCopyFunc) gst_object_ref, NULL); + timeline->priv->new_track = new_track ? gst_object_ref (new_track) : NULL; + UNLOCK_DYN (timeline); + + /* create core elements */ + for (tmp = tracks; tmp; tmp = tmp->next) { + GESTrack *track = GES_TRACK (tmp->data); + if (new_track && track != new_track) + continue; + + list = ges_clip_create_track_elements (clip, track->type); + /* just_added only used for pointer comparison, so safe to include + * elements that may be destroyed because they fail to be added to + * the clip */ + just_added = g_list_concat (just_added, list); + + for (created = list; created; created = created->next) { + GESTimelineElement *el = created->data; + + gst_object_ref (el); + + /* make track selection be handled by clip_track_element_added_cb + * This is needed for backward-compatibility: when adding a clip to + * a layer, the track is set for the core elements of the clip + * during the child-added signal emission, just before the user's + * own connection. + * NOTE: for the children that have not just been created, they + * are already part of the clip and so child-added will not be + * released. And when a child is selected for multiple tracks, their + * copy will be added to the clip before the track is selected, so + * the track will not be set in the child-added signal */ + ges_timeline_set_track_selection_error (timeline, FALSE, NULL); + ges_clip_set_add_error (clip, NULL); + if (!ges_container_add (GES_CONTAINER (clip), el)) { + no_errors = FALSE; + if (!error) + GST_ERROR_OBJECT (clip, "Could not add the core element %s " + "to the clip", el->name); + } + gst_object_unref (el); + ges_clip_take_add_error (clip, error); + + if (error && !no_errors) + goto done; + + if (ges_timeline_take_track_selection_error (timeline, error)) { + no_errors = FALSE; + if (error) + goto done; + /* else, carry on as much as we can */ + } + } + } + + /* set the tracks for the other children, with core elements first to + * make sure the non-core can be placed above them in the track (a + * non-core can not be in a track by itself) */ + /* include just_added as a blacklist to ensure we do not try the track + * selection a second time when track selection returns no tracks */ + if (!_add_clip_children_to_tracks (timeline, clip, TRUE, new_track, + just_added, error)) { + no_errors = FALSE; + if (error) + goto done; + } + + if (!_add_clip_children_to_tracks (timeline, clip, FALSE, new_track, + just_added, error)) { + no_errors = FALSE; + if (error) + goto done; + } + +done: + g_list_free_full (tracks, gst_object_unref); + + LOCK_DYN (timeline); + gst_clear_object (&timeline->priv->new_track); + UNLOCK_DYN (timeline); + + g_list_free (just_added); + + return no_errors; +} + +static void +layer_active_changed_cb (GESLayer * layer, gboolean active G_GNUC_UNUSED, + GPtrArray * tracks G_GNUC_UNUSED, GESTimeline * timeline) +{ + timeline_tree_reset_layer_active (timeline->priv->tree, layer); +} + +static void +layer_auto_transition_changed_cb (GESLayer * layer, + GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) +{ + GList *tmp, *clips; + + timeline_tree_create_transitions (timeline->priv->tree, + _create_auto_transition_from_transitions); + clips = ges_layer_get_clips (layer); + for (tmp = clips; tmp; tmp = tmp->next) { + if (GES_IS_TRANSITION_CLIP (tmp->data)) { + GList *tmpautotrans; + gboolean found = FALSE; + + for (tmpautotrans = timeline->priv->auto_transitions; tmpautotrans; + tmpautotrans = tmpautotrans->next) { + if (GES_AUTO_TRANSITION (tmpautotrans->data)->transition_clip == + tmp->data) { + found = TRUE; + break; + } + } + + if (!found) { + GST_ERROR_OBJECT (timeline, + "Transition %s could not be wrapped into an auto transition" + " REMOVING it", GES_TIMELINE_ELEMENT_NAME (tmp->data)); + + ges_layer_remove_clip (layer, tmp->data); + } + } + } + g_list_free_full (clips, gst_object_unref); +} + +/* returns TRUE if selecting of tracks did not error */ +gboolean +ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip, GError ** error) +{ + GESProject *project; + gboolean ret; + + ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip), timeline); + + /* We make sure not to be connected twice */ + g_signal_handlers_disconnect_by_func (clip, clip_track_element_added_cb, + timeline); + g_signal_handlers_disconnect_by_func (clip, clip_track_element_removed_cb, + timeline); + + /* And we connect to the object */ + g_signal_connect (clip, "child-added", + G_CALLBACK (clip_track_element_added_cb), timeline); + g_signal_connect (clip, "child-removed", + G_CALLBACK (clip_track_element_removed_cb), timeline); + + GST_DEBUG ("Making sure that the asset is in our project"); + project = + GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline))); + ges_project_add_asset (project, + ges_extractable_get_asset (GES_EXTRACTABLE (clip))); + + if (ges_clip_is_moving_from_layer (clip)) { + GST_DEBUG ("Clip %p moving from one layer to another, not creating " + "TrackElement", clip); + /* timeline-tree handles creation of auto-transitions */ + ret = TRUE; + } else { + ret = add_object_to_tracks (timeline, clip, NULL, error); + } + + GST_DEBUG ("Done"); + + return ret; +} + +static void +layer_priority_changed_cb (GESLayer * layer, + GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) +{ + if (timeline->priv->resyncing_layers) + return; + + timeline->layers = g_list_sort (timeline->layers, (GCompareFunc) + sort_layers); +} + +void +ges_timeline_remove_clip (GESTimeline * timeline, GESClip * clip) +{ + GList *tmp; + + if (ges_clip_is_moving_from_layer (clip)) { + GST_DEBUG ("Clip %p is moving from a layer to another, not doing" + " anything on it", clip); + return; + } + + GST_DEBUG_OBJECT (timeline, "Clip %" GES_FORMAT " removed from layer", + GES_ARGS (clip)); + + LOCK_DYN (timeline); + for (tmp = timeline->tracks; tmp; tmp = tmp->next) + ges_clip_empty_from_track (clip, tmp->data); + UNLOCK_DYN (timeline); + + g_signal_handlers_disconnect_by_func (clip, clip_track_element_added_cb, + timeline); + g_signal_handlers_disconnect_by_func (clip, clip_track_element_removed_cb, + timeline); + + ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip), NULL); + + GST_DEBUG ("Done"); +} + +static gboolean +update_stream_object (TrackPrivate * tr_priv) +{ + gboolean res = FALSE; + GstStreamType type = GST_STREAM_TYPE_UNKNOWN; + gchar *stream_id; + + g_object_get (tr_priv->track, "id", &stream_id, NULL); + if (tr_priv->track->type == GES_TRACK_TYPE_VIDEO) + type = GST_STREAM_TYPE_VIDEO; + if (tr_priv->track->type == GES_TRACK_TYPE_AUDIO) + type = GST_STREAM_TYPE_AUDIO; + + if (!tr_priv->stream || + g_strcmp0 (stream_id, gst_stream_get_stream_id (tr_priv->stream))) { + res = TRUE; + gst_object_replace ((GstObject **) & tr_priv->stream, + (GstObject *) gst_stream_new (stream_id, + (GstCaps *) ges_track_get_caps (tr_priv->track), + type, GST_STREAM_FLAG_NONE) + ); + } + + g_free (stream_id); + + return res; +} + +static GstPadProbeReturn +_pad_probe_cb (GstPad * mixer_pad, GstPadProbeInfo * info, + TrackPrivate * tr_priv) +{ + GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + GESTimeline *timeline = tr_priv->timeline; + + if (GST_EVENT_TYPE (event) == GST_EVENT_STREAM_START) { + LOCK_DYN (timeline); + if (timeline->priv->stream_start_group_id == -1) { + if (!gst_event_parse_group_id (event, + &timeline->priv->stream_start_group_id)) + timeline->priv->stream_start_group_id = gst_util_group_id_next (); + } + + gst_event_unref (event); + event = info->data = + gst_event_new_stream_start (gst_stream_get_stream_id (tr_priv->stream)); + gst_event_set_stream (event, tr_priv->stream); + gst_event_set_group_id (event, timeline->priv->stream_start_group_id); + UNLOCK_DYN (timeline); + + return GST_PAD_PROBE_REMOVE; + } + + return GST_PAD_PROBE_OK; +} + +static void +_ghost_track_srcpad (TrackPrivate * tr_priv) +{ + GstPad *pad; + gchar *padname; + gboolean no_more; + GList *tmp; + GESTrack *track = tr_priv->track; + + pad = gst_element_get_static_pad (GST_ELEMENT (track), "src"); + + GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad)); + + /* Remember the pad */ + LOCK_DYN (tr_priv->timeline); + GST_OBJECT_LOCK (track); + tr_priv->pad = pad; + + no_more = TRUE; + for (tmp = tr_priv->timeline->priv->priv_tracks; tmp; tmp = g_list_next (tmp)) { + TrackPrivate *tr_priv = (TrackPrivate *) tmp->data; + + if (!tr_priv->pad) { + GST_LOG ("Found track without pad %p", tr_priv->track); + no_more = FALSE; + } + } + GST_OBJECT_UNLOCK (track); + + /* ghost it ! */ + GST_DEBUG ("Ghosting pad and adding it to ourself"); + padname = g_strdup_printf ("track_%p_src", track); + tr_priv->ghostpad = gst_ghost_pad_new (padname, pad); + g_free (padname); + gst_pad_set_active (tr_priv->ghostpad, TRUE); + gst_element_add_pad (GST_ELEMENT (tr_priv->timeline), tr_priv->ghostpad); + + if (no_more) { + GST_DEBUG ("Signaling no-more-pads"); + gst_element_no_more_pads (GST_ELEMENT (tr_priv->timeline)); + } + + tr_priv->probe_id = gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + (GstPadProbeCallback) _pad_probe_cb, tr_priv, NULL); + + UNLOCK_DYN (tr_priv->timeline); +} + +gboolean +timeline_add_element (GESTimeline * timeline, GESTimelineElement * element) +{ + /* FIXME: handle NULL element->name */ + GESTimelineElement *same_name = + g_hash_table_lookup (timeline->priv->all_elements, + element->name); + + GST_DEBUG_OBJECT (timeline, "Adding element: %s", element->name); + if (same_name) { + GST_ERROR_OBJECT (timeline, "%s Already in the timeline %" GST_PTR_FORMAT, + element->name, same_name); + return FALSE; + } + + /* FIXME: why is the hash table using the name of the element, rather than + * the pointer to the element itself as the key? This makes it awkward + * to change the name of an element after it has been added. See + * ges_timeline_element_set_name. It means we have to remove and then + * re-add the element. */ + g_hash_table_insert (timeline->priv->all_elements, + ges_timeline_element_get_name (element), gst_object_ref (element)); + + timeline_tree_track_element (timeline->priv->tree, element); + if (GES_IS_SOURCE (element)) { + ges_source_set_rendering_smartly (GES_SOURCE (element), + timeline->priv->rendering_smartly); + } + + return TRUE; +} + +gboolean +timeline_remove_element (GESTimeline * timeline, GESTimelineElement * element) +{ + if (g_hash_table_remove (timeline->priv->all_elements, element->name)) { + timeline_tree_stop_tracking_element (timeline->priv->tree, element); + + return TRUE; + } + + return FALSE; +} + +void +timeline_fill_gaps (GESTimeline * timeline) +{ + GList *tmp; + + LOCK_DYN (timeline); + for (tmp = timeline->tracks; tmp; tmp = tmp->next) { + track_resort_and_fill_gaps (tmp->data); + } + UNLOCK_DYN (timeline); +} + +GNode * +timeline_get_tree (GESTimeline * timeline) +{ + return timeline->priv->tree; +} + +void +ges_timeline_set_smart_rendering (GESTimeline * timeline, + gboolean rendering_smartly) +{ + if (rendering_smartly) { + GList *tmp; + + for (tmp = timeline->tracks; tmp; tmp = tmp->next) { + if (ges_track_get_mixing (tmp->data)) { + GST_INFO_OBJECT (timeline, "Smart rendering will not" + " work as track %" GST_PTR_FORMAT " is doing mixing", tmp->data); + } else { + ges_track_set_smart_rendering (tmp->data, rendering_smartly); + } + } + } + timeline_tree_set_smart_rendering (timeline->priv->tree, rendering_smartly); + timeline->priv->rendering_smartly = rendering_smartly; +} + +gboolean +ges_timeline_get_smart_rendering (GESTimeline * timeline) +{ + return timeline->priv->rendering_smartly; +} + +/**** API *****/ +/** + * ges_timeline_new: + * + * Creates a new empty timeline. + * + * Returns: (transfer floating): The new timeline. + */ + +GESTimeline * +ges_timeline_new (void) +{ + GESProject *project = ges_project_new (NULL); + GESExtractable *timeline = g_object_new (GES_TYPE_TIMELINE, NULL); + + ges_extractable_set_asset (timeline, GES_ASSET (project)); + gst_object_unref (project); + + return GES_TIMELINE (timeline); +} + +/** + * ges_timeline_new_from_uri: + * @uri: The URI to load from + * @error: (out) (allow-none): An error to be set if loading fails, or + * %NULL to ignore + * + * Creates a timeline from the given URI. + * + * Returns: (transfer floating) (nullable): A new timeline if the uri was loaded + * successfully, or %NULL if the uri could not be loaded. + */ +GESTimeline * +ges_timeline_new_from_uri (const gchar * uri, GError ** error) +{ + GESTimeline *ret; + GESProject *project = ges_project_new (uri); + + ret = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), error)); + gst_object_unref (project); + + return ret; +} + +/** + * ges_timeline_load_from_uri: + * @timeline: An empty #GESTimeline into which to load the formatter + * @uri: The URI to load from + * @error: (out) (allow-none): An error to be set if loading fails, or + * %NULL to ignore + * + * Loads the contents of URI into the timeline. + * + * Returns: %TRUE if the timeline was loaded successfully from @uri. + */ +gboolean +ges_timeline_load_from_uri (GESTimeline * timeline, const gchar * uri, + GError ** error) +{ + GESProject *project; + gboolean ret = FALSE; + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + g_return_val_if_fail ((ges_extractable_get_asset (GES_EXTRACTABLE + (timeline)) == NULL), FALSE); + + project = ges_project_new (uri); + ret = ges_project_load (project, timeline, error); + gst_object_unref (project); + + return ret; +} + +/** + * ges_timeline_save_to_uri: + * @timeline: The #GESTimeline + * @uri: The location to save to + * @formatter_asset: (allow-none): The formatter asset to use, or %NULL + * @overwrite: %TRUE to overwrite file if it exists + * @error: (out) (allow-none): An error to be set if saving fails, or + * %NULL to ignore + * + * Saves the timeline to the given location. If @formatter_asset is %NULL, + * the method will attempt to save in the same format the timeline was + * loaded from, before defaulting to the formatter with highest rank. + * + * Returns: %TRUE if @timeline was successfully saved to @uri. + */ +gboolean +ges_timeline_save_to_uri (GESTimeline * timeline, const gchar * uri, + GESAsset * formatter_asset, gboolean overwrite, GError ** error) +{ + GESProject *project; + + gboolean ret, created_proj = FALSE; + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + project = + GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline))); + + if (project == NULL) { + project = ges_project_new (NULL); + created_proj = TRUE; + } + + ret = ges_project_save (project, timeline, uri, formatter_asset, overwrite, + error); + + if (created_proj) + gst_object_unref (project); + + return ret; +} + +/** + * ges_timeline_get_groups: + * @timeline: The #GESTimeline + * + * Get the list of #GESGroup-s present in the timeline. + * + * Returns: (transfer none) (element-type GESGroup): The list of + * groups that contain clips present in @timeline's layers. + * Must not be changed. + */ +GList * +ges_timeline_get_groups (GESTimeline * timeline) +{ + g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL); + CHECK_THREAD (timeline); + + return timeline->priv->groups; +} + +/** + * ges_timeline_append_layer: + * @timeline: The #GESTimeline + * + * Append a newly created layer to the timeline. The layer will + * be added at the lowest #GESLayer:priority (numerically, the highest). + * + * Returns: (transfer none): The newly created layer. + */ +GESLayer * +ges_timeline_append_layer (GESTimeline * timeline) +{ + GList *tmp; + guint32 priority; + GESLayer *layer; + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL); + CHECK_THREAD (timeline); + + layer = ges_layer_new (); + + priority = 0; + for (tmp = timeline->layers; tmp; tmp = tmp->next) + priority = MAX (priority, ges_layer_get_priority (tmp->data) + 1); + + ges_layer_set_priority (layer, priority); + + ges_timeline_add_layer (timeline, layer); + + return layer; +} + +/** + * ges_timeline_add_layer: + * @timeline: The #GESTimeline + * @layer: (transfer floating): The layer to add + * + * Add a layer to the timeline. + * + * If the layer contains #GESClip-s, then this may trigger the creation of + * their core track element children for the timeline's tracks, and the + * placement of the clip's children in the tracks of the timeline using + * #GESTimeline::select-tracks-for-object. Some errors may occur if this + * would break one of the configuration rules of the timeline in one of + * its tracks. In such cases, some track elements would fail to be added + * to their tracks, but this method would still return %TRUE. As such, it + * is advised that you only add clips to layers that already part of a + * timeline. In such situations, ges_layer_add_clip() is able to fail if + * adding the clip would cause such an error. + * + * Deprecated: 1.18: This method requires you to ensure the layer's + * #GESLayer:priority will be unique to the timeline. Use + * ges_timeline_append_layer() and ges_timeline_move_layer() instead. + * + * Returns: %TRUE if @layer was properly added. + */ +gboolean +ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer) +{ + gboolean auto_transition; + GList *objects, *tmp; + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + g_return_val_if_fail (GES_IS_LAYER (layer), FALSE); + CHECK_THREAD (timeline); + + GST_DEBUG ("timeline:%p, layer:%p", timeline, layer); + + /* We can only add a layer that doesn't already belong to another timeline */ + if (G_UNLIKELY (layer->timeline)) { + GST_WARNING ("Layer belongs to another timeline, can't add it"); + gst_object_ref_sink (layer); + gst_object_unref (layer); + return FALSE; + } + + /* Add to the list of layers, make sure we don't already control it */ + if (G_UNLIKELY (g_list_find (timeline->layers, (gconstpointer) layer))) { + GST_WARNING ("Layer is already controlled by this timeline"); + gst_object_ref_sink (layer); + gst_object_unref (layer); + return FALSE; + } + + /* FIXME: ensure the layer->priority does not conflict with an existing + * layer in the timeline. Currently can add several layers with equal + * layer priorities */ + + auto_transition = ges_layer_get_auto_transition (layer); + + /* If the user doesn't explicitely set layer auto_transition, then set our */ + if (!auto_transition) { + auto_transition = ges_timeline_get_auto_transition (timeline); + ges_layer_set_auto_transition (layer, auto_transition); + } + + gst_object_ref_sink (layer); + timeline->layers = g_list_insert_sorted (timeline->layers, layer, + (GCompareFunc) sort_layers); + + /* Inform the layer that it belongs to a new timeline */ + ges_layer_set_timeline (layer, timeline); + + /* Connect to 'clip-added'/'clip-removed' signal from the new layer */ + g_signal_connect (layer, "notify::priority", + G_CALLBACK (layer_priority_changed_cb), timeline); + g_signal_connect (layer, "notify::auto-transition", + G_CALLBACK (layer_auto_transition_changed_cb), timeline); + g_signal_connect_after (layer, "active-changed", + G_CALLBACK (layer_active_changed_cb), timeline); + + GST_DEBUG ("Done adding layer, emitting 'layer-added' signal"); + g_signal_emit (timeline, ges_timeline_signals[LAYER_ADDED], 0, layer); + + /* add any existing clips to the timeline */ + objects = ges_layer_get_clips (layer); + for (tmp = objects; tmp; tmp = tmp->next) + ges_timeline_add_clip (timeline, tmp->data, NULL); + g_list_free_full (objects, gst_object_unref); + + return TRUE; +} + +/** + * ges_timeline_remove_layer: + * @timeline: The #GESTimeline + * @layer: The layer to remove + * + * Removes a layer from the timeline. + * + * Returns: %TRUE if @layer was properly removed. + */ + +gboolean +ges_timeline_remove_layer (GESTimeline * timeline, GESLayer * layer) +{ + GList *layer_objects, *tmp; + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + g_return_val_if_fail (GES_IS_LAYER (layer), FALSE); + + if (!timeline->priv->disposed) + CHECK_THREAD (timeline); + + GST_DEBUG ("timeline:%p, layer:%p", timeline, layer); + + if (G_UNLIKELY (!g_list_find (timeline->layers, layer))) { + GST_WARNING ("Layer doesn't belong to this timeline"); + return FALSE; + } + + /* remove objects from any private data structures */ + + layer_objects = ges_layer_get_clips (layer); + for (tmp = layer_objects; tmp; tmp = tmp->next) + ges_timeline_remove_clip (timeline, tmp->data); + g_list_free_full (layer_objects, gst_object_unref); + + /* Disconnect signals */ + GST_DEBUG ("Disconnecting signal callbacks"); + g_signal_handlers_disconnect_by_func (layer, layer_priority_changed_cb, + timeline); + g_signal_handlers_disconnect_by_func (layer, + layer_auto_transition_changed_cb, timeline); + g_signal_handlers_disconnect_by_func (layer, layer_active_changed_cb, + timeline); + + timeline->layers = g_list_remove (timeline->layers, layer); + ges_layer_set_timeline (layer, NULL); + /* FIXME: we should resync the layer priorities */ + + g_signal_emit (timeline, ges_timeline_signals[LAYER_REMOVED], 0, layer); + + gst_object_unref (layer); + + return TRUE; +} + +/** + * ges_timeline_add_track: + * @timeline: The #GESTimeline + * @track: (transfer full): The track to add + * + * Add a track to the timeline. + * + * If the timeline already contains clips, then this may trigger the + * creation of their core track element children for the track, and the + * placement of the clip's children in the track of the timeline using + * #GESTimeline::select-tracks-for-object. Some errors may occur if this + * would break one of the configuration rules for the timeline in the + * track. In such cases, some track elements would fail to be added to the + * track, but this method would still return %TRUE. As such, it is advised + * that you avoid adding tracks to timelines that already contain clips. + * + * Returns: %TRUE if @track was properly added. + */ + +/* FIXME: create track elements for clips which have already been + * added to existing layers. + */ + +gboolean +ges_timeline_add_track (GESTimeline * timeline, GESTrack * track) +{ + TrackPrivate *tr_priv; + GList *tmp; + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + g_return_val_if_fail (GES_IS_TRACK (track), FALSE); + CHECK_THREAD (timeline); + + GST_DEBUG ("timeline:%p, track:%p", timeline, track); + + /* make sure we don't already control it */ + LOCK_DYN (timeline); + if (G_UNLIKELY (g_list_find (timeline->tracks, (gconstpointer) track))) { + UNLOCK_DYN (timeline); + GST_WARNING ("Track is already controlled by this timeline"); + return FALSE; + } + + /* Add the track to ourself (as a GstBin) + * Reference is stolen ! */ + if (G_UNLIKELY (!gst_bin_add (GST_BIN (timeline), GST_ELEMENT (track)))) { + UNLOCK_DYN (timeline); + GST_WARNING ("Couldn't add track to ourself (GST)"); + return FALSE; + } + + tr_priv = g_new0 (TrackPrivate, 1); + tr_priv->timeline = timeline; + tr_priv->track = track; + tr_priv->track_element_added_sigid = g_signal_connect (track, + "track-element-added", G_CALLBACK (track_element_added_cb), timeline); + + update_stream_object (tr_priv); + gst_stream_collection_add_stream (timeline->priv->stream_collection, + gst_object_ref (tr_priv->stream)); + + /* Add the track to the list of tracks we track */ + timeline->priv->priv_tracks = g_list_append (timeline->priv->priv_tracks, + tr_priv); + timeline->tracks = g_list_append (timeline->tracks, track); + + /* Inform the track that it's currently being used by ourself */ + ges_track_set_timeline (track, timeline); + + GST_DEBUG ("Done adding track, emitting 'track-added' signal"); + + _ghost_track_srcpad (tr_priv); + UNLOCK_DYN (timeline); + + /* emit 'track-added' */ + g_signal_emit (timeline, ges_timeline_signals[TRACK_ADDED], 0, track); + + /* ensure that each existing clip has the opportunity to create a + * track element for this track*/ + + for (tmp = timeline->layers; tmp; tmp = tmp->next) { + GList *objects, *obj; + objects = ges_layer_get_clips (tmp->data); + + for (obj = objects; obj; obj = obj->next) + add_object_to_tracks (timeline, obj->data, track, NULL); + + g_list_free_full (objects, gst_object_unref); + } + + /* FIXME Check if we should rollback if we can't sync state */ + gst_element_sync_state_with_parent (GST_ELEMENT (track)); + g_object_set (track, "message-forward", TRUE, NULL); + + return TRUE; +} + +/** + * ges_timeline_remove_track: + * @timeline: The #GESTimeline + * @track: The track to remove + * + * Remove a track from the timeline. + * + * Returns: %TRUE if @track was properly removed. + */ + +/* FIXME: release any track elements associated with this layer. currenly this + * will not happen if you remove the track before removing *all* + * clips which have a track element in this track. + */ + +gboolean +ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track) +{ + GList *tmp; + TrackPrivate *tr_priv; + GESTimelinePrivate *priv; + + g_return_val_if_fail (GES_IS_TRACK (track), FALSE); + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + + GST_DEBUG ("timeline:%p, track:%p", timeline, track); + + priv = timeline->priv; + LOCK_DYN (timeline); + if (G_UNLIKELY (!(tmp = g_list_find_custom (priv->priv_tracks, + track, (GCompareFunc) custom_find_track)))) { + GST_WARNING ("Track doesn't belong to this timeline"); + UNLOCK_DYN (timeline); + return FALSE; + } + + tr_priv = tmp->data; + gst_object_unref (tr_priv->pad); + priv->priv_tracks = g_list_remove (priv->priv_tracks, tr_priv); + UNLOCK_DYN (timeline); + + /* empty track of all elements that belong to the timeline's clips */ + /* elements with no parent can stay in the track, but their timeline + * will be set to NULL when the track's timeline is set to NULL */ + + for (tmp = timeline->layers; tmp; tmp = tmp->next) { + GList *clips, *clip; + clips = ges_layer_get_clips (tmp->data); + + for (clip = clips; clip; clip = clip->next) + ges_clip_empty_from_track (clip->data, track); + + g_list_free_full (clips, gst_object_unref); + } + + timeline->tracks = g_list_remove (timeline->tracks, track); + ges_track_set_timeline (track, NULL); + + /* Remove ghost pad */ + if (tr_priv->ghostpad) { + GST_DEBUG ("Removing ghostpad"); + gst_pad_set_active (tr_priv->ghostpad, FALSE); + gst_ghost_pad_set_target ((GstGhostPad *) tr_priv->ghostpad, NULL); + gst_element_remove_pad (GST_ELEMENT (timeline), tr_priv->ghostpad); + } + + /* Signal track removal to all layers/objects */ + g_signal_emit (timeline, ges_timeline_signals[TRACK_REMOVED], 0, track); + + /* remove track from our bin */ + gst_object_ref (track); + if (G_UNLIKELY (!gst_bin_remove (GST_BIN (timeline), GST_ELEMENT (track)))) { + GST_WARNING ("Couldn't remove track to ourself (GST)"); + gst_object_unref (track); + return FALSE; + } + + g_signal_handler_disconnect (track, tr_priv->track_element_added_sigid); + + /* set track state to NULL */ + gst_element_set_state (GST_ELEMENT (track), GST_STATE_NULL); + + gst_object_unref (track); + + g_free (tr_priv); + + return TRUE; +} + +/** + * ges_timeline_get_track_for_pad: + * @timeline: The #GESTimeline + * @pad: A pad + * + * Search for the #GESTrack corresponding to the given timeline's pad. + * + * Returns: (transfer none) (nullable): The track corresponding to @pad, + * or %NULL if there is an error. + */ + +GESTrack * +ges_timeline_get_track_for_pad (GESTimeline * timeline, GstPad * pad) +{ + GList *tmp; + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL); + + LOCK_DYN (timeline); + for (tmp = timeline->priv->priv_tracks; tmp; tmp = g_list_next (tmp)) { + TrackPrivate *tr_priv = (TrackPrivate *) tmp->data; + if (pad == tr_priv->ghostpad) { + UNLOCK_DYN (timeline); + return tr_priv->track; + } + } + UNLOCK_DYN (timeline); + + return NULL; +} + +/** + * ges_timeline_get_pad_for_track: + * @timeline: The #GESTimeline + * @track: A track + * + * Search for the #GstPad corresponding to the given timeline's track. + * You can link to this pad to receive the output data of the given track. + * + * Returns: (transfer none) (nullable): The pad corresponding to @track, + * or %NULL if there is an error. + */ + +GstPad * +ges_timeline_get_pad_for_track (GESTimeline * timeline, GESTrack * track) +{ + GList *tmp; + + LOCK_DYN (timeline); + for (tmp = timeline->priv->priv_tracks; tmp; tmp = g_list_next (tmp)) { + TrackPrivate *tr_priv = (TrackPrivate *) tmp->data; + + if (track == tr_priv->track) { + if (tr_priv->ghostpad) + gst_object_ref (tr_priv->ghostpad); + + UNLOCK_DYN (timeline); + return tr_priv->ghostpad; + } + } + UNLOCK_DYN (timeline); + + return NULL; +} + +/** + * ges_timeline_get_tracks: + * @timeline: The #GESTimeline + * + * Get the list of #GESTrack-s used by the timeline. + * + * Returns: (transfer full) (element-type GESTrack): The list of tracks + * used by @timeline. + */ +GList * +ges_timeline_get_tracks (GESTimeline * timeline) +{ + GList *res = NULL; + g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL); + + LOCK_DYN (timeline); + res = g_list_copy_deep (timeline->tracks, (GCopyFunc) gst_object_ref, NULL); + UNLOCK_DYN (timeline); + + return res; +} + +/** + * ges_timeline_get_layers: + * @timeline: The #GESTimeline + * + * Get the list of #GESLayer-s present in the timeline. + * + * Returns: (transfer full) (element-type GESLayer): The list of + * layers present in @timeline sorted by priority. + */ +GList * +ges_timeline_get_layers (GESTimeline * timeline) +{ + GList *tmp, *res = NULL; + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL); + CHECK_THREAD (timeline); + + for (tmp = timeline->layers; tmp; tmp = g_list_next (tmp)) { + res = g_list_insert_sorted (res, gst_object_ref (tmp->data), + (GCompareFunc) sort_layers); + } + + return res; +} + +static void +track_commited_cb (GESTrack * track, GESTimeline * timeline) +{ + gboolean emit_commited = FALSE; + GST_OBJECT_LOCK (timeline); + timeline->priv->expected_commited -= 1; + if (timeline->priv->expected_commited == 0) + emit_commited = TRUE; + g_signal_handlers_disconnect_by_func (track, track_commited_cb, timeline); + GST_OBJECT_UNLOCK (timeline); + + if (emit_commited) { + g_signal_emit (timeline, ges_timeline_signals[COMMITED], 0); + } +} + +/* Must be called with the timeline's DYN_LOCK */ +static gboolean +ges_timeline_commit_unlocked (GESTimeline * timeline) +{ + GList *tmp; + gboolean res = TRUE; + + if (timeline->priv->commit_frozen) { + GST_DEBUG_OBJECT (timeline, "commit locked"); + timeline->priv->commit_delayed = TRUE; + return res; + } + + GST_DEBUG_OBJECT (timeline, "commiting changes"); + + timeline_tree_create_transitions (timeline->priv->tree, + ges_timeline_find_auto_transition); + for (tmp = timeline->layers; tmp; tmp = tmp->next) { + GESLayer *layer = tmp->data; + + /* Ensure clip priorities are correct after an edit */ + ges_layer_resync_priorities (layer); + } + + timeline->priv->expected_commited = + g_list_length (timeline->priv->priv_tracks); + + if (timeline->priv->expected_commited == 0) { + g_signal_emit (timeline, ges_timeline_signals[COMMITED], 0); + } else { + GstStreamCollection *collection = gst_stream_collection_new (NULL); + + LOCK_DYN (timeline); + for (tmp = timeline->tracks; tmp; tmp = tmp->next) { + TrackPrivate *tr_priv = + g_list_find_custom (timeline->priv->priv_tracks, tmp->data, + (GCompareFunc) custom_find_track)->data; + + update_stream_object (tr_priv); + gst_stream_collection_add_stream (collection, + gst_object_ref (tr_priv->stream)); + g_signal_connect (tmp->data, "commited", G_CALLBACK (track_commited_cb), + timeline); + if (!ges_track_commit (GES_TRACK (tmp->data))) + res = FALSE; + } + + gst_object_unref (timeline->priv->stream_collection); + timeline->priv->stream_collection = collection; + UNLOCK_DYN (timeline); + } + + return res; +} + +/** + * ges_timeline_commit: + * @timeline: A #GESTimeline + * + * Commit all the pending changes of the clips contained in the + * timeline. + * + * When changes happen in a timeline, they are not immediately executed + * internally, in a way that effects the output data of the timeline. You + * should call this method when you are done with a set of changes and you + * want them to be executed. + * + * Any pending changes will be executed in the backend. The + * #GESTimeline::commited signal will be emitted once this has completed. + * You should not try to change the state of the timeline, seek it or add + * tracks to it before receiving this signal. You can use + * ges_timeline_commit_sync() if you do not want to perform other tasks in + * the mean time. + * + * Note that all the pending changes will automatically be executed when + * the timeline goes from #GST_STATE_READY to #GST_STATE_PAUSED, which is + * usually triggered by a corresponding state changes in a containing + * #GESPipeline. + * + * Returns: %TRUE if pending changes were committed, or %FALSE if nothing + * needed to be committed. + */ +gboolean +ges_timeline_commit (GESTimeline * timeline) +{ + gboolean ret; + GstStreamCollection *pcollection = timeline->priv->stream_collection; + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + + LOCK_DYN (timeline); + ret = ges_timeline_commit_unlocked (timeline); + UNLOCK_DYN (timeline); + + if (pcollection != timeline->priv->stream_collection) { + gst_element_post_message ((GstElement *) timeline, + gst_message_new_stream_collection ((GstObject *) timeline, + timeline->priv->stream_collection)); + } + + ges_timeline_emit_snapping (timeline, NULL, NULL, GST_CLOCK_TIME_NONE); + return ret; +} + +static void +commited_cb (GESTimeline * timeline) +{ + g_mutex_lock (&timeline->priv->commited_lock); + g_cond_signal (&timeline->priv->commited_cond); + g_mutex_unlock (&timeline->priv->commited_lock); +} + +/** + * ges_timeline_commit_sync: + * @timeline: A #GESTimeline + * + * Commit all the pending changes of the clips contained in the + * timeline and wait for the changes to complete. + * + * See ges_timeline_commit(). + * + * Returns: %TRUE if pending changes were committed, or %FALSE if nothing + * needed to be committed. + */ +gboolean +ges_timeline_commit_sync (GESTimeline * timeline) +{ + gboolean ret; + gboolean wait_for_signal; + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + + /* Let's make sure our state is stable */ + gst_element_get_state (GST_ELEMENT (timeline), NULL, NULL, + GST_CLOCK_TIME_NONE); + + /* Let's make sure no track gets added between now and the actual commiting */ + LOCK_DYN (timeline); + wait_for_signal = g_list_length (timeline->priv->priv_tracks) > 0 + && GST_STATE (timeline) >= GST_STATE_PAUSED; + + if (!wait_for_signal) { + ret = ges_timeline_commit_unlocked (timeline); + } else { + gulong handler_id = + g_signal_connect (timeline, "commited", (GCallback) commited_cb, NULL); + + g_mutex_lock (&timeline->priv->commited_lock); + + ret = ges_timeline_commit_unlocked (timeline); + g_cond_wait (&timeline->priv->commited_cond, + &timeline->priv->commited_lock); + g_mutex_unlock (&timeline->priv->commited_lock); + g_signal_handler_disconnect (timeline, handler_id); + } + + UNLOCK_DYN (timeline); + + return ret; +} + +/** + * ges_timeline_freeze_commit: + * @timeline: The #GESTimeline + * + * Freezes the timeline from being committed. This is usually needed while the + * timeline is being rendered to ensure that not change to the timeline are + * taken into account during that moment. Once the rendering is done, you + * should call #ges_timeline_thaw_commit so that comiting becomes possible + * again and any call to `commit()` that happened during the rendering is + * actually taken into account. + * + * Since: 1.20 + * + */ +void +ges_timeline_freeze_commit (GESTimeline * timeline) +{ + LOCK_DYN (timeline); + timeline->priv->commit_frozen = TRUE; + UNLOCK_DYN (timeline); +} + +/** + * ges_timeline_thaw_commit: + * @timeline: The #GESTimeline + * + * Thaw the timeline so that comiting becomes possible + * again and any call to `commit()` that happened during the rendering is + * actually taken into account. + * + * Since: 1.20 + * + */ +void +ges_timeline_thaw_commit (GESTimeline * timeline) +{ + LOCK_DYN (timeline); + timeline->priv->commit_frozen = FALSE; + UNLOCK_DYN (timeline); + if (timeline->priv->commit_delayed) { + ges_timeline_commit (timeline); + timeline->priv->commit_delayed = FALSE; + } +} + +/** + * ges_timeline_get_duration: + * @timeline: The #GESTimeline + * + * Get the current #GESTimeline:duration of the timeline + * + * Returns: The current duration of @timeline. + */ +GstClockTime +ges_timeline_get_duration (GESTimeline * timeline) +{ + g_return_val_if_fail (GES_IS_TIMELINE (timeline), GST_CLOCK_TIME_NONE); + CHECK_THREAD (timeline); + + return timeline->priv->duration; +} + +/** + * ges_timeline_get_auto_transition: + * @timeline: The #GESTimeline + * + * Gets #GESTimeline:auto-transition for the timeline. + * + * Returns: The auto-transition of @self. + */ +gboolean +ges_timeline_get_auto_transition (GESTimeline * timeline) +{ + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + CHECK_THREAD (timeline); + + return timeline->priv->auto_transition; +} + +/** + * ges_timeline_set_auto_transition: + * @timeline: The #GESTimeline + * @auto_transition: Whether transitions should be automatically added + * to @timeline's layers + * + * Sets #GESTimeline:auto-transition for the timeline. This will also set + * the corresponding #GESLayer:auto-transition for all of the timeline's + * layers to the same value. See ges_layer_set_auto_transition() if you + * wish to set the layer's #GESLayer:auto-transition individually. + */ +void +ges_timeline_set_auto_transition (GESTimeline * timeline, + gboolean auto_transition) +{ + GList *layers; + GESLayer *layer; + + g_return_if_fail (GES_IS_TIMELINE (timeline)); + CHECK_THREAD (timeline); + + timeline->priv->auto_transition = auto_transition; + g_object_notify (G_OBJECT (timeline), "auto-transition"); + + layers = timeline->layers; + for (; layers; layers = layers->next) { + layer = layers->data; + ges_layer_set_auto_transition (layer, auto_transition); + } +} + +/** + * ges_timeline_get_snapping_distance: + * @timeline: The #GESTimeline + * + * Gets the #GESTimeline:snapping-distance for the timeline. + * + * Returns: The snapping distance (in nanoseconds) of @timeline. + */ +GstClockTime +ges_timeline_get_snapping_distance (GESTimeline * timeline) +{ + g_return_val_if_fail (GES_IS_TIMELINE (timeline), GST_CLOCK_TIME_NONE); + CHECK_THREAD (timeline); + + return timeline->priv->snapping_distance; + +} + +/** + * ges_timeline_set_snapping_distance: + * @timeline: The #GESTimeline + * @snapping_distance: The snapping distance to use (in nanoseconds) + * + * Sets #GESTimeline:snapping-distance for the timeline. This new value + * will only effect future snappings and will not be used to snap the + * current element positions within the timeline. + */ +void +ges_timeline_set_snapping_distance (GESTimeline * timeline, + GstClockTime snapping_distance) +{ + g_return_if_fail (GES_IS_TIMELINE (timeline)); + g_return_if_fail (GST_CLOCK_TIME_IS_VALID (snapping_distance)); + CHECK_THREAD (timeline); + + timeline->priv->snapping_distance = snapping_distance; +} + +/** + * ges_timeline_get_element: + * @timeline: The #GESTimeline + * @name: The name of the element to find + * + * Gets the element contained in the timeline with the given name. + * + * Returns: (transfer full) (nullable): The timeline element in @timeline + * with the given @name, or %NULL if it was not found. + */ +GESTimelineElement * +ges_timeline_get_element (GESTimeline * timeline, const gchar * name) +{ + GESTimelineElement *ret; + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL); + CHECK_THREAD (timeline); + + /* FIXME: handle NULL name */ + ret = g_hash_table_lookup (timeline->priv->all_elements, name); + + if (ret) + return gst_object_ref (ret); + +#ifndef GST_DISABLE_GST_DEBUG + { + GList *element_names, *tmp; + element_names = g_hash_table_get_keys (timeline->priv->all_elements); + + GST_INFO_OBJECT (timeline, "Does not contain element %s", name); + + for (tmp = element_names; tmp; tmp = tmp->next) { + GST_DEBUG_OBJECT (timeline, "Containes: %s", (gchar *) tmp->data); + } + g_list_free (element_names); + } +#endif + + return NULL; +} + +/** + * ges_timeline_is_empty: + * @timeline: The #GESTimeline + * + * Check whether the timeline is empty or not. + * + * Returns: %TRUE if @timeline is empty. + */ +gboolean +ges_timeline_is_empty (GESTimeline * timeline) +{ + GHashTableIter iter; + gpointer key, value; + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + CHECK_THREAD (timeline); + + if (g_hash_table_size (timeline->priv->all_elements) == 0) + return TRUE; + + g_hash_table_iter_init (&iter, timeline->priv->all_elements); + while (g_hash_table_iter_next (&iter, &key, &value)) { + if (GES_IS_SOURCE (value) && + ges_track_element_is_active (GES_TRACK_ELEMENT (value))) + return FALSE; + } + + return TRUE; +} + +/** + * ges_timeline_get_layer: + * @timeline: The #GESTimeline to retrieve a layer from + * @priority: The priority/index of the layer to find + * + * Retrieve the layer whose index in the timeline matches the given + * priority. + * + * Returns: (transfer full) (nullable): The layer with the given + * @priority, or %NULL if none was found. + * + * Since 1.6 + */ +GESLayer * +ges_timeline_get_layer (GESTimeline * timeline, guint priority) +{ + GList *tmp; + GESLayer *layer = NULL; + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL); + CHECK_THREAD (timeline); + + for (tmp = timeline->layers; tmp; tmp = tmp->next) { + GESLayer *tmp_layer = GES_LAYER (tmp->data); + guint tmp_priority; + + g_object_get (tmp_layer, "priority", &tmp_priority, NULL); + if (tmp_priority == priority) { + layer = gst_object_ref (tmp_layer); + break; + } + } + + return layer; +} + +gboolean +ges_timeline_layer_priority_in_gap (GESTimeline * timeline, guint priority) +{ + GList *tmp; + + CHECK_THREAD (timeline); + + for (tmp = timeline->layers; tmp; tmp = tmp->next) { + GESLayer *layer = GES_LAYER (tmp->data); + guint tmp_priority = ges_layer_get_priority (layer); + + if (tmp_priority == priority) + return FALSE; + else if (tmp_priority > priority) + return TRUE; + } + + return FALSE; +} + +/** + * ges_timeline_paste_element: + * @timeline: The #GESTimeline onto which @element should be pasted + * @element: The element to paste + * @position: The position in the timeline @element should be pasted to, + * i.e. the #GESTimelineElement:start value for the pasted element. + * @layer_priority: The layer into which the element should be pasted. + * -1 means paste to the same layer from which @element has been copied from + * + * Paste an element inside the timeline. @element **must** be the return of + * ges_timeline_element_copy() with `deep=TRUE`, + * and it should not be changed before pasting. @element itself is not + * placed in the timeline, instead a new element is created, alike to the + * originally copied element. Note that the originally copied element must + * also lie within @timeline, at both the point of copying and pasting. + * + * Pasting may fail if it would place the timeline in an unsupported + * configuration. + * + * After calling this function @element should not be used. In particular, + * @element can **not** be pasted again. Instead, you can copy the + * returned element and paste that copy (although, this is only possible + * if the paste was successful). + * + * See also ges_timeline_element_paste(). + * + * Returns: (transfer full) (nullable): The newly created element, or + * %NULL if pasting fails. + */ +GESTimelineElement * +ges_timeline_paste_element (GESTimeline * timeline, + GESTimelineElement * element, GstClockTime position, gint layer_priority) +{ + GESTimelineElement *res, *copied_from; + GESTimelineElementClass *element_class; + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (element), FALSE); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), FALSE); + CHECK_THREAD (timeline); + + element_class = GES_TIMELINE_ELEMENT_GET_CLASS (element); + /* steal ownership of the copied element */ + copied_from = ges_timeline_element_get_copied_from (element); + + if (!copied_from) { + GST_ERROR_OBJECT (element, "Is not being 'deeply' copied!"); + + return NULL; + } + + if (!element_class->paste) { + GST_ERROR_OBJECT (element, "No paste vmethod implemented"); + gst_object_unref (copied_from); + return NULL; + } + + /* + * Currently the API only supports pasting onto the same layer from which + * the @element has been copied from, i.e., @layer_priority needs to be -1. + */ + if (layer_priority != -1) { + GST_WARNING_OBJECT (timeline, + "Only -1 value for layer priority is supported"); + gst_object_unref (copied_from); + return NULL; + } + + res = element_class->paste (element, copied_from, position); + + gst_object_unref (copied_from); + + return res ? g_object_ref_sink (res) : res; +} + +/** + * ges_timeline_move_layer: + * @timeline: A #GESTimeline + * @layer: A layer within @timeline, whose priority should be changed + * @new_layer_priority: The new index for @layer + * + * Moves a layer within the timeline to the index given by + * @new_layer_priority. + * An index of 0 corresponds to the layer with the highest priority in a + * timeline. If @new_layer_priority is greater than the number of layers + * present in the timeline, it will become the lowest priority layer. + * + * Since: 1.16 + */ +gboolean +ges_timeline_move_layer (GESTimeline * timeline, GESLayer * layer, + guint new_layer_priority) +{ + gint current_priority; + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + g_return_val_if_fail (GES_IS_LAYER (layer), FALSE); + g_return_val_if_fail (ges_layer_get_timeline (layer) == timeline, FALSE); + CHECK_THREAD (timeline); + + current_priority = ges_layer_get_priority (layer); + + if (new_layer_priority == current_priority) { + GST_DEBUG_OBJECT (timeline, + "Nothing to do for %" GST_PTR_FORMAT ", same priorities", layer); + + return TRUE; + } + + timeline->layers = g_list_remove (timeline->layers, layer); + timeline->layers = g_list_insert (timeline->layers, layer, + (gint) new_layer_priority); + + _resync_layers (timeline); + + return TRUE; +} + +/** + * ges_timeline_get_frame_time: + * @self: The self on which to retrieve the timestamp for @frame_number + * @frame_number: The frame number to get the corresponding timestamp of in the + * timeline coordinates + * + * This method allows you to convert a timeline output frame number into a + * timeline #GstClockTime. For example, this time could be used to seek to a + * particular frame in the timeline's output, or as the edit position for + * an element within the timeline. + * + * Returns: The timestamp corresponding to @frame_number in the output of @self. + * + * Since: 1.18 + */ +GstClockTime +ges_timeline_get_frame_time (GESTimeline * self, GESFrameNumber frame_number) +{ + gint fps_n, fps_d; + + g_return_val_if_fail (GES_IS_TIMELINE (self), GST_CLOCK_TIME_NONE); + g_return_val_if_fail (GES_FRAME_NUMBER_IS_VALID (frame_number), + GST_CLOCK_TIME_NONE); + + timeline_get_framerate (self, &fps_n, &fps_d); + + return gst_util_uint64_scale_ceil (frame_number, fps_d * GST_SECOND, fps_n); +} + +/** + * ges_timeline_get_frame_at: + * @self: A #GESTimeline + * @timestamp: The timestamp to get the corresponding frame number of + * + * This method allows you to convert a timeline #GstClockTime into its + * corresponding #GESFrameNumber in the timeline's output. + * + * Returns: The frame number @timestamp corresponds to. + * + * Since: 1.18 + */ +GESFrameNumber +ges_timeline_get_frame_at (GESTimeline * self, GstClockTime timestamp) +{ + gint fps_n, fps_d; + + g_return_val_if_fail (GES_IS_TIMELINE (self), GES_FRAME_NUMBER_NONE); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), + GES_FRAME_NUMBER_NONE); + + timeline_get_framerate (self, &fps_n, &fps_d); + + return gst_util_uint64_scale (timestamp, fps_n, fps_d * GST_SECOND); +} diff --git a/ges/ges-timeline.h b/ges/ges-timeline.h new file mode 100644 index 0000000000..00947d8ba6 --- /dev/null +++ b/ges/ges-timeline.h @@ -0,0 +1,163 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <gst/gst.h> +#include <gst/pbutils/gstdiscoverer.h> +#include <ges/ges-types.h> + +G_BEGIN_DECLS + +#define GES_TYPE_TIMELINE ges_timeline_get_type() +GES_DECLARE_TYPE(Timeline, timeline, TIMELINE); + +#define GES_TIMELINE_GET_TRACKS(obj) (GES_TIMELINE (obj)->tracks) +#define GES_TIMELINE_GET_LAYERS(obj) (GES_TIMELINE (obj)->layers) + +/** + * ges_timeline_get_project: + * @obj: The #GESTimeline from which to retrieve the project + * + * Helper macro to retrieve the project from which @obj was extracted + */ +#define ges_timeline_get_project(obj) (GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE(obj)))) + +typedef struct _GESTimelinePrivate GESTimelinePrivate; + +/** + * GESTimeline: + * @layers: (element-type GES.Layer): A list of #GESLayer-s sorted by + * priority. NOTE: Do not modify. + * @tracks: Deprecated:1.10: (element-type GES.Track): This is not thread + * safe, use #ges_timeline_get_tracks instead. + */ +struct _GESTimeline { + GstBin parent; + + /*< public > */ + /* <readonly> */ + GList *layers; + GList *tracks; + + /*< private >*/ + GESTimelinePrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESTimelineClass: + * @parent_class: parent class + */ + +struct _GESTimelineClass { + GstBinClass parent_class; + + /*< private >*/ + + void (*track_added) (GESTimeline *timeline, GESTrack * track); + void (*track_removed) (GESTimeline *timeline, GESTrack * track); + void (*layer_added) (GESTimeline *timeline, GESLayer *layer); + void (*layer_removed) (GESTimeline *timeline, GESLayer *layer); + void (*group_added) (GESTimeline *timeline, GESGroup *group); + void (*group_removed) (GESTimeline *timeline, GESGroup *group, GPtrArray *children); + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API +GESTimeline* ges_timeline_new (void); +GES_API +GESTimeline* ges_timeline_new_from_uri (const gchar *uri, GError **error); + +GES_API +gboolean ges_timeline_load_from_uri (GESTimeline *timeline, const gchar *uri, GError **error); +GES_API +gboolean ges_timeline_save_to_uri (GESTimeline * timeline, const gchar * uri, + GESAsset *formatter_asset, gboolean overwrite, GError ** error); +GES_API +gboolean ges_timeline_add_layer (GESTimeline *timeline, GESLayer *layer); +GES_API +GESLayer * ges_timeline_append_layer (GESTimeline * timeline); +GES_API +gboolean ges_timeline_remove_layer (GESTimeline *timeline, GESLayer *layer); +GES_API +GList* ges_timeline_get_layers (GESTimeline *timeline); +GES_API +GESLayer* ges_timeline_get_layer (GESTimeline *timeline, guint priority); + +GES_API +gboolean ges_timeline_add_track (GESTimeline *timeline, GESTrack *track); +GES_API +gboolean ges_timeline_remove_track (GESTimeline *timeline, GESTrack *track); + +GES_API +GESTrack * ges_timeline_get_track_for_pad (GESTimeline *timeline, GstPad *pad); +GES_API +GstPad * ges_timeline_get_pad_for_track (GESTimeline * timeline, GESTrack *track); +GES_API +GList *ges_timeline_get_tracks (GESTimeline *timeline); + +GES_API +GList* ges_timeline_get_groups (GESTimeline * timeline); + +GES_API +gboolean ges_timeline_commit (GESTimeline * timeline); +GES_API +gboolean ges_timeline_commit_sync (GESTimeline * timeline); +GES_API +void ges_timeline_freeze_commit (GESTimeline * timeline); +GES_API +void ges_timeline_thaw_commit (GESTimeline * timeline); + +GES_API +GstClockTime ges_timeline_get_duration (GESTimeline *timeline); + +GES_API +gboolean ges_timeline_get_auto_transition (GESTimeline * timeline); +GES_API +void ges_timeline_set_auto_transition (GESTimeline * timeline, gboolean auto_transition); +GES_API +GstClockTime ges_timeline_get_snapping_distance (GESTimeline * timeline); +GES_API +void ges_timeline_set_snapping_distance (GESTimeline * timeline, GstClockTime snapping_distance); +GES_API +GESTimelineElement * ges_timeline_get_element (GESTimeline * timeline, const gchar *name); +GES_API +gboolean ges_timeline_is_empty (GESTimeline * timeline); +GES_API +GESTimelineElement * ges_timeline_paste_element (GESTimeline * timeline, + GESTimelineElement * element, GstClockTime position, gint layer_priority); +GES_API +gboolean ges_timeline_move_layer (GESTimeline *timeline, GESLayer *layer, guint new_layer_priority); + +GES_API +GstClockTime ges_timeline_get_frame_time(GESTimeline *self, + GESFrameNumber frame_number); + +GES_API +GESFrameNumber ges_timeline_get_frame_at (GESTimeline *self, + GstClockTime timestamp); + +G_END_DECLS diff --git a/ges/ges-title-clip.c b/ges/ges-title-clip.c new file mode 100644 index 0000000000..b5fafd9061 --- /dev/null +++ b/ges/ges-title-clip.c @@ -0,0 +1,701 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gestitleclip + * @title: GESTitleClip + * @short_description: Render stand-alone titles in GESLayer. + * + * Renders the given text in the specified font, at specified position, and + * with the specified background pattern. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-title-clip.h" +#include "ges-source-clip.h" +#include "ges-track-element.h" +#include "ges-title-source.h" +#include <string.h> + +#define DEFAULT_TEXT "" +#define DEFAULT_FONT_DESC "Serif 36" +#define GES_TITLE_CLIP_VALIGN_TYPE (ges_title_clip_valign_get_type()) +#define GES_TITLE_CLIP_HALIGN_TYPE (ges_title_clip_halign_get_type()) + +struct _GESTitleClipPrivate +{ + GSList *track_titles; +}; + +enum +{ + PROP_0, + PROP_TEXT, + PROP_FONT_DESC, + PROP_HALIGNMENT, + PROP_VALIGNMENT, + PROP_COLOR, + PROP_BACKGROUND, + PROP_XPOS, + PROP_YPOS, +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GESTitleClip, ges_title_clip, GES_TYPE_SOURCE_CLIP); + +static GESTrackElement + * ges_title_clip_create_track_element (GESClip * clip, GESTrackType type); + +static void _child_added (GESContainer * container, + GESTimelineElement * element); +static void _child_removed (GESContainer * container, + GESTimelineElement * element); + +static void +ges_title_clip_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESTimelineElement *child, *tmpsrc = NULL; + GESTitleClipPrivate *priv = GES_TITLE_CLIP (object)->priv; + + if (!priv->track_titles) + child = tmpsrc = GES_TIMELINE_ELEMENT (ges_title_source_new ()); + else + child = priv->track_titles->data; + + switch (property_id) { + /* Falltrough all over */ + case PROP_TEXT: + case PROP_FONT_DESC: + case PROP_HALIGNMENT: + case PROP_VALIGNMENT: + case PROP_COLOR: + case PROP_BACKGROUND: + case PROP_XPOS: + case PROP_YPOS: + ges_timeline_element_get_child_property (child, pspec->name, value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + + if (tmpsrc) + g_object_unref (tmpsrc); +} + +static void +ges_title_clip_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + case PROP_TEXT: + case PROP_FONT_DESC: + case PROP_HALIGNMENT: + case PROP_VALIGNMENT: + case PROP_COLOR: + case PROP_BACKGROUND: + case PROP_XPOS: + case PROP_YPOS: + ges_timeline_element_set_child_property (GES_TIMELINE_ELEMENT (object), + pspec->name, value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_title_clip_dispose (GObject * object) +{ + G_OBJECT_CLASS (ges_title_clip_parent_class)->dispose (object); +} + +static void +ges_title_clip_class_init (GESTitleClipClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESClipClass *clip_class = GES_CLIP_CLASS (klass); + GESContainerClass *container_class = GES_CONTAINER_CLASS (klass); + + object_class->get_property = ges_title_clip_get_property; + object_class->set_property = ges_title_clip_set_property; + object_class->dispose = ges_title_clip_dispose; + + /** + * GESTitleClip:text: + * + * The text to diplay + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties or + * #ges_timeline_element_get_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ + g_object_class_install_property (object_class, PROP_TEXT, + g_param_spec_string ("text", "Text", "The text to display", + DEFAULT_TEXT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | + GES_PARAM_NO_SERIALIZATION)); + + /** + * GESTitleClip:font-desc: + * + * Pango font description string + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties or + * #ges_timeline_element_get_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC, + g_param_spec_string ("font-desc", "font description", + "Pango font description of font to be used for rendering. " + "See documentation of pango_font_description_from_string " + "for syntax.", DEFAULT_FONT_DESC, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | + GES_PARAM_NO_SERIALIZATION)); + + /** + * GESTitleClip:valignment: + * + * Vertical alignent of the text + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties or + * #ges_timeline_element_get_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT, + g_param_spec_enum ("valignment", "vertical alignment", + "Vertical alignment of the text", GES_TEXT_VALIGN_TYPE, + DEFAULT_VALIGNMENT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | + GES_PARAM_NO_SERIALIZATION)); + /** + * GESTitleClip:halignment: + * + * Horizontal alignment of the text + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties or + * #ges_timeline_element_get_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT, + g_param_spec_enum ("halignment", "horizontal alignment", + "Horizontal alignment of the text", + GES_TEXT_HALIGN_TYPE, DEFAULT_HALIGNMENT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | + GES_PARAM_NO_SERIALIZATION)); + + clip_class->create_track_element = ges_title_clip_create_track_element; + + container_class->child_added = _child_added; + container_class->child_removed = _child_removed; + + /** + * GESTitleClip:color: + * + * The color of the text + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties or + * #ges_timeline_element_get_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ + + g_object_class_install_property (object_class, PROP_COLOR, + g_param_spec_uint ("color", "Color", "The color of the text", + 0, G_MAXUINT32, G_MAXUINT32, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | + GES_PARAM_NO_SERIALIZATION)); + + /** + * GESTitleClip:background: + * + * The background of the text + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties or + * #ges_timeline_element_get_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ + + g_object_class_install_property (object_class, PROP_BACKGROUND, + g_param_spec_uint ("background", "Background", + "The background of the text", 0, G_MAXUINT32, G_MAXUINT32, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | GES_PARAM_NO_SERIALIZATION)); + + /** + * GESTitleClip:xpos: + * + * The horizontal position of the text + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties or + * #ges_timeline_element_get_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ + + g_object_class_install_property (object_class, PROP_XPOS, + g_param_spec_double ("xpos", "Xpos", "The horizontal position", + 0, 1, 0.5, G_PARAM_READWRITE | G_PARAM_CONSTRUCT + | GES_PARAM_NO_SERIALIZATION)); + + /** + * GESTitleClip:ypos: + * + * The vertical position of the text + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties or + * #ges_timeline_element_get_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ + + g_object_class_install_property (object_class, PROP_YPOS, + g_param_spec_double ("ypos", "Ypos", "The vertical position", + 0, 1, 0.5, G_PARAM_READWRITE | G_PARAM_CONSTRUCT + | GES_PARAM_NO_SERIALIZATION)); +} + +static void +ges_title_clip_init (GESTitleClip * self) +{ + self->priv = ges_title_clip_get_instance_private (self); + + GES_TIMELINE_ELEMENT (self)->duration = 0; + /* Not 100% required since a new gobject's content will always be memzero'd */ +} + +/** + * ges_title_clip_set_text: + * @self: the #GESTitleClip* to set text on + * @text: the text to render. an internal copy of this text will be + * made. + * + * Sets the text this clip will render. + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ +void +ges_title_clip_set_text (GESTitleClip * self, const gchar * text) +{ + GSList *tmp; + + GST_DEBUG_OBJECT (self, "text:%s", text); + + for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) { + ges_timeline_element_set_child_properties (tmp->data, "text", text, NULL); + } +} + +/** + * ges_title_clip_set_font_desc: + * @self: the #GESTitleClip* + * @font_desc: the pango font description + * + * Sets the pango font description of the text. + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ +void +ges_title_clip_set_font_desc (GESTitleClip * self, const gchar * font_desc) +{ + GSList *tmp; + + GST_DEBUG_OBJECT (self, "font_desc:%s", font_desc); + + for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) { + ges_timeline_element_set_child_properties (tmp->data, + "font-desc", font_desc, NULL); + } +} + +/** + * ges_title_clip_set_halignment: + * @self: the #GESTitleClip* to set horizontal alignement of text on + * @halign: #GESTextHAlign + * + * Sets the horizontal aligment of the text. + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ +void +ges_title_clip_set_halignment (GESTitleClip * self, GESTextHAlign halign) +{ + GSList *tmp; + + GST_DEBUG_OBJECT (self, "halign:%d", halign); + + for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) { + ges_timeline_element_set_child_properties (tmp->data, + "halignment", halign, NULL); + } +} + +/** + * ges_title_clip_set_valignment: + * @self: the #GESTitleClip* to set vertical alignement of text on + * @valign: #GESTextVAlign + * + * Sets the vertical aligment of the text. + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ +void +ges_title_clip_set_valignment (GESTitleClip * self, GESTextVAlign valign) +{ + GSList *tmp; + + GST_DEBUG_OBJECT (self, "valign:%d", valign); + + for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) { + ges_timeline_element_set_child_properties (tmp->data, + "valignment", valign, NULL); + } +} + +/** + * ges_title_clip_set_color: + * @self: the #GESTitleClip* to set + * @color: The color @self is being set to + * + * Sets the color of the text. + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ +void +ges_title_clip_set_color (GESTitleClip * self, guint32 color) +{ + GSList *tmp; + + GST_DEBUG_OBJECT (self, "color:%d", color); + + for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) { + ges_timeline_element_set_child_properties (tmp->data, "color", color, NULL); + } +} + +/** + * ges_title_clip_set_background: + * @self: the #GESTitleClip* to set + * @background: The color @self is being set to + * + * Sets the background of the text. + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ +void +ges_title_clip_set_background (GESTitleClip * self, guint32 background) +{ + GSList *tmp; + + GST_DEBUG_OBJECT (self, "background:%d", background); + + for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) { + ges_timeline_element_set_child_properties (tmp->data, + "foreground-color", background, NULL); + } +} + +/** + * ges_title_clip_set_xpos: + * @self: the #GESTitleClip* to set + * @position: The horizontal position @self is being set to + * + * Sets the horizontal position of the text. + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ +void +ges_title_clip_set_xpos (GESTitleClip * self, gdouble position) +{ + GSList *tmp; + + GST_DEBUG_OBJECT (self, "xpos:%f", position); + + for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) { + ges_timeline_element_set_child_properties (tmp->data, "xpos", position, + NULL); + } +} + +/** + * ges_title_clip_set_ypos: + * @self: the #GESTitleClip* to set + * @position: The vertical position @self is being set to + * + * Sets the vertical position of the text. + * + * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ +void +ges_title_clip_set_ypos (GESTitleClip * self, gdouble position) +{ + GSList *tmp; + + GST_DEBUG_OBJECT (self, "ypos:%f", position); + + for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) { + ges_timeline_element_set_child_properties (tmp->data, "ypos", position, + NULL); + } +} + +/** + * ges_title_clip_get_text: + * @self: a #GESTitleClip + * + * Get the text currently set on @self. + * + * Returns: The text currently set on @self. + * + * Deprecated:1.6: use #ges_timeline_element_get_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ +const gchar * +ges_title_clip_get_text (GESTitleClip * self) +{ + gchar *text; + + ges_timeline_element_get_child_properties (self->priv->track_titles->data, + "text", &text, NULL); + + return text; +} + +/** + * ges_title_clip_get_font_desc: + * @self: a #GESTitleClip + * + * Get the pango font description used by @self. + * + * Returns: The pango font description used by @self. + * + * Deprecated:1.6: use #ges_timeline_element_get_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ +const char * +ges_title_clip_get_font_desc (GESTitleClip * self) +{ + gchar *font_desc; + + ges_timeline_element_get_child_properties (self->priv->track_titles->data, + "font-desc", &font_desc, NULL); + + return font_desc; +} + +/** + * ges_title_clip_get_halignment: + * @self: a #GESTitleClip + * + * Get the horizontal aligment used by @self. + * + * Returns: The horizontal aligment used by @self. + * + * Deprecated:1.6: use #ges_timeline_element_get_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ +GESTextHAlign +ges_title_clip_get_halignment (GESTitleClip * self) +{ + GESTextHAlign halign; + + ges_timeline_element_get_child_properties (self->priv->track_titles->data, + "halignment", &halign, NULL); + + return halign; +} + +/** + * ges_title_clip_get_valignment: + * @self: a #GESTitleClip + * + * Get the vertical aligment used by @self. + * + * Returns: The vertical aligment used by @self. + * + * Deprecated:1.6: use #ges_timeline_element_get_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ +GESTextVAlign +ges_title_clip_get_valignment (GESTitleClip * self) +{ + GESTextVAlign valign; + + ges_timeline_element_get_child_properties (self->priv->track_titles->data, + "valignment", &valign, NULL); + + return valign; +} + +/** + * ges_title_clip_get_text_color: + * @self: a #GESTitleClip + * + * Get the color used by @self. + * + * Returns: The color used by @self. + * + * Deprecated:1.6: use #ges_timeline_element_get_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ +const guint32 +ges_title_clip_get_text_color (GESTitleClip * self) +{ + guint32 color; + + ges_timeline_element_get_child_properties (self->priv->track_titles->data, + "color", &color, NULL); + + return color; +} + +/** + * ges_title_clip_get_background_color: + * @self: a #GESTitleClip + * + * Get the background used by @self. + * + * Returns: The color used by @self. + * + * Deprecated:1.6: use #ges_timeline_element_get_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ +const guint32 +ges_title_clip_get_background_color (GESTitleClip * self) +{ + guint32 color; + + ges_timeline_element_get_child_properties (self->priv->track_titles->data, + "foreground-color", &color, NULL); + + return color; +} + +/** + * ges_title_clip_get_xpos: + * @self: a #GESTitleClip + * + * Get the horizontal position used by @self. + * + * Returns: The horizontal position used by @self. + * + * Deprecated:1.6: use #ges_timeline_element_get_children_properties instead. + * See #GESTitleSource for more information about exposed properties + */ +const gdouble +ges_title_clip_get_xpos (GESTitleClip * self) +{ + gdouble xpos; + + ges_timeline_element_get_child_properties (self->priv->track_titles->data, + "xpos", &xpos, NULL); + + return xpos; +} + +/** + * ges_title_clip_get_ypos: + * @self: a #GESTitleClip + * + * Get the vertical position used by @self. + * + * Returns: The vertical position used by @self. + * + * Deprecated:1.6: use #ges_timeline_element_get_children_property instead + */ +const gdouble +ges_title_clip_get_ypos (GESTitleClip * self) +{ + gdouble ypos; + + ges_timeline_element_get_child_properties (self->priv->track_titles->data, + "ypos", &ypos, NULL); + + return ypos; +} + +static void +_child_removed (GESContainer * container, GESTimelineElement * element) +{ + GESTitleClipPrivate *priv = GES_TITLE_CLIP (container)->priv; + + /* If this is called, we should be sure the element exists */ + if (GES_IS_TITLE_SOURCE (element)) { + GST_DEBUG_OBJECT (container, "%" GST_PTR_FORMAT " removed", element); + priv->track_titles = g_slist_remove (priv->track_titles, element); + gst_object_unref (element); + } + + GES_CONTAINER_CLASS (ges_title_clip_parent_class)->child_removed (container, + element); +} + +static void +_child_added (GESContainer * container, GESTimelineElement * element) +{ + GESTitleClipPrivate *priv = GES_TITLE_CLIP (container)->priv; + + if (GES_IS_TITLE_SOURCE (element)) { + GST_DEBUG_OBJECT (container, "%" GST_PTR_FORMAT " added", element); + priv->track_titles = g_slist_prepend (priv->track_titles, + gst_object_ref (element)); + } + + GES_CONTAINER_CLASS (ges_title_clip_parent_class)->child_added (container, + element); +} + +static GESTrackElement * +ges_title_clip_create_track_element (GESClip * clip, GESTrackType type) +{ + + GESTrackElement *res = NULL; + + GST_DEBUG_OBJECT (clip, "a GESTitleSource"); + + if (type == GES_TRACK_TYPE_VIDEO) + res = (GESTrackElement *) ges_title_source_new (); + + return res; +} + +/** + * ges_title_clip_new: + * + * Creates a new #GESTitleClip + * + * Returns: (transfer floating) (nullable): The newly created #GESTitleClip, + * or %NULL if there was an error. + */ +GESTitleClip * +ges_title_clip_new (void) +{ + GESTitleClip *new_clip; + GESAsset *asset = ges_asset_request (GES_TYPE_TITLE_CLIP, NULL, NULL); + + new_clip = GES_TITLE_CLIP (ges_asset_extract (asset, NULL)); + gst_object_unref (asset); + + return new_clip; +} diff --git a/ges/ges-title-clip.h b/ges/ges-title-clip.h new file mode 100644 index 0000000000..9df0b34c62 --- /dev/null +++ b/ges/ges-title-clip.h @@ -0,0 +1,108 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> +#include <ges/ges-source-clip.h> +#include <ges/ges-track.h> + +G_BEGIN_DECLS + +#define GES_TYPE_TITLE_CLIP ges_title_clip_get_type() +GES_DECLARE_TYPE(TitleClip, title_clip, TITLE_CLIP); + +/** + * GESTitleClip: + * + * Render stand-alone titles in GESLayer. + */ + +struct _GESTitleClip { + GESSourceClip parent; + + /*< private >*/ + GESTitleClipPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESTitleClipClass { + /*< private >*/ + GESSourceClipClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void +ges_title_clip_set_text( GESTitleClip * self, const gchar * text); + +GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void +ges_title_clip_set_font_desc (GESTitleClip * self, const gchar * font_desc); + +GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void +ges_title_clip_set_valignment (GESTitleClip * self, GESTextVAlign valign); + +GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void +ges_title_clip_set_halignment (GESTitleClip * self, GESTextHAlign halign); + +GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void +ges_title_clip_set_color (GESTitleClip * self, guint32 color); + +GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void +ges_title_clip_set_background (GESTitleClip * self, guint32 background); + +GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void +ges_title_clip_set_xpos (GESTitleClip * self, gdouble position); + +GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void +ges_title_clip_set_ypos (GESTitleClip * self, gdouble position); + +GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) const gchar* +ges_title_clip_get_font_desc (GESTitleClip * self); + +GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) GESTextVAlign +ges_title_clip_get_valignment (GESTitleClip * self); + +GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) GESTextHAlign +ges_title_clip_get_halignment (GESTitleClip * self); + +GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) const guint32 +ges_title_clip_get_text_color (GESTitleClip * self); + +GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) const guint32 +ges_title_clip_get_background_color (GESTitleClip * self); + +GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) const gdouble +ges_title_clip_get_xpos (GESTitleClip * self); + +GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) const gdouble +ges_title_clip_get_ypos (GESTitleClip * self); + +GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) +const gchar* ges_title_clip_get_text (GESTitleClip * self); + +GES_API +GESTitleClip* ges_title_clip_new (void); + +G_END_DECLS diff --git a/ges/ges-title-source.c b/ges/ges-title-source.c new file mode 100644 index 0000000000..d20ed2fce0 --- /dev/null +++ b/ges/ges-title-source.c @@ -0,0 +1,565 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gestitlesource + * @title: GESTitleSource + * @short_description: render stand-alone text titles + * + * #GESTitleSource is a GESTimelineElement that implements the notion + * of titles in GES. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-track-element.h" +#include "ges-title-source.h" +#include "ges-video-test-source.h" + +#define DEFAULT_TEXT "" +#define DEFAULT_FONT_DESC "Serif 36" + +struct _GESTitleSourcePrivate +{ + gchar *text; + gchar *font_desc; + GESTextHAlign halign; + GESTextVAlign valign; + guint32 color; + guint32 background; + gdouble xpos; + gdouble ypos; + GstElement *text_el; + GstElement *background_el; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GESTitleSource, ges_title_source, + GES_TYPE_VIDEO_SOURCE); + +enum +{ + PROP_0, +}; + +static void ges_title_source_dispose (GObject * object); + +static void ges_title_source_get_property (GObject * object, guint + property_id, GValue * value, GParamSpec * pspec); + +static void ges_title_source_set_property (GObject * object, guint + property_id, const GValue * value, GParamSpec * pspec); + +static GstElement *ges_title_source_create_source (GESSource * self); + +static gboolean +_lookup_child (GESTimelineElement * object, + const gchar * prop_name, GObject ** element, GParamSpec ** pspec) +{ + gboolean res; + + gchar *clean_name; + + if (!g_strcmp0 (prop_name, "background")) + clean_name = g_strdup ("foreground-color"); + else if (!g_strcmp0 (prop_name, "GstTextOverlay:background")) + clean_name = g_strdup ("foreground-color"); + else + clean_name = g_strdup (prop_name); + + res = + GES_TIMELINE_ELEMENT_CLASS (ges_title_source_parent_class)->lookup_child + (object, clean_name, element, pspec); + + g_free (clean_name); + + return res; +} + +static void +ges_title_source_class_init (GESTitleSourceClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESSourceClass *source_class = GES_SOURCE_CLASS (klass); + GESVideoSourceClass *vsource_class = GES_VIDEO_SOURCE_CLASS (klass); + GESTimelineElementClass *timeline_element_class = + GES_TIMELINE_ELEMENT_CLASS (klass); + + object_class->get_property = ges_title_source_get_property; + object_class->set_property = ges_title_source_set_property; + object_class->dispose = ges_title_source_dispose; + + timeline_element_class->lookup_child = _lookup_child; + vsource_class->ABI.abi.disable_scale_in_compositor = TRUE; + source_class->create_source = ges_title_source_create_source; + + GES_TRACK_ELEMENT_CLASS_DEFAULT_HAS_INTERNAL_SOURCE (klass) = FALSE; +} + +static void +ges_title_source_init (GESTitleSource * self) +{ + self->priv = ges_title_source_get_instance_private (self); + + self->priv->text = g_strdup (DEFAULT_TEXT); + self->priv->font_desc = g_strdup (DEFAULT_FONT_DESC); + self->priv->text_el = NULL; + self->priv->halign = DEFAULT_HALIGNMENT; + self->priv->valign = DEFAULT_VALIGNMENT; + self->priv->color = G_MAXUINT32; + self->priv->background = G_MAXUINT32; + self->priv->xpos = 0.5; + self->priv->ypos = 0.5; + self->priv->background_el = NULL; +} + +static void +ges_title_source_dispose (GObject * object) +{ + GESTitleSource *self = GES_TITLE_SOURCE (object); + if (self->priv->text) { + g_free (self->priv->text); + } + + if (self->priv->font_desc) { + g_free (self->priv->font_desc); + } + + if (self->priv->text_el) { + gst_object_unref (self->priv->text_el); + self->priv->text_el = NULL; + } + + if (self->priv->background_el) { + gst_object_unref (self->priv->background_el); + self->priv->background_el = NULL; + } + + G_OBJECT_CLASS (ges_title_source_parent_class)->dispose (object); +} + +static void +ges_title_source_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_title_source_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static GstElement * +ges_title_source_create_source (GESSource * source) +{ + GstElement *topbin, *background, *text; + GstPad *src, *pad; + + GESTitleSource *self = GES_TITLE_SOURCE (source); + GESTitleSourcePrivate *priv = self->priv; + const gchar *bg_props[] = { "pattern", "foreground-color", NULL }; + const gchar *text_props[] = { "text", "font-desc", "valignment", "halignment", + "color", "xpos", "ypos", "x-absolute", "y-absolute", "outline-color", + "shaded-background", "draw-shadow", + "text-x", "text-y", "text-width", "text-height", NULL + }; + + topbin = gst_bin_new ("titlesrc-bin"); + background = gst_element_factory_make ("videotestsrc", "titlesrc-bg"); + + text = gst_element_factory_make ("textoverlay", "titlsrc-text"); + if (priv->text) { + g_object_set (text, "text", priv->text, NULL); + } + if (priv->font_desc) { + g_object_set (text, "font-desc", priv->font_desc, NULL); + } + g_object_set (text, "valignment", (gint) priv->valign, "halignment", + (gint) priv->halign, NULL); + g_object_set (text, "color", (guint) self->priv->color, NULL); + g_object_set (text, "xpos", (gdouble) self->priv->xpos, NULL); + g_object_set (text, "ypos", (gdouble) self->priv->ypos, NULL); + + + g_object_set (background, "pattern", (gint) GES_VIDEO_TEST_PATTERN_SOLID, + NULL); + g_object_set (background, "foreground-color", (guint) self->priv->background, + NULL); + + gst_bin_add_many (GST_BIN (topbin), background, text, NULL); + + gst_element_link_pads_full (background, "src", text, "video_sink", + GST_PAD_LINK_CHECK_NOTHING); + + pad = gst_element_get_static_pad (text, "src"); + src = gst_ghost_pad_new ("src", pad); + gst_object_unref (pad); + gst_element_add_pad (topbin, src); + + gst_object_ref (text); + gst_object_ref (background); + + priv->text_el = text; + priv->background_el = background; + + ges_track_element_add_children_props (GES_TRACK_ELEMENT (source), text, NULL, + NULL, text_props); + ges_track_element_add_children_props (GES_TRACK_ELEMENT (source), background, + NULL, NULL, bg_props); + + return topbin; +} + +/** + * ges_title_source_set_text: + * @self: the #GESTitleSource* to set text on + * @text: the text to render. an internal copy of this text will be + * made. + * + * Sets the text this track element will render. + * + * Deprecated: use ges_track_element_get/set_children_properties on the + * GESTrackElement instead + */ + +void +ges_title_source_set_text (GESTitleSource * self, const gchar * text) +{ + if (self->priv->text) + g_free (self->priv->text); + + GST_DEBUG ("self:%p, text:%s", self, text); + + self->priv->text = g_strdup (text); + if (self->priv->text_el) + g_object_set (self->priv->text_el, "text", text, NULL); +} + +/** + * ges_title_source_set_font_desc: + * @self: the #GESTitleSource + * @font_desc: the pango font description + * + * Set the pango font description this source will use to render + * the text. + */ + +void +ges_title_source_set_font_desc (GESTitleSource * self, const gchar * font_desc) +{ + if (self->priv->font_desc) + g_free (self->priv->font_desc); + + GST_DEBUG ("self:%p, font_dec:%s", self, font_desc); + + self->priv->font_desc = g_strdup (font_desc); + if (self->priv->text_el) + g_object_set (self->priv->text_el, "font-desc", font_desc, NULL); +} + +/** + * ges_title_source_set_valignment: + * @self: the #GESTitleSource* to set text on + * @valign: #GESTextVAlign + * + * Sets the vertical aligment of the text. + */ +void +ges_title_source_set_valignment (GESTitleSource * self, GESTextVAlign valign) +{ + GST_DEBUG ("self:%p, valign:%d", self, valign); + + self->priv->valign = valign; + if (self->priv->text_el) + g_object_set (self->priv->text_el, "valignment", valign, NULL); +} + +/** + * ges_title_source_set_halignment: + * @self: the #GESTitleSource* to set text on + * @halign: #GESTextHAlign + * + * Sets the vertical aligment of the text. + */ +void +ges_title_source_set_halignment (GESTitleSource * self, GESTextHAlign halign) +{ + GST_DEBUG ("self:%p, halign:%d", self, halign); + + self->priv->halign = halign; + if (self->priv->text_el) + g_object_set (self->priv->text_el, "halignment", halign, NULL); +} + +/** + * ges_title_source_set_text_color: + * @self: the #GESTitleSource* to set + * @color: the color @self is being set to + * + * Sets the color of the text. + */ +void +ges_title_source_set_text_color (GESTitleSource * self, guint32 color) +{ + GST_DEBUG ("self:%p, color:%d", self, color); + + self->priv->color = color; + if (self->priv->text_el) + g_object_set (self->priv->text_el, "color", color, NULL); +} + +/** + * ges_title_source_set_background_color: + * @self: the #GESTitleSource* to set + * @color: the color @self is being set to + * + * Sets the color of the background + */ +void +ges_title_source_set_background_color (GESTitleSource * self, guint32 color) +{ + GST_DEBUG ("self:%p, background color:%d", self, color); + + self->priv->background = color; + if (self->priv->background_el) + g_object_set (self->priv->background_el, "foreground-color", color, NULL); +} + +/** + * ges_title_source_set_xpos: + * @self: the #GESTitleSource* to set + * @position: the horizontal position @self is being set to + * + * Sets the horizontal position of the text. + */ +void +ges_title_source_set_xpos (GESTitleSource * self, gdouble position) +{ + GST_DEBUG ("self:%p, xpos:%f", self, position); + + self->priv->xpos = position; + if (self->priv->text_el) + g_object_set (self->priv->text_el, "xpos", position, NULL); +} + +/** + * ges_title_source_set_ypos: + * @self: the #GESTitleSource* to set + * @position: the color @self is being set to + * + * Sets the vertical position of the text. + */ +void +ges_title_source_set_ypos (GESTitleSource * self, gdouble position) +{ + GST_DEBUG ("self:%p, ypos:%f", self, position); + + self->priv->ypos = position; + if (self->priv->text_el) + g_object_set (self->priv->text_el, "ypos", position, NULL); +} + +/** + * ges_title_source_get_text: + * @source: a #GESTitleSource + * + * Get the text currently set on the @source. + * + * Returns: (transfer full): The text currently set on the @source. + * + * Deprecated: 1.16: Use ges_timeline_element_get_child_property instead + * (this actually returns a newly allocated string) + */ +const gchar * +ges_title_source_get_text (GESTitleSource * source) +{ + gchar *text; + + ges_track_element_get_child_properties (GES_TRACK_ELEMENT (source), "text", + &text, NULL); + + return text; +} + +/** + * ges_title_source_get_font_desc: + * @source: a #GESTitleSource + * + * Get the pango font description used by @source. + * + * Returns: (transfer full): The pango font description used by this + * @source. + * + * Deprecated: 1.16: Use ges_timeline_element_get_child_property instead + * (this actually returns a newly allocated string) + */ +const gchar * +ges_title_source_get_font_desc (GESTitleSource * source) +{ + gchar *font_desc; + + ges_track_element_get_child_properties (GES_TRACK_ELEMENT (source), + "font-desc", &font_desc, NULL); + + return font_desc; +} + +/** + * ges_title_source_get_halignment: + * @source: a #GESTitleSource + * + * Get the horizontal aligment used by @source. + * + * Returns: The horizontal aligment used by @source. + */ +GESTextHAlign +ges_title_source_get_halignment (GESTitleSource * source) +{ + GESTextHAlign halign; + + ges_track_element_get_child_properties (GES_TRACK_ELEMENT (source), + "halignment", &halign, NULL); + + return halign; +} + +/** + * ges_title_source_get_valignment: + * @source: a #GESTitleSource + * + * Get the vertical aligment used by @source. + * + * Returns: The vertical aligment used by @source. + */ +GESTextVAlign +ges_title_source_get_valignment (GESTitleSource * source) +{ + GESTextVAlign valign; + + ges_track_element_get_child_properties (GES_TRACK_ELEMENT (source), + "valignment", &valign, NULL); + + return valign; +} + +/** + * ges_title_source_get_text_color: + * @source: a #GESTitleSource + * + * Get the color used by @source. + * + * Returns: The color used by @source. + */ +const guint32 +ges_title_source_get_text_color (GESTitleSource * source) +{ + guint32 color; + + ges_track_element_get_child_properties (GES_TRACK_ELEMENT (source), "color", + &color, NULL); + + return color; +} + +/** + * ges_title_source_get_background_color: + * @source: a #GESTitleSource + * + * Get the background used by @source. + * + * Returns: The background used by @source. + */ +const guint32 +ges_title_source_get_background_color (GESTitleSource * source) +{ + guint32 color; + + ges_track_element_get_child_properties (GES_TRACK_ELEMENT (source), + "foreground-color", &color, NULL); + + return color; +} + +/** + * ges_title_source_get_xpos: + * @source: a #GESTitleSource + * + * Get the horizontal position used by @source. + * + * Returns: The horizontal position used by @source. + */ +const gdouble +ges_title_source_get_xpos (GESTitleSource * source) +{ + gdouble xpos; + + ges_track_element_get_child_properties (GES_TRACK_ELEMENT (source), "xpos", + &xpos, NULL); + + return xpos; +} + +/** + * ges_title_source_get_ypos: + * @source: a #GESTitleSource + * + * Get the vertical position used by @source. + * + * Returns: The vertical position used by @source. + */ +const gdouble +ges_title_source_get_ypos (GESTitleSource * source) +{ + gdouble ypos; + + ges_track_element_get_child_properties (GES_TRACK_ELEMENT (source), "ypos", + &ypos, NULL); + + return ypos; +} + +/* ges_title_source_new: + * + * Creates a new #GESTitleSource. + * + * Returns: (transfer floating) (nullable): The newly created #GESTitleSource, + * or %NULL if there was an error. + */ +GESTitleSource * +ges_title_source_new (void) +{ + GESTitleSource *res; + GESAsset *asset = ges_asset_request (GES_TYPE_TITLE_SOURCE, NULL, NULL); + + res = GES_TITLE_SOURCE (ges_asset_extract (asset, NULL)); + gst_object_unref (asset); + + return res; +} diff --git a/ges/ges-title-source.h b/ges/ges-title-source.h new file mode 100644 index 0000000000..8f6ac6d8c0 --- /dev/null +++ b/ges/ges-title-source.h @@ -0,0 +1,108 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> +#include <ges/ges-video-source.h> + +G_BEGIN_DECLS + +#define GES_TYPE_TITLE_SOURCE ges_title_source_get_type() +GES_DECLARE_TYPE(TitleSource, title_source, TITLE_SOURCE); + +/** + * GESTitleSource: + * + * ## Children Properties + * + * {{ libs/GESTitleSource-children-props.md }} + */ +struct _GESTitleSource { + GESVideoSource parent; + + /*< private >*/ + GESTitleSourcePrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESTitleSourceClass: + * @parent_class: parent class + */ +struct _GESTitleSourceClass { + GESVideoSourceClass parent_class; + + /*< private >*/ + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING - 1]; +}; + +GES_API +void ges_title_source_set_text (GESTitleSource *self, + const gchar *text); + +GES_API +void ges_title_source_set_font_desc (GESTitleSource *self, + const gchar *font_desc); + +GES_API +void ges_title_source_set_halignment (GESTitleSource *self, + GESTextHAlign halign); + +GES_API +void ges_title_source_set_valignment (GESTitleSource *self, + GESTextVAlign valign); + +GES_API +void ges_title_source_set_text_color (GESTitleSource *self, + guint32 color); +GES_API +void ges_title_source_set_background_color (GESTitleSource *self, + guint32 color); +GES_API +void ges_title_source_set_xpos (GESTitleSource *self, + gdouble position); +GES_API +void ges_title_source_set_ypos (GESTitleSource *self, + gdouble position); + +GES_API +const gchar *ges_title_source_get_text (GESTitleSource *source); +GES_API +const gchar *ges_title_source_get_font_desc (GESTitleSource *source); +GES_API +GESTextHAlign ges_title_source_get_halignment (GESTitleSource *source); +GES_API +GESTextVAlign ges_title_source_get_valignment (GESTitleSource *source); +GES_API +const guint32 ges_title_source_get_text_color (GESTitleSource *source); +GES_API +const guint32 ges_title_source_get_background_color (GESTitleSource *source); +GES_API +const gdouble ges_title_source_get_xpos (GESTitleSource *source); +GES_API +const gdouble ges_title_source_get_ypos (GESTitleSource *source); + +G_END_DECLS diff --git a/ges/ges-track-element-asset.c b/ges/ges-track-element-asset.c new file mode 100644 index 0000000000..87a9a198ae --- /dev/null +++ b/ges/ges-track-element-asset.c @@ -0,0 +1,175 @@ +/* Gstreamer Editing Services + * + * Copyright (C) <2012> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION: gestrackelementasset + * @title: GESTrackElementAsset + * @short_description: A GESAsset subclass specialized in GESTrackElement extraction + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-track-element-asset.h" + +enum +{ + PROP_0, + PROP_TRACK_TYPE, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +struct _GESTrackElementAssetPrivate +{ + GESTrackType type; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GESTrackElementAsset, ges_track_element_asset, + GES_TYPE_ASSET); + +static void +_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESTrackElementAsset *asset = GES_TRACK_ELEMENT_ASSET (object); + + switch (property_id) { + case PROP_TRACK_TYPE: + g_value_set_flags (value, asset->priv->type); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESTrackElementAsset *asset = GES_TRACK_ELEMENT_ASSET (object); + + switch (property_id) { + case PROP_TRACK_TYPE: + asset->priv->type = g_value_get_flags (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_track_element_asset_class_init (GESTrackElementAssetClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = _get_property; + object_class->set_property = _set_property; + + /** + * GESClip:track-type: + * + * The formats supported by the object. + */ + properties[PROP_TRACK_TYPE] = g_param_spec_flags ("track-type", + "Track type", "The GESTrackType in which the object is", + GES_TYPE_TRACK_TYPE, GES_TRACK_TYPE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + g_object_class_install_property (object_class, PROP_TRACK_TYPE, + properties[PROP_TRACK_TYPE]); +} + +static void +ges_track_element_asset_init (GESTrackElementAsset * self) +{ + GESTrackElementAssetPrivate *priv; + + priv = self->priv = ges_track_element_asset_get_instance_private (self); + + priv->type = GES_TRACK_TYPE_UNKNOWN; + +} + +/** + * ges_track_element_asset_set_track_type: + * @asset: A #GESAsset + * @type: A #GESTrackType + * + * Set the #GESTrackType the #GESTrackElement extracted from @self + * should get into + */ +void +ges_track_element_asset_set_track_type (GESTrackElementAsset * asset, + GESTrackType type) +{ + g_return_if_fail (GES_IS_TRACK_ELEMENT_ASSET (asset)); + + asset->priv->type = type; +} + +/** + * ges_track_element_asset_get_track_type: + * @asset: A #GESAsset + * + * Get the GESAssetTrackType the #GESTrackElement extracted from @self + * should get into + * + * Returns: a #GESTrackType + */ +const GESTrackType +ges_track_element_asset_get_track_type (GESTrackElementAsset * asset) +{ + g_return_val_if_fail (GES_IS_TRACK_ELEMENT_ASSET (asset), + GES_TRACK_TYPE_UNKNOWN); + + return asset->priv->type; +} + +/** + * ges_track_element_asset_get_natural_framerate: + * @self: A #GESAsset + * @framerate_n: The framerate numerator + * @framerate_d: The framerate denominator + * + * Result: %TRUE if @self has a natural framerate %FALSE otherwise + * + * Since: 1.18 + */ +gboolean +ges_track_element_asset_get_natural_framerate (GESTrackElementAsset * self, + gint * framerate_n, gint * framerate_d) +{ + GESTrackElementAssetClass *klass; + + g_return_val_if_fail (GES_IS_TRACK_ELEMENT_ASSET (self), FALSE); + g_return_val_if_fail (framerate_n && framerate_d, FALSE); + + klass = GES_TRACK_ELEMENT_ASSET_GET_CLASS (self); + + *framerate_n = 0; + *framerate_d = -1; + + if (klass->get_natural_framerate) + return klass->get_natural_framerate (self, framerate_n, framerate_d); + + return FALSE; +} diff --git a/ges/ges-track-element-asset.h b/ges/ges-track-element-asset.h new file mode 100644 index 0000000000..14a9f7cfde --- /dev/null +++ b/ges/ges-track-element-asset.h @@ -0,0 +1,71 @@ +/* GStreamer Editing Services + * + * Copyright (C) 2012 Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#pragma once + +#include <glib-object.h> +#include <gio/gio.h> +#include <ges/ges-types.h> +#include <ges/ges-asset.h> + +G_BEGIN_DECLS + +#define GES_TYPE_TRACK_ELEMENT_ASSET ges_track_element_asset_get_type() +GES_DECLARE_TYPE(TrackElementAsset, track_element_asset, TRACK_ELEMENT_ASSET); + +struct _GESTrackElementAsset +{ + GESAsset parent; + + /* <private> */ + GESTrackElementAssetPrivate *priv; + + /* Padding for API extension */ + gpointer __ges_reserved[GES_PADDING]; +}; + +struct _GESTrackElementAssetClass +{ + GESAssetClass parent_class; + + /** + * GESTrackElementAssetClass::get_natural_framerate: + * @self: A #GESTrackElementAsset + * @framerate_n: The framerate numerator to retrieve + * @framerate_d: The framerate denominator to retrieve + * + * Returns: %TRUE if @self has a natural framerate @FALSE otherwise. + * + * Since: 1.18 + */ + gboolean (*get_natural_framerate) (GESTrackElementAsset *self, gint *framerate_n, gint *framerate_d); + + gpointer _ges_reserved[GES_PADDING - 1]; +}; + +GES_API +const GESTrackType ges_track_element_asset_get_track_type (GESTrackElementAsset *asset); +GES_API +void ges_track_element_asset_set_track_type (GESTrackElementAsset * asset, GESTrackType type); +GES_API +gboolean ges_track_element_asset_get_natural_framerate (GESTrackElementAsset *self, + gint *framerate_n, + gint *framerate_d); + +G_END_DECLS diff --git a/ges/ges-track-element-deprecated.h b/ges/ges-track-element-deprecated.h new file mode 100644 index 0000000000..6132fcfdf9 --- /dev/null +++ b/ges/ges-track-element-deprecated.h @@ -0,0 +1,61 @@ +#pragma once + +GES_DEPRECATED_FOR(ges_track_element_get_nleobject) +GstElement * ges_track_element_get_gnlobject (GESTrackElement * object); + + +GES_DEPRECATED_FOR(ges_timeline_element_list_children_properties) +GParamSpec ** +ges_track_element_list_children_properties (GESTrackElement *object, + guint *n_properties); + +GES_DEPRECATED_FOR(ges_timeline_element_lookup_child) +gboolean ges_track_element_lookup_child (GESTrackElement *object, + const gchar *prop_name, + GstElement **element, + GParamSpec **pspec); + +GES_DEPRECATED_FOR(ges_timeline_element_get_child_property_valist) +void +ges_track_element_get_child_property_valist (GESTrackElement * object, + const gchar * first_property_name, + va_list var_args); + +GES_DEPRECATED_FOR(ges_timeline_element_get_child_properties) +void ges_track_element_get_child_properties (GESTrackElement *object, + const gchar * first_property_name, + ...) G_GNUC_NULL_TERMINATED; + +GES_DEPRECATED_FOR(ges_timeline_element_set_child_property_valist) +void +ges_track_element_set_child_property_valist (GESTrackElement * object, + const gchar * first_property_name, + va_list var_args); + +GES_DEPRECATED_FOR(ges_timeline_element_set_child_property_by_spec) +void +ges_track_element_set_child_property_by_pspec (GESTrackElement * object, + GParamSpec * pspec, + GValue * value); + +GES_DEPRECATED_FOR(ges_timeline_element_set_child_properties) +void +ges_track_element_set_child_properties (GESTrackElement * object, + const gchar * first_property_name, + ...) G_GNUC_NULL_TERMINATED; + +GES_DEPRECATED_FOR(ges_timeline_element_set_child_property) +gboolean ges_track_element_set_child_property (GESTrackElement *object, + const gchar *property_name, + GValue * value); + +GES_DEPRECATED_FOR(ges_timeline_element_get_child_property) +gboolean ges_track_element_get_child_property (GESTrackElement *object, + const gchar *property_name, + GValue * value); + +GES_DEPRECATED_FOR(ges_timeline_element_edit) +gboolean +ges_track_element_edit (GESTrackElement * object, + GList *layers, GESEditMode mode, + GESEdge edge, guint64 position); diff --git a/ges/ges-track-element.c b/ges/ges-track-element.c new file mode 100644 index 0000000000..eb2e7f17dc --- /dev/null +++ b/ges/ges-track-element.c @@ -0,0 +1,2122 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gestrackelement + * @title: GESTrackElement + * @short_description: Base Class for the elements of a #GESTrack + * + * A #GESTrackElement is a #GESTimelineElement that specifically belongs + * to a single #GESTrack of its #GESTimelineElement:timeline. Its + * #GESTimelineElement:start and #GESTimelineElement:duration specify its + * temporal extent in the track. Specifically, a track element wraps some + * nleobject, such as an #nlesource or #nleoperation, which can be + * retrieved with ges_track_element_get_nleobject(), and its + * #GESTimelineElement:start, #GESTimelineElement:duration, + * #GESTimelineElement:in-point, #GESTimelineElement:priority and + * #GESTrackElement:active properties expose the corresponding nleobject + * properties. When a track element is added to a track, its nleobject is + * added to the corresponding #nlecomposition that the track wraps. + * + * Most users will not have to work directly with track elements since a + * #GESClip will automatically create track elements for its timeline's + * tracks and take responsibility for updating them. The only track + * elements that are not automatically created by clips, but a user is + * likely to want to create, are #GESEffect-s. + * + * ## Control Bindings for Children Properties + * + * You can set up control bindings for a track element child property + * using ges_track_element_set_control_source(). A + * #GstTimedValueControlSource should specify the timed values using the + * internal source coordinates (see #GESTimelineElement). By default, + * these will be updated to lie between the #GESTimelineElement:in-point + * and out-point of the element. This can be switched off by setting + * #GESTrackElement:auto-clamp-control-sources to %FALSE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-extractable.h" +#include "ges-track-element.h" +#include "ges-clip.h" +#include "ges-meta-container.h" + +struct _GESTrackElementPrivate +{ + GESTrackType track_type; + + GstElement *nleobject; /* The NleObject */ + GstElement *element; /* The element contained in the nleobject (can be NULL) */ + + GESTrack *track; + gboolean has_internal_source_forbidden; + gboolean has_internal_source; + + gboolean layer_active; + + GHashTable *bindings_hashtable; /* We need this if we want to be able to serialize + and deserialize keyframes */ + GESAsset *creator_asset; + + GstClockTime outpoint; + gboolean freeze_control_sources; + gboolean auto_clamp_control_sources; +}; + +enum +{ + PROP_0, + PROP_ACTIVE, + PROP_TRACK_TYPE, + PROP_TRACK, + PROP_HAS_INTERNAL_SOURCE, + PROP_AUTO_CLAMP_CONTROL_SOURCES, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +enum +{ + CONTROL_BINDING_ADDED, + CONTROL_BINDING_REMOVED, + LAST_SIGNAL +}; + +static guint ges_track_element_signals[LAST_SIGNAL] = { 0 }; + +static void ges_track_element_set_asset (GESExtractable * extractable, + GESAsset * asset); + +static void +ges_extractable_interface_init (GESExtractableInterface * iface) +{ + iface->set_asset = ges_track_element_set_asset; + iface->asset_type = GES_TYPE_TRACK_ELEMENT_ASSET; +} + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESTrackElement, ges_track_element, + GES_TYPE_TIMELINE_ELEMENT, G_ADD_PRIVATE (GESTrackElement) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, + ges_extractable_interface_init)); + +static GstElement *ges_track_element_create_gnl_object_func (GESTrackElement * + object); + +static gboolean _set_start (GESTimelineElement * element, GstClockTime start); +static gboolean _set_inpoint (GESTimelineElement * element, + GstClockTime inpoint); +static gboolean _set_duration (GESTimelineElement * element, + GstClockTime duration); +static gboolean _set_max_duration (GESTimelineElement * element, + GstClockTime max_duration); +static gboolean _set_priority (GESTimelineElement * element, guint32 priority); +GESTrackType _get_track_types (GESTimelineElement * object); + +static gboolean +_lookup_child (GESTrackElement * object, + const gchar * prop_name, GstElement ** element, GParamSpec ** pspec) +{ + return + GES_TIMELINE_ELEMENT_GET_CLASS (object)->lookup_child + (GES_TIMELINE_ELEMENT (object), prop_name, (GObject **) element, pspec); +} + +static gboolean +strv_find_str (const gchar ** strv, const char *str) +{ + guint i; + + if (strv == NULL) + return FALSE; + + for (i = 0; strv[i]; i++) { + if (g_strcmp0 (strv[i], str) == 0) + return TRUE; + } + + return FALSE; +} + +static guint32 +_get_layer_priority (GESTimelineElement * element) +{ + if (!element->parent) + return GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY; + + return ges_timeline_element_get_layer_priority (element->parent); +} + +static gboolean +_get_natural_framerate (GESTimelineElement * self, gint * framerate_n, + gint * framerate_d) +{ + GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (self)); + + /* FIXME: asset should **never** be NULL */ + if (asset && + ges_track_element_asset_get_natural_framerate (GES_TRACK_ELEMENT_ASSET + (asset), framerate_n, framerate_d)) + return TRUE; + + if (self->parent) + return ges_timeline_element_get_natural_framerate (self->parent, + framerate_n, framerate_d); + + return FALSE; +} + +static void +ges_track_element_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESTrackElement *track_element = GES_TRACK_ELEMENT (object); + + switch (property_id) { + case PROP_ACTIVE: + g_value_set_boolean (value, ges_track_element_is_active (track_element)); + break; + case PROP_TRACK_TYPE: + g_value_set_flags (value, track_element->priv->track_type); + break; + case PROP_TRACK: + g_value_set_object (value, track_element->priv->track); + break; + case PROP_HAS_INTERNAL_SOURCE: + g_value_set_boolean (value, + ges_track_element_has_internal_source (track_element)); + break; + case PROP_AUTO_CLAMP_CONTROL_SOURCES: + g_value_set_boolean (value, + ges_track_element_get_auto_clamp_control_sources (track_element)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_track_element_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESTrackElement *track_element = GES_TRACK_ELEMENT (object); + + switch (property_id) { + case PROP_ACTIVE: + ges_track_element_set_active (track_element, g_value_get_boolean (value)); + break; + case PROP_TRACK_TYPE: + ges_track_element_set_track_type (track_element, + g_value_get_flags (value)); + break; + case PROP_HAS_INTERNAL_SOURCE: + ges_track_element_set_has_internal_source (track_element, + g_value_get_boolean (value)); + break; + case PROP_AUTO_CLAMP_CONTROL_SOURCES: + ges_track_element_set_auto_clamp_control_sources (track_element, + g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_track_element_dispose (GObject * object) +{ + GESTrackElement *element = GES_TRACK_ELEMENT (object); + GESTrackElementPrivate *priv = element->priv; + + if (priv->bindings_hashtable) + g_hash_table_destroy (priv->bindings_hashtable); + + if (priv->nleobject) { + GstState cstate; + + if (priv->track != NULL) { + g_error ("%p Still in %p, this means that you forgot" + " to remove it from the GESTrack it is contained in. You always need" + " to remove a GESTrackElement from its track before dropping the last" + " reference\n" + "This problem may also be caused by a refcounting bug in" + " the application or GES itself.", object, priv->track); + gst_element_get_state (priv->nleobject, &cstate, NULL, 0); + if (cstate != GST_STATE_NULL) + gst_element_set_state (priv->nleobject, GST_STATE_NULL); + } + + g_object_set_qdata (G_OBJECT (priv->nleobject), + NLE_OBJECT_TRACK_ELEMENT_QUARK, NULL); + gst_object_unref (priv->nleobject); + priv->nleobject = NULL; + } + + G_OBJECT_CLASS (ges_track_element_parent_class)->dispose (object); +} + +static void +ges_track_element_set_asset (GESExtractable * extractable, GESAsset * asset) +{ + GESTrackElementClass *class; + GstElement *nleobject; + gchar *tmp; + GESTrackElement *object = GES_TRACK_ELEMENT (extractable); + + if (ges_track_element_get_track_type (object) == GES_TRACK_TYPE_UNKNOWN) { + ges_track_element_set_track_type (object, + ges_track_element_asset_get_track_type (GES_TRACK_ELEMENT_ASSET + (asset))); + } + + class = GES_TRACK_ELEMENT_GET_CLASS (object); + g_assert (class->create_gnl_object); + + nleobject = class->create_gnl_object (object); + if (G_UNLIKELY (nleobject == NULL)) { + GST_ERROR_OBJECT (object, "Could not create NleObject"); + + return; + } + + tmp = g_strdup_printf ("%s:%s", G_OBJECT_TYPE_NAME (object), + GST_OBJECT_NAME (nleobject)); + gst_object_set_name (GST_OBJECT (nleobject), tmp); + g_free (tmp); + + object->priv->nleobject = gst_object_ref (nleobject); + g_object_set_qdata (G_OBJECT (nleobject), NLE_OBJECT_TRACK_ELEMENT_QUARK, + object); + + /* Set some properties on the NleObject */ + g_object_set (object->priv->nleobject, + "start", GES_TIMELINE_ELEMENT_START (object), + "inpoint", GES_TIMELINE_ELEMENT_INPOINT (object), + "duration", GES_TIMELINE_ELEMENT_DURATION (object), + "priority", GES_TIMELINE_ELEMENT_PRIORITY (object), + "active", object->active & object->priv->layer_active, NULL); +} + +static void +ges_track_element_constructed (GObject * object) +{ + GESTrackElement *self = GES_TRACK_ELEMENT (object); + + if (self->priv->track_type == GES_TRACK_TYPE_UNKNOWN) + ges_track_element_set_track_type (GES_TRACK_ELEMENT (object), + GES_TRACK_ELEMENT_GET_CLASS (object)->ABI.abi.default_track_type); + + /* set the default has-internal-source */ + ges_track_element_set_has_internal_source (self, + GES_TRACK_ELEMENT_CLASS_DEFAULT_HAS_INTERNAL_SOURCE + (GES_TRACK_ELEMENT_GET_CLASS (object))); + + G_OBJECT_CLASS (ges_track_element_parent_class)->constructed (object); +} + +static void +ges_track_element_class_init (GESTrackElementClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass); + + object_class->get_property = ges_track_element_get_property; + object_class->set_property = ges_track_element_set_property; + object_class->dispose = ges_track_element_dispose; + object_class->constructed = ges_track_element_constructed; + + /** + * GESTrackElement:active: + * + * Whether the effect of the element should be applied in its + * #GESTrackElement:track. If set to %FALSE, it will not be used in + * the output of the track. + */ + properties[PROP_ACTIVE] = + g_param_spec_boolean ("active", "Active", "Use object in output", TRUE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_property (object_class, PROP_ACTIVE, + properties[PROP_ACTIVE]); + + /** + * GESTrackElement:track-type: + * + * The track type of the element, which determines the type of track the + * element can be added to (see #GESTrack:track-type). This should + * correspond to the type of data that the element can produce or + * process. + */ + properties[PROP_TRACK_TYPE] = g_param_spec_flags ("track-type", "Track Type", + "The track type of the object", GES_TYPE_TRACK_TYPE, + GES_TRACK_TYPE_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | + G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_property (object_class, PROP_TRACK_TYPE, + properties[PROP_TRACK_TYPE]); + + /** + * GESTrackElement:track: + * + * The track that this element belongs to, or %NULL if it does not + * belong to a track. + */ + properties[PROP_TRACK] = g_param_spec_object ("track", "Track", + "The track the object is in", GES_TYPE_TRACK, G_PARAM_READABLE); + g_object_class_install_property (object_class, PROP_TRACK, + properties[PROP_TRACK]); + + /** + * GESTrackElement:has-internal-source: + * + * This property is used to determine whether the 'internal time' + * properties of the element have any meaning. In particular, unless + * this is set to %TRUE, the #GESTimelineElement:in-point and + * #GESTimelineElement:max-duration can not be set to any value other + * than the default 0 and #GST_CLOCK_TIME_NONE, respectively. + * + * If an element has some *internal* *timed* source #GstElement that it + * reads stream data from as part of its function in a #GESTrack, then + * you'll likely want to set this to %TRUE to allow the + * #GESTimelineElement:in-point and #GESTimelineElement:max-duration to + * be set. + * + * The default value is determined by the #GESTrackElementClass + * @default_has_internal_source class property. For most + * #GESSourceClass-es, this will be %TRUE, with the exception of those + * that have a potentially *static* source, such as #GESImageSourceClass + * and #GESTitleSourceClass. Otherwise, this will usually be %FALSE. + * + * For most #GESOperation-s you will likely want to leave this set to + * %FALSE. The exception may be for an operation that reads some stream + * data from some private internal source as part of manipulating the + * input data from the usual linked upstream #GESTrackElement. + * + * For example, you may want to set this to %TRUE for a + * #GES_TRACK_TYPE_VIDEO operation that wraps a #textoverlay that reads + * from a subtitle file and places its text on top of the received video + * data. The #GESTimelineElement:in-point of the element would be used + * to shift the initial seek time on the #textoverlay away from 0, and + * the #GESTimelineElement:max-duration could be set to reflect the + * time at which the subtitle file runs out of data. + * + * Note that GES can not support track elements that have both internal + * content and manipulate the timing of their data streams (time + * effects). + * + * Since: 1.18 + */ + properties[PROP_HAS_INTERNAL_SOURCE] = + g_param_spec_boolean ("has-internal-source", "Has Internal Source", + "Whether the element has some internal source of stream data", FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_property (object_class, PROP_HAS_INTERNAL_SOURCE, + properties[PROP_HAS_INTERNAL_SOURCE]); + + /** + * GESTrackElement:auto-clamp-control-sources: + * + * Whether the control sources on the element (see + * ges_track_element_set_control_source()) will be automatically + * updated whenever the #GESTimelineElement:in-point or out-point of the + * element change in value. + * + * See ges_track_element_clamp_control_source() for how this is done + * per control source. + * + * Default value: %TRUE + * + * Since: 1.18 + */ + properties[PROP_AUTO_CLAMP_CONTROL_SOURCES] = + g_param_spec_boolean ("auto-clamp-control-sources", + "Auto-Clamp Control Sources", "Whether to automatically update the " + "control sources with a change in in-point or out-point", TRUE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_property (object_class, + PROP_AUTO_CLAMP_CONTROL_SOURCES, + properties[PROP_AUTO_CLAMP_CONTROL_SOURCES]); + + /** + * GESTrackElement::control-binding-added: + * @track_element: A #GESTrackElement + * @control_binding: The control binding that has been added + * + * This is emitted when a control binding is added to a child property + * of the track element. + */ + ges_track_element_signals[CONTROL_BINDING_ADDED] = + g_signal_new ("control-binding-added", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, GST_TYPE_CONTROL_BINDING); + + /** + * GESTrackElement::control-binding-removed: + * @track_element: A #GESTrackElement + * @control_binding: The control binding that has been removed + * + * This is emitted when a control binding is removed from a child + * property of the track element. + */ + ges_track_element_signals[CONTROL_BINDING_REMOVED] = + g_signal_new ("control-binding-removed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, GST_TYPE_CONTROL_BINDING); + + element_class->set_start = _set_start; + element_class->set_duration = _set_duration; + element_class->set_inpoint = _set_inpoint; + element_class->set_max_duration = _set_max_duration; + element_class->set_priority = _set_priority; + element_class->get_track_types = _get_track_types; + element_class->deep_copy = ges_track_element_copy_properties; + element_class->get_layer_priority = _get_layer_priority; + element_class->get_natural_framerate = _get_natural_framerate; + + klass->create_gnl_object = ges_track_element_create_gnl_object_func; + klass->lookup_child = _lookup_child; + klass->ABI.abi.default_track_type = GES_TRACK_TYPE_UNKNOWN; +} + +static void +ges_track_element_init (GESTrackElement * self) +{ + GESTrackElementPrivate *priv = self->priv = + ges_track_element_get_instance_private (self); + + /* Sane default values */ + GES_TIMELINE_ELEMENT_START (self) = 0; + GES_TIMELINE_ELEMENT_INPOINT (self) = 0; + GES_TIMELINE_ELEMENT_DURATION (self) = GST_SECOND; + GES_TIMELINE_ELEMENT_PRIORITY (self) = 0; + self->active = TRUE; + self->priv->layer_active = TRUE; + + priv->bindings_hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + + /* NOTE: make sure we set this flag to TRUE so that + * g_object_new (, "has-internal-source", TRUE, "in-point", 10, NULL); + * can succeed. The problem is that "in-point" will always be set before + * has-internal-source is set, so we first assume that it is TRUE. + * Note that if we call + * g_object_new (, "has-internal-source", FALSE, "in-point", 10, NULL); + * then "in-point" will be allowed to be set, but then when + * "has-internal-source" is later set to TRUE, this will set the + * "in-point" back to 0. + * This is particularly needed for the ges_timeline_element_copy method + * because it calls g_object_new_with_properties. + */ + self->priv->has_internal_source = TRUE; + + self->priv->outpoint = GST_CLOCK_TIME_NONE; + self->priv->auto_clamp_control_sources = TRUE; +} + +static gfloat +interpolate_values_for_position (GstTimedValue * first_value, + GstTimedValue * second_value, guint64 position, gboolean absolute) +{ + gfloat diff; + GstClockTime interval; + gfloat value_at_pos; + + g_assert (second_value || first_value); + + if (first_value == NULL) + return second_value->value; + + if (second_value == NULL) + return first_value->value; + + diff = second_value->value - first_value->value; + interval = second_value->timestamp - first_value->timestamp; + + /* FIXME: properly support non-linear timed control sources */ + if (position > first_value->timestamp) + value_at_pos = + first_value->value + ((float) (position - + first_value->timestamp) / (float) interval) * diff; + else + value_at_pos = + first_value->value - ((float) (first_value->timestamp - + position) / (float) interval) * diff; + + if (!absolute) + value_at_pos = CLAMP (value_at_pos, 0.0, 1.0); + + return value_at_pos; +} + +static void +_update_control_source (GstTimedValueControlSource * source, gboolean absolute, + GstClockTime inpoint, GstClockTime outpoint) +{ + GList *values, *tmp; + GstTimedValue *last, *first, *prev = NULL, *next = NULL; + gfloat value_at_pos; + + if (inpoint == outpoint) { + gst_timed_value_control_source_unset_all (source); + return; + } + + values = gst_timed_value_control_source_get_all (source); + + if (g_list_length (values) == 0) + return; + + first = values->data; + + for (tmp = values->next; tmp; tmp = tmp->next) { + next = tmp->data; + + if (next->timestamp == inpoint) { + /* just leave this value in place */ + first = NULL; + break; + } + if (next->timestamp > inpoint) + break; + } + g_list_free (values); + + if (first) { + value_at_pos = + interpolate_values_for_position (first, next, inpoint, absolute); + gst_timed_value_control_source_unset (source, first->timestamp); + gst_timed_value_control_source_set (source, inpoint, value_at_pos); + } + + if (GST_CLOCK_TIME_IS_VALID (outpoint)) { + values = gst_timed_value_control_source_get_all (source); + + last = g_list_last (values)->data; + + for (tmp = g_list_last (values)->prev; tmp; tmp = tmp->prev) { + prev = tmp->data; + + if (prev->timestamp == outpoint) { + /* leave this value in place */ + last = NULL; + break; + } + if (prev->timestamp < outpoint) + break; + } + g_list_free (values); + + if (last) { + value_at_pos = + interpolate_values_for_position (prev, last, outpoint, absolute); + + gst_timed_value_control_source_unset (source, last->timestamp); + gst_timed_value_control_source_set (source, outpoint, value_at_pos); + } + } + + values = gst_timed_value_control_source_get_all (source); + + for (tmp = values; tmp; tmp = tmp->next) { + GstTimedValue *value = tmp->data; + if (value->timestamp < inpoint) + gst_timed_value_control_source_unset (source, value->timestamp); + else if (GST_CLOCK_TIME_IS_VALID (outpoint) && value->timestamp > outpoint) + gst_timed_value_control_source_unset (source, value->timestamp); + } + g_list_free (values); +} + +static void +_update_control_bindings (GESTrackElement * self, GstClockTime inpoint, + GstClockTime outpoint) +{ + gchar *name; + GstControlBinding *binding; + GstControlSource *source; + gboolean absolute; + gpointer value, key; + GHashTableIter iter; + + if (self->priv->freeze_control_sources) + return; + + g_hash_table_iter_init (&iter, self->priv->bindings_hashtable); + while (g_hash_table_iter_next (&iter, &key, &value)) { + binding = value; + name = key; + g_object_get (binding, "control-source", &source, "absolute", &absolute, + NULL); + + if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) { + GST_INFO_OBJECT (self, "Not updating %s because it does not have a" + " timed value control source", name); + gst_object_unref (source); + continue; + } + + _update_control_source (GST_TIMED_VALUE_CONTROL_SOURCE (source), absolute, + inpoint, outpoint); + gst_object_unref (source); + } +} + +static gboolean +_set_start (GESTimelineElement * element, GstClockTime start) +{ + GESTrackElement *object = GES_TRACK_ELEMENT (element); + + g_return_val_if_fail (object->priv->nleobject, FALSE); + + g_object_set (object->priv->nleobject, "start", start, NULL); + + return TRUE; +} + +static void +ges_track_element_update_outpoint_full (GESTrackElement * self, + GstClockTime inpoint, GstClockTime duration) +{ + GstClockTime current_inpoint = _INPOINT (self); + gboolean increase = (inpoint > current_inpoint); + GstClockTime outpoint = GST_CLOCK_TIME_NONE; + GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (self); + GESTrackElementPrivate *priv = self->priv; + + if (GES_IS_CLIP (parent) && ges_track_element_get_track (self) + && ges_track_element_is_active (self) + && GST_CLOCK_TIME_IS_VALID (duration)) { + outpoint = + ges_clip_get_internal_time_from_timeline_time (GES_CLIP (parent), self, + _START (self) + duration, NULL); + + if (!GST_CLOCK_TIME_IS_VALID (outpoint)) + GST_ERROR_OBJECT (self, "Got an invalid out-point"); + else if (increase) + outpoint += (inpoint - current_inpoint); + else + outpoint -= (current_inpoint - inpoint); + } + + if ((priv->outpoint != outpoint || inpoint != current_inpoint) + && self->priv->auto_clamp_control_sources) + _update_control_bindings (self, inpoint, outpoint); + + priv->outpoint = outpoint; +} + +void +ges_track_element_update_outpoint (GESTrackElement * self) +{ + GESTimelineElement *el = GES_TIMELINE_ELEMENT (self); + ges_track_element_update_outpoint_full (self, el->inpoint, el->duration); +} + +static gboolean +_set_inpoint (GESTimelineElement * element, GstClockTime inpoint) +{ + GESTrackElement *object = GES_TRACK_ELEMENT (element); + GESTimelineElement *parent = element->parent; + GError *error = NULL; + + g_return_val_if_fail (object->priv->nleobject, FALSE); + if (inpoint && !object->priv->has_internal_source) { + GST_WARNING_OBJECT (element, "Cannot set an in-point for a track " + "element that is not registered with internal content"); + return FALSE; + } + + if (GES_IS_CLIP (parent) + && !ges_clip_can_set_inpoint_of_child (GES_CLIP (parent), object, + inpoint, &error)) { + GST_WARNING_OBJECT (element, "Cannot set an in-point of %" + GST_TIME_FORMAT " because the parent clip %" GES_FORMAT + " would not allow it%s%s", GST_TIME_ARGS (inpoint), + GES_ARGS (parent), error ? ": " : "", error ? error->message : ""); + g_clear_error (&error); + return FALSE; + } + + g_object_set (object->priv->nleobject, "inpoint", inpoint, NULL); + + ges_track_element_update_outpoint_full (object, inpoint, element->duration); + + return TRUE; +} + +static gboolean +_set_duration (GESTimelineElement * element, GstClockTime duration) +{ + GESTrackElement *object = GES_TRACK_ELEMENT (element); + GESTrackElementPrivate *priv = object->priv; + + g_return_val_if_fail (priv->nleobject, FALSE); + + g_object_set (priv->nleobject, "duration", duration, NULL); + + ges_track_element_update_outpoint_full (object, element->inpoint, duration); + + return TRUE; +} + +static gboolean +_set_max_duration (GESTimelineElement * element, GstClockTime max_duration) +{ + GESTrackElement *object = GES_TRACK_ELEMENT (element); + GESTimelineElement *parent = element->parent; + GError *error = NULL; + + if (GST_CLOCK_TIME_IS_VALID (max_duration) + && !object->priv->has_internal_source) { + GST_WARNING_OBJECT (element, "Cannot set a max-duration for a track " + "element that is not registered with internal content"); + return FALSE; + } + + if (GES_IS_CLIP (parent) + && !ges_clip_can_set_max_duration_of_child (GES_CLIP (parent), object, + max_duration, &error)) { + GST_WARNING_OBJECT (element, "Cannot set a max-duration of %" + GST_TIME_FORMAT " because the parent clip %" GES_FORMAT + " would not allow it%s%s", GST_TIME_ARGS (max_duration), + GES_ARGS (parent), error ? ": " : "", error ? error->message : ""); + g_clear_error (&error); + return FALSE; + } + + return TRUE; +} + + +static gboolean +_set_priority (GESTimelineElement * element, guint32 priority) +{ + GESTrackElement *object = GES_TRACK_ELEMENT (element); + GESTimelineElement *parent = element->parent; + GError *error = NULL; + + g_return_val_if_fail (object->priv->nleobject, FALSE); + + if (priority < MIN_NLE_PRIO) { + GST_INFO_OBJECT (element, "Priority (%d) < MIN_NLE_PRIO, setting it to %d", + priority, MIN_NLE_PRIO); + priority = MIN_NLE_PRIO; + } + + GST_DEBUG_OBJECT (object, "priority:%" G_GUINT32_FORMAT, priority); + + if (G_UNLIKELY (priority == _PRIORITY (object))) + return FALSE; + + if (GES_IS_CLIP (parent) + && !ges_clip_can_set_priority_of_child (GES_CLIP (parent), object, + priority, &error)) { + GST_WARNING_OBJECT (element, "Cannot set a priority of %" + G_GUINT32_FORMAT " because the parent clip %" GES_FORMAT + " would not allow it%s%s", priority, GES_ARGS (parent), + error ? ": " : "", error ? error->message : ""); + g_clear_error (&error); + return FALSE; + } + + g_object_set (object->priv->nleobject, "priority", priority, NULL); + + return TRUE; +} + +GESTrackType +_get_track_types (GESTimelineElement * object) +{ + return ges_track_element_get_track_type (GES_TRACK_ELEMENT (object)); +} + +/** + * ges_track_element_set_active: + * @object: A #GESTrackElement + * @active: Whether @object should be active in its track + * + * Sets #GESTrackElement:active for the element. + * + * Returns: %TRUE if the property was *toggled*. + */ +gboolean +ges_track_element_set_active (GESTrackElement * object, gboolean active) +{ + GESTimelineElement *parent; + GError *error = NULL; + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + g_return_val_if_fail (object->priv->nleobject, FALSE); + + GST_DEBUG_OBJECT (object, "object:%p, active:%d", object, active); + + if (G_UNLIKELY (active == object->active)) + return FALSE; + + parent = GES_TIMELINE_ELEMENT_PARENT (object); + if (GES_IS_CLIP (parent) + && !ges_clip_can_set_active_of_child (GES_CLIP (parent), object, active, + &error)) { + GST_WARNING_OBJECT (object, + "Cannot set active to %i because the parent clip %" GES_FORMAT + " would not allow it%s%s", active, GES_ARGS (parent), error ? ": " : "", + error ? error->message : ""); + g_clear_error (&error); + return FALSE; + } + + g_object_set (object->priv->nleobject, "active", + active & object->priv->layer_active, NULL); + + object->active = active; + if (GES_TRACK_ELEMENT_GET_CLASS (object)->active_changed) + GES_TRACK_ELEMENT_GET_CLASS (object)->active_changed (object, active); + + g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_ACTIVE]); + + return TRUE; +} + +/** + * ges_track_element_set_has_internal_source: + * @object: A #GESTrackElement + * @has_internal_source: Whether the @object should be allowed to have its + * 'internal time' properties set. + * + * Sets #GESTrackElement:has-internal-source for the element. If this is + * set to %FALSE, this method will also set the + * #GESTimelineElement:in-point of the element to 0 and its + * #GESTimelineElement:max-duration to #GST_CLOCK_TIME_NONE. + * + * Returns: %FALSE if @has_internal_source is forbidden for @object and + * %TRUE in any other case. + * + * Since: 1.18 + */ +gboolean +ges_track_element_set_has_internal_source (GESTrackElement * object, + gboolean has_internal_source) +{ + GESTimelineElement *element; + + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + + GST_DEBUG_OBJECT (object, "object:%p, has-internal-source: %s", object, + has_internal_source ? "TRUE" : "FALSE"); + + if (has_internal_source && object->priv->has_internal_source_forbidden) { + GST_WARNING_OBJECT (object, "Setting an internal source for this " + "element is forbidden"); + return FALSE; + } + + if (G_UNLIKELY (has_internal_source == object->priv->has_internal_source)) + return TRUE; + + object->priv->has_internal_source = has_internal_source; + + if (!has_internal_source) { + element = GES_TIMELINE_ELEMENT (object); + ges_timeline_element_set_inpoint (element, 0); + ges_timeline_element_set_max_duration (element, GST_CLOCK_TIME_NONE); + } + + g_object_notify_by_pspec (G_OBJECT (object), + properties[PROP_HAS_INTERNAL_SOURCE]); + + return TRUE; +} + +void +ges_track_element_set_has_internal_source_is_forbidden (GESTrackElement * + element) +{ + element->priv->has_internal_source_forbidden = TRUE; +} + +/** + * ges_track_element_set_track_type: + * @object: A #GESTrackElement + * @type: The new track-type for @object + * + * Sets the #GESTrackElement:track-type for the element. + */ +void +ges_track_element_set_track_type (GESTrackElement * object, GESTrackType type) +{ + g_return_if_fail (GES_IS_TRACK_ELEMENT (object)); + + if (object->priv->track_type != type) { + object->priv->track_type = type; + g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_TRACK_TYPE]); + } +} + +/** + * ges_track_element_get_track_type: + * @object: A #GESTrackElement + * + * Gets the #GESTrackElement:track-type for the element. + * + * Returns: The track-type of @object. + */ +GESTrackType +ges_track_element_get_track_type (GESTrackElement * object) +{ + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), GES_TRACK_TYPE_UNKNOWN); + + return object->priv->track_type; +} + +/* default 'create_gnl_object' virtual method implementation */ +static GstElement * +ges_track_element_create_gnl_object_func (GESTrackElement * self) +{ + GESTrackElementClass *klass = NULL; + GstElement *child = NULL; + GstElement *nleobject; + + klass = GES_TRACK_ELEMENT_GET_CLASS (self); + + if (G_UNLIKELY (self->priv->nleobject != NULL)) + goto already_have_nleobject; + + if (G_UNLIKELY (klass->nleobject_factorytype == NULL)) + goto no_nlefactory; + + GST_DEBUG ("Creating a supporting nleobject of type '%s'", + klass->nleobject_factorytype); + + nleobject = gst_element_factory_make (klass->nleobject_factorytype, NULL); + + if (G_UNLIKELY (nleobject == NULL)) + goto no_nleobject; + + if (klass->create_element) { + GST_DEBUG ("Calling subclass 'create_element' vmethod"); + child = klass->create_element (self); + + if (G_UNLIKELY (!child)) + goto child_failure; + + if (!gst_bin_add (GST_BIN (nleobject), child)) + goto add_failure; + + GST_DEBUG ("Successfully got the element to put in the nleobject"); + self->priv->element = child; + } + + GST_DEBUG ("done"); + return nleobject; + + + /* ERROR CASES */ + +already_have_nleobject: + { + GST_ERROR ("Already controlling a NleObject %s", + GST_ELEMENT_NAME (self->priv->nleobject)); + return NULL; + } + +no_nlefactory: + { + GST_ERROR ("No GESTrackElement::nleobject_factorytype implementation!"); + return NULL; + } + +no_nleobject: + { + GST_ERROR ("Error creating a nleobject of type '%s'", + klass->nleobject_factorytype); + return NULL; + } + +child_failure: + { + GST_ERROR ("create_element returned NULL"); + gst_object_unref (nleobject); + return NULL; + } + +add_failure: + { + GST_ERROR ("Error adding the contents to the nleobject"); + gst_object_unref (child); + gst_object_unref (nleobject); + return NULL; + } +} + +static void +ges_track_element_add_child_props (GESTrackElement * self, + GstElement * child, const gchar ** wanted_categories, + const gchar ** blacklist, const gchar ** whitelist) +{ + GstElementFactory *factory; + const gchar *klass; + GParamSpec **parray; + GObjectClass *gobject_klass; + gchar **categories; + guint i; + + factory = gst_element_get_factory (child); + /* FIXME: handle NULL factory */ + klass = gst_element_factory_get_metadata (factory, + GST_ELEMENT_METADATA_KLASS); + + if (strv_find_str (blacklist, GST_OBJECT_NAME (factory))) { + GST_DEBUG_OBJECT (self, "%s blacklisted", GST_OBJECT_NAME (factory)); + return; + } + + GST_DEBUG_OBJECT (self, "Looking at element '%s' of klass '%s'", + GST_ELEMENT_NAME (child), klass); + + categories = g_strsplit (klass, "/", 0); + + for (i = 0; categories[i]; i++) { + if ((!wanted_categories || + strv_find_str (wanted_categories, categories[i]))) { + guint i, nb_specs; + + gobject_klass = G_OBJECT_GET_CLASS (child); + parray = g_object_class_list_properties (gobject_klass, &nb_specs); + for (i = 0; i < nb_specs; i++) { + if ((!whitelist && (parray[i]->flags & G_PARAM_WRITABLE)) + || (strv_find_str (whitelist, parray[i]->name))) { + ges_timeline_element_add_child_property (GES_TIMELINE_ELEMENT + (self), parray[i], G_OBJECT (child)); + } + } + g_free (parray); + + GST_DEBUG + ("%d configurable properties of '%s' added to property hashtable", + nb_specs, GST_ELEMENT_NAME (child)); + break; + } + } + + g_strfreev (categories); +} + +/** + * ges_track_element_add_children_props: + * @self: A #GESTrackElement + * @element: The child object to retrieve properties from + * @wanted_categories: (array zero-terminated=1) (transfer none) (allow-none): + * An array of element factory "klass" categories to whitelist, or %NULL + * to accept all categories + * @blacklist: (array zero-terminated=1) (transfer none) (allow-none): A + * blacklist of element factory names, or %NULL to not blacklist any + * element factory + * @whitelist: (array zero-terminated=1) (transfer none) (allow-none): A + * whitelist of element property names, or %NULL to whitelist all + * writeable properties + * + * Adds all the properties of a #GstElement that match the criteria as + * children properties of the track element. If the name of @element's + * #GstElementFactory is not in @blacklist, and the factory's + * #GST_ELEMENT_METADATA_KLASS contains at least one member of + * @wanted_categories (e.g. #GST_ELEMENT_FACTORY_KLASS_DECODER), then + * all the properties of @element that are also in @whitelist are added as + * child properties of @self using + * ges_timeline_element_add_child_property(). + * + * This is intended to be used by subclasses when constructing. + */ +void +ges_track_element_add_children_props (GESTrackElement * self, + GstElement * element, const gchar ** wanted_categories, + const gchar ** blacklist, const gchar ** whitelist) +{ + GValue item = { 0, }; + GstIterator *it; + gboolean done = FALSE; + + if (!GST_IS_BIN (element)) { + ges_track_element_add_child_props (self, element, wanted_categories, + blacklist, whitelist); + return; + } + + /* We go over child elements recursively, and add writable properties to the + * hashtable */ + it = gst_bin_iterate_recurse (GST_BIN (element)); + while (!done) { + switch (gst_iterator_next (it, &item)) { + case GST_ITERATOR_OK: + { + GstElement *child = g_value_get_object (&item); + ges_track_element_add_child_props (self, child, wanted_categories, + blacklist, whitelist); + g_value_reset (&item); + break; + } + case GST_ITERATOR_RESYNC: + /* FIXME, properly restart the process */ + GST_DEBUG ("iterator resync"); + gst_iterator_resync (it); + break; + + case GST_ITERATOR_DONE: + GST_DEBUG ("iterator done"); + done = TRUE; + break; + + default: + break; + } + g_value_unset (&item); + } + gst_iterator_free (it); +} + +/* INTERNAL USAGE */ +gboolean +ges_track_element_set_track (GESTrackElement * object, GESTrack * track, + GError ** error) +{ + GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (object); + + g_return_val_if_fail (object->priv->nleobject, FALSE); + + GST_DEBUG_OBJECT (object, "new track: %" GST_PTR_FORMAT, track); + + if (GES_IS_CLIP (parent) + && !ges_clip_can_set_track_of_child (GES_CLIP (parent), object, track, + error)) { + GST_INFO_OBJECT (object, + "The parent clip %" GES_FORMAT " would not allow the track to be " + "set to %" GST_PTR_FORMAT, GES_ARGS (parent), track); + return FALSE; + } + + object->priv->track = track; + + if (object->priv->track) { + ges_track_element_set_track_type (object, track->type); + + g_object_set (object->priv->nleobject, + "caps", ges_track_get_caps (object->priv->track), NULL); + } + + g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_TRACK]); + return TRUE; +} + +void +ges_track_element_set_layer_active (GESTrackElement * element, gboolean active) +{ + if (element->priv->layer_active == active) + return; + + element->priv->layer_active = active; + g_object_set (element->priv->nleobject, "active", active & element->active, + NULL); +} + +/** + * ges_track_element_get_all_control_bindings + * @trackelement: A #GESTrackElement + * + * Get all the control bindings that have been created for the children + * properties of the track element using + * ges_track_element_set_control_source(). The keys used in the returned + * hash table are the child property names that were passed to + * ges_track_element_set_control_source(), and their values are the + * corresponding created #GstControlBinding. + * + * Returns: (element-type gchar* GstControlBinding*)(transfer none): A + * hash table containing all child-property-name/control-binding pairs + * for @trackelement. + */ +GHashTable * +ges_track_element_get_all_control_bindings (GESTrackElement * trackelement) +{ + GESTrackElementPrivate *priv = GES_TRACK_ELEMENT (trackelement)->priv; + + return priv->bindings_hashtable; +} + +/** + * ges_track_element_get_track: + * @object: A #GESTrackElement + * + * Get the #GESTrackElement:track for the element. + * + * Returns: (transfer none) (nullable): The track that @object belongs to, + * or %NULL if it does not belong to a track. + */ +GESTrack * +ges_track_element_get_track (GESTrackElement * object) +{ + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), NULL); + + return object->priv->track; +} + +/** + * ges_track_element_get_gnlobject: + * @object: A #GESTrackElement + * + * Get the GNonLin object this object is controlling. + * + * Returns: (transfer none): The GNonLin object this object is controlling. + * + * Deprecated: use #ges_track_element_get_nleobject instead. + */ +GstElement * +ges_track_element_get_gnlobject (GESTrackElement * object) +{ + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), NULL); + + return object->priv->nleobject; +} + +/** + * ges_track_element_get_nleobject: + * @object: A #GESTrackElement + * + * Get the nleobject that this element wraps. + * + * Returns: (transfer none): The nleobject that @object wraps. + * + * Since: 1.6 + */ +GstElement * +ges_track_element_get_nleobject (GESTrackElement * object) +{ + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), NULL); + + return object->priv->nleobject; +} + +/** + * ges_track_element_get_element: + * @object: A #GESTrackElement + * + * Get the #GstElement that the track element's underlying nleobject + * controls. + * + * Returns: (transfer none): The #GstElement being controlled by the + * nleobject that @object wraps. + */ +GstElement * +ges_track_element_get_element (GESTrackElement * object) +{ + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), NULL); + + return object->priv->element; +} + +/** + * ges_track_element_is_active: + * @object: A #GESTrackElement + * + * Gets #GESTrackElement:active for the element. + * + * Returns: %TRUE if @object is active in its track. + */ +gboolean +ges_track_element_is_active (GESTrackElement * object) +{ + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + g_return_val_if_fail (object->priv->nleobject, FALSE); + + return object->active; +} + +/** + * ges_track_element_has_internal_source: + * @object: A #GESTrackElement + * + * Gets #GESTrackElement:has-internal-source for the element. + * + * Returns: %TRUE if @object can have its 'internal time' properties set. + * + * Since: 1.18 + */ +gboolean +ges_track_element_has_internal_source (GESTrackElement * object) +{ + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + + return object->priv->has_internal_source; +} + +/** + * ges_track_element_lookup_child: + * @object: Object to lookup the property in + * @prop_name: Name of the property to look up. You can specify the name of the + * class as such: "ClassName::property-name", to guarantee that you get the + * proper GParamSpec in case various GstElement-s contain the same property + * name. If you don't do so, you will get the first element found, having + * this property and the and the corresponding GParamSpec. + * @element: (out) (allow-none) (transfer full): pointer to a #GstElement that + * takes the real object to set property on + * @pspec: (out) (allow-none) (transfer full): pointer to take the specification + * describing the property + * + * Looks up which @element and @pspec would be effected by the given @name. If various + * contained elements have this property name you will get the first one, unless you + * specify the class name in @name. + * + * Returns: TRUE if @element and @pspec could be found. FALSE otherwise. In that + * case the values for @pspec and @element are not modified. Unref @element after + * usage. + * + * Deprecated: Use #ges_timeline_element_lookup_child + */ +gboolean +ges_track_element_lookup_child (GESTrackElement * object, + const gchar * prop_name, GstElement ** element, GParamSpec ** pspec) +{ + return ges_timeline_element_lookup_child (GES_TIMELINE_ELEMENT (object), + prop_name, ((GObject **) element), pspec); +} + +/** + * ges_track_element_set_child_property_by_pspec: (skip): + * @object: A #GESTrackElement + * @pspec: The #GParamSpec that specifies the property you want to set + * @value: The value + * + * Sets a property of a child of @object. + * + * Deprecated: Use #ges_timeline_element_set_child_property_by_spec + */ +void +ges_track_element_set_child_property_by_pspec (GESTrackElement * object, + GParamSpec * pspec, GValue * value) +{ + g_return_if_fail (GES_IS_TRACK_ELEMENT (object)); + + ges_timeline_element_set_child_property_by_pspec (GES_TIMELINE_ELEMENT + (object), pspec, value); + + return; +} + +/** + * ges_track_element_set_child_property_valist: (skip): + * @object: The #GESTrackElement parent object + * @first_property_name: The name of the first property to set + * @var_args: Value for the first property, followed optionally by more + * name/return location pairs, followed by NULL + * + * Sets a property of a child of @object. If there are various child elements + * that have the same property name, you can distinguish them using the following + * syntax: 'ClasseName::property_name' as property name. If you don't, the + * corresponding property of the first element found will be set. + * + * Deprecated: Use #ges_timeline_element_set_child_property_valist + */ +void +ges_track_element_set_child_property_valist (GESTrackElement * object, + const gchar * first_property_name, va_list var_args) +{ + ges_timeline_element_set_child_property_valist (GES_TIMELINE_ELEMENT (object), + first_property_name, var_args); +} + +/** + * ges_track_element_set_child_properties: (skip): + * @object: The #GESTrackElement parent object + * @first_property_name: The name of the first property to set + * @...: value for the first property, followed optionally by more + * name/return location pairs, followed by NULL + * + * Sets a property of a child of @object. If there are various child elements + * that have the same property name, you can distinguish them using the following + * syntax: 'ClasseName::property_name' as property name. If you don't, the + * corresponding property of the first element found will be set. + * + * Deprecated: Use #ges_timeline_element_set_child_properties + */ +void +ges_track_element_set_child_properties (GESTrackElement * object, + const gchar * first_property_name, ...) +{ + va_list var_args; + + g_return_if_fail (GES_IS_TRACK_ELEMENT (object)); + + va_start (var_args, first_property_name); + ges_track_element_set_child_property_valist (object, first_property_name, + var_args); + va_end (var_args); +} + +/** + * ges_track_element_get_child_property_valist: (skip): + * @object: The #GESTrackElement parent object + * @first_property_name: The name of the first property to get + * @var_args: Value for the first property, followed optionally by more + * name/return location pairs, followed by NULL + * + * Gets a property of a child of @object. If there are various child elements + * that have the same property name, you can distinguish them using the following + * syntax: 'ClasseName::property_name' as property name. If you don't, the + * corresponding property of the first element found will be set. + * + * Deprecated: Use #ges_timeline_element_get_child_property_valist + */ +void +ges_track_element_get_child_property_valist (GESTrackElement * object, + const gchar * first_property_name, va_list var_args) +{ + ges_timeline_element_get_child_property_valist (GES_TIMELINE_ELEMENT (object), + first_property_name, var_args); +} + +/** + * ges_track_element_list_children_properties: + * @object: The #GESTrackElement to get the list of children properties from + * @n_properties: (out): return location for the length of the returned array + * + * Gets an array of #GParamSpec* for all configurable properties of the + * children of @object. + * + * Returns: (transfer full) (array length=n_properties): An array of #GParamSpec* which should be freed after use or + * %NULL if something went wrong. + * + * Deprecated: Use #ges_timeline_element_list_children_properties + */ +GParamSpec ** +ges_track_element_list_children_properties (GESTrackElement * object, + guint * n_properties) +{ + return + ges_timeline_element_list_children_properties (GES_TIMELINE_ELEMENT + (object), n_properties); +} + +/** + * ges_track_element_get_child_properties: (skip): + * @object: The origin #GESTrackElement + * @first_property_name: The name of the first property to get + * @...: return location for the first property, followed optionally by more + * name/return location pairs, followed by NULL + * + * Gets properties of a child of @object. + * + * Deprecated: Use #ges_timeline_element_get_child_properties + */ +void +ges_track_element_get_child_properties (GESTrackElement * object, + const gchar * first_property_name, ...) +{ + va_list var_args; + + g_return_if_fail (GES_IS_TRACK_ELEMENT (object)); + + va_start (var_args, first_property_name); + ges_track_element_get_child_property_valist (object, first_property_name, + var_args); + va_end (var_args); +} + +/** + * ges_track_element_get_child_property_by_pspec: (skip): + * @object: A #GESTrackElement + * @pspec: The #GParamSpec that specifies the property you want to get + * @value: (out): return location for the value + * + * Gets a property of a child of @object. + * + * Deprecated: Use #ges_timeline_element_get_child_property_by_pspec + */ +void +ges_track_element_get_child_property_by_pspec (GESTrackElement * object, + GParamSpec * pspec, GValue * value) +{ + ges_timeline_element_get_child_property_by_pspec (GES_TIMELINE_ELEMENT + (object), pspec, value); +} + +/** + * ges_track_element_set_child_property: (skip): + * @object: The origin #GESTrackElement + * @property_name: The name of the property + * @value: The value + * + * Sets a property of a GstElement contained in @object. + * + * Note that #ges_track_element_set_child_property is really + * intended for language bindings, #ges_track_element_set_child_properties + * is much more convenient for C programming. + * + * Returns: %TRUE if the property was set, %FALSE otherwise. + * + * Deprecated: use #ges_timeline_element_set_child_property instead + */ +gboolean +ges_track_element_set_child_property (GESTrackElement * object, + const gchar * property_name, GValue * value) +{ + return ges_timeline_element_set_child_property (GES_TIMELINE_ELEMENT (object), + property_name, value); +} + +/** + * ges_track_element_get_child_property: (skip): + * @object: The origin #GESTrackElement + * @property_name: The name of the property + * @value: (out): return location for the property value, it will + * be initialized if it is initialized with 0 + * + * In general, a copy is made of the property contents and + * the caller is responsible for freeing the memory by calling + * g_value_unset(). + * + * Gets a property of a GstElement contained in @object. + * + * Note that #ges_track_element_get_child_property is really + * intended for language bindings, #ges_track_element_get_child_properties + * is much more convenient for C programming. + * + * Returns: %TRUE if the property was found, %FALSE otherwise. + * + * Deprecated: Use #ges_timeline_element_get_child_property + */ +gboolean +ges_track_element_get_child_property (GESTrackElement * object, + const gchar * property_name, GValue * value) +{ + return ges_timeline_element_get_child_property (GES_TIMELINE_ELEMENT (object), + property_name, value); +} + +void +ges_track_element_copy_properties (GESTimelineElement * element, + GESTimelineElement * elementcopy) +{ + GParamSpec **specs; + guint n, n_specs; + GValue val = { 0 }; + GESTrackElement *copy = GES_TRACK_ELEMENT (elementcopy); + + specs = + ges_track_element_list_children_properties (GES_TRACK_ELEMENT (element), + &n_specs); + for (n = 0; n < n_specs; ++n) { + if ((specs[n]->flags & G_PARAM_READWRITE) != G_PARAM_READWRITE) + continue; + if (specs[n]->flags & G_PARAM_CONSTRUCT_ONLY) + continue; + g_value_init (&val, specs[n]->value_type); + ges_track_element_get_child_property_by_pspec (GES_TRACK_ELEMENT (element), + specs[n], &val); + ges_track_element_set_child_property_by_pspec (copy, specs[n], &val); + g_value_unset (&val); + } + + g_free (specs); +} + +void +ges_track_element_set_creator_asset (GESTrackElement * self, + GESAsset * creator_asset) +{ + self->priv->creator_asset = creator_asset; +} + +GESAsset * +ges_track_element_get_creator_asset (GESTrackElement * self) +{ + return self->priv->creator_asset; +} + +static void +_split_binding (GESTrackElement * element, GESTrackElement * new_element, + guint64 position, GstTimedValueControlSource * source, + GstTimedValueControlSource * new_source, gboolean absolute) +{ + GstTimedValue *last_value = NULL; + gboolean past_position = FALSE; + GList *values, *tmp; + + values = + gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE + (source)); + + for (tmp = values; tmp; tmp = tmp->next) { + GstTimedValue *value = tmp->data; + + if (value->timestamp > position && !past_position) { + gfloat value_at_pos; + + /* FIXME We should be able to use gst_control_source_get_value so + * all modes are handled. Right now that method only works if the value + * we are looking for is between two actual keyframes which is not enough + * in our case. bug #706621 */ + value_at_pos = + interpolate_values_for_position (last_value, value, position, + absolute); + + past_position = TRUE; + + gst_timed_value_control_source_set (new_source, position, value_at_pos); + gst_timed_value_control_source_set (new_source, value->timestamp, + value->value); + + gst_timed_value_control_source_unset (source, value->timestamp); + gst_timed_value_control_source_set (source, position, value_at_pos); + } else if (past_position) { + gst_timed_value_control_source_set (new_source, value->timestamp, + value->value); + gst_timed_value_control_source_unset (source, value->timestamp); + } + last_value = value; + + } + g_list_free (values); +} + +static void +_copy_binding (GESTrackElement * element, GESTrackElement * new_element, + guint64 position, GstTimedValueControlSource * source, + GstTimedValueControlSource * new_source, gboolean absolute) +{ + GList *values, *tmp; + + values = + gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE + (source)); + for (tmp = values; tmp; tmp = tmp->next) { + GstTimedValue *value = tmp->data; + + gst_timed_value_control_source_set (new_source, value->timestamp, + value->value); + } + g_list_free (values); +} + +/* position == GST_CLOCK_TIME_NONE means that we do a simple copy + * other position means that the function will do a splitting + * and thus interpollate the values in the element and new_element + */ +void +ges_track_element_copy_bindings (GESTrackElement * element, + GESTrackElement * new_element, guint64 position) +{ + GParamSpec **specs; + guint n, n_specs; + gboolean absolute; + GstControlBinding *binding; + GstTimedValueControlSource *source, *new_source; + + specs = + ges_track_element_list_children_properties (GES_TRACK_ELEMENT (element), + &n_specs); + for (n = 0; n < n_specs; ++n) { + GstInterpolationMode mode; + + binding = ges_track_element_get_control_binding (element, specs[n]->name); + if (!binding) + continue; + + g_object_get (binding, "control-source", &source, "absolute", &absolute, + NULL); + if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) { + GST_FIXME_OBJECT (element, + "Implement support for control source type: %s", + G_OBJECT_TYPE_NAME (source)); + gst_object_unref (source); + continue; + } + + g_object_get (source, "mode", &mode, NULL); + + new_source = + GST_TIMED_VALUE_CONTROL_SOURCE (gst_interpolation_control_source_new + ()); + g_object_set (new_source, "mode", mode, NULL); + + if (GST_CLOCK_TIME_IS_VALID (position)) + _split_binding (element, new_element, position, source, new_source, + absolute); + else + _copy_binding (element, new_element, position, source, new_source, + absolute); + + /* We only manage direct (absolute) bindings, see TODO in set_control_source */ + if (absolute) + ges_track_element_set_control_source (new_element, + GST_CONTROL_SOURCE (new_source), specs[n]->name, "direct-absolute"); + else + ges_track_element_set_control_source (new_element, + GST_CONTROL_SOURCE (new_source), specs[n]->name, "direct"); + + gst_object_unref (source); + gst_object_unref (new_source); + } + + g_free (specs); +} + +/** + * ges_track_element_edit: + * @object: The #GESTrackElement to edit + * @layers: (element-type GESLayer) (nullable): A whitelist of layers + * where the edit can be performed, %NULL allows all layers in the + * timeline + * @mode: The edit mode + * @edge: The edge of @object where the edit should occur + * @position: The edit position: a new location for the edge of @object + * (in nanoseconds) + * + * Edits the element within its track. + * + * Returns: %TRUE if the edit of @object completed, %FALSE on failure. + * + * Deprecated: 1.18: use #ges_timeline_element_edit instead. + */ +gboolean +ges_track_element_edit (GESTrackElement * object, + GList * layers, GESEditMode mode, GESEdge edge, guint64 position) +{ + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + + return ges_timeline_element_edit (GES_TIMELINE_ELEMENT (object), + layers, -1, mode, edge, position); +} + +/** + * ges_track_element_remove_control_binding: + * @object: A #GESTrackElement + * @property_name: The name of the child property to remove the control + * binding from + * + * Removes the #GstControlBinding that was created for the specified child + * property of the track element using + * ges_track_element_set_control_source(). The given @property_name must + * be the same name of the child property that was passed to + * ges_track_element_set_control_source(). + * + * Returns: %TRUE if the control binding was removed from the specified + * child property of @object, or %FALSE if an error occurred. + */ +gboolean +ges_track_element_remove_control_binding (GESTrackElement * object, + const gchar * property_name) +{ + GESTrackElementPrivate *priv; + GstControlBinding *binding; + GstObject *target; + + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + + priv = GES_TRACK_ELEMENT (object)->priv; + binding = + (GstControlBinding *) g_hash_table_lookup (priv->bindings_hashtable, + property_name); + + if (binding) { + g_object_get (binding, "object", &target, NULL); + GST_DEBUG_OBJECT (object, "Removing binding %p for property %s", binding, + property_name); + + gst_object_ref (binding); + gst_object_remove_control_binding (target, binding); + + g_signal_emit (object, ges_track_element_signals[CONTROL_BINDING_REMOVED], + 0, binding); + + gst_object_unref (target); + gst_object_unref (binding); + g_hash_table_remove (priv->bindings_hashtable, property_name); + + return TRUE; + } + + return FALSE; +} + +/** + * ges_track_element_set_control_source: + * @object: A #GESTrackElement + * @source: The control source to bind the child property to + * @property_name: The name of the child property to control + * @binding_type: The type of binding to create ("direct" or + * "direct-absolute") + * + * Creates a #GstControlBinding for the specified child property of the + * track element using the given control source. The given @property_name + * should refer to an existing child property of the track element, as + * used in ges_timeline_element_lookup_child(). + * + * If @binding_type is "direct", then the control binding is created with + * gst_direct_control_binding_new() using the given control source. If + * @binding_type is "direct-absolute", it is created with + * gst_direct_control_binding_new_absolute() instead. + * + * Returns: %TRUE if the specified child property could be bound to + * @source, or %FALSE if an error occurred. + */ +gboolean +ges_track_element_set_control_source (GESTrackElement * object, + GstControlSource * source, + const gchar * property_name, const gchar * binding_type) +{ + gboolean ret = FALSE; + GESTrackElementPrivate *priv; + GstElement *element; + GstControlBinding *binding; + gboolean direct, direct_absolute; + + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + priv = GES_TRACK_ELEMENT (object)->priv; + + if (G_UNLIKELY (!(GST_IS_CONTROL_SOURCE (source)))) { + GST_WARNING + ("You need to provide a non-null control source to build a new control binding"); + return FALSE; + } + + if (!ges_track_element_lookup_child (object, property_name, &element, NULL)) { + GST_WARNING ("You need to provide a valid and controllable property name"); + return FALSE; + } + + /* TODO : update this according to new types of bindings */ + direct = !g_strcmp0 (binding_type, "direct"); + direct_absolute = !g_strcmp0 (binding_type, "direct-absolute"); + + if (!direct && !direct_absolute) { + GST_WARNING_OBJECT (object, "Binding type must be in " + "[direct, direct-absolute]"); + goto done; + } + + /* First remove existing binding */ + if (ges_track_element_remove_control_binding (object, property_name)) + GST_LOG_OBJECT (object, "Removed old binding for property %s", + property_name); + + if (direct_absolute) + binding = gst_direct_control_binding_new_absolute (GST_OBJECT (element), + property_name, source); + else + binding = gst_direct_control_binding_new (GST_OBJECT (element), + property_name, source); + + gst_object_add_control_binding (GST_OBJECT (element), binding); + /* FIXME: maybe we should force the + * "ChildTypeName:property-name" + * format convention for child property names in bindings_hashtable. + * Currently the table may also contain + * "property-name" + * as keys. + */ + g_hash_table_insert (priv->bindings_hashtable, g_strdup (property_name), + binding); + + if (GST_IS_TIMED_VALUE_CONTROL_SOURCE (source) + && priv->auto_clamp_control_sources) { + /* Make sure we have the control source used by the binding */ + g_object_get (binding, "control-source", &source, NULL); + + _update_control_source (GST_TIMED_VALUE_CONTROL_SOURCE (source), + direct_absolute, _INPOINT (object), priv->outpoint); + + gst_object_unref (source); + } + + g_signal_emit (object, ges_track_element_signals[CONTROL_BINDING_ADDED], + 0, binding); + + ret = TRUE; + +done: + gst_object_unref (element); + + return ret; +} + +/** + * ges_track_element_get_control_binding: + * @object: A #GESTrackElement + * @property_name: The name of the child property to return the control + * binding of + * + * Gets the control binding that was created for the specified child + * property of the track element using + * ges_track_element_set_control_source(). The given @property_name must + * be the same name of the child property that was passed to + * ges_track_element_set_control_source(). + * + * Returns: (transfer none) (nullable): The control binding that was + * created for the specified child property of @object, or %NULL if + * @property_name does not correspond to any control binding. + */ +GstControlBinding * +ges_track_element_get_control_binding (GESTrackElement * object, + const gchar * property_name) +{ + GESTrackElementPrivate *priv; + GstControlBinding *binding; + + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), NULL); + + priv = GES_TRACK_ELEMENT (object)->priv; + + binding = + (GstControlBinding *) g_hash_table_lookup (priv->bindings_hashtable, + property_name); + return binding; +} + +/** + * ges_track_element_clamp_control_source: + * @object: A #GESTrackElement + * @property_name: The name of the child property to clamp the control + * source of + * + * Clamp the #GstTimedValueControlSource for the specified child property + * to lie between the #GESTimelineElement:in-point and out-point of the + * element. The out-point is the #GES_TIMELINE_ELEMENT_END of the element + * translated from the timeline coordinates to the internal source + * coordinates of the element. + * + * If the property does not have a #GstTimedValueControlSource set by + * ges_track_element_set_control_source(), nothing happens. Otherwise, if + * a timed value for the control source lies before the in-point of the + * element, or after its out-point, then it will be removed. At the + * in-point and out-point times, a new interpolated value will be placed. + * + * Since: 1.18 + */ +void +ges_track_element_clamp_control_source (GESTrackElement * object, + const gchar * property_name) +{ + GstControlBinding *binding; + GstControlSource *source; + gboolean absolute; + + g_return_if_fail (GES_IS_TRACK_ELEMENT (object)); + + binding = ges_track_element_get_control_binding (object, property_name); + + if (!binding) + return; + + g_object_get (binding, "control-source", &source, "absolute", &absolute, + NULL); + + if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) { + gst_object_unref (source); + return; + } + + _update_control_source (GST_TIMED_VALUE_CONTROL_SOURCE (source), absolute, + _INPOINT (object), object->priv->outpoint); + gst_object_unref (source); +} + +/** + * ges_track_element_set_auto_clamp_control_sources: + * @object: A #GESTrackElement + * @auto_clamp: Whether to automatically clamp the control sources for the + * child properties of @object + * + * Sets #GESTrackElement:auto-clamp-control-sources. If set to %TRUE, this + * will immediately clamp all the control sources. + * + * Since: 1.18 + */ +void +ges_track_element_set_auto_clamp_control_sources (GESTrackElement * object, + gboolean auto_clamp) +{ + g_return_if_fail (GES_IS_TRACK_ELEMENT (object)); + + if (auto_clamp == object->priv->auto_clamp_control_sources) + return; + + object->priv->auto_clamp_control_sources = auto_clamp; + if (auto_clamp) + _update_control_bindings (object, _INPOINT (object), + object->priv->outpoint); + + g_object_notify_by_pspec (G_OBJECT (object), + properties[PROP_AUTO_CLAMP_CONTROL_SOURCES]); +} + +/** + * ges_track_element_get_auto_clamp_control_sources: + * @object: A #GESTrackElement + * + * Gets #GESTrackElement:auto-clamp-control-sources. + * + * Returns: Whether the control sources for the child properties of + * @object are automatically clamped. + * Since: 1.18 + */ +gboolean +ges_track_element_get_auto_clamp_control_sources (GESTrackElement * object) +{ + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + + return object->priv->auto_clamp_control_sources; +} + +void +ges_track_element_freeze_control_sources (GESTrackElement * object, + gboolean freeze) +{ + object->priv->freeze_control_sources = freeze; + if (!freeze && object->priv->auto_clamp_control_sources) + _update_control_bindings (object, _INPOINT (object), + object->priv->outpoint); +} + +/** + * ges_track_element_is_core: + * @object: A #GESTrackElement + * + * Get whether the given track element is a core track element. That is, + * it was created by the @create_track_elements #GESClipClass method for + * some #GESClip. + * + * Note that such a track element can only be added to a clip that shares + * the same #GESAsset as the clip that created it. For example, you are + * allowed to move core children between clips that resulted from + * ges_container_ungroup(), but you could not move the core child from a + * #GESUriClip to a #GESTitleClip or another #GESUriClip with a different + * #GESUriClip:uri. + * + * Moreover, if a core track element is added to a clip, it will always be + * added as a core child. Therefore, if this returns %TRUE, then @element + * will be a core child of its parent clip. + * + * Returns: %TRUE if @element is a core track element. + * Since: 1.18 + */ +gboolean +ges_track_element_is_core (GESTrackElement * object) +{ + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + + return (ges_track_element_get_creator_asset (object) != NULL); +} diff --git a/ges/ges-track-element.h b/ges/ges-track-element.h new file mode 100644 index 0000000000..507fe87eb3 --- /dev/null +++ b/ges/ges-track-element.h @@ -0,0 +1,218 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <gst/gst.h> +#include <ges/ges-types.h> +#include <ges/ges-clip.h> +#include <ges/ges-track.h> +#include <gst/controller/gstdirectcontrolbinding.h> +#include <gst/controller/gstinterpolationcontrolsource.h> + +G_BEGIN_DECLS + +#define GES_TYPE_TRACK_ELEMENT ges_track_element_get_type() +GES_DECLARE_TYPE(TrackElement, track_element, TRACK_ELEMENT) + +/** + * GES_TRACK_ELEMENT_CLASS_DEFAULT_HAS_INTERNAL_SOURCE: + * @klass: A #GESTrackElementClass + * + * What the default #GESTrackElement:has-internal-source value should be + * for new elements from this class. + */ +#define GES_TRACK_ELEMENT_CLASS_DEFAULT_HAS_INTERNAL_SOURCE(klass) \ + ((GES_TRACK_ELEMENT_CLASS (klass))->ABI.abi.default_has_internal_source) + +/** + * GESTrackElement: + * + * The #GESTrackElement base class. + */ +struct _GESTrackElement { + GESTimelineElement parent; + + /*< private >*/ + gboolean active; + + GESTrackElementPrivate *priv; + + GESAsset *asset; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING_LARGE]; +}; + +/** + * GESTrackElementClass: + */ +struct _GESTrackElementClass { + /*< private >*/ + GESTimelineElementClass parent_class; + + /*< public >*/ + /** + * GESTrackElementClass::nleobject_factorytype: + * + * The name of the #GstElementFactory to use to create the underlying + * nleobject of a track element + */ + const gchar *nleobject_factorytype; + + /** + * GESTrackElementClass::create_gnl_object: + * @object: The #GESTrackElement + * + * Returns: (transfer floating): the #NLEObject to use in the #nlecomposition + */ + GstElement* (*create_gnl_object) (GESTrackElement * object); + + /** + * GESTrackElementClass::create_element: + * @object: The #GESTrackElement + * + * Returns: (transfer floating): the #GstElement that the underlying nleobject + * controls. + */ + GstElement* (*create_element) (GESTrackElement * object); + + /** + * GESTrackElementClass::active_changed: + * @object: A #GESTrackElement + * @active: Whether the element is active or not inside the #nlecomposition + * + * Notify when the #GESTrackElement:active property changes + */ + void (*active_changed) (GESTrackElement *object, gboolean active); + + /*< private >*/ + /* signals (currently unused) */ + /** + * GESTrackElementClass::changed: + * + * Deprecated: + */ + void (*changed) (GESTrackElement * object); + + /** + * GESTrackElementClass::list_children_properties: + * + * Listing children properties is handled by + * ges_timeline_element_list_children_properties() instead. + * + * Deprecated: 1.14: Use #GESTimelineElementClass::list_children_properties + * instead + */ + GParamSpec** (*list_children_properties) (GESTrackElement * object, + guint *n_properties); + + /** + * GESTrackElementClass::lookup_child: + * + * Deprecated: 1.14: Use #GESTimelineElementClass::lookup_child + * instead + */ + gboolean (*lookup_child) (GESTrackElement *object, + const gchar *prop_name, + GstElement **element, + GParamSpec **pspec); + /*< protected >*/ + union { + gpointer _ges_reserved[GES_PADDING_LARGE]; + struct { + gboolean default_has_internal_source; + GESTrackType default_track_type; + } abi; + } ABI; +}; + +GES_API +GESTrack* ges_track_element_get_track (GESTrackElement * object); + +GES_API +GESTrackType ges_track_element_get_track_type (GESTrackElement * object); +GES_API +void ges_track_element_set_track_type (GESTrackElement * object, + GESTrackType type); + +GES_API +GstElement * ges_track_element_get_nleobject (GESTrackElement * object); + +GES_API +GstElement * ges_track_element_get_element (GESTrackElement * object); + +GES_API +gboolean ges_track_element_is_core (GESTrackElement * object); + +GES_API +gboolean ges_track_element_set_active (GESTrackElement * object, + gboolean active); + +GES_API +gboolean ges_track_element_is_active (GESTrackElement * object); + +GES_API gboolean +ges_track_element_set_has_internal_source (GESTrackElement * object, + gboolean has_internal_source); + +GES_API +gboolean ges_track_element_has_internal_source (GESTrackElement * object); + +GES_API void +ges_track_element_get_child_property_by_pspec (GESTrackElement * object, + GParamSpec * pspec, + GValue * value); + +GES_API gboolean +ges_track_element_set_control_source (GESTrackElement *object, + GstControlSource *source, + const gchar *property_name, + const gchar *binding_type); + +GES_API void +ges_track_element_clamp_control_source (GESTrackElement * object, + const gchar * property_name); + +GES_API void +ges_track_element_set_auto_clamp_control_sources (GESTrackElement * object, + gboolean auto_clamp); +GES_API gboolean +ges_track_element_get_auto_clamp_control_sources (GESTrackElement * object); + +GES_API GstControlBinding * +ges_track_element_get_control_binding (GESTrackElement *object, + const gchar *property_name); +GES_API void +ges_track_element_add_children_props (GESTrackElement *self, + GstElement *element, + const gchar ** wanted_categories, + const gchar **blacklist, + const gchar **whitelist); +GES_API GHashTable * +ges_track_element_get_all_control_bindings (GESTrackElement * trackelement); +GES_API gboolean +ges_track_element_remove_control_binding (GESTrackElement * object, + const gchar * property_name); + +#include "ges-track-element-deprecated.h" + +G_END_DECLS diff --git a/ges/ges-track.c b/ges/ges-track.c new file mode 100644 index 0000000000..d3bd2ef8b6 --- /dev/null +++ b/ges/ges-track.c @@ -0,0 +1,1464 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gestrack + * @title: GESTrack + * @short_description: The output source of a #GESTimeline + * + * A #GESTrack acts an output source for a #GESTimeline. Each one + * essentially provides an additional #GstPad for the timeline, with + * #GESTrack:restriction-caps capabilities. Internally, a track + * wraps an #nlecomposition filtered by a #capsfilter. + * + * A track will contain a number of #GESTrackElement-s, and its role is + * to select and activate these elements according to their timings when + * the timeline in played. For example, a track would activate a + * #GESSource when its #GESTimelineElement:start is reached by outputting + * its data for its #GESTimelineElement:duration. Similarly, a + * #GESOperation would be activated by applying its effect to the source + * data, starting from its #GESTimelineElement:start time and lasting for + * its #GESTimelineElement:duration. + * + * For most users, it will usually be sufficient to add newly created + * tracks to a timeline, but never directly add an element to a track. + * Whenever a #GESClip is added to a timeline, the clip adds its + * elements to the timeline's tracks and assumes responsibility for + * updating them. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-track.h" +#include "ges-track-element.h" +#include "ges-meta-container.h" +#include "ges-video-track.h" +#include "ges-audio-track.h" + +#define CHECK_THREAD(track) g_assert(track->priv->valid_thread == g_thread_self()) + +static GstStaticPadTemplate ges_track_src_pad_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +/* Structure that represents gaps and keep knowledge + * of the gaps filled in the track */ +typedef struct +{ + GstElement *nleobj; + + GstClockTime start; + GstClockTime duration; + GESTrack *track; +} Gap; + +struct _GESTrackPrivate +{ + /*< private > */ + GESTimeline *timeline; + GSequence *trackelements_by_start; + GHashTable *trackelements_iter; + GList *gaps; + gboolean last_gap_disabled; + + guint64 duration; + + GstCaps *caps; + GstCaps *restriction_caps; + + GstElement *composition; /* The composition associated with this track */ + GstPad *srcpad; /* The source GhostPad */ + + gboolean updating; + + gboolean mixing; + GstElement *mixing_operation; + GstElement *capsfilter; + + /* Virtual method to create GstElement that fill gaps */ + GESCreateElementForGapFunc create_element_for_gaps; + + GThread *valid_thread; +}; + +enum +{ + ARG_0, + ARG_CAPS, + ARG_RESTRICTION_CAPS, + ARG_TYPE, + ARG_DURATION, + ARG_MIXING, + ARG_ID, + ARG_LAST, + TRACK_ELEMENT_ADDED, + TRACK_ELEMENT_REMOVED, + COMMITED, + LAST_SIGNAL +}; + +static guint ges_track_signals[LAST_SIGNAL] = { 0 }; + +static GParamSpec *properties[ARG_LAST]; + +G_DEFINE_TYPE_WITH_CODE (GESTrack, ges_track, GST_TYPE_BIN, + G_ADD_PRIVATE (GESTrack) + G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, NULL)); + + +static void composition_duration_cb (GstElement * composition, GParamSpec * arg + G_GNUC_UNUSED, GESTrack * obj); +static void ges_track_set_caps (GESTrack * track, const GstCaps * caps); + +/* Private methods/functions/callbacks */ +static void +add_trackelement_to_list_foreach (GESTrackElement * trackelement, GList ** list) +{ + gst_object_ref (trackelement); + *list = g_list_prepend (*list, trackelement); +} + +static Gap * +gap_new (GESTrack * track, GstClockTime start, GstClockTime duration) +{ + GstElement *nlesrc, *elem; + + Gap *new_gap; + + nlesrc = gst_element_factory_make ("nlesource", NULL); + elem = track->priv->create_element_for_gaps (track); + if (G_UNLIKELY (gst_bin_add (GST_BIN (nlesrc), elem) == FALSE)) { + GST_WARNING_OBJECT (track, "Could not create gap filler"); + + if (nlesrc) + gst_object_unref (nlesrc); + + if (elem) + gst_object_unref (elem); + + return NULL; + } + + if (G_UNLIKELY (ges_nle_composition_add_object (track->priv->composition, + nlesrc) == FALSE)) { + GST_WARNING_OBJECT (track, "Could not add gap to the composition"); + + if (nlesrc) + gst_object_unref (nlesrc); + + if (elem) + gst_object_unref (elem); + + return NULL; + } + + new_gap = g_slice_new (Gap); + new_gap->start = start; + new_gap->duration = duration; + new_gap->track = track; + new_gap->nleobj = nlesrc; + + + g_object_set (nlesrc, "start", new_gap->start, "duration", new_gap->duration, + "priority", 1, NULL); + + GST_DEBUG_OBJECT (track, + "Created gap with start %" GST_TIME_FORMAT " duration %" GST_TIME_FORMAT, + GST_TIME_ARGS (new_gap->start), GST_TIME_ARGS (new_gap->duration)); + + + return new_gap; +} + +static void +free_gap (Gap * gap) +{ + GESTrack *track = gap->track; + + GST_DEBUG_OBJECT (track, "Removed gap with start %" GST_TIME_FORMAT + " duration %" GST_TIME_FORMAT, GST_TIME_ARGS (gap->start), + GST_TIME_ARGS (gap->duration)); + ges_nle_composition_remove_object (track->priv->composition, gap->nleobj); + + g_slice_free (Gap, gap); +} + +static inline void +update_gaps (GESTrack * track) +{ + Gap *gap; + GList *gaps; + GSequenceIter *it; + + GESTrackElement *trackelement; + GstClockTime start, end, duration = 0, timeline_duration = 0; + + GESTrackPrivate *priv = track->priv; + + if (priv->create_element_for_gaps == NULL) { + GST_INFO ("Not filling the gaps as no create_element_for_gaps vmethod" + " provided"); + return; + } + + gaps = priv->gaps; + priv->gaps = NULL; + + /* 1- And recalculate gaps */ + for (it = g_sequence_get_begin_iter (priv->trackelements_by_start); + g_sequence_iter_is_end (it) == FALSE; it = g_sequence_iter_next (it)) { + trackelement = g_sequence_get (it); + + if (!ges_track_element_is_active (trackelement)) + continue; + + if (priv->timeline) { + guint32 layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (trackelement); + + if (layer_prio != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY) { + GESLayer *layer = g_list_nth_data (priv->timeline->layers, layer_prio); + + if (!ges_layer_get_active_for_track (layer, track)) + continue; + } + } + + start = _START (trackelement); + end = start + _DURATION (trackelement); + + if (start > duration) { + /* 2- Fill gap */ + gap = gap_new (track, duration, start - duration); + + if (G_LIKELY (gap != NULL)) + priv->gaps = g_list_prepend (priv->gaps, gap); + } + + duration = MAX (duration, end); + } + + /* 3- Add a gap at the end of the timeline if needed */ + if (priv->timeline) { + g_object_get (priv->timeline, "duration", &timeline_duration, NULL); + + if (duration < timeline_duration) { + gap = gap_new (track, duration, timeline_duration - duration); + + if (G_LIKELY (gap != NULL)) { + priv->gaps = g_list_prepend (priv->gaps, gap); + } + + /* FIXME: here the duration is set to the duration of the timeline, + * but elsewhere it is set to the duration of the composition. Are + * these always the same? */ + priv->duration = timeline_duration; + } + } + + if (!track->priv->last_gap_disabled) { + GST_DEBUG_OBJECT (track, "Adding a one second gap at the end"); + gap = gap_new (track, timeline_duration, 1); + priv->gaps = g_list_prepend (priv->gaps, gap); + } + + /* 4- Remove old gaps */ + g_list_free_full (gaps, (GDestroyNotify) free_gap); +} + +void +track_disable_last_gap (GESTrack * track, gboolean disabled) +{ + track->priv->last_gap_disabled = disabled; + update_gaps (track); +} + +void +track_resort_and_fill_gaps (GESTrack * track) +{ + g_sequence_sort (track->priv->trackelements_by_start, + (GCompareDataFunc) element_start_compare, NULL); + + if (track->priv->updating == TRUE) { + update_gaps (track); + } +} + +static gboolean +update_field (GQuark field_id, const GValue * value, GstStructure * original) +{ + gst_structure_id_set_value (original, field_id, value); + return TRUE; +} + +/* callbacks */ +static void +_ghost_nlecomposition_srcpad (GESTrack * track) +{ + GstPad *capsfilter_sink; + GstPad *capsfilter_src; + GESTrackPrivate *priv = track->priv; + GstPad *pad = gst_element_get_static_pad (priv->composition, "src"); + + capsfilter_sink = gst_element_get_static_pad (priv->capsfilter, "sink"); + + GST_DEBUG ("track:%p, pad %s:%s", track, GST_DEBUG_PAD_NAME (pad)); + + gst_pad_link (pad, capsfilter_sink); + gst_object_unref (capsfilter_sink); + gst_object_unref (pad); + + capsfilter_src = gst_element_get_static_pad (priv->capsfilter, "src"); + /* ghost the pad */ + priv->srcpad = gst_ghost_pad_new ("src", capsfilter_src); + gst_object_unref (capsfilter_src); + gst_pad_set_active (priv->srcpad, TRUE); + gst_element_add_pad (GST_ELEMENT (track), priv->srcpad); + + GST_DEBUG ("done"); +} + +static void +composition_duration_cb (GstElement * composition, + GParamSpec * arg G_GNUC_UNUSED, GESTrack * track) +{ + guint64 duration; + + g_object_get (composition, "duration", &duration, NULL); + + if (track->priv->duration != duration) { + GST_DEBUG_OBJECT (track, + "composition duration : %" GST_TIME_FORMAT " current : %" + GST_TIME_FORMAT, GST_TIME_ARGS (duration), + GST_TIME_ARGS (track->priv->duration)); + + /* FIXME: here the duration is set to the duration of the composition, + * but elsewhere it is set to the duration of the timeline. Are these + * always the same? */ + track->priv->duration = duration; + + g_object_notify_by_pspec (G_OBJECT (track), properties[ARG_DURATION]); + } +} + +static void +composition_commited_cb (GstElement * composition, gboolean changed, + GESTrack * self) +{ + g_signal_emit (self, ges_track_signals[COMMITED], 0); +} + +/* Internal */ +GstElement * +ges_track_get_composition (GESTrack * track) +{ + return track->priv->composition; +} + +void +ges_track_set_smart_rendering (GESTrack * track, gboolean rendering_smartly) +{ + GESTrackPrivate *priv = track->priv; + + g_object_set (priv->capsfilter, "caps", + rendering_smartly ? NULL : priv->restriction_caps, NULL); +} + +/* FIXME: Find out how to avoid doing this "hack" using the GDestroyNotify + * function pointer in the trackelements_by_start GSequence + * + * Remove @object from @track, but keeps it in the sequence this is needed + * when finalizing as we can not change a GSequence at the same time we are + * accessing it + */ +static gboolean +remove_object_internal (GESTrack * track, GESTrackElement * object, + gboolean emit, GError ** error) +{ + GESTrackPrivate *priv; + GstElement *nleobject; + + GST_DEBUG_OBJECT (track, "object:%p", object); + + priv = track->priv; + + if (G_UNLIKELY (ges_track_element_get_track (object) != track)) { + GST_WARNING_OBJECT (track, "Object belongs to another track"); + return FALSE; + } + + if (!ges_track_element_set_track (object, NULL, error)) { + GST_INFO_OBJECT (track, "Failed to unset the track for %" GES_FORMAT, + GES_ARGS (object)); + return FALSE; + } + ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL); + + if ((nleobject = ges_track_element_get_nleobject (object))) { + GST_DEBUG ("Removing NleObject '%s' from composition '%s'", + GST_ELEMENT_NAME (nleobject), GST_ELEMENT_NAME (priv->composition)); + + if (!ges_nle_composition_remove_object (priv->composition, nleobject)) { + GST_WARNING_OBJECT (track, "Failed to remove nleobject from composition"); + return FALSE; + } + } + + if (emit) + g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_REMOVED], 0, + GES_TRACK_ELEMENT (object)); + + gst_object_unref (object); + + return TRUE; +} + +static void +dispose_trackelements_foreach (GESTrackElement * trackelement, GESTrack * track) +{ + remove_object_internal (track, trackelement, TRUE, NULL); +} + +/* GstElement virtual methods */ + +static GstStateChangeReturn +ges_track_change_state (GstElement * element, GstStateChange transition) +{ + GESTrack *track = GES_TRACK (element); + + if (transition == GST_STATE_CHANGE_READY_TO_PAUSED && + track->priv->valid_thread == g_thread_self ()) + track_resort_and_fill_gaps (GES_TRACK (element)); + + return GST_ELEMENT_CLASS (ges_track_parent_class)->change_state (element, + transition); +} + +static void +ges_track_handle_message (GstBin * bin, GstMessage * message) +{ + GESTrack *track = GES_TRACK (bin); + + if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_STREAM_COLLECTION) { + gint i; + GList *selected_streams = NULL; + GstStreamCollection *collection; + + gst_message_parse_stream_collection (message, &collection); + + for (i = 0; i < gst_stream_collection_get_size (collection); i++) { + GstStream *stream = gst_stream_collection_get_stream (collection, i); + GstStreamType stype = gst_stream_get_stream_type (stream); + + if ((track->type == GES_TRACK_TYPE_VIDEO + && stype == GST_STREAM_TYPE_VIDEO) + || (track->type == GES_TRACK_TYPE_AUDIO + && stype == GST_STREAM_TYPE_AUDIO) + || (stype == GST_STREAM_TYPE_UNKNOWN)) { + + selected_streams = + g_list_append (selected_streams, + (gchar *) gst_stream_get_stream_id (stream)); + } + } + + if (selected_streams) { + gst_element_send_event (GST_ELEMENT (GST_MESSAGE_SRC (message)), + gst_event_new_select_streams (selected_streams)); + g_list_free (selected_streams); + } + } + gst_element_post_message (GST_ELEMENT_CAST (bin), message); +} + +/* GObject virtual methods */ +static void +ges_track_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESTrack *track = GES_TRACK (object); + + switch (property_id) { + case ARG_CAPS: + gst_value_set_caps (value, track->priv->caps); + break; + case ARG_TYPE: + g_value_set_flags (value, track->type); + break; + case ARG_DURATION: + g_value_set_uint64 (value, track->priv->duration); + break; + case ARG_RESTRICTION_CAPS: + gst_value_set_caps (value, track->priv->restriction_caps); + break; + case ARG_MIXING: + g_value_set_boolean (value, track->priv->mixing); + break; + case ARG_ID: + g_object_get_property (G_OBJECT (track->priv->composition), "id", value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_track_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESTrack *track = GES_TRACK (object); + + switch (property_id) { + case ARG_CAPS: + ges_track_set_caps (track, gst_value_get_caps (value)); + break; + case ARG_TYPE: + track->type = g_value_get_flags (value); + break; + case ARG_RESTRICTION_CAPS: + ges_track_set_restriction_caps (track, gst_value_get_caps (value)); + break; + case ARG_MIXING: + ges_track_set_mixing (track, g_value_get_boolean (value)); + break; + case ARG_ID: + g_object_set_property (G_OBJECT (track->priv->composition), "id", value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_track_dispose (GObject * object) +{ + GESTrack *track = (GESTrack *) object; + GESTrackPrivate *priv = track->priv; + + /* Remove all TrackElements and drop our reference */ + g_hash_table_unref (priv->trackelements_iter); + g_sequence_foreach (track->priv->trackelements_by_start, + (GFunc) dispose_trackelements_foreach, track); + g_sequence_free (priv->trackelements_by_start); + g_list_free_full (priv->gaps, (GDestroyNotify) free_gap); + ges_nle_object_commit (track->priv->composition, TRUE); + + gst_clear_object (&track->priv->mixing_operation); + if (priv->composition) { + gst_element_remove_pad (GST_ELEMENT (track), priv->srcpad); + gst_bin_remove (GST_BIN (object), priv->composition); + priv->composition = NULL; + } + + if (priv->caps) { + gst_caps_unref (priv->caps); + priv->caps = NULL; + } + + if (priv->restriction_caps) { + gst_caps_unref (priv->restriction_caps); + priv->restriction_caps = NULL; + } + + G_OBJECT_CLASS (ges_track_parent_class)->dispose (object); +} + +static void +ges_track_finalize (GObject * object) +{ + G_OBJECT_CLASS (ges_track_parent_class)->finalize (object); +} + +static void +ges_track_constructed (GObject * object) +{ + GESTrack *self = GES_TRACK (object); + gchar *componame = NULL; + gchar *capsfiltername = NULL; + + if (self->type == GES_TRACK_TYPE_VIDEO) { + componame = + g_strdup_printf ("video_%s", GST_OBJECT_NAME (self->priv->composition)); + capsfiltername = + g_strdup_printf ("video_restriction_%s", + GST_OBJECT_NAME (self->priv->capsfilter)); + } else if (self->type == GES_TRACK_TYPE_AUDIO) { + componame = + g_strdup_printf ("audio_%s", GST_OBJECT_NAME (self->priv->composition)); + capsfiltername = + g_strdup_printf ("audio_restriction_%s", + GST_OBJECT_NAME (self->priv->capsfilter)); + } + + if (componame) { + gst_object_set_name (GST_OBJECT (self->priv->composition), componame); + gst_object_set_name (GST_OBJECT (self->priv->capsfilter), capsfiltername); + + g_free (componame); + g_free (capsfiltername); + } + + if (!gst_bin_add (GST_BIN (self), self->priv->composition)) + GST_ERROR ("Couldn't add composition to bin !"); + + if (!gst_bin_add (GST_BIN (self), self->priv->capsfilter)) + GST_ERROR ("Couldn't add capsfilter to bin !"); + + _ghost_nlecomposition_srcpad (self); + if (GES_TRACK_GET_CLASS (self)->get_mixing_element) { + GstElement *nleobject; + GstElement *mixer = GES_TRACK_GET_CLASS (self)->get_mixing_element (self); + + if (mixer == NULL) { + GST_WARNING_OBJECT (self, "Got no element fron get_mixing_element"); + + return; + } + + nleobject = gst_element_factory_make ("nleoperation", "mixing-operation"); + if (!gst_bin_add (GST_BIN (nleobject), mixer)) { + GST_WARNING_OBJECT (self, "Could not add the mixer to our composition"); + gst_object_unref (mixer); + gst_object_unref (nleobject); + + return; + } + g_object_set (nleobject, "expandable", TRUE, NULL); + + if (self->priv->mixing) { + if (!ges_nle_composition_add_object (self->priv->composition, nleobject)) { + GST_WARNING_OBJECT (self, "Could not add the mixer to our composition"); + gst_object_unref (nleobject); + + return; + } + } + + self->priv->mixing_operation = gst_object_ref (nleobject); + + } else { + GST_INFO_OBJECT (self, "No way to create a main mixer"); + } +} + +static void +ges_track_class_init (GESTrackClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstElementClass *gstelement_class = (GstElementClass *) klass; + GstBinClass *bin_class = GST_BIN_CLASS (klass); + + gstelement_class->change_state = GST_DEBUG_FUNCPTR (ges_track_change_state); + + bin_class->handle_message = GST_DEBUG_FUNCPTR (ges_track_handle_message); + + object_class->get_property = ges_track_get_property; + object_class->set_property = ges_track_set_property; + object_class->dispose = ges_track_dispose; + object_class->finalize = ges_track_finalize; + object_class->constructed = ges_track_constructed; + + /** + * GESTrack:caps: + * + * The capabilities used to choose the output of the #GESTrack's + * elements. Internally, this is used to select output streams when + * several may be available, by determining whether its #GstPad is + * compatible (see #NleObject:caps for #nlecomposition). As such, + * this is used as a weaker indication of the desired output type of the + * track, **before** the #GESTrack:restriction-caps is applied. + * Therefore, this should be set to a *generic* superset of the + * #GESTrack:restriction-caps, such as "video/x-raw(ANY)". In addition, + * it should match with the track's #GESTrack:track-type. + * + * Note that when you set this property, the #GstCapsFeatures of all its + * #GstStructure-s will be automatically set to #GST_CAPS_FEATURES_ANY. + * + * Once a track has been added to a #GESTimeline, you should not change + * this. + * + * Default value: #GST_CAPS_ANY. + */ + properties[ARG_CAPS] = g_param_spec_boxed ("caps", "Caps", + "Caps used to choose the output stream", + GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, ARG_CAPS, + properties[ARG_CAPS]); + + /** + * GESTrack:restriction-caps: + * + * The capabilities that specifies the final output format of the + * #GESTrack. For example, for a video track, it would specify the + * height, width, framerate and other properties of the stream. + * + * You may change this property after the track has been added to a + * #GESTimeline, but it must remain compatible with the track's + * #GESTrack:caps. + * + * Default value: #GST_CAPS_ANY. + */ + properties[ARG_RESTRICTION_CAPS] = + g_param_spec_boxed ("restriction-caps", "Restriction caps", + "Caps used as a final filter on the output stream", GST_TYPE_CAPS, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, ARG_RESTRICTION_CAPS, + properties[ARG_RESTRICTION_CAPS]); + + /** + * GESTrack:duration: + * + * Current duration of the track + * + * Default value: O + */ + /* FIXME: is duration the duration of the timeline or the duration of + * the underlying composition? */ + properties[ARG_DURATION] = g_param_spec_uint64 ("duration", "Duration", + "The current duration of the track", 0, G_MAXUINT64, GST_SECOND, + G_PARAM_READABLE); + g_object_class_install_property (object_class, ARG_DURATION, + properties[ARG_DURATION]); + + /** + * GESTrack:track-type: + * + * The track type of the track. This controls the type of + * #GESTrackElement-s that can be added to the track. This should + * match with the track's #GESTrack:caps. + * + * Once a track has been added to a #GESTimeline, you should not change + * this. + */ + properties[ARG_TYPE] = g_param_spec_flags ("track-type", "TrackType", + "Type of stream the track outputs", + GES_TYPE_TRACK_TYPE, GES_TRACK_TYPE_CUSTOM, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, ARG_TYPE, + properties[ARG_TYPE]); + + /** + * GESTrack:mixing: + * + * Whether the track should support the mixing of #GESLayer data, such + * as composing the video data of each layer (when part of the video + * data is transparent, the next layer will become visible) or adding + * together the audio data. As such, for audio and video tracks, you'll + * likely want to keep this set to %TRUE. + */ + properties[ARG_MIXING] = g_param_spec_boolean ("mixing", "Mixing", + "Whether layer mixing is activated on the track or not", + TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_property (object_class, ARG_MIXING, + properties[ARG_MIXING]); + + /** + * GESTrack:id: + * + * The #nlecomposition:id of the underlying #nlecomposition. + * + * Since: 1.18 + */ + properties[ARG_ID] = + g_param_spec_string ("id", "Id", "The stream-id of the composition", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_DOC_SHOW_DEFAULT); + g_object_class_install_property (object_class, ARG_ID, properties[ARG_ID]); + + gst_element_class_add_static_pad_template (gstelement_class, + &ges_track_src_pad_template); + + /** + * GESTrack::track-element-added: + * @object: The #GESTrack + * @effect: The element that was added + * + * Will be emitted after a track element is added to the track. + */ + ges_track_signals[TRACK_ELEMENT_ADDED] = + g_signal_new ("track-element-added", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, GES_TYPE_TRACK_ELEMENT); + + /** + * GESTrack::track-element-removed: + * @object: The #GESTrack + * @effect: The element that was removed + * + * Will be emitted after a track element is removed from the track. + */ + ges_track_signals[TRACK_ELEMENT_REMOVED] = + g_signal_new ("track-element-removed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, GES_TYPE_TRACK_ELEMENT); + + /** + * GESTrack::commited: + * @track: The #GESTrack + * + * This signal will be emitted once the changes initiated by + * ges_track_commit() have been executed in the backend. In particular, + * this will be emitted whenever the underlying #nlecomposition has been + * committed (see #nlecomposition::commited). + */ + ges_track_signals[COMMITED] = + g_signal_new ("commited", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + + klass->get_mixing_element = NULL; +} + +static void +ges_track_init (GESTrack * self) +{ + self->priv = ges_track_get_instance_private (self); + self->priv->valid_thread = g_thread_self (); + + self->priv->composition = gst_element_factory_make ("nlecomposition", NULL); + self->priv->capsfilter = gst_element_factory_make ("capsfilter", NULL); + self->priv->updating = TRUE; + self->priv->trackelements_by_start = g_sequence_new (NULL); + self->priv->trackelements_iter = + g_hash_table_new (g_direct_hash, g_direct_equal); + self->priv->create_element_for_gaps = NULL; + self->priv->gaps = NULL; + self->priv->mixing = TRUE; + self->priv->restriction_caps = NULL; + self->priv->last_gap_disabled = TRUE; + + g_signal_connect (G_OBJECT (self->priv->composition), "notify::duration", + G_CALLBACK (composition_duration_cb), self); + g_signal_connect (G_OBJECT (self->priv->composition), "commited", + G_CALLBACK (composition_commited_cb), self); +} + +/** + * ges_track_new: + * @type: The #GESTrack:track-type for the track + * @caps: (transfer full): The #GESTrack:caps for the track + * + * Creates a new track with the given track-type and caps. + * + * If @type is #GES_TRACK_TYPE_VIDEO, and @caps is a subset of + * "video/x-raw(ANY)", then a #GESVideoTrack is created. This will + * automatically choose a gap creation method suitable for video data. You + * will likely want to set #GESTrack:restriction-caps separately. You may + * prefer to use the ges_video_track_new() method instead. + * + * If @type is #GES_TRACK_TYPE_AUDIO, and @caps is a subset of + * "audio/x-raw(ANY)", then a #GESAudioTrack is created. This will + * automatically choose a gap creation method suitable for audio data, and + * will set the #GESTrack:restriction-caps to the default for + * #GESAudioTrack. You may prefer to use the ges_audio_track_new() method + * instead. + * + * Otherwise, a plain #GESTrack is returned. You will likely want to set + * the #GESTrack:restriction-caps and call + * ges_track_set_create_element_for_gap_func() on the returned track. + * + * Returns: (transfer floating): A new track. + */ +GESTrack * +ges_track_new (GESTrackType type, GstCaps * caps) +{ + GESTrack *track; + GstCaps *tmpcaps; + + /* TODO Be smarter with well known track types */ + if (type == GES_TRACK_TYPE_VIDEO) { + tmpcaps = gst_caps_new_empty_simple ("video/x-raw"); + gst_caps_set_features (tmpcaps, 0, gst_caps_features_new_any ()); + + if (gst_caps_is_subset (caps, tmpcaps)) { + track = GES_TRACK (ges_video_track_new ()); + ges_track_set_caps (track, caps); + + gst_caps_unref (tmpcaps); + /* FIXME: ges_track_set_caps does not take ownership of caps, + * so we also need to unref caps */ + return track; + } + gst_caps_unref (tmpcaps); + } else if (type == GES_TRACK_TYPE_AUDIO) { + tmpcaps = gst_caps_new_empty_simple ("audio/x-raw"); + gst_caps_set_features (tmpcaps, 0, gst_caps_features_new_any ()); + + if (gst_caps_is_subset (caps, tmpcaps)) { + track = GES_TRACK (ges_audio_track_new ()); + ges_track_set_caps (track, caps); + + gst_caps_unref (tmpcaps); + /* FIXME: ges_track_set_caps does not take ownership of caps, + * so we also need to unref caps */ + return track; + } + + gst_caps_unref (tmpcaps); + } + + track = g_object_new (GES_TYPE_TRACK, "caps", caps, "track-type", type, NULL); + gst_caps_unref (caps); + + return track; +} + + +/** + * ges_track_set_timeline: + * @track: A #GESTrack + * @timeline (nullable): A #GESTimeline + * + * Informs the track that it belongs to the given timeline. Calling this + * does not actually add the track to the timeline. For that, you should + * use ges_timeline_add_track(), which will also take care of informing + * the track that it belongs to the timeline. As such, there is no need + * for you to call this method. + */ +/* FIXME: this should probably be deprecated and only used internally */ +void +ges_track_set_timeline (GESTrack * track, GESTimeline * timeline) +{ + GSequenceIter *it; + g_return_if_fail (GES_IS_TRACK (track)); + g_return_if_fail (timeline == NULL || GES_IS_TIMELINE (timeline)); + GST_DEBUG ("track:%p, timeline:%p", track, timeline); + + track->priv->timeline = timeline; + + for (it = g_sequence_get_begin_iter (track->priv->trackelements_by_start); + g_sequence_iter_is_end (it) == FALSE; it = g_sequence_iter_next (it)) { + GESTimelineElement *trackelement = + GES_TIMELINE_ELEMENT (g_sequence_get (it)); + ges_timeline_element_set_timeline (trackelement, timeline); + } + track_resort_and_fill_gaps (track); +} + +/** + * ges_track_set_caps: + * @track: A #GESTrack + * @caps: The new caps for @track + * + * Sets the #GESTrack:caps for the track. The new #GESTrack:caps of the + * track will be a copy of @caps, except its #GstCapsFeatures will be + * automatically set to #GST_CAPS_FEATURES_ANY. + */ +void +ges_track_set_caps (GESTrack * track, const GstCaps * caps) +{ + GESTrackPrivate *priv; + gint i; + + g_return_if_fail (GES_IS_TRACK (track)); + CHECK_THREAD (track); + + GST_DEBUG ("track:%p, caps:%" GST_PTR_FORMAT, track, caps); + g_return_if_fail (GST_IS_CAPS (caps)); + + priv = track->priv; + + if (priv->caps) + gst_caps_unref (priv->caps); + priv->caps = gst_caps_copy (caps); + + for (i = 0; i < (int) gst_caps_get_size (priv->caps); i++) + gst_caps_set_features (priv->caps, i, gst_caps_features_new_any ()); + + g_object_set (priv->composition, "caps", caps, NULL); + /* FIXME : update all trackelements ? */ +} + +/** + * ges_track_set_restriction_caps: + * @track: A #GESTrack + * @caps: The new restriction-caps for @track + * + * Sets the #GESTrack:restriction-caps for the track. + * + * > **NOTE**: Restriction caps are **not** taken into account when + * > using #GESPipeline:mode=#GES_PIPELINE_MODE_SMART_RENDER. + */ +void +ges_track_set_restriction_caps (GESTrack * track, const GstCaps * caps) +{ + GESTrackPrivate *priv; + + g_return_if_fail (GES_IS_TRACK (track)); + CHECK_THREAD (track); + + GST_DEBUG ("track:%p, restriction caps:%" GST_PTR_FORMAT, track, caps); + g_return_if_fail (GST_IS_CAPS (caps)); + + priv = track->priv; + + if (priv->restriction_caps) + gst_caps_unref (priv->restriction_caps); + priv->restriction_caps = gst_caps_copy (caps); + + if (!track->priv->timeline || + !ges_timeline_get_smart_rendering (track->priv->timeline)) + g_object_set (priv->capsfilter, "caps", caps, NULL); + + g_object_notify (G_OBJECT (track), "restriction-caps"); +} + +/** + * ges_track_update_restriction_caps: + * @track: A #GESTrack + * @caps: The caps to update the restriction-caps with + * + * Updates the #GESTrack:restriction-caps of the track using the fields + * found in the given caps. Each of the #GstStructure-s in @caps is + * compared against the existing structure with the same index in the + * current #GESTrack:restriction-caps. If there is no corresponding + * existing structure at that index, then the new structure is simply + * copied to that index. Otherwise, any fields in the new structure are + * copied into the existing structure. This will replace existing values, + * and may introduce new ones, but any fields 'missing' in the new + * structure are left unchanged in the existing structure. + * + * For example, if the existing #GESTrack:restriction-caps are + * "video/x-raw, width=480, height=360", and the updating caps is + * "video/x-raw, format=I420, width=500; video/x-bayer, width=400", then + * the new #GESTrack:restriction-caps after calling this will be + * "video/x-raw, width=500, height=360, format=I420; video/x-bayer, + * width=400". + */ +void +ges_track_update_restriction_caps (GESTrack * self, const GstCaps * caps) +{ + guint i; + GstCaps *new_restriction_caps; + + g_return_if_fail (GES_IS_TRACK (self)); + CHECK_THREAD (self); + + if (!self->priv->restriction_caps) { + ges_track_set_restriction_caps (self, caps); + return; + } + + new_restriction_caps = gst_caps_copy (self->priv->restriction_caps); + for (i = 0; i < gst_caps_get_size (caps); i++) { + GstStructure *new = gst_caps_get_structure (caps, i); + + if (gst_caps_get_size (new_restriction_caps) > i) { + GstStructure *original = gst_caps_get_structure (new_restriction_caps, i); + gst_structure_foreach (new, (GstStructureForeachFunc) update_field, + original); + } else + gst_caps_append_structure (new_restriction_caps, + gst_structure_copy (new)); + /* FIXME: maybe appended structure should also have its CapsFeatures + * copied over? */ + } + + ges_track_set_restriction_caps (self, new_restriction_caps); + gst_caps_unref (new_restriction_caps); +} + +/** + * ges_track_set_mixing: + * @track: A #GESTrack + * @mixing: Whether @track should be mixing + * + * Sets the #GESTrack:mixing for the track. + */ +void +ges_track_set_mixing (GESTrack * track, gboolean mixing) +{ + g_return_if_fail (GES_IS_TRACK (track)); + CHECK_THREAD (track); + + if (mixing == track->priv->mixing) { + GST_DEBUG_OBJECT (track, "Mixing is already set to the same value"); + + return; + } + + if (!track->priv->mixing_operation) { + GST_DEBUG_OBJECT (track, "Track will be set to mixing = %d", mixing); + goto notify; + } + + if (mixing) { + if (!ges_nle_composition_add_object (track->priv->composition, + track->priv->mixing_operation)) { + GST_WARNING_OBJECT (track, "Could not add the mixer to our composition"); + return; + } + } else { + if (!ges_nle_composition_remove_object (track->priv->composition, + track->priv->mixing_operation)) { + GST_WARNING_OBJECT (track, + "Could not remove the mixer from our composition"); + return; + } + } + +notify: + track->priv->mixing = mixing; + + if (track->priv->timeline) + ges_timeline_set_smart_rendering (track->priv->timeline, + ges_timeline_get_smart_rendering (track->priv->timeline)); + g_object_notify_by_pspec (G_OBJECT (track), properties[ARG_MIXING]); + + GST_DEBUG_OBJECT (track, "The track has been set to mixing = %d", mixing); +} + +static gboolean +remove_element_internal (GESTrack * track, GESTrackElement * object, + gboolean emit, GError ** error) +{ + GSequenceIter *it; + GESTrackPrivate *priv = track->priv; + + GST_DEBUG_OBJECT (track, "Removing %" GST_PTR_FORMAT, object); + + it = g_hash_table_lookup (priv->trackelements_iter, object); + g_sequence_remove (it); + + if (remove_object_internal (track, object, emit, error) == TRUE) { + ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL); + + return TRUE; + } + + g_hash_table_insert (track->priv->trackelements_iter, object, + g_sequence_insert_sorted (track->priv->trackelements_by_start, object, + (GCompareDataFunc) element_start_compare, NULL)); + + return FALSE; +} + +/** + * ges_track_add_element_full: + * @track: A #GESTrack + * @object: (transfer floating): The element to add + * @error: (nullable): Return location for an error + * + * Adds the given track element to the track, which takes ownership of the + * element. + * + * Note that this can fail if it would break a configuration rule of the + * track's #GESTimeline. + * + * Note that a #GESTrackElement can only be added to one track. + * + * Returns: %TRUE if @object was successfully added to @track. + * Since: 1.18 + */ +gboolean +ges_track_add_element_full (GESTrack * track, GESTrackElement * object, + GError ** error) +{ + GESTimeline *timeline; + GESTimelineElement *el; + + g_return_val_if_fail (GES_IS_TRACK (track), FALSE); + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + el = GES_TIMELINE_ELEMENT (object); + + CHECK_THREAD (track); + + GST_DEBUG ("track:%p, object:%p", track, object); + + if (G_UNLIKELY (ges_track_element_get_track (object) != NULL)) { + GST_WARNING ("Object already belongs to another track"); + gst_object_ref_sink (object); + gst_object_unref (object); + return FALSE; + } + + if (!ges_track_element_set_track (object, track, error)) { + GST_INFO_OBJECT (track, "Failed to set the track for %" GES_FORMAT, + GES_ARGS (object)); + gst_object_ref_sink (object); + gst_object_unref (object); + return FALSE; + } + ges_timeline_element_set_timeline (el, NULL); + + GST_DEBUG ("Adding object %s to ourself %s", + GST_OBJECT_NAME (ges_track_element_get_nleobject (object)), + GST_OBJECT_NAME (track->priv->composition)); + + if (G_UNLIKELY (!ges_nle_composition_add_object (track->priv->composition, + ges_track_element_get_nleobject (object)))) { + GST_WARNING ("Couldn't add object to the NleComposition"); + if (!ges_track_element_set_track (object, NULL, NULL)) + GST_ERROR_OBJECT (track, "Failed to unset track of element %" + GES_FORMAT, GES_ARGS (object)); + gst_object_ref_sink (object); + gst_object_unref (object); + return FALSE; + } + + gst_object_ref_sink (object); + g_hash_table_insert (track->priv->trackelements_iter, object, + g_sequence_insert_sorted (track->priv->trackelements_by_start, object, + (GCompareDataFunc) element_start_compare, NULL)); + + timeline = track->priv->timeline; + ges_timeline_element_set_timeline (el, timeline); + /* check that we haven't broken the timeline configuration by adding this + * element to the track */ + if (timeline + && !timeline_tree_can_move_element (timeline_get_tree (timeline), el, + GES_TIMELINE_ELEMENT_LAYER_PRIORITY (el), el->start, el->duration, + error)) { + GST_INFO_OBJECT (track, + "Could not add the track element %" GES_FORMAT + " to the track because it breaks the timeline " "configuration rules", + GES_ARGS (el)); + remove_element_internal (track, object, FALSE, NULL); + return FALSE; + } + + g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_ADDED], 0, + GES_TRACK_ELEMENT (object)); + + return TRUE; +} + +/** + * ges_track_add_element: + * @track: A #GESTrack + * @object: (transfer floating): The element to add + * + * See ges_track_add_element(), which also gives an error. + * + * Returns: %TRUE if @object was successfully added to @track. + */ +gboolean +ges_track_add_element (GESTrack * track, GESTrackElement * object) +{ + return ges_track_add_element_full (track, object, NULL); +} + +/** + * ges_track_get_elements: + * @track: A #GESTrack + * + * Gets the track elements contained in the track. The returned list is + * sorted by the element's #GESTimelineElement:priority and + * #GESTimelineElement:start. + * + * Returns: (transfer full) (element-type GESTrackElement): A list of + * all the #GESTrackElement-s in @track. + */ +GList * +ges_track_get_elements (GESTrack * track) +{ + GList *ret = NULL; + + g_return_val_if_fail (GES_IS_TRACK (track), NULL); + CHECK_THREAD (track); + + g_sequence_foreach (track->priv->trackelements_by_start, + (GFunc) add_trackelement_to_list_foreach, &ret); + + ret = g_list_reverse (ret); + return ret; +} + +/** + * ges_track_remove_element_full: + * @track: A #GESTrack + * @object: The element to remove + * @error: (nullable): Return location for an error + * + * Removes the given track element from the track, which revokes + * ownership of the element. + * + * Returns: %TRUE if @object was successfully removed from @track. + * Since: 1.18 + */ +gboolean +ges_track_remove_element_full (GESTrack * track, GESTrackElement * object, + GError ** error) +{ + g_return_val_if_fail (GES_IS_TRACK (track), FALSE); + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + if (!track->priv->timeline + || !ges_timeline_is_disposed (track->priv->timeline)) + CHECK_THREAD (track); + + return remove_element_internal (track, object, TRUE, error); +} + +/** + * ges_track_remove_element: + * @track: A #GESTrack + * @object: The element to remove + * + * See ges_track_remove_element_full(), which also returns an error. + * + * Returns: %TRUE if @object was successfully removed from @track. + */ +gboolean +ges_track_remove_element (GESTrack * track, GESTrackElement * object) +{ + return ges_track_remove_element_full (track, object, NULL); +} + +/** + * ges_track_get_caps: + * @track: A #GESTrack + * + * Get the #GESTrack:caps of the track. + * + * Returns: The caps of @track. + */ +const GstCaps * +ges_track_get_caps (GESTrack * track) +{ + g_return_val_if_fail (GES_IS_TRACK (track), NULL); + CHECK_THREAD (track); + + return track->priv->caps; +} + +/** + * ges_track_get_timeline: + * @track: A #GESTrack + * + * Get the timeline this track belongs to. + * + * Returns: (nullable): The timeline that @track belongs to, or %NULL if + * it does not belong to a timeline. + */ +const GESTimeline * +ges_track_get_timeline (GESTrack * track) +{ + g_return_val_if_fail (GES_IS_TRACK (track), NULL); + CHECK_THREAD (track); + + return track->priv->timeline; +} + +/** + * ges_track_get_mixing: + * @track: A #GESTrack + * + * Gets the #GESTrack:mixing of the track. + * + * Returns: Whether @track is mixing. + */ +gboolean +ges_track_get_mixing (GESTrack * track) +{ + g_return_val_if_fail (GES_IS_TRACK (track), FALSE); + + return track->priv->mixing; +} + +/** + * ges_track_commit: + * @track: A #GESTrack + * + * Commits all the pending changes for the elements contained in the + * track. + * + * When changes are made to the timing or priority of elements within a + * track, they are not directly executed for the underlying + * #nlecomposition and its children. This method will finally execute + * these changes so they are reflected in the data output of the track. + * + * Any pending changes will be executed in the backend. The + * #GESTimeline::commited signal will be emitted once this has completed. + * + * Note that ges_timeline_commit() will call this method on all of its + * tracks, so you are unlikely to need to use this directly. + * + * Returns: %TRUE if pending changes were committed, or %FALSE if nothing + * needed to be committed. + */ +gboolean +ges_track_commit (GESTrack * track) +{ + g_return_val_if_fail (GES_IS_TRACK (track), FALSE); + CHECK_THREAD (track); + + track_resort_and_fill_gaps (track); + + return ges_nle_object_commit (track->priv->composition, TRUE); +} + + +/** + * ges_track_set_create_element_for_gap_func: + * @track: A #GESTrack + * @func: (scope notified): The function to be used to create a source + * #GstElement that can fill gaps in @track + * + * Sets the function that will be used to create a #GstElement that can be + * used as a source to fill the gaps of the track. A gap is a timeline + * region where the track has no #GESTrackElement sources. Therefore, you + * are likely to want the #GstElement returned by the function to always + * produce 'empty' content, defined relative to the stream type, such as + * transparent frames for a video, or mute samples for audio. + * + * #GESAudioTrack and #GESVideoTrack objects are created with such a + * function already set appropriately. + */ +void +ges_track_set_create_element_for_gap_func (GESTrack * track, + GESCreateElementForGapFunc func) +{ + g_return_if_fail (GES_IS_TRACK (track)); + CHECK_THREAD (track); + + track->priv->create_element_for_gaps = func; +} + +/** + * ges_track_get_restriction_caps: + * @track: A #GESTrack + * + * Gets the #GESTrack:restriction-caps of the track. + * + * Returns: (transfer full): The restriction-caps of @track. + * + * Since: 1.18 + */ +GstCaps * +ges_track_get_restriction_caps (GESTrack * track) +{ + GESTrackPrivate *priv; + + g_return_val_if_fail (GES_IS_TRACK (track), NULL); + CHECK_THREAD (track); + + priv = track->priv; + + if (priv->restriction_caps) + return gst_caps_ref (priv->restriction_caps); + + return NULL; +} diff --git a/ges/ges-track.h b/ges/ges-track.h new file mode 100644 index 0000000000..46da9c8ccf --- /dev/null +++ b/ges/ges-track.h @@ -0,0 +1,123 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <gst/gst.h> +#include <ges/ges-types.h> +#include <ges/ges-enums.h> + +G_BEGIN_DECLS + +#define GES_TYPE_TRACK ges_track_get_type() +GES_DECLARE_TYPE(Track, track, TRACK); + +/** + * GESCreateElementForGapFunc: + * @track: The #GESTrack + * + * A function that creates a #GstElement that can be used as a source to + * fill the gaps of the track. A gap is a timeline region where the track + * has no #GESTrackElement sources. + * + * Returns: A source #GstElement to fill gaps in @track. + */ +typedef GstElement* (*GESCreateElementForGapFunc) (GESTrack *track); + +/** + * GESTrack: + * @type: The #GESTrack:track-type of the track + */ +struct _GESTrack +{ + GstBin parent; + + /*< public >*/ + /* READ-ONLY */ + GESTrackType type; + + /*< private >*/ + GESTrackPrivate* priv; + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESTrackClass: + */ +struct _GESTrackClass +{ + /*< private >*/ + GstBinClass parent_class; + + GstElement * (*get_mixing_element) (GESTrack *track); + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API +const GstCaps* ges_track_get_caps (GESTrack *track); +GES_API +GList* ges_track_get_elements (GESTrack *track); +GES_API +const GESTimeline* ges_track_get_timeline (GESTrack *track); +GES_API +gboolean ges_track_commit (GESTrack *track); +GES_API +void ges_track_set_timeline (GESTrack *track, + GESTimeline *timeline); +GES_API +gboolean ges_track_add_element (GESTrack *track, + GESTrackElement *object); +GES_API +gboolean ges_track_add_element_full (GESTrack *track, + GESTrackElement *object, + GError ** error); +GES_API +gboolean ges_track_remove_element (GESTrack *track, + GESTrackElement *object); +GES_API +gboolean ges_track_remove_element_full (GESTrack *track, + GESTrackElement *object, + GError ** error); +GES_API +void ges_track_set_create_element_for_gap_func (GESTrack *track, + GESCreateElementForGapFunc func); +GES_API +void ges_track_set_mixing (GESTrack *track, + gboolean mixing); +GES_API +gboolean ges_track_get_mixing (GESTrack *track); +GES_API +void ges_track_set_restriction_caps (GESTrack *track, + const GstCaps *caps); +GES_API +void ges_track_update_restriction_caps (GESTrack *track, + const GstCaps *caps); +GES_API +GstCaps * ges_track_get_restriction_caps (GESTrack * track); + +GES_API +GESTrack* ges_track_new (GESTrackType type, + GstCaps * caps); + +G_END_DECLS diff --git a/ges/ges-transition-clip.c b/ges/ges-transition-clip.c new file mode 100644 index 0000000000..0ffc60f1a8 --- /dev/null +++ b/ges/ges-transition-clip.c @@ -0,0 +1,441 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION: gestransitionclip + * @title: GESTransitionClip + * @short_description: Transition from one clip to another in a GESLayer + * + * Creates an object that mixes together the two underlying objects, A and B. + * The A object is assumed to have a higher prioirity (lower number) than the + * B object. At the transition in point, only A will be visible, and by the + * end only B will be visible. + * + * The shape of the video transition depends on the value of the "vtype" + * property. The default value is "crossfade". For audio, only "crossfade" is + * supported. + * + * The ID of the ExtractableType is the nickname of the vtype property value. Note + * that this value can be changed after creation and the GESExtractable.asset value + * will be updated when needed. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <ges/ges.h> +#include "ges-internal.h" + +struct _GESTransitionClipPrivate +{ + GSList *video_transitions; + + const gchar *vtype_name; +}; + +enum +{ + PROP_VTYPE = 5, +}; + +static GESTrackElement *_create_track_element (GESClip + * self, GESTrackType type); +static void _child_added (GESContainer * container, + GESTimelineElement * element); +static void _child_removed (GESContainer * container, + GESTimelineElement * element); + +/* Internal methods */ +static void +ges_transition_clip_update_vtype_internal (GESClip * + self, GESVideoStandardTransitionType value, gboolean set_asset) +{ + GSList *tmp; + guint index; + GEnumClass *enum_class; + const gchar *asset_id = NULL; + GESTransitionClip *trself = GES_TRANSITION_CLIP (self); + + enum_class = g_type_class_peek (GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE); + for (index = 0; index < enum_class->n_values; index++) { + if (enum_class->values[index].value == value) { + asset_id = enum_class->values[index].value_nick; + break; + } + } + + if (asset_id == NULL) { + GST_WARNING_OBJECT (self, "Wrong transition type value: %i can not set it", + value); + + return; + } + + for (tmp = trself->priv->video_transitions; tmp; tmp = tmp->next) { + if (!ges_video_transition_set_transition_type + (GES_VIDEO_TRANSITION (tmp->data), value)) + return; + } + + trself->vtype = value; + trself->priv->vtype_name = asset_id; + + if (set_asset) { + GESAsset *asset = + ges_asset_request (GES_TYPE_TRANSITION_CLIP, asset_id, NULL); + + /* We already checked the value, so we can be sure no error occurred */ + ges_extractable_set_asset (GES_EXTRACTABLE (self), asset); + gst_object_unref (asset); + } +} + +/* GESExtractable interface overrides */ +G_GNUC_BEGIN_IGNORE_DEPRECATIONS; /* Start ignoring GParameter deprecation */ +static GParameter * +extractable_get_parameters_from_id (const gchar * id, guint * n_params) +{ + GEnumClass *enum_class = + g_type_class_peek (GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE); + GParameter *params = g_new0 (GParameter, 1); + + GEnumValue *value = g_enum_get_value_by_nick (enum_class, id); + + params[0].name = "vtype"; + g_value_init (¶ms[0].value, GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE); + g_value_set_enum (¶ms[0].value, value->value); + *n_params = 1; + + return params; +} + +G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */ +static gchar * +extractable_check_id (GType type, const gchar * id) +{ + guint index; + GEnumClass *enum_class; + enum_class = g_type_class_peek (GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE); + + for (index = 0; index < enum_class->n_values; index++) { + if (g_strcmp0 (enum_class->values[index].value_nick, id) == 0) + return g_strdup (id); + } + + return NULL; +} + +static gchar * +extractable_get_id (GESExtractable * self) +{ + guint index; + GEnumClass *enum_class; + guint value = GES_TRANSITION_CLIP (self)->vtype; + + enum_class = g_type_class_peek (GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE); + for (index = 0; index < enum_class->n_values; index++) { + if (enum_class->values[index].value == value) + return g_strdup (enum_class->values[index].value_nick); + } + + return NULL; +} + +static gboolean +extractable_set_asset (GESExtractable * self, GESAsset * asset) +{ + GEnumClass *enum_class; + GESVideoStandardTransitionType value; + GESTransitionClip *trans = GES_TRANSITION_CLIP (self); + const gchar *vtype = ges_asset_get_id (asset); + GESAsset *prev_asset = ges_extractable_get_asset (self); + GList *tmp; + + if (!(ges_clip_get_supported_formats (GES_CLIP (self)) & + GES_TRACK_TYPE_VIDEO)) { + return FALSE; + } + + /* Update the transition type if we actually changed it */ + if (g_strcmp0 (vtype, trans->priv->vtype_name)) { + guint index; + + value = GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE; + enum_class = g_type_class_peek (GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE); + + /* Find the in value in use */ + for (index = 0; index < enum_class->n_values; index++) { + if (g_strcmp0 (enum_class->values[index].value_nick, vtype) == 0) { + value = enum_class->values[index].value; + break; + } + } + ges_transition_clip_update_vtype_internal (GES_CLIP (self), value, FALSE); + } + + if (!prev_asset) + return TRUE; + + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + if (ges_track_element_get_creator_asset (tmp->data) == prev_asset) + ges_track_element_set_creator_asset (tmp->data, asset); + } + + return TRUE; +} + +static void +ges_extractable_interface_init (GESExtractableInterface * iface) +{ + iface->check_id = (GESExtractableCheckId) extractable_check_id; + iface->get_id = extractable_get_id; + iface->get_parameters_from_id = extractable_get_parameters_from_id; + iface->can_update_asset = TRUE; + iface->set_asset_full = extractable_set_asset; +} + +G_DEFINE_TYPE_WITH_CODE (GESTransitionClip, + ges_transition_clip, GES_TYPE_BASE_TRANSITION_CLIP, + G_ADD_PRIVATE (GESTransitionClip) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, + ges_extractable_interface_init)); + +static void +ges_transition_clip_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec) +{ + GESTransitionClip *self = GES_TRANSITION_CLIP (object); + switch (property_id) { + case PROP_VTYPE: + g_value_set_enum (value, self->vtype); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_transition_clip_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec) +{ + GESClip *self = GES_CLIP (object); + + switch (property_id) { + case PROP_VTYPE: + ges_transition_clip_update_vtype_internal (self, + g_value_get_enum (value), TRUE); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static gboolean +_lookup_child (GESTimelineElement * self, const gchar * prop_name, + GObject ** child, GParamSpec ** pspec) +{ + GESTimelineElementClass *element_klass = + g_type_class_peek (GES_TYPE_TIMELINE_ELEMENT); + + /* Bypass the container implementation as we handle children properties directly */ + /* FIXME Implement a syntax to precisely get properties by path */ + if (element_klass->lookup_child (self, prop_name, child, pspec)) + return TRUE; + + return FALSE; +} + + +static void +ges_transition_clip_class_init (GESTransitionClipClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESClipClass *timobj_class = GES_CLIP_CLASS (klass); + GESContainerClass *container_class = GES_CONTAINER_CLASS (klass); + + object_class->get_property = ges_transition_clip_get_property; + object_class->set_property = ges_transition_clip_set_property; + + /** + * GESTransitionClip:vtype: + * + * a #GESVideoStandardTransitionType representing the wipe to use + */ + g_object_class_install_property (object_class, PROP_VTYPE, + g_param_spec_enum ("vtype", "VType", + "The SMPTE video wipe to use, or 0 for crossfade", + GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE, + GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + GES_TIMELINE_ELEMENT_CLASS (klass)->lookup_child = _lookup_child; + container_class->child_added = _child_added; + container_class->child_removed = _child_removed; + + timobj_class->create_track_element = _create_track_element; +} + +static void +ges_transition_clip_init (GESTransitionClip * self) +{ + + self->priv = ges_transition_clip_get_instance_private (self); + + self->vtype = GES_VIDEO_STANDARD_TRANSITION_TYPE_NONE; + self->priv->vtype_name = NULL; +} + +static void +_child_removed (GESContainer * container, GESTimelineElement * element) +{ + GESTransitionClipPrivate *priv = GES_TRANSITION_CLIP (container)->priv; + + /* If this is called, we should be sure the trackelement exists */ + if (GES_IS_VIDEO_TRANSITION (element)) { + GST_DEBUG_OBJECT (container, "%" GST_PTR_FORMAT " removed", element); + priv->video_transitions = g_slist_remove (priv->video_transitions, element); + gst_object_unref (element); + } + /* call parent method */ + GES_CONTAINER_CLASS (ges_transition_clip_parent_class)->child_removed + (container, element); +} + +static void +_child_added (GESContainer * container, GESTimelineElement * element) +{ + GESTransitionClipPrivate *priv = GES_TRANSITION_CLIP (container)->priv; + + if (GES_IS_VIDEO_TRANSITION (element)) { + GObjectClass *eklass = G_OBJECT_GET_CLASS (element); + + GST_DEBUG_OBJECT (container, "%" GST_PTR_FORMAT " added", element); + priv->video_transitions = + g_slist_prepend (priv->video_transitions, gst_object_ref (element)); + + ges_timeline_element_add_child_property (GES_TIMELINE_ELEMENT (container), + g_object_class_find_property (eklass, "invert"), G_OBJECT (element)); + ges_timeline_element_add_child_property (GES_TIMELINE_ELEMENT (container), + g_object_class_find_property (eklass, "border"), G_OBJECT (element)); + } + /* call parent method */ + GES_CONTAINER_CLASS (ges_transition_clip_parent_class)->child_added + (container, element); +} + +static GESTrackElement * +_create_track_element (GESClip * clip, GESTrackType type) +{ + GESTransitionClip *transition = (GESTransitionClip *) clip; + GESTrackElement *res = NULL; + GESTrackType supportedformats; + + GST_DEBUG ("Creating a GESTransition"); + + supportedformats = ges_clip_get_supported_formats (clip); + if (type == GES_TRACK_TYPE_VIDEO) { + if (supportedformats == GES_TRACK_TYPE_UNKNOWN || + supportedformats & GES_TRACK_TYPE_VIDEO) { + GESVideoTransition *trans; + + trans = ges_video_transition_new (); + ges_video_transition_set_transition_type (trans, transition->vtype); + + res = GES_TRACK_ELEMENT (trans); + } else { + GST_DEBUG ("Not creating transition as video track not on" + " supportedformats"); + } + + } else if (type == GES_TRACK_TYPE_AUDIO) { + + if (supportedformats == GES_TRACK_TYPE_UNKNOWN || + supportedformats & GES_TRACK_TYPE_AUDIO) + res = GES_TRACK_ELEMENT (ges_audio_transition_new ()); + else + GST_DEBUG ("Not creating transition as audio track" + " not on supportedformats"); + + } else + GST_WARNING ("Transitions don't handle this track type"); + + return res; +} + +/** + * ges_transition_clip_new: + * @vtype: the type of transition to create + * + * Creates a new #GESTransitionClip. + * + * Returns: (transfer floating) (nullable): a newly created #GESTransitionClip, + * or %NULL if something went wrong. + */ +GESTransitionClip * +ges_transition_clip_new (GESVideoStandardTransitionType vtype) +{ + GEnumValue *value; + GEnumClass *klass; + GESTransitionClip *ret = NULL; + + klass = + G_ENUM_CLASS (g_type_class_ref (GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE)); + if (!klass) { + GST_ERROR ("Could not find the StandarTransitionType enum class"); + return NULL; + } + + value = g_enum_get_value (klass, vtype); + if (!value) { + GST_ERROR ("Could not find enum value for %i", vtype); + return NULL; + } + + ret = ges_transition_clip_new_for_nick (((gchar *) value->value_nick)); + g_type_class_unref (klass); + + return ret; +} + +/** + * ges_transition_clip_new_for_nick: + * @nick: a string representing the type of transition to create + * + * Creates a new #GESTransitionClip for the provided @nick. + * + * Returns: (transfer floating) (nullable): The newly created #GESTransitionClip, + * or %NULL if something went wrong + */ + +GESTransitionClip * +ges_transition_clip_new_for_nick (gchar * nick) +{ + GESTransitionClip *ret = NULL; + GESAsset *asset = ges_asset_request (GES_TYPE_TRANSITION_CLIP, nick, NULL); + + if (asset != NULL) { + ret = GES_TRANSITION_CLIP (ges_asset_extract (asset, NULL)); + + gst_object_unref (asset); + } else + GST_WARNING ("No asset found for nick: %s", nick); + + return ret; +} diff --git a/ges/ges-transition-clip.h b/ges/ges-transition-clip.h new file mode 100644 index 0000000000..86c31b9e38 --- /dev/null +++ b/ges/ges-transition-clip.h @@ -0,0 +1,73 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> +#include <ges/ges-base-transition-clip.h> + +G_BEGIN_DECLS + +#define GES_TYPE_TRANSITION_CLIP ges_transition_clip_get_type() +GES_DECLARE_TYPE(TransitionClip, transition_clip, TRANSITION_CLIP); + +/** + * GESTransitionClip: + * @vtype: a #GESVideoStandardTransitionType indicating the type of video transition + * to apply. + * + * ### Children Properties + * + * {{ libs/GESTransitionClip-children-props.md }} + */ +struct _GESTransitionClip { + /*< private >*/ + GESBaseTransitionClip parent; + + /*< public >*/ + GESVideoStandardTransitionType vtype; + + /*< private >*/ + GESTransitionClipPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESTransitionClipClass: + * + */ + +struct _GESTransitionClipClass { + /*< private >*/ + GESBaseTransitionClipClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API +GESTransitionClip *ges_transition_clip_new (GESVideoStandardTransitionType vtype); +GES_API +GESTransitionClip *ges_transition_clip_new_for_nick (char *nick); + +G_END_DECLS diff --git a/ges/ges-transition.c b/ges/ges-transition.c new file mode 100644 index 0000000000..f1de5a9729 --- /dev/null +++ b/ges/ges-transition.c @@ -0,0 +1,44 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon@collabora.co.uk> + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gestransition + * @title: GESTransition + * @short_description: base class for audio and video transitions + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <ges/ges.h> +#include "ges-internal.h" + +G_DEFINE_ABSTRACT_TYPE (GESTransition, ges_transition, GES_TYPE_OPERATION); + +static void +ges_transition_class_init (GESTransitionClass * klass) +{ +} + +static void +ges_transition_init (GESTransition * self) +{ +} diff --git a/ges/ges-transition.h b/ges/ges-transition.h new file mode 100644 index 0000000000..e08b030718 --- /dev/null +++ b/ges/ges-transition.h @@ -0,0 +1,64 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon@collabora.co.uk> + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <gst/gst.h> +#include <gst/controller/gstinterpolationcontrolsource.h> +#include <ges/ges-types.h> +#include <ges/ges-operation.h> + +G_BEGIN_DECLS + +#define GES_TYPE_TRANSITION ges_transition_get_type() +GES_DECLARE_TYPE(Transition, transition, TRANSITION); + +/** + * GESTransition: + * + * Base class for media transitions. + */ + +struct _GESTransition +{ + /*< private >*/ + GESOperation parent; + + GESTransitionPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESTransitionClass: + */ + +struct _GESTransitionClass { + /*< private >*/ + GESOperationClass parent_class; + + /*< private >*/ + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +G_END_DECLS diff --git a/ges/ges-types.h b/ges/ges-types.h new file mode 100644 index 0000000000..7b26af6b12 --- /dev/null +++ b/ges/ges-types.h @@ -0,0 +1,267 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gestypes + * @title: GES Types + * @short_description: GStreamer Editing Services data types + * + * GStreamer Editing Services data types + */ + +#pragma once + +#include <glib.h> +#include <ges/ges-prelude.h> + +G_BEGIN_DECLS + +/** + * GES_PADDING: (attributes doc.skip=true) + */ +#define GES_PADDING 4 + +/** + * GES_PADDING_LARGE: (attributes doc.skip=true) + */ +#define GES_PADDING_LARGE 20 + +/** + * GESFrameNumber: + * + * A datatype to hold a frame number. + * + * Since: 1.18 + */ +typedef gint64 GESFrameNumber; + +/** + * GES_FRAME_NUMBER_NONE: (value 9223372036854775807) (type GESFrameNumber) + * + * Constant to define an undefined frame number + */ +#define GES_FRAME_NUMBER_NONE ((gint64) 9223372036854775807) + +/** + * GES_FRAME_NUMBER_IS_VALID: + * Tests if a given GESFrameNumber represents a valid frame + */ +#define GES_FRAME_NUMBER_IS_VALID(frames) (((GESFrameNumber) frames) != GES_FRAME_NUMBER_NONE) + +/** + * GES_TYPE_FRAME_NUMBER: + * + * The #GType of a #GESFrameNumber. + */ +#define GES_TYPE_FRAME_NUMBER G_TYPE_UINT64 + +/* Type definitions */ + +typedef struct _GESTimeline GESTimeline; +typedef struct _GESTimelineClass GESTimelineClass; + +typedef struct _GESLayer GESLayer; +typedef struct _GESLayerClass GESLayerClass; + +typedef struct _GESTimelineElementClass GESTimelineElementClass; +typedef struct _GESTimelineElement GESTimelineElement; + +typedef struct _GESContainer GESContainer; +typedef struct _GESContainerClass GESContainerClass; + +typedef struct _GESClip GESClip; +typedef struct _GESClipClass GESClipClass; + +typedef struct _GESOperationClip GESOperationClip; +typedef struct _GESOperationClipClass GESOperationClipClass; + +typedef struct _GESPipeline GESPipeline; +typedef struct _GESPipelineClass GESPipelineClass; + +typedef struct _GESSourceClip GESSourceClip; +typedef struct _GESSourceClipClass GESSourceClipClass; + +typedef struct _GESBaseEffectClip GESBaseEffectClip; +typedef struct _GESBaseEffectClipClass GESBaseEffectClipClass; + +typedef struct _GESUriClip GESUriClip; +typedef struct _GESUriClipClass GESUriClipClass; + +typedef struct _GESBaseTransitionClip GESBaseTransitionClip; +typedef struct _GESBaseTransitionClipClass GESBaseTransitionClipClass; + +typedef struct _GESTransitionClip GESTransitionClip; +typedef struct _GESTransitionClipClass GESTransitionClipClass; + +typedef struct _GESTestClip GESTestClip; +typedef struct _GESTestClipClass GESTestClipClass; + +typedef struct _GESTitleClip GESTitleClip; +typedef struct _GESTitleClipClass GESTitleClipClass; + +typedef struct _GESOverlayClip GESOverlayClip; +typedef struct _GESOverlayClipClass GESOverlayClipClass; + +typedef struct _GESTextOverlayClip GESTextOverlayClip; +typedef struct _GESTextOverlayClipClass GESTextOverlayClipClass; + +typedef struct _GESEffectClip GESEffectClip; +typedef struct _GESEffectClipClass GESEffectClipClass; + +typedef struct _GESGroup GESGroup; +typedef struct _GESGroupClass GESGroupClass; + +typedef struct _GESTrack GESTrack; +typedef struct _GESTrackClass GESTrackClass; + +typedef struct _GESTrackElement GESTrackElement; +typedef struct _GESTrackElementClass GESTrackElementClass; + +typedef struct _GESSource GESSource; +typedef struct _GESSourceClass GESSourceClass; + +typedef struct _GESOperation GESOperation; +typedef struct _GESOperationClass GESOperationClass; + +typedef struct _GESBaseEffect GESBaseEffect; +typedef struct _GESBaseEffectClass GESBaseEffectClass; + +typedef struct _GESEffect GESEffect; +typedef struct _GESEffectClass GESEffectClass; + +typedef struct _GESVideoSource GESVideoSource; +typedef struct _GESVideoSourceClass GESVideoSourceClass; + +typedef struct _GESAudioSource GESAudioSource; +typedef struct _GESAudioSourceClass GESAudioSourceClass; + +typedef struct _GESVideoUriSource GESVideoUriSource; +typedef struct _GESVideoUriSourceClass GESVideoUriSourceClass; + +typedef struct _GESAudioUriSource GESAudioUriSource; +typedef struct _GESAudioUriSourceClass GESAudioUriSourceClass; + +typedef struct _GESImageSource GESImageSource; +typedef struct _GESImageSourceClass GESImageSourceClass; + +typedef struct _GESMultiFileSource GESMultiFileSource; +typedef struct _GESMultiFileSourceClass GESMultiFileSourceClass; + +typedef struct _GESTransition GESTransition; +typedef struct _GESTransitionClass GESTransitionClass; + +typedef struct _GESAudioTransition GESAudioTransition; +typedef struct _GESAudioTransitionClass + GESAudioTransitionClass; + +typedef struct _GESVideoTransition GESVideoTransition; +typedef struct _GESVideoTransitionClass + GESVideoTransitionClass; + +typedef struct _GESVideoTestSource GESVideoTestSource; +typedef struct _GESVideoTestSourceClass + GESVideoTestSourceClass; + +typedef struct _GESAudioTestSource GESAudioTestSource; +typedef struct _GESAudioTestSourceClass + GESAudioTestSourceClass; + +typedef struct _GESTitleSource GESTitleSource; +typedef struct _GESTitleSourceClass + GESTitleSourceClass; + +typedef struct _GESTextOverlay GESTextOverlay; +typedef struct _GESTextOverlayClass + GESTextOverlayClass; + +typedef struct _GESFormatter GESFormatter; +typedef struct _GESFormatterClass GESFormatterClass; + +typedef struct _GESPitiviFormatter GESPitiviFormatter; +typedef struct _GESPitiviFormatterClass GESPitiviFormatterClass; + +typedef struct _GESAsset GESAsset; +typedef struct _GESAssetClass GESAssetClass; + +typedef struct _GESClipAsset GESClipAsset; +typedef struct _GESClipAssetClass GESClipAssetClass; + +typedef struct _GESUriClipAsset GESUriClipAsset; +typedef struct _GESUriClipAssetClass GESUriClipAssetClass; + +typedef struct _GESTrackElementAsset GESTrackElementAsset; +typedef struct _GESTrackElementAssetClass GESTrackElementAssetClass; + +typedef struct _GESUriSourceAsset GESUriSourceAsset; +typedef struct _GESUriSourceAssetClass GESUriSourceAssetClass; + +typedef struct _GESProject GESProject; +typedef struct _GESProjectClass GESProjectClass; + +typedef struct _GESExtractable GESExtractable; +typedef struct _GESExtractableInterface GESExtractableInterface; + +typedef struct _GESVideoTrackClass GESVideoTrackClass; +typedef struct _GESVideoTrack GESVideoTrack; + +typedef struct _GESAudioTrackClass GESAudioTrackClass; +typedef struct _GESAudioTrack GESAudioTrack; + +typedef struct _GESMarkerList GESMarkerList; +typedef struct _GESMarker GESMarker; + +typedef struct _GESEffectAssetClass GESEffectAssetClass; +typedef struct _GESEffectAsset GESEffectAsset; + +typedef struct _GESXmlFormatterClass GESXmlFormatterClass; +typedef struct _GESXmlFormatter GESXmlFormatter; + +/** + * GES_DECLARE_TYPE: (attributes doc.skip=true) + */ +#define GES_DECLARE_TYPE(ObjName, obj_name, OBJ_NAME) \ + GES_API GType ges_##obj_name##_get_type(void); \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + typedef struct _GES##ObjName##Private GES##ObjName##Private; \ + \ + G_DEFINE_AUTOPTR_CLEANUP_FUNC(GES##ObjName, gst_object_unref) \ + \ + static G_GNUC_UNUSED inline GES##ObjName *GES_##OBJ_NAME(gpointer ptr) { \ + return G_TYPE_CHECK_INSTANCE_CAST(ptr, ges_##obj_name##_get_type(), \ + GES##ObjName); \ + } \ + static G_GNUC_UNUSED inline GES##ObjName##Class *GES_##OBJ_NAME##_CLASS(gpointer ptr) { \ + return G_TYPE_CHECK_CLASS_CAST(ptr, ges_##obj_name##_get_type(), \ + GES##ObjName##Class); \ + } \ + static G_GNUC_UNUSED inline gboolean GES_IS_##OBJ_NAME(gpointer ptr) { \ + return G_TYPE_CHECK_INSTANCE_TYPE(ptr, ges_##obj_name##_get_type()); \ + } \ + static G_GNUC_UNUSED inline gboolean GES_IS_##OBJ_NAME##_CLASS(gpointer ptr) { \ + return G_TYPE_CHECK_CLASS_TYPE(ptr, ges_##obj_name##_get_type()); \ + } \ + static G_GNUC_UNUSED inline GES##ObjName##Class *GES_##OBJ_NAME##_GET_CLASS( \ + gpointer ptr) { \ + return G_TYPE_INSTANCE_GET_CLASS(ptr, ges_##obj_name##_get_type(), \ + GES##ObjName##Class); \ + } \ + G_GNUC_END_IGNORE_DEPRECATIONS + +G_END_DECLS diff --git a/ges/ges-uri-asset.c b/ges/ges-uri-asset.c new file mode 100644 index 0000000000..f33d8e99b7 --- /dev/null +++ b/ges/ges-uri-asset.c @@ -0,0 +1,997 @@ +/* GStreamer Editing Services + * + * Copyright (C) 2012 Thibault Saunier <thibault.saunier@collabora.com> + * Copyright (C) 2012 Volodymyr Rudyi <vladimir.rudoy@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION: gesuriasset + * @title: GESUriClipAsset + * @short_description: A GESAsset subclass specialized in GESUriClip extraction + * + * The #GESUriClipAsset is a special #GESAsset that lets you handle + * the media file to use inside the GStreamer Editing Services. It has APIs that + * let you get information about the medias. Also, the tags found in the media file are + * set as Metadata of the Asset. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <errno.h> +#include <gst/pbutils/pbutils.h> +#include "ges.h" +#include "ges-internal.h" +#include "ges-track-element-asset.h" + +#define DEFAULT_DISCOVERY_TIMEOUT (60 * GST_SECOND) + +static GHashTable *parent_newparent_table = NULL; + +G_LOCK_DEFINE_STATIC (discoverers_lock); +static GstClockTime discovering_timeout = DEFAULT_DISCOVERY_TIMEOUT; +static GHashTable *discoverers = NULL; /* Thread ID -> GstDiscoverer */ +static void discoverer_discovered_cb (GstDiscoverer * discoverer, + GstDiscovererInfo * info, GError * err, gpointer user_data); + +/* WITH discoverers_lock */ +static GstDiscoverer * +create_discoverer (void) +{ + GstDiscoverer *disco = gst_discoverer_new (discovering_timeout, NULL); + + g_signal_connect (disco, "discovered", G_CALLBACK (discoverer_discovered_cb), + NULL); + GST_INFO_OBJECT (disco, "Creating new discoverer"); + g_hash_table_insert (discoverers, g_thread_self (), disco); + gst_discoverer_start (disco); + + return disco; +} + +static GstDiscoverer * +get_discoverer (void) +{ + GstDiscoverer *disco; + + G_LOCK (discoverers_lock); + g_assert (discoverers); + disco = g_hash_table_lookup (discoverers, g_thread_self ()); + if (!disco) { + disco = create_discoverer (); + } + disco = gst_object_ref (disco); + G_UNLOCK (discoverers_lock); + + return disco; +} + +static void +initable_iface_init (GInitableIface * initable_iface) +{ + /* We can not iniate synchronously */ + initable_iface->init = NULL; +} + +/* TODO: We should monitor files here, and add some way of reporting changes + * to user + */ +enum +{ + PROP_0, + PROP_DURATION, + PROP_IS_NESTED_TIMELINE, + PROP_LAST +}; +static GParamSpec *properties[PROP_LAST]; + +struct _GESUriClipAssetPrivate +{ + GstDiscovererInfo *info; + GstClockTime duration; + GstClockTime max_duration; + gboolean is_image; + gboolean is_nested_timeline; + + GList *asset_trackfilesources; +}; + +typedef struct +{ + GMainLoop *ml; + GESAsset *asset; + GError *error; +} RequestSyncData; + +struct _GESUriSourceAssetPrivate +{ + GstDiscovererStreamInfo *sinfo; + GESUriClipAsset *creator_asset; + + const gchar *uri; +}; + +G_DEFINE_TYPE_WITH_CODE (GESUriClipAsset, ges_uri_clip_asset, + GES_TYPE_SOURCE_CLIP_ASSET, G_ADD_PRIVATE (GESUriClipAsset) + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)); + +static void +ges_uri_clip_asset_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESUriClipAssetPrivate *priv = GES_URI_CLIP_ASSET (object)->priv; + + switch (property_id) { + case PROP_DURATION: + g_value_set_uint64 (value, priv->duration); + break; + case PROP_IS_NESTED_TIMELINE: + g_value_set_boolean (value, priv->is_nested_timeline); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_uri_clip_asset_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESUriClipAssetPrivate *priv = GES_URI_CLIP_ASSET (object)->priv; + + switch (property_id) { + case PROP_DURATION: + priv->duration = g_value_get_uint64 (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static GESAssetLoadingReturn +_start_loading (GESAsset * asset, GError ** error) +{ + gboolean ret; + const gchar *uri; + GstDiscoverer *discoverer = get_discoverer (); + + uri = ges_asset_get_id (asset); + GST_DEBUG_OBJECT (discoverer, "Started loading %s", uri); + + ret = gst_discoverer_discover_uri_async (discoverer, uri); + gst_object_unref (discoverer); + + if (ret) + return GES_ASSET_LOADING_ASYNC; + + return GES_ASSET_LOADING_ERROR; +} + +static gboolean +_request_id_update (GESAsset * self, gchar ** proposed_new_id, GError * error) +{ + if (error->domain == GST_RESOURCE_ERROR && + (error->code == GST_RESOURCE_ERROR_NOT_FOUND || + error->code == GST_RESOURCE_ERROR_OPEN_READ)) { + const gchar *uri = ges_asset_get_id (self); + GFile *parent, *file = g_file_new_for_uri (uri); + + /* Check if we have the new parent in cache */ + parent = g_file_get_parent (file); + if (parent) { + GFile *new_parent = g_hash_table_lookup (parent_newparent_table, parent); + + if (new_parent) { + gchar *basename = g_file_get_basename (file); + GFile *new_file = g_file_get_child (new_parent, basename); + + /* FIXME Handle the GCancellable */ + if (g_file_query_exists (new_file, NULL)) { + *proposed_new_id = g_file_get_uri (new_file); + GST_DEBUG_OBJECT (self, "Proposing path: %s as proxy", + *proposed_new_id); + } + + gst_object_unref (new_file); + g_free (basename); + } + gst_object_unref (parent); + } + + gst_object_unref (file); + + return TRUE; + } + + return FALSE; +} + +static gboolean +ges_uri_source_asset_get_natural_framerate (GESTrackElementAsset * asset, + gint * framerate_n, gint * framerate_d) +{ + GESUriSourceAssetPrivate *priv = GES_URI_SOURCE_ASSET (asset)->priv; + + if (!GST_IS_DISCOVERER_VIDEO_INFO (priv->sinfo)) + return FALSE; + + *framerate_d = + gst_discoverer_video_info_get_framerate_denom (GST_DISCOVERER_VIDEO_INFO + (priv->sinfo)); + *framerate_n = + gst_discoverer_video_info_get_framerate_num (GST_DISCOVERER_VIDEO_INFO + (priv->sinfo)); + + if ((*framerate_n == 0 && *framerate_d == 1) || *framerate_d == 0 + || *framerate_d == G_MAXINT) { + GST_INFO_OBJECT (asset, "No framerate information about the file."); + + *framerate_n = 0; + *framerate_d = -1; + return FALSE; + } + + return TRUE; +} + +static gboolean +_get_natural_framerate (GESClipAsset * self, gint * framerate_n, + gint * framerate_d) +{ + GList *tmp; + + for (tmp = (GList *) + ges_uri_clip_asset_get_stream_assets (GES_URI_CLIP_ASSET (self)); tmp; + tmp = tmp->next) { + + if (ges_track_element_asset_get_natural_framerate (tmp->data, framerate_n, + framerate_d)) + return TRUE; + } + + return FALSE; +} + +static void +_asset_proxied (GESAsset * self, const gchar * new_uri) +{ + const gchar *uri = ges_asset_get_id (self); + GFile *parent, *new_parent, *new_file = g_file_new_for_uri (new_uri), + *file = g_file_new_for_uri (uri); + + parent = g_file_get_parent (file); + new_parent = g_file_get_parent (new_file); + g_hash_table_insert (parent_newparent_table, parent, new_parent); + gst_object_unref (file); + gst_object_unref (new_file); +} + +static void +ges_uri_clip_asset_dispose (GObject * object) +{ + GESUriClipAsset *self = GES_URI_CLIP_ASSET (object); + GESUriClipAssetPrivate *prif = self->priv; + + if (prif->asset_trackfilesources) { + g_list_free_full (prif->asset_trackfilesources, + (GDestroyNotify) gst_object_unref); + prif->asset_trackfilesources = NULL; + } + + gst_clear_object (&prif->info); + + G_OBJECT_CLASS (ges_uri_clip_asset_parent_class)->dispose (object); +} + +static void +ges_uri_clip_asset_class_init (GESUriClipAssetClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = ges_uri_clip_asset_get_property; + object_class->set_property = ges_uri_clip_asset_set_property; + object_class->dispose = ges_uri_clip_asset_dispose; + + GES_ASSET_CLASS (klass)->start_loading = _start_loading; + GES_ASSET_CLASS (klass)->request_id_update = _request_id_update; + GES_ASSET_CLASS (klass)->inform_proxy = _asset_proxied; + + GES_CLIP_ASSET_CLASS (klass)->get_natural_framerate = _get_natural_framerate; + + klass->discovered = discoverer_discovered_cb; + + + /** + * GESUriClipAsset:duration: + * + * The duration (in nanoseconds) of the media file + */ + properties[PROP_DURATION] = + g_param_spec_uint64 ("duration", "Duration", "The duration to use", 0, + G_MAXUINT64, GST_CLOCK_TIME_NONE, G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_DURATION, + properties[PROP_DURATION]); + + /** + * GESUriClipAsset:is-nested-timeline: + * + * The duration (in nanoseconds) of the media file + * + * Since: 1.18 + */ + properties[PROP_IS_NESTED_TIMELINE] = + g_param_spec_boolean ("is-nested-timeline", "Is nested timeline", + "Whether this is a nested timeline", FALSE, G_PARAM_READABLE); + g_object_class_install_property (object_class, PROP_IS_NESTED_TIMELINE, + properties[PROP_IS_NESTED_TIMELINE]); + + _ges_uri_asset_ensure_setup (klass); +} + +static void +ges_uri_clip_asset_init (GESUriClipAsset * self) +{ + GESUriClipAssetPrivate *priv; + + priv = self->priv = ges_uri_clip_asset_get_instance_private (self); + + priv->info = NULL; + priv->max_duration = priv->duration = GST_CLOCK_TIME_NONE; + priv->is_image = FALSE; +} + +static void +_create_uri_source_asset (GESUriClipAsset * asset, + GstDiscovererStreamInfo * sinfo, GESTrackType type) +{ + GESAsset *src_asset; + GESUriSourceAssetPrivate *src_priv; + GESUriClipAssetPrivate *priv = asset->priv; + gchar *stream_id = + g_strdup (gst_discoverer_stream_info_get_stream_id (sinfo)); + + if (stream_id == NULL) { + GST_WARNING ("No stream ID found, using the pointer instead"); + + stream_id = g_strdup_printf ("%i", GPOINTER_TO_INT (sinfo)); + } + + if (type == GES_TRACK_TYPE_VIDEO) + src_asset = ges_asset_request (GES_TYPE_VIDEO_URI_SOURCE, stream_id, NULL); + else + src_asset = ges_asset_request (GES_TYPE_AUDIO_URI_SOURCE, stream_id, NULL); + g_free (stream_id); + + src_priv = GES_URI_SOURCE_ASSET (src_asset)->priv; + src_priv->uri = ges_asset_get_id (GES_ASSET (asset)); + src_priv->sinfo = gst_object_ref (sinfo); + src_priv->creator_asset = asset; + ges_track_element_asset_set_track_type (GES_TRACK_ELEMENT_ASSET + (src_asset), type); + + priv->is_image |= + ges_uri_source_asset_is_image (GES_URI_SOURCE_ASSET (src_asset)); + priv->asset_trackfilesources = + g_list_append (priv->asset_trackfilesources, src_asset); +} + +static void +ges_uri_clip_asset_set_info (GESUriClipAsset * self, GstDiscovererInfo * info) +{ + GList *tmp, *stream_list; + + GESTrackType supportedformats = GES_TRACK_TYPE_UNKNOWN; + GESUriClipAssetPrivate *priv = GES_URI_CLIP_ASSET (self)->priv; + const GstTagList *tlist = gst_discoverer_info_get_tags (info); + + /* Extract infos from the GstDiscovererInfo */ + stream_list = gst_discoverer_info_get_stream_list (info); + for (tmp = stream_list; tmp; tmp = tmp->next) { + GESTrackType type = GES_TRACK_TYPE_UNKNOWN; + GstDiscovererStreamInfo *sinf = (GstDiscovererStreamInfo *) tmp->data; + + if (GST_IS_DISCOVERER_AUDIO_INFO (sinf)) { + if (supportedformats == GES_TRACK_TYPE_UNKNOWN) + supportedformats = GES_TRACK_TYPE_AUDIO; + else + supportedformats |= GES_TRACK_TYPE_AUDIO; + + type = GES_TRACK_TYPE_AUDIO; + } else if (GST_IS_DISCOVERER_VIDEO_INFO (sinf)) { + if (supportedformats == GES_TRACK_TYPE_UNKNOWN) + supportedformats = GES_TRACK_TYPE_VIDEO; + else + supportedformats |= GES_TRACK_TYPE_VIDEO; + type = GES_TRACK_TYPE_VIDEO; + } + + GST_DEBUG_OBJECT (self, "Creating GESUriSourceAsset for stream: %s", + gst_discoverer_stream_info_get_stream_id (sinf)); + _create_uri_source_asset (self, sinf, type); + } + ges_clip_asset_set_supported_formats (GES_CLIP_ASSET + (self), supportedformats); + + if (stream_list) + gst_discoverer_stream_info_list_free (stream_list); + + if (tlist) + gst_tag_list_get_boolean (tlist, "is-ges-timeline", + &priv->is_nested_timeline); + + if (priv->is_image == FALSE) { + priv->max_duration = priv->duration = + gst_discoverer_info_get_duration (info); + if (priv->is_nested_timeline) + priv->max_duration = GST_CLOCK_TIME_NONE; + } + /* else we keep #GST_CLOCK_TIME_NONE */ + + priv->info = gst_object_ref (info); +} + +static void +_set_meta_file_size (const gchar * uri, GESUriClipAsset * asset) +{ + GError *error = NULL; + GFileInfo *file_info = NULL; + guint64 file_size; + GFile *gfile = NULL; + + GESMetaContainer *container = GES_META_CONTAINER (asset); + + gfile = g_file_new_for_uri (uri); + file_info = g_file_query_info (gfile, "standard::size", + G_FILE_QUERY_INFO_NONE, NULL, &error); + if (!error) { + file_size = g_file_info_get_attribute_uint64 (file_info, "standard::size"); + ges_meta_container_register_meta_uint64 (container, GES_META_READ_WRITE, + "file-size", file_size); + } else { + g_error_free (error); + } + if (gfile) + g_object_unref (gfile); + if (file_info) + g_object_unref (file_info); +} + +static void +_set_meta_foreach (const GstTagList * tags, const gchar * tag, + GESMetaContainer * container) +{ + GValue value = { 0 }; + + if (gst_tag_list_copy_value (&value, tags, tag)) { + ges_meta_container_set_meta (container, tag, &value); + g_value_unset (&value); + } else { + GST_INFO ("Could not set metadata: %s", tag); + } +} + +static void +discoverer_discovered_cb (GstDiscoverer * discoverer, + GstDiscovererInfo * info, GError * err, gpointer user_data) +{ + GError *error = NULL; + const GstTagList *tags; + + const gchar *uri = gst_discoverer_info_get_uri (info); + GESUriClipAsset *mfs = + GES_URI_CLIP_ASSET (ges_asset_cache_lookup (GES_TYPE_URI_CLIP, uri)); + + tags = gst_discoverer_info_get_tags (info); + if (tags) + gst_tag_list_foreach (tags, (GstTagForeachFunc) _set_meta_foreach, mfs); + + _set_meta_file_size (uri, mfs); + + if (gst_discoverer_info_get_result (info) == GST_DISCOVERER_OK) { + ges_uri_clip_asset_set_info (mfs, info); + } else { + if (err) { + error = g_error_copy (err); + } else { + error = g_error_new (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED, + "Stream %s discovering failed (error code: %d)", + uri, gst_discoverer_info_get_result (info)); + } + } + + ges_asset_cache_set_loaded (GES_TYPE_URI_CLIP, uri, error); + + if (error) + g_error_free (error); +} + +static void +asset_ready_cb (GESAsset * source, GAsyncResult * res, RequestSyncData * data) +{ + data->asset = ges_asset_request_finish (res, &data->error); + + if (data->error) { + gchar *possible_uri = ges_uri_asset_try_update_id (data->error, source); + + if (possible_uri) { + ges_asset_try_proxy (source, possible_uri); + g_clear_error (&data->error); + ges_asset_request_async (GES_TYPE_URI_CLIP, possible_uri, NULL, + (GAsyncReadyCallback) asset_ready_cb, data); + g_free (possible_uri); + + return; + } + } + g_main_loop_quit (data->ml); +} + +/* API implementation */ +/** + * ges_uri_clip_asset_get_info: + * @self: Target asset + * + * Gets #GstDiscovererInfo about the file + * + * Returns: (transfer none): #GstDiscovererInfo of specified asset + */ +GstDiscovererInfo * +ges_uri_clip_asset_get_info (const GESUriClipAsset * self) +{ + g_return_val_if_fail (GES_IS_URI_CLIP_ASSET ((GESUriClipAsset *) self), NULL); + + return self->priv->info; +} + +/** + * ges_uri_clip_asset_get_duration: + * @self: a #GESUriClipAsset + * + * Gets duration of the file represented by @self + * + * Returns: The duration of @self + */ +GstClockTime +ges_uri_clip_asset_get_duration (GESUriClipAsset * self) +{ + g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (self), GST_CLOCK_TIME_NONE); + + return self->priv->duration; +} + + +/** + * ges_uri_clip_asset_get_max_duration: + * @self: a #GESUriClipAsset + * + * Gets maximum duration of the file represented by @self, + * it is usually the same as GESUriClipAsset::duration, + * but in the case of nested timelines, for example, they + * are different as those can be extended 'infinitely'. + * + * Returns: The maximum duration of @self + * + * Since: 1.18 + */ +GstClockTime +ges_uri_clip_asset_get_max_duration (GESUriClipAsset * self) +{ + g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (self), GST_CLOCK_TIME_NONE); + + return self->priv->max_duration; +} + +/** + * ges_uri_clip_asset_is_image: + * @self: a #GESUriClipAsset + * + * Gets Whether the file represented by @self is an image or not + * + * Returns: Whether the file represented by @self is an image or not + * + * Since: 1.18 + */ +gboolean +ges_uri_clip_asset_is_image (GESUriClipAsset * self) +{ + g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (self), FALSE); + + return self->priv->is_image; +} + +/** + * ges_uri_clip_asset_new: + * @uri: The URI of the file for which to create a #GESUriClipAsset + * @cancellable: optional %GCancellable object, %NULL to ignore. + * @callback: (scope async): a #GAsyncReadyCallback to call when the initialization is finished + * @user_data: The user data to pass when @callback is called + * + * Creates a #GESUriClipAsset for @uri + * + * Example of request of a GESUriClipAsset: + * |[ + * // The request callback + * static void + * filesource_asset_loaded_cb (GESAsset * source, GAsyncResult * res, gpointer user_data) + * { + * GError *error = NULL; + * GESUriClipAsset *filesource_asset; + * + * filesource_asset = ges_uri_clip_asset_finish (res, &error); + * if (filesource_asset) { + * gst_print ("The file: %s is usable as a FileSource, it is%s an image and lasts %" GST_TIME_FORMAT, + * ges_asset_get_id (GES_ASSET (filesource_asset)) + * ges_uri_clip_asset_is_image (filesource_asset) ? "" : " not", + * GST_TIME_ARGS (ges_uri_clip_asset_get_duration (filesource_asset)); + * } else { + * gst_print ("The file: %s is *not* usable as a FileSource because: %s", + * ges_asset_get_id (source), error->message); + * } + * + * gst_object_unref (mfs); + * } + * + * // The request: + * ges_uri_clip_asset_new (uri, (GAsyncReadyCallback) filesource_asset_loaded_cb, user_data); + * ]| + */ +void +ges_uri_clip_asset_new (const gchar * uri, GCancellable * cancellable, + GAsyncReadyCallback callback, gpointer user_data) +{ + ges_asset_request_async (GES_TYPE_URI_CLIP, uri, cancellable, + callback, user_data); +} + +/** + * ges_uri_clip_asset_finish: + * @res: The #GAsyncResult from which to get the newly created #GESUriClipAsset + * @error: An error to be set in case something wrong happens or %NULL + * + * Finalize the request of an async #GESUriClipAsset + * + * Returns: (transfer full): The #GESUriClipAsset previously requested + * + * Since: 1.16 + */ +GESUriClipAsset * +ges_uri_clip_asset_finish (GAsyncResult * res, GError ** error) +{ + GESAsset *asset; + + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + + asset = ges_asset_request_finish (res, error); + if (asset != NULL) { + return GES_URI_CLIP_ASSET (asset); + } + + return NULL; +} + +/** + * ges_uri_clip_asset_request_sync: + * @uri: The URI of the file for which to create a #GESUriClipAsset. + * You can also use multi file uris for #GESMultiFileSource. + * @error: An error to be set in case something wrong happens or %NULL + * + * Creates a #GESUriClipAsset for @uri syncronously. You should avoid + * to use it in application, and rather create #GESUriClipAsset asynchronously + * + * Returns: (transfer full): A reference to the requested asset or %NULL if + * an error happened + */ +GESUriClipAsset * +ges_uri_clip_asset_request_sync (const gchar * uri, GError ** error) +{ + GError *lerror = NULL; + GESUriClipAsset *asset; + RequestSyncData data = { 0, }; + GstDiscoverer *previous_discoverer; + + asset = GES_URI_CLIP_ASSET (ges_asset_request (GES_TYPE_URI_CLIP, uri, + &lerror)); + + if (asset) + return asset; + + data.ml = g_main_loop_new (NULL, TRUE); + previous_discoverer = get_discoverer (); + create_discoverer (); + + ges_asset_request_async (GES_TYPE_URI_CLIP, uri, NULL, + (GAsyncReadyCallback) asset_ready_cb, &data); + g_main_loop_run (data.ml); + g_main_loop_unref (data.ml); + + G_LOCK (discoverers_lock); + g_hash_table_insert (discoverers, g_thread_self (), previous_discoverer); + G_UNLOCK (discoverers_lock); + + if (data.error) { + GST_ERROR ("Got an error requesting asset: %s", data.error->message); + if (error != NULL) + g_propagate_error (error, data.error); + + return NULL; + } + + return GES_URI_CLIP_ASSET (data.asset); +} + +/** + * ges_uri_clip_asset_class_set_timeout: + * @klass: The #GESUriClipAssetClass on which to set the discoverer timeout + * @timeout: The timeout to set + * + * Sets the timeout of #GESUriClipAsset loading + */ +void +ges_uri_clip_asset_class_set_timeout (GESUriClipAssetClass * klass, + GstClockTime timeout) +{ + GHashTableIter iter; + gpointer value; + + g_return_if_fail (GES_IS_URI_CLIP_ASSET_CLASS (klass)); + + discovering_timeout = timeout; + + G_LOCK (discoverers_lock); + g_hash_table_iter_init (&iter, discoverers); + while (g_hash_table_iter_next (&iter, NULL, &value)) + g_object_set (value, "timeout", timeout, NULL); + G_UNLOCK (discoverers_lock); +} + +/** + * ges_uri_clip_asset_get_stream_assets: + * @self: A #GESUriClipAsset + * + * Get the GESUriSourceAsset @self containes + * + * Returns: (transfer none) (element-type GESUriSourceAsset): a + * #GList of #GESUriSourceAsset + */ +const GList * +ges_uri_clip_asset_get_stream_assets (GESUriClipAsset * self) +{ + g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (self), FALSE); + + return self->priv->asset_trackfilesources; +} + +/***************************************************************** + * GESUriSourceAsset implementation * + *****************************************************************/ +G_DEFINE_TYPE_WITH_PRIVATE (GESUriSourceAsset, ges_uri_source_asset, + GES_TYPE_TRACK_ELEMENT_ASSET); + +static GESExtractable * +_extract (GESAsset * asset, GError ** error) +{ + gchar *uri = NULL; + GESTrackElement *trackelement; + GESUriSourceAssetPrivate *priv = GES_URI_SOURCE_ASSET (asset)->priv; + + if (GST_IS_DISCOVERER_STREAM_INFO (priv->sinfo) == FALSE) { + GST_WARNING_OBJECT (asset, "Can not extract as no strean info set"); + + return NULL; + } + + if (priv->uri == NULL) { + GST_WARNING_OBJECT (asset, "Can not extract as no uri set"); + + return NULL; + } + + uri = g_strdup (priv->uri); + + if (g_str_has_prefix (priv->uri, GES_MULTI_FILE_URI_PREFIX)) + trackelement = GES_TRACK_ELEMENT (ges_multi_file_source_new (uri)); + else if (GST_IS_DISCOVERER_VIDEO_INFO (priv->sinfo)) + trackelement = GES_TRACK_ELEMENT (ges_video_uri_source_new (uri)); + else + trackelement = GES_TRACK_ELEMENT (ges_audio_uri_source_new (uri)); + + ges_track_element_set_track_type (trackelement, + ges_track_element_asset_get_track_type (GES_TRACK_ELEMENT_ASSET (asset))); + + g_free (uri); + + return GES_EXTRACTABLE (trackelement); +} + +static void +ges_uri_source_asset_dispose (GObject * object) +{ + GESUriSourceAsset *self = GES_URI_SOURCE_ASSET (object); + GESUriSourceAssetPrivate *priv = self->priv; + + gst_clear_object (&priv->sinfo); + + G_OBJECT_CLASS (ges_uri_source_asset_parent_class)->dispose (object); +} + +static void +ges_uri_source_asset_class_init (GESUriSourceAssetClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = ges_uri_source_asset_dispose; + + GES_ASSET_CLASS (klass)->extract = _extract; + GES_TRACK_ELEMENT_ASSET_CLASS (klass)->get_natural_framerate = + ges_uri_source_asset_get_natural_framerate; +} + +static void +ges_uri_source_asset_init (GESUriSourceAsset * self) +{ + GESUriSourceAssetPrivate *priv; + + priv = self->priv = ges_uri_source_asset_get_instance_private (self); + + priv->sinfo = NULL; + priv->creator_asset = NULL; + priv->uri = NULL; +} + +/** + * ges_uri_source_asset_get_stream_info: + * @asset: A #GESUriClipAsset + * + * Get the #GstDiscovererStreamInfo user by @asset + * + * Returns: (transfer none): a #GESUriClipAsset + */ +GstDiscovererStreamInfo * +ges_uri_source_asset_get_stream_info (GESUriSourceAsset * asset) +{ + g_return_val_if_fail (GES_IS_URI_SOURCE_ASSET (asset), NULL); + + return asset->priv->sinfo; +} + +const gchar * +ges_uri_source_asset_get_stream_uri (GESUriSourceAsset * asset) +{ + g_return_val_if_fail (GES_IS_URI_SOURCE_ASSET (asset), NULL); + + return asset->priv->uri; +} + +/** + * ges_uri_source_asset_get_filesource_asset: + * @asset: A #GESUriClipAsset + * + * Get the #GESUriClipAsset @self is contained in + * + * Returns: a #GESUriClipAsset + */ +const GESUriClipAsset * +ges_uri_source_asset_get_filesource_asset (GESUriSourceAsset * asset) +{ + g_return_val_if_fail (GES_IS_URI_SOURCE_ASSET (asset), NULL); + + return asset->priv->creator_asset; +} + +/** + * ges_uri_source_asset_is_image: + * @asset: A #GESUriClipAsset + * + * Check if @asset contains a single image + * + * Returns: %TRUE if the video stream corresponds to an image (i.e. only + * contains one frame) + * + * Since: 1.18 + */ +gboolean +ges_uri_source_asset_is_image (GESUriSourceAsset * asset) +{ + g_return_val_if_fail (GES_IS_URI_SOURCE_ASSET (asset), FALSE); + + if (!GST_IS_DISCOVERER_VIDEO_INFO (asset->priv->sinfo)) + return FALSE; + + return gst_discoverer_video_info_is_image ((GstDiscovererVideoInfo *) + asset->priv->sinfo); +} + +void +_ges_uri_asset_cleanup (void) +{ + if (parent_newparent_table) { + g_hash_table_destroy (parent_newparent_table); + parent_newparent_table = NULL; + } + + G_LOCK (discoverers_lock); + if (discoverers) { + g_hash_table_destroy (discoverers); + discoverers = NULL; + } + gst_clear_object (&GES_URI_CLIP_ASSET_CLASS (g_type_class_peek + (GES_TYPE_URI_CLIP_ASSET))->discoverer); + G_UNLOCK (discoverers_lock); +} + +gboolean +_ges_uri_asset_ensure_setup (gpointer uriasset_class) +{ + GESUriClipAssetClass *klass; + GError *err; + GstClockTime timeout; + const gchar *timeout_str; + GstDiscoverer *discoverer = NULL; + + g_return_val_if_fail (GES_IS_URI_CLIP_ASSET_CLASS (uriasset_class), FALSE); + + klass = GES_URI_CLIP_ASSET_CLASS (uriasset_class); + + timeout = DEFAULT_DISCOVERY_TIMEOUT; + errno = 0; + timeout_str = g_getenv ("GES_DISCOVERY_TIMEOUT"); + if (timeout_str) + timeout = g_ascii_strtod (timeout_str, NULL) * GST_SECOND; + else + errno = 10; + + if (errno) + timeout = DEFAULT_DISCOVERY_TIMEOUT; + + if (!klass->discoverer) { + discoverer = gst_discoverer_new (timeout, &err); + if (!discoverer) { + GST_ERROR ("Could not create discoverer: %s", err->message); + g_error_free (err); + return FALSE; + } + } + + /* The class structure keeps weak pointers on the discoverers so they + * can be properly cleaned up in _ges_uri_asset_cleanup(). */ + if (!klass->discoverer) { + klass->discoverer = klass->sync_discoverer = discoverer; + g_object_add_weak_pointer (G_OBJECT (discoverer), + (gpointer *) & klass->discoverer); + g_object_add_weak_pointer (G_OBJECT (discoverer), + (gpointer *) & klass->sync_discoverer); + + g_signal_connect (klass->discoverer, "discovered", + G_CALLBACK (klass->discovered), NULL); + gst_discoverer_start (klass->discoverer); + } + + G_LOCK (discoverers_lock); + if (discoverers == NULL) { + discoverers = g_hash_table_new_full (g_direct_hash, + (GEqualFunc) g_direct_equal, NULL, g_object_unref); + } + G_UNLOCK (discoverers_lock); + + /* We just start the discoverer and let it live */ + if (parent_newparent_table == NULL) { + parent_newparent_table = g_hash_table_new_full (g_file_hash, + (GEqualFunc) g_file_equal, g_object_unref, g_object_unref); + } + + return TRUE; +} diff --git a/ges/ges-uri-asset.h b/ges/ges-uri-asset.h new file mode 100644 index 0000000000..53fa5446dd --- /dev/null +++ b/ges/ges-uri-asset.h @@ -0,0 +1,122 @@ +/* GStreamer Editing Services + * + * Copyright (C) 2012 Thibault Saunier <thibault.saunier@collabora.com> + * Copyright (C) 2012 Volodymyr Rudyi <vladimir.rudoy@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <gio/gio.h> +#include <ges/ges-types.h> +#include <ges/ges-asset.h> +#include <ges/ges-source-clip-asset.h> +#include <ges/ges-track-element-asset.h> + +G_BEGIN_DECLS +#define GES_TYPE_URI_CLIP_ASSET ges_uri_clip_asset_get_type() +GES_DECLARE_TYPE(UriClipAsset, uri_clip_asset, URI_CLIP_ASSET); + +struct _GESUriClipAsset +{ + GESSourceClipAsset parent; + + /* <private> */ + GESUriClipAssetPrivate *priv; + + /* Padding for API extension */ + gpointer __ges_reserved[GES_PADDING]; +}; + +struct _GESUriClipAssetClass +{ + GESSourceClipAssetClass parent_class; + + /* <private> */ + GstDiscoverer *discoverer; /* Unused */ + GstDiscoverer *sync_discoverer; /* Unused */ + + void (*discovered) (GstDiscoverer * discoverer, /* Unused */ + GstDiscovererInfo * info, + GError * err, + gpointer user_data); + + gpointer _ges_reserved[GES_PADDING -1]; +}; + +GES_API +GstDiscovererInfo *ges_uri_clip_asset_get_info (const GESUriClipAsset * self); +GES_API +GstClockTime ges_uri_clip_asset_get_duration (GESUriClipAsset *self); +GES_API +GstClockTime ges_uri_clip_asset_get_max_duration (GESUriClipAsset *self); +GES_API +gboolean ges_uri_clip_asset_is_image (GESUriClipAsset *self); +GES_API +void ges_uri_clip_asset_new (const gchar *uri, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GES_API +GESUriClipAsset * ges_uri_clip_asset_finish (GAsyncResult * res, GError ** error); +GES_API +GESUriClipAsset* ges_uri_clip_asset_request_sync (const gchar *uri, GError **error); +GES_API +void ges_uri_clip_asset_class_set_timeout (GESUriClipAssetClass *klass, + GstClockTime timeout); +GES_API +const GList * ges_uri_clip_asset_get_stream_assets (GESUriClipAsset *self); + +#define GES_TYPE_URI_SOURCE_ASSET ges_uri_source_asset_get_type() +GES_DECLARE_TYPE(UriSourceAsset, uri_source_asset, URI_SOURCE_ASSET); + +/** + * GESUriSourceAsset: + * + * Asset to create a stream specific #GESSource for a media file. + * + * NOTE: You should never request such a #GESAsset as they will be created automatically + * by #GESUriClipAsset-s. + */ +struct _GESUriSourceAsset +{ + GESTrackElementAsset parent; + + /* <private> */ + GESUriSourceAssetPrivate *priv; + + /* Padding for API extension */ + gpointer __ges_reserved[GES_PADDING]; +}; + +struct _GESUriSourceAssetClass +{ + GESTrackElementAssetClass parent_class; + + gpointer _ges_reserved[GES_PADDING]; +}; +GES_API +GstDiscovererStreamInfo * ges_uri_source_asset_get_stream_info (GESUriSourceAsset *asset); +GES_API +const gchar * ges_uri_source_asset_get_stream_uri (GESUriSourceAsset *asset); +GES_API +const GESUriClipAsset *ges_uri_source_asset_get_filesource_asset (GESUriSourceAsset *asset); +GES_API +gboolean ges_uri_source_asset_is_image (GESUriSourceAsset *asset); + +G_END_DECLS \ No newline at end of file diff --git a/ges/ges-uri-clip.c b/ges/ges-uri-clip.c new file mode 100644 index 0000000000..fccd3841b4 --- /dev/null +++ b/ges/ges-uri-clip.c @@ -0,0 +1,638 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION: gesuriclip + * @title: GESUriClip + * @short_description: An object for manipulating media files in a GESTimeline + * + * Represents all the output streams from a particular uri. It is assumed that + * the URI points to a file of some type. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-uri-clip.h" +#include "ges-source-clip.h" +#include "ges-video-uri-source.h" +#include "ges-audio-uri-source.h" +#include "ges-uri-asset.h" +#include "ges-track-element-asset.h" +#include "ges-extractable.h" +#include "ges-image-source.h" +#include "ges-audio-test-source.h" +#include "ges-multi-file-source.h" +#include "ges-layer.h" + +static void ges_extractable_interface_init (GESExtractableInterface * iface); + +#define parent_class ges_uri_clip_parent_class + +struct _GESUriClipPrivate +{ + gchar *uri; + + gboolean mute; + gboolean is_image; +}; + +enum +{ + PROP_0, + PROP_URI, + PROP_MUTE, + PROP_IS_IMAGE, + PROP_SUPPORTED_FORMATS, +}; + +G_DEFINE_TYPE_WITH_CODE (GESUriClip, ges_uri_clip, + GES_TYPE_SOURCE_CLIP, G_ADD_PRIVATE (GESUriClip) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, + ges_extractable_interface_init)); + +static GList *ges_uri_clip_create_track_elements (GESClip * + clip, GESTrackType type); +static void ges_uri_clip_set_uri (GESUriClip * self, gchar * uri); + +gboolean +uri_clip_set_max_duration (GESTimelineElement * element, + GstClockTime maxduration); + +static void +ges_uri_clip_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESUriClipPrivate *priv = GES_URI_CLIP (object)->priv; + + switch (property_id) { + case PROP_URI: + g_value_set_string (value, priv->uri); + break; + case PROP_MUTE: + g_value_set_boolean (value, priv->mute); + break; + case PROP_IS_IMAGE: + g_value_set_boolean (value, priv->is_image); + break; + case PROP_SUPPORTED_FORMATS: + g_value_set_flags (value, + ges_clip_get_supported_formats (GES_CLIP (object))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_uri_clip_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESUriClip *uriclip = GES_URI_CLIP (object); + + switch (property_id) { + case PROP_URI: + ges_uri_clip_set_uri (uriclip, g_value_dup_string (value)); + break; + case PROP_MUTE: + ges_uri_clip_set_mute (uriclip, g_value_get_boolean (value)); + break; + case PROP_IS_IMAGE: + ges_uri_clip_set_is_image (uriclip, g_value_get_boolean (value)); + break; + case PROP_SUPPORTED_FORMATS: + ges_clip_set_supported_formats (GES_CLIP (uriclip), + g_value_get_flags (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_uri_clip_finalize (GObject * object) +{ + GESUriClipPrivate *priv = GES_URI_CLIP (object)->priv; + + if (priv->uri) + g_free (priv->uri); + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +ges_uri_clip_class_init (GESUriClipClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESClipClass *clip_class = GES_CLIP_CLASS (klass); + GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass); + + object_class->get_property = ges_uri_clip_get_property; + object_class->set_property = ges_uri_clip_set_property; + object_class->finalize = ges_uri_clip_finalize; + + + /** + * GESUriClip:uri: + * + * The location of the file/resource to use. + */ + g_object_class_install_property (object_class, PROP_URI, + g_param_spec_string ("uri", "URI", "uri of the resource", NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + /** + * GESUriClip:mute: + * + * Whether the sound will be played or not. + */ + g_object_class_install_property (object_class, PROP_MUTE, + g_param_spec_boolean ("mute", "Mute", "Mute audio track", + FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + /** + * GESUriClip:is-image: + * + * Whether this uri clip represents a still image or not. This must be set + * before create_track_elements is called. + */ + g_object_class_install_property (object_class, PROP_IS_IMAGE, + g_param_spec_boolean ("is-image", "Is still image", + "Whether the clip represents a still image or not", + FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + /* Redefine the supported formats property so the default value is UNKNOWN + * and not AUDIO | VIDEO */ + g_object_class_install_property (object_class, PROP_SUPPORTED_FORMATS, + g_param_spec_flags ("supported-formats", + "Supported formats", "Formats supported by the file", + GES_TYPE_TRACK_TYPE, GES_TRACK_TYPE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + element_class->set_max_duration = uri_clip_set_max_duration; + + clip_class->create_track_elements = ges_uri_clip_create_track_elements; +} + +static gchar * +extractable_check_id (GType type, const gchar * id) +{ + if (gst_uri_is_valid (id)) + return g_strdup (id); + + return NULL; +} + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS; /* Start ignoring GParameter deprecation */ + +static GParameter * +extractable_get_parameters_from_id (const gchar * id, guint * n_params) +{ + GParameter *params = g_new0 (GParameter, 2); + + params[0].name = "uri"; + g_value_init (¶ms[0].value, G_TYPE_STRING); + g_value_set_string (¶ms[0].value, id); + + *n_params = 1; + + return params; +} + +G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */ + +static gchar * +extractable_get_id (GESExtractable * self) +{ + return g_strdup (GES_URI_CLIP (self)->priv->uri); +} + +static GList * +get_auto_transitions_around_source (GESTrackElement * child) +{ + GList *transitions = NULL; + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (child); + gint i; + GESEdge edges[] = { GES_EDGE_START, GES_EDGE_END }; + + if (!timeline) + return NULL; + + for (i = 0; i < G_N_ELEMENTS (edges); i++) { + GESAutoTransition *transition = + ges_timeline_get_auto_transition_at_edge (timeline, child, edges[i]); + if (transition) + transitions = g_list_prepend (transitions, transition); + } + + return transitions; +} + +static gboolean +extractable_set_asset (GESExtractable * self, GESAsset * asset) +{ + gboolean res = TRUE, contains_core; + GESUriClip *uriclip = GES_URI_CLIP (self); + GESUriClipAsset *uri_clip_asset; + GESClip *clip = GES_CLIP (self); + GESContainer *container = GES_CONTAINER (clip); + GESTimelineElement *element = GES_TIMELINE_ELEMENT (self); + GESLayer *layer = ges_clip_get_layer (clip); + GList *tmp, *children; + GHashTable *source_by_track, *auto_transitions_on_sources; + GstClockTime max_duration; + GESAsset *prev_asset; + GList *transitions = NULL; + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (self); + + g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (asset), FALSE); + + uri_clip_asset = GES_URI_CLIP_ASSET (asset); + + /* new sources elements will have their max-duration set to + * max_duration. Check that this is possible with the new uri + * NOTE: we are assuming that all the new core children will end up + * in the same tracks as the previous core children */ + max_duration = ges_uri_clip_asset_get_max_duration (uri_clip_asset); + if (!ges_clip_can_set_max_duration_of_all_core (clip, max_duration, NULL)) { + GST_INFO_OBJECT (self, "Can not set asset to %p as its max-duration %" + GST_TIME_FORMAT " is too low", asset, GST_TIME_ARGS (max_duration)); + + return FALSE; + } + + if (!container->children && !GST_CLOCK_TIME_IS_VALID (element->duration)) { + if (!ges_timeline_element_set_duration (element, + ges_uri_clip_asset_get_duration (uri_clip_asset))) { + GST_ERROR_OBJECT (self, "Failed to set the duration using a new " + "uri asset when we have no children. This should not happen"); + return FALSE; + } + } + + ges_uri_clip_set_is_image (uriclip, + ges_uri_clip_asset_is_image (uri_clip_asset)); + + if (ges_clip_get_supported_formats (clip) == GES_TRACK_TYPE_UNKNOWN) { + ges_clip_set_supported_formats (clip, + ges_clip_asset_get_supported_formats (GES_CLIP_ASSET (uri_clip_asset))); + } + + prev_asset = element->asset; + element->asset = asset; + + /* FIXME: it would be much better if we could have a way to replace + * each source one-to-one with a new source in the same track, e.g. + * a user supplied + * GESSource * ges_uri_clip_swap_source ( + * GESClip * clip, GESSource * replace, GList * new_sources, + * gpointer user_data) + * + * and they select a new source from new_sources to replace @replace, or + * %NULL to remove it without a replacement. The default would swap + * one video for another video, etc. + * + * Then we could use this information with + * ges_clip_can_update_duration_limit, using the new max-duration and + * replacing each source in the same track, to test that the operation + * can succeed (basically extending + * ges_clip_can_set_max_duration_of_all_core, but with the added + * information that sources without a replacement will not contribute + * to the duration-limit, and all of the siblings in the same track will + * also be removed from the track). + * + * Then we can perform the replacement, whilst avoiding track-selection + * (similar to GESClip's _transfer_child). */ + + source_by_track = g_hash_table_new_full (NULL, NULL, + gst_object_unref, gst_object_unref); + auto_transitions_on_sources = g_hash_table_new_full (NULL, NULL, + gst_object_unref, (GDestroyNotify) g_list_free); + + if (timeline) + ges_timeline_freeze_auto_transitions (timeline, TRUE); + + children = ges_container_get_children (container, FALSE); + for (tmp = children; tmp; tmp = tmp->next) { + GESTrackElement *child = tmp->data; + GESTrack *track; + + /* remove our core children */ + if (!ges_track_element_is_core (child)) + continue; + + track = ges_track_element_get_track (child); + if (track) + g_hash_table_insert (source_by_track, gst_object_ref (track), + gst_object_ref (child)); + + transitions = get_auto_transitions_around_source (child); + if (transitions) + g_hash_table_insert (auto_transitions_on_sources, gst_object_ref (child), + transitions); + + /* removing the track element from its clip whilst it is in a + * timeline will remove it from its track */ + /* removing the core element will also empty its non-core siblings + * from the same track */ + ges_container_remove (container, GES_TIMELINE_ELEMENT (child)); + } + g_list_free_full (children, g_object_unref); + + contains_core = FALSE; + + /* keep alive */ + gst_object_ref (self); + if (layer) { + + res = ges_layer_remove_clip (layer, clip); + + if (res) { + /* adding back to the layer will trigger the re-creation of the core + * children */ + res = ges_layer_add_clip (layer, clip); + + if (!res) + GST_ERROR_OBJECT (self, "Failed to add the uri clip %s back into " + "its layer. This is likely caused by track-selection for the " + "core sources and effects failing because the core sources " + "were not replaced in the same tracks", element->name); + + /* NOTE: assume that core children in the same tracks correspond to + * the same source! */ + for (tmp = container->children; tmp; tmp = tmp->next) { + GESTrackElement *child = tmp->data; + GESTrackElement *orig_source; + + if (!ges_track_element_is_core (child)) + continue; + + contains_core = TRUE; + orig_source = g_hash_table_lookup (source_by_track, + ges_track_element_get_track (child)); + + if (!orig_source) + continue; + + ges_track_element_copy_properties (GES_TIMELINE_ELEMENT + (orig_source), GES_TIMELINE_ELEMENT (child)); + ges_track_element_copy_bindings (orig_source, child, + GST_CLOCK_TIME_NONE); + + transitions = + g_hash_table_lookup (auto_transitions_on_sources, orig_source); + for (; transitions; transitions = transitions->next) { + GESAutoTransition *transition = transitions->data; + + if (transition->previous_source == orig_source) + ges_auto_transition_set_source (transition, child, GES_EDGE_START); + else if (transition->next_source == orig_source) + ges_auto_transition_set_source (transition, child, GES_EDGE_END); + } + } + } else { + GST_ERROR_OBJECT (self, "Failed to remove from the layer. This " + "should not happen"); + } + gst_object_unref (layer); + } + g_hash_table_unref (source_by_track); + g_hash_table_unref (auto_transitions_on_sources); + + if (timeline) + ges_timeline_freeze_auto_transitions (timeline, FALSE); + + if (res) { + g_free (uriclip->priv->uri); + uriclip->priv->uri = g_strdup (ges_asset_get_id (asset)); + + if (!contains_core) { + if (!ges_timeline_element_set_max_duration (element, max_duration)) + GST_ERROR_OBJECT (self, "Failed to set the max-duration on the uri " + "clip when it has no children. This should not happen"); + } + } else { + element->asset = prev_asset; + } + + /* if re-adding failed, clip may be destroyed */ + gst_object_unref (self); + + return res; +} + +static void +ges_extractable_interface_init (GESExtractableInterface * iface) +{ + iface->asset_type = GES_TYPE_URI_CLIP_ASSET; + iface->check_id = (GESExtractableCheckId) extractable_check_id; + iface->get_parameters_from_id = extractable_get_parameters_from_id; + iface->get_id = extractable_get_id; + iface->get_id = extractable_get_id; + iface->can_update_asset = TRUE; + iface->set_asset_full = extractable_set_asset; +} + +static void +ges_uri_clip_init (GESUriClip * self) +{ + self->priv = ges_uri_clip_get_instance_private (self); + + /* Setting the duration to -1 by default. */ + GES_TIMELINE_ELEMENT (self)->duration = GST_CLOCK_TIME_NONE; +} + +/** + * ges_uri_clip_set_mute: + * @self: the #GESUriClip on which to mute or unmute the audio track + * @mute: %TRUE to mute @self audio track, %FALSE to unmute it + * + * Sets whether the audio track of this clip is muted or not. + * + */ +void +ges_uri_clip_set_mute (GESUriClip * self, gboolean mute) +{ + GList *tmp; + + GST_DEBUG ("self:%p, mute:%d", self, mute); + + self->priv->mute = mute; + + /* Go over tracked objects, and update 'active' status on all audio objects */ + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = g_list_next (tmp)) { + GESTrackElement *trackelement = (GESTrackElement *) tmp->data; + GESTrack *track = ges_track_element_get_track (trackelement); + + if (track && track->type == GES_TRACK_TYPE_AUDIO) + ges_track_element_set_active (trackelement, !mute); + } +} + +gboolean +uri_clip_set_max_duration (GESTimelineElement * element, + GstClockTime maxduration) +{ + gboolean ret = + GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_max_duration (element, + maxduration); + + if (ret) { + GstClockTime limit = ges_clip_get_duration_limit (GES_CLIP (element)); + if (GST_CLOCK_TIME_IS_VALID (limit) && (element->duration == 0)) + _set_duration0 (element, limit); + } + + return ret; +} + +/** + * ges_uri_clip_set_is_image: + * @self: the #GESUriClip + * @is_image: %TRUE if @self is a still image, %FALSE otherwise + * + * Sets whether the clip is a still image or not. + */ +void +ges_uri_clip_set_is_image (GESUriClip * self, gboolean is_image) +{ + self->priv->is_image = is_image; +} + +/** + * ges_uri_clip_is_muted: + * @self: the #GESUriClip + * + * Lets you know if the audio track of @self is muted or not. + * + * Returns: %TRUE if the audio track of @self is muted, %FALSE otherwise. + */ +gboolean +ges_uri_clip_is_muted (GESUriClip * self) +{ + return self->priv->mute; +} + +/** + * ges_uri_clip_is_image: + * @self: the #GESUriClip + * + * Lets you know if @self is an image or not. + * + * Returns: %TRUE if @self is a still image %FALSE otherwise. + */ +gboolean +ges_uri_clip_is_image (GESUriClip * self) +{ + return self->priv->is_image; +} + +/** + * ges_uri_clip_get_uri: + * @self: the #GESUriClip + * + * Get the location of the resource. + * + * Returns: The location of the resource. + */ +const gchar * +ges_uri_clip_get_uri (GESUriClip * self) +{ + return self->priv->uri; +} + +static GList * +ges_uri_clip_create_track_elements (GESClip * clip, GESTrackType type) +{ + GList *res = NULL; + const GList *tmp, *stream_assets; + GESAsset *asset = GES_TIMELINE_ELEMENT (clip)->asset; + GESUriClipAsset *uri_asset; + GstClockTime max_duration; + + g_return_val_if_fail (asset, NULL); + + uri_asset = GES_URI_CLIP_ASSET (asset); + + max_duration = ges_uri_clip_asset_get_max_duration (uri_asset); + stream_assets = ges_uri_clip_asset_get_stream_assets (uri_asset); + + for (tmp = stream_assets; tmp; tmp = tmp->next) { + GESTrackElementAsset *element_asset = GES_TRACK_ELEMENT_ASSET (tmp->data); + + if (ges_track_element_asset_get_track_type (element_asset) == type) { + GESTrackElement *element = + GES_TRACK_ELEMENT (ges_asset_extract (GES_ASSET (element_asset), + NULL)); + ges_timeline_element_set_max_duration (GES_TIMELINE_ELEMENT (element), + max_duration); + res = g_list_prepend (res, element); + } + } + + return res; +} + +/** + * ges_uri_clip_new: + * @uri: the URI the source should control + * + * Creates a new #GESUriClip for the provided @uri. + * + * > **WARNING**: This function might 'discover` @uri **synchrounously**, it is + * > an IO and processing intensive task that you probably don't want to run in + * > an application mainloop. Have a look at #ges_asset_request_async to see how + * > to make that operation happen **asynchronously**. + * + * Returns: (transfer floating) (nullable): The newly created #GESUriClip, or + * %NULL if there was an error. + */ +GESUriClip * +ges_uri_clip_new (const gchar * uri) +{ + GError *err = NULL; + GESUriClip *res = NULL; + GESAsset *asset = GES_ASSET (ges_uri_clip_asset_request_sync (uri, &err)); + + if (asset) { + res = GES_URI_CLIP (ges_asset_extract (asset, &err)); + if (!res && err) + GST_ERROR ("Could not analyze %s: %s", uri, err->message); + + gst_object_unref (asset); + } else + GST_ERROR ("Could not create asset for uri: %s", uri); + + return res; +} + +void +ges_uri_clip_set_uri (GESUriClip * self, gchar * uri) +{ + if (GES_CONTAINER_CHILDREN (self)) { + /* FIXME handle this case properly */ + GST_WARNING_OBJECT (self, "Can not change uri when already" + "containing TrackElements"); + + return; + } + + self->priv->uri = uri; +} diff --git a/ges/ges-uri-clip.h b/ges/ges-uri-clip.h new file mode 100644 index 0000000000..b4137cda1d --- /dev/null +++ b/ges/ges-uri-clip.h @@ -0,0 +1,72 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> +#include <ges/ges-source-clip.h> +#include <ges/ges-track.h> + +G_BEGIN_DECLS + +#define GES_TYPE_URI_CLIP ges_uri_clip_get_type() +GES_DECLARE_TYPE(UriClip, uri_clip, URI_CLIP); + +struct _GESUriClip { + GESSourceClip parent; + + /*< private >*/ + GESUriClipPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESUriClipClass: + */ + +struct _GESUriClipClass { + /*< private >*/ + GESSourceClipClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API void +ges_uri_clip_set_mute (GESUriClip * self, gboolean mute); + +GES_API void +ges_uri_clip_set_is_image (GESUriClip * self, + gboolean is_image); + +GES_API +gboolean ges_uri_clip_is_muted (GESUriClip * self); +GES_API +gboolean ges_uri_clip_is_image (GESUriClip * self); +GES_API +const gchar *ges_uri_clip_get_uri (GESUriClip * self); + +GES_API +GESUriClip* ges_uri_clip_new (const gchar *uri); + +G_END_DECLS diff --git a/ges/ges-uri-source.c b/ges/ges-uri-source.c new file mode 100644 index 0000000000..e98ce10fee --- /dev/null +++ b/ges/ges-uri-source.c @@ -0,0 +1,223 @@ +/* GStreamer Editing Services + * Copyright (C) 2020 Ubicast S.A + * Author: Thibault Saunier <tsaunier@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-uri-source.h" + +GST_DEBUG_CATEGORY_STATIC (uri_source_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT uri_source_debug + +#define DEFAULT_RAW_CAPS \ + "video/x-raw; " \ + "audio/x-raw; " \ + "text/x-raw; " \ + "subpicture/x-dvd; " \ + "subpicture/x-pgs" + +static GstStaticCaps default_raw_caps = GST_STATIC_CAPS (DEFAULT_RAW_CAPS); + +static inline gboolean +are_raw_caps (const GstCaps * caps) +{ + GstCaps *raw = gst_static_caps_get (&default_raw_caps); + gboolean res = gst_caps_can_intersect (caps, raw); + + gst_caps_unref (raw); + return res; +} + +typedef enum +{ + GST_AUTOPLUG_SELECT_TRY, + GST_AUTOPLUG_SELECT_EXPOSE, + GST_AUTOPLUG_SELECT_SKIP, +} GstAutoplugSelectResult; + +static gint +autoplug_select_cb (GstElement * bin, GstPad * pad, GstCaps * caps, + GstElementFactory * factory, GESUriSource * self) +{ + GstElement *nlesrc; + GstCaps *downstream_caps; + GstQuery *segment_query = NULL; + GstFormat segment_format; + GstAutoplugSelectResult res = GST_AUTOPLUG_SELECT_TRY; + gchar *stream_id = gst_pad_get_stream_id (pad); + const gchar *wanted_id = + gst_discoverer_stream_info_get_stream_id + (ges_uri_source_asset_get_stream_info (GES_URI_SOURCE_ASSET + (ges_extractable_get_asset (GES_EXTRACTABLE (self->element))))); + gboolean wanted = !g_strcmp0 (stream_id, wanted_id); + + if (!ges_source_get_rendering_smartly (GES_SOURCE (self->element))) { + if (!are_raw_caps (caps)) + goto done; + + if (!wanted) { + GST_INFO_OBJECT (self->element, "Not matching stream id: %s -> SKIPPING", + stream_id); + res = GST_AUTOPLUG_SELECT_SKIP; + } else { + GST_INFO_OBJECT (self->element, "Using stream %s", stream_id); + } + goto done; + } + + segment_query = gst_query_new_segment (GST_FORMAT_TIME); + if (!gst_pad_query (pad, segment_query)) { + GST_DEBUG_OBJECT (pad, "Could not query segment"); + + goto done; + } + + gst_query_parse_segment (segment_query, NULL, &segment_format, NULL, NULL); + if (segment_format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (pad, + "Segment not in %s != time for %" GST_PTR_FORMAT + "... continue plugin elements", gst_format_get_name (segment_format), + caps); + + goto done; + } + + nlesrc = ges_track_element_get_nleobject (self->element); + downstream_caps = gst_pad_peer_query_caps (nlesrc->srcpads->data, NULL); + if (downstream_caps && gst_caps_can_intersect (downstream_caps, caps)) { + if (wanted) { + res = GST_AUTOPLUG_SELECT_EXPOSE; + GST_INFO_OBJECT (self->element, + "Exposing %" GST_PTR_FORMAT " with stream id: %s", caps, stream_id); + } else { + res = GST_AUTOPLUG_SELECT_SKIP; + GST_DEBUG_OBJECT (self->element, "Totally skipping %s", stream_id); + } + } + gst_clear_caps (&downstream_caps); + +done: + g_free (stream_id); + gst_clear_query (&segment_query); + + return res; +} + +GstElement * +ges_uri_source_create_source (GESUriSource * self) +{ + GESTrack *track; + GstElement *decodebin; + const GstCaps *caps = NULL; + + track = ges_track_element_get_track (self->element); + + self->decodebin = decodebin = gst_element_factory_make ("uridecodebin", NULL); + GST_DEBUG_OBJECT (self->element, + "%" GST_PTR_FORMAT " - Track! %" GST_PTR_FORMAT, self->decodebin, track); + + if (track) + caps = ges_track_get_caps (track); + + g_object_set (decodebin, "caps", caps, + "expose-all-streams", FALSE, "uri", self->uri, NULL); + g_signal_connect (decodebin, "autoplug-select", + G_CALLBACK (autoplug_select_cb), self); + + return decodebin; + +} + +static void +ges_uri_source_track_set_cb (GESTrackElement * element, + GParamSpec * arg G_GNUC_UNUSED, GESUriSource * self) +{ + GESTrack *track; + const GstCaps *caps = NULL; + + if (!self->decodebin) + return; + + track = ges_track_element_get_track (GES_TRACK_ELEMENT (element)); + if (!track) + return; + + caps = ges_track_get_caps (track); + + GST_INFO_OBJECT (element, + "Setting %" GST_PTR_FORMAT "caps to: %" GST_PTR_FORMAT, self->decodebin, + caps); + g_object_set (self->decodebin, "caps", caps, NULL); +} + + + +void +ges_uri_source_init (GESTrackElement * element, GESUriSource * self) +{ + static gsize once = 0; + + if (g_once_init_enter (&once)) { + GST_DEBUG_CATEGORY_INIT (uri_source_debug, "gesurisource", 0, + "GES uri source"); + g_once_init_leave (&once, 1); + } + + self->element = element; + g_signal_connect (element, "notify::track", + G_CALLBACK (ges_uri_source_track_set_cb), self); +} + +gboolean +ges_uri_source_select_pad (GESSource * self, GstPad * pad) +{ + gboolean res = TRUE; + gboolean is_nested_timeline; + GESUriSourceAsset *asset = + GES_URI_SOURCE_ASSET (ges_extractable_get_asset (GES_EXTRACTABLE (self))); + const GESUriClipAsset *clip_asset = + ges_uri_source_asset_get_filesource_asset (asset); + const gchar *wanted_stream_id = ges_asset_get_id (GES_ASSET (asset)); + gchar *stream_id; + + if (clip_asset) { + g_object_get (G_OBJECT (clip_asset), "is-nested-timeline", + &is_nested_timeline, NULL); + + if (is_nested_timeline) { + GST_DEBUG_OBJECT (self, "Nested timeline track selection is handled" + " by the timeline SELECT_STREAM events handling."); + + return TRUE; + } + } + + stream_id = gst_pad_get_stream_id (pad); + res = !g_strcmp0 (stream_id, wanted_stream_id); + + GST_INFO_OBJECT (self, "%s pad with stream id: %s as %s wanted", + res ? "Using" : "Ignoring", stream_id, wanted_stream_id); + g_free (stream_id); + + return res; +} diff --git a/ges/ges-uri-source.h b/ges/ges-uri-source.h new file mode 100644 index 0000000000..86b35adb43 --- /dev/null +++ b/ges/ges-uri-source.h @@ -0,0 +1,42 @@ +/* GStreamer Editing Services + * Copyright (C) 2020 Ubicast SAS + * Author: Thibault Saunier <tsaunier@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges.h> + +G_BEGIN_DECLS + +typedef struct _GESUriSource GESUriSource; + +struct _GESUriSource +{ + GstElement *decodebin; /* Reference owned by parent class */ + gchar *uri; + + GESTrackElement *element; +}; + +G_GNUC_INTERNAL gboolean ges_uri_source_select_pad (GESSource *self, GstPad *pad); +G_GNUC_INTERNAL GstElement *ges_uri_source_create_source (GESUriSource *self); +G_GNUC_INTERNAL void ges_uri_source_init (GESTrackElement *element, GESUriSource *self); + +G_END_DECLS diff --git a/ges/ges-utils.c b/ges/ges-utils.c new file mode 100644 index 0000000000..36940680a4 --- /dev/null +++ b/ges/ges-utils.c @@ -0,0 +1,318 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Edward Hervey <edward.hervey@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:ges-utils + * @title: GES utilities + * @short_description: Convenience methods + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> + +#include "ges-internal.h" +#include "ges-timeline.h" +#include "ges-track.h" +#include "ges-layer.h" +#include "ges.h" +#include <gst/base/base.h> + +static GstElementFactory *compositor_factory = NULL; + +/** + * ges_timeline_new_audio_video: + * + * Creates a new timeline containing a single #GESAudioTrack and a + * single #GESVideoTrack. + * + * Returns: (transfer floating): The new timeline, or %NULL if the tracks + * could not be created and added. + */ + +GESTimeline * +ges_timeline_new_audio_video (void) +{ + GESTrack *tracka, *trackv; + GESTimeline *timeline; + + /* This is our main GESTimeline */ + timeline = ges_timeline_new (); + + tracka = GES_TRACK (ges_audio_track_new ()); + trackv = GES_TRACK (ges_video_track_new ()); + + if (!ges_timeline_add_track (timeline, trackv) || + !ges_timeline_add_track (timeline, tracka)) { + gst_object_unref (timeline); + timeline = NULL; + } + + return timeline; +} + +/* Internal utilities */ +gint +element_start_compare (GESTimelineElement * a, GESTimelineElement * b) +{ + if (a->start == b->start) { + if (a->priority < b->priority) + return -1; + if (a->priority > b->priority) + return 1; + if (a->duration < b->duration) + return -1; + if (a->duration > b->duration) + return 1; + return 0; + } else if (a->start < b->start) + return -1; + + return 1; +} + +gint +element_end_compare (GESTimelineElement * a, GESTimelineElement * b) +{ + if (GES_TIMELINE_ELEMENT_END (a) == GES_TIMELINE_ELEMENT_END (b)) { + if (a->priority < b->priority) + return -1; + if (a->priority > b->priority) + return 1; + if (a->duration < b->duration) + return -1; + if (a->duration > b->duration) + return 1; + return 0; + } else if (GES_TIMELINE_ELEMENT_END (a) < (GES_TIMELINE_ELEMENT_END (b))) + return -1; + + return 1; +} + +gboolean +ges_pspec_equal (gconstpointer key_spec_1, gconstpointer key_spec_2) +{ + const GParamSpec *key1 = key_spec_1; + const GParamSpec *key2 = key_spec_2; + + return (key1->owner_type == key2->owner_type && + strcmp (key1->name, key2->name) == 0); +} + +guint +ges_pspec_hash (gconstpointer key_spec) +{ + const GParamSpec *key = key_spec; + const gchar *p; + guint h = key->owner_type; + + for (p = key->name; *p; p++) + h = (h << 5) - h + *p; + + return h; +} + +static gboolean +find_compositor (GstPluginFeature * feature, gpointer udata) +{ + gboolean res = FALSE; + const gchar *klass; + GstPluginFeature *loaded_feature = NULL; + GstElement *elem = NULL; + + if (G_UNLIKELY (!GST_IS_ELEMENT_FACTORY (feature))) + return FALSE; + + klass = gst_element_factory_get_metadata (GST_ELEMENT_FACTORY_CAST (feature), + GST_ELEMENT_METADATA_KLASS); + + if (strstr (klass, "Compositor") == NULL) + return FALSE; + + loaded_feature = gst_plugin_feature_load (feature); + if (!loaded_feature) { + GST_ERROR ("Could not load feature: %" GST_PTR_FORMAT, feature); + return FALSE; + } + + /* Some hardware compositor elements (d3d11compositor for example) consist of + * bin with internal mixer elements */ + if (g_type_is_a (gst_element_factory_get_element_type (GST_ELEMENT_FACTORY + (loaded_feature)), GST_TYPE_BIN)) { + GParamSpec *pspec; + GstElement *mixer = NULL; + + elem = + gst_element_factory_create (GST_ELEMENT_FACTORY_CAST (loaded_feature), + NULL); + + /* Checks whether this element has mixer property and the internal element + * is aggregator subclass */ + if (!elem) { + GST_ERROR ("Could not create element from factory %" GST_PTR_FORMAT, + feature); + goto done; + } + + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (elem), "mixer"); + if (!pspec) + goto done; + + if (!g_type_is_a (pspec->value_type, GST_TYPE_ELEMENT)) + goto done; + + g_object_get (elem, "mixer", &mixer, NULL); + if (!mixer) + goto done; + + if (GST_IS_AGGREGATOR (mixer)) + res = TRUE; + + gst_object_unref (mixer); + } else { + res = + g_type_is_a (gst_element_factory_get_element_type (GST_ELEMENT_FACTORY + (loaded_feature)), GST_TYPE_AGGREGATOR); + } + +done: + gst_clear_object (&elem); + gst_object_unref (loaded_feature); + return res; +} + +gboolean +ges_util_structure_get_clocktime (GstStructure * structure, const gchar * name, + GstClockTime * val, GESFrameNumber * frames) +{ + gboolean found = FALSE; + + const GValue *gvalue; + + if (!val && !frames) + return FALSE; + + gvalue = gst_structure_get_value (structure, name); + if (!gvalue) + return FALSE; + + if (frames) + *frames = GES_FRAME_NUMBER_NONE; + + found = TRUE; + if (val && G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME) { + *val = (GstClockTime) g_value_get_uint64 (gvalue); + } else if (val && G_VALUE_TYPE (gvalue) == G_TYPE_UINT64) { + *val = (GstClockTime) g_value_get_uint64 (gvalue); + } else if (val && G_VALUE_TYPE (gvalue) == G_TYPE_UINT) { + *val = (GstClockTime) g_value_get_uint (gvalue); + } else if (val && G_VALUE_TYPE (gvalue) == G_TYPE_INT) { + *val = (GstClockTime) g_value_get_int (gvalue); + } else if (val && G_VALUE_TYPE (gvalue) == G_TYPE_INT64) { + *val = (GstClockTime) g_value_get_int64 (gvalue); + } else if (val && G_VALUE_TYPE (gvalue) == G_TYPE_DOUBLE) { + gdouble d = g_value_get_double (gvalue); + + if (d == -1.0) + *val = GST_CLOCK_TIME_NONE; + else + *val = d * GST_SECOND; + } else if (frames && G_VALUE_TYPE (gvalue) == G_TYPE_STRING) { + const gchar *str = g_value_get_string (gvalue); + + found = FALSE; + if (str && str[0] == 'f') { + GValue v = G_VALUE_INIT; + + g_value_init (&v, G_TYPE_UINT64); + if (gst_value_deserialize (&v, &str[1])) { + *frames = g_value_get_uint64 (&v); + if (val) + *val = GST_CLOCK_TIME_NONE; + found = TRUE; + } + g_value_reset (&v); + } + } else { + found = FALSE; + + } + + return found; +} + + +GstElementFactory * +ges_get_compositor_factory (void) +{ + GList *result; + + if (compositor_factory) + return compositor_factory; + + result = gst_registry_feature_filter (gst_registry_get (), + (GstPluginFeatureFilter) find_compositor, FALSE, NULL); + + /* sort on rank and name */ + result = g_list_sort (result, gst_plugin_feature_rank_compare_func); + g_assert (result); + + compositor_factory = result->data; + gst_plugin_feature_list_free (result); + + return compositor_factory; +} + +void +ges_idle_add (GSourceFunc func, gpointer udata, GDestroyNotify notify) +{ + GMainContext *context = g_main_context_get_thread_default (); + GSource *source = g_idle_source_new (); + if (!context) + context = g_main_context_default (); + + g_source_set_callback (source, func, udata, notify); + g_source_attach (source, context); + +} + +gboolean +ges_nle_composition_add_object (GstElement * comp, GstElement * object) +{ + return gst_bin_add (GST_BIN (comp), object); +} + +gboolean +ges_nle_composition_remove_object (GstElement * comp, GstElement * object) +{ + return gst_bin_remove (GST_BIN (comp), object); +} + +gboolean +ges_nle_object_commit (GstElement * nlesource, gboolean recurse) +{ + gboolean ret; + + g_signal_emit_by_name (nlesource, "commit", recurse, &ret); + + return ret; +} diff --git a/ges/ges-utils.h b/ges/ges-utils.h new file mode 100644 index 0000000000..84252b6390 --- /dev/null +++ b/ges/ges-utils.h @@ -0,0 +1,36 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Edward Hervey <edward.hervey@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <gst/gst.h> +#include <ges/ges-types.h> + +G_BEGIN_DECLS + +GES_API +GESTimeline * ges_timeline_new_audio_video (void); +GES_API +gboolean ges_pspec_equal (gconstpointer key_spec_1, gconstpointer key_spec_2); +GES_API +guint ges_pspec_hash (gconstpointer key_spec); + + +G_END_DECLS diff --git a/ges/ges-validate.c b/ges/ges-validate.c new file mode 100644 index 0000000000..3b666ed5a8 --- /dev/null +++ b/ges/ges-validate.c @@ -0,0 +1,1875 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2014> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <ges/ges.h> + +#ifdef HAVE_GST_VALIDATE +#include <gst/validate/validate.h> +#include <gst/validate/gst-validate-scenario.h> +#include <gst/validate/gst-validate-utils.h> +#include "ges-internal.h" +#include "ges-structured-interface.h" + +#define MONITOR_ON_PIPELINE "validate-monitor" +#define RUNNER_ON_PIPELINE "runner-monitor" + +typedef struct +{ + GMainLoop *ml; + GError *error; +} LoadTimelineData; + +static gboolean +_get_clocktime (GstStructure * structure, const gchar * name, + GstClockTime * val, GESFrameNumber * frames) +{ + const GValue *gvalue = gst_structure_get_value (structure, name); + + if (!gvalue) + return FALSE; + + if (frames && G_VALUE_TYPE (gvalue) == G_TYPE_STRING) { + const gchar *str = g_value_get_string (gvalue); + + if (str && str[0] == 'f') { + GValue v = G_VALUE_INIT; + + g_value_init (&v, G_TYPE_UINT64); + if (gst_value_deserialize (&v, &str[1])) { + *frames = g_value_get_uint64 (&v); + if (val) + *val = GST_CLOCK_TIME_NONE; + g_value_reset (&v); + + return TRUE; + } + g_value_reset (&v); + } + } + + if (!val) + return FALSE; + + return gst_validate_utils_get_clocktime (structure, name, val); +} + +static void +project_loaded_cb (GESProject * project, GESTimeline * timeline, + LoadTimelineData * data) +{ + g_main_loop_quit (data->ml); +} + +static void +error_loading_asset_cb (GESProject * project, GError * err, + const gchar * unused_id, GType extractable_type, LoadTimelineData * data) +{ + data->error = g_error_copy (err); + g_main_loop_quit (data->ml); +} + +static GESTimeline * +_ges_load_timeline (GstValidateScenario * scenario, GstValidateAction * action, + const gchar * project_uri) +{ + GESProject *project = ges_project_new (project_uri); + GESTimeline *timeline; + LoadTimelineData data = { 0 }; + + data.ml = g_main_loop_new (NULL, TRUE); + timeline = + GES_TIMELINE (ges_asset_extract (GES_ASSET (project), &data.error)); + if (!timeline) + goto done; + + g_signal_connect (project, "loaded", (GCallback) project_loaded_cb, &data); + g_signal_connect (project, "error-loading-asset", + (GCallback) error_loading_asset_cb, &data); + g_main_loop_run (data.ml); + g_signal_handlers_disconnect_by_func (project, project_loaded_cb, &data); + g_signal_handlers_disconnect_by_func (project, error_loading_asset_cb, &data); + GST_INFO_OBJECT (scenario, "Loaded timeline from %s", project_uri); + +done: + if (data.error) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, "Can not load timeline from: %s (%s)", + project_uri, data.error->message); + g_clear_error (&data.error); + gst_clear_object (&timeline); + } + + g_main_loop_unref (data.ml); + gst_object_unref (project); + return timeline; +} + +#ifdef G_HAVE_ISO_VARARGS +#define REPORT_UNLESS(condition, errpoint, ...) \ + G_STMT_START { \ + if (!(condition)) { \ + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; \ + gst_validate_report_action(GST_VALIDATE_REPORTER(scenario), action, \ + SCENARIO_ACTION_EXECUTION_ERROR, \ + __VA_ARGS__); \ + goto errpoint; \ + } \ + } \ + G_STMT_END +#else /* G_HAVE_GNUC_VARARGS */ +#ifdef G_HAVE_GNUC_VARARGS +#define REPORT_UNLESS(condition, errpoint, args...) \ + G_STMT_START { \ + if (!(condition)) { \ + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; \ + gst_validate_report_action(GST_VALIDATE_REPORTER(scenario), action, \ + SCENARIO_ACTION_EXECUTION_ERROR, ##args); \ + goto errpoint; \ + } \ + } \ + G_STMT_END +#endif /* G_HAVE_ISO_VARARGS */ +#endif /* G_HAVE_GNUC_VARARGS */ + +#define DECLARE_AND_GET_TIMELINE_AND_PIPELINE(scenario, action) \ + GESTimeline *timeline = NULL; \ + GstElement *pipeline = NULL; \ + const gchar *project_uri = \ + gst_structure_get_string(action->structure, "project-uri"); \ + if (!project_uri) { \ + pipeline = gst_validate_scenario_get_pipeline(scenario); \ + REPORT_UNLESS(GES_IS_PIPELINE(pipeline), done, \ + "Can't execute a '%s' action after the pipeline " \ + "has been destroyed.", \ + action->type); \ + g_object_get(pipeline, "timeline", &timeline, NULL); \ + } else { \ + timeline = _ges_load_timeline(scenario, action, project_uri); \ + if (!timeline) \ + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; \ + } + +#define DECLARE_AND_GET_TIMELINE(scenario, action) \ + DECLARE_AND_GET_TIMELINE_AND_PIPELINE(scenario, action); \ + if (pipeline) \ + gst_object_unref(pipeline); + +#define GES_START_VALIDATE_ACTION(funcname) \ +static gint \ +funcname(GstValidateScenario *scenario, GstValidateAction *action) { \ + GstValidateActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; \ + DECLARE_AND_GET_TIMELINE_AND_PIPELINE(scenario, action); + +#define GST_END_VALIDATE_ACTION \ +done: \ + if (res == GST_VALIDATE_EXECUTE_ACTION_OK) { \ + REPORT_UNLESS( \ + _ges_save_timeline_if_needed(timeline, action->structure, NULL), \ + done_no_save, "Could not save timeline to %s", \ + gst_structure_get_string(action->structure, "project-id")); \ + } \ + \ +done_no_save: \ + gst_clear_object(&pipeline); \ + gst_clear_object(&timeline); \ + return res; \ +} + +#define TRY_GET(name,type,var,def) G_STMT_START {\ + if (!gst_structure_get (action->structure, name, type, var, NULL)) {\ + *var = def; \ + } \ +} G_STMT_END + +GES_START_VALIDATE_ACTION (_serialize_project) +{ + const gchar *uri = gst_structure_get_string (action->structure, "uri"); + gchar *location = gst_uri_get_location (uri), + *dir = g_path_get_dirname (location); + + gst_validate_printf (action, "Saving project to %s", uri); + + g_mkdir_with_parents (dir, 0755); + g_free (location); + g_free (dir); + + res = ges_timeline_save_to_uri (timeline, uri, NULL, TRUE, NULL); +} + +GST_END_VALIDATE_ACTION; + +GES_START_VALIDATE_ACTION (_remove_asset) +{ + const gchar *id = NULL; + const gchar *type_string = NULL; + GType type; + GESAsset *asset; + GESProject *project; + + project = ges_timeline_get_project (timeline); + + id = gst_structure_get_string (action->structure, "id"); + type_string = gst_structure_get_string (action->structure, "type"); + + REPORT_UNLESS (type_string && id, done, + "Missing parameters, we got type %s and id %s", type_string, id); + REPORT_UNLESS ((type = g_type_from_name (type_string)), done, + "This type doesn't exist : %s", type_string); + + asset = ges_project_get_asset (project, id, type); + REPORT_UNLESS (asset, done, "No asset with id %s and type %s", id, + type_string); + res = ges_project_remove_asset (project, asset); + gst_object_unref (asset); +} + +GST_END_VALIDATE_ACTION; + +GES_START_VALIDATE_ACTION (_add_asset) +{ + const gchar *id = NULL; + const gchar *type_string = NULL; + GType type; + GESAsset *asset = NULL; + GESProject *project; + + project = ges_timeline_get_project (timeline); + + id = gst_structure_get_string (action->structure, "id"); + type_string = gst_structure_get_string (action->structure, "type"); + + gst_validate_printf (action, "Adding asset of type %s with ID %s\n", + id, type_string); + + REPORT_UNLESS (type_string + && id, beach, "Missing parameters, we got type %s and id %s", type_string, + id); + REPORT_UNLESS ((type = + g_type_from_name (type_string)), beach, + "This type doesn't exist : %s", type_string); + + asset = _ges_get_asset_from_timeline (timeline, type, id, NULL); + + REPORT_UNLESS (asset, beach, "Could not get asset for %s id: %s", type_string, + id); + res = ges_project_add_asset (project, asset); + +beach: + gst_clear_object (&asset); +} + +GST_END_VALIDATE_ACTION; + +GES_START_VALIDATE_ACTION (_add_layer) +{ + GESLayer *layer; + gint priority; + gboolean auto_transition = FALSE; + + REPORT_UNLESS (gst_structure_get_int (action->structure, "priority", + &priority), done, "priority is needed when adding a layer"); + REPORT_UNLESS ((layer = + _ges_get_layer_by_priority (timeline, priority)), done, + "No layer with priority: %d", priority); + + gst_structure_get_boolean (action->structure, "auto-transition", + &auto_transition); + g_object_set (layer, "priority", priority, "auto-transition", auto_transition, + NULL); + gst_object_unref (layer); +} + +GST_END_VALIDATE_ACTION; + +GES_START_VALIDATE_ACTION (_remove_layer) +{ + GESLayer *layer = NULL; + gint priority; + + REPORT_UNLESS (gst_structure_get_int (action->structure, "priority", + &priority), done, "'priority' is required when removing a layer"); + layer = _ges_get_layer_by_priority (timeline, priority); + REPORT_UNLESS (layer, beach, "No layer with priority %d", priority); + + res = ges_timeline_remove_layer (timeline, layer); + +beach: + gst_clear_object (&layer); +} + +GST_END_VALIDATE_ACTION; + +GES_START_VALIDATE_ACTION (_remove_clip) +{ + GESTimelineElement *clip; + GESLayer *layer = NULL; + const gchar *name; + + name = gst_structure_get_string (action->structure, "name"); + clip = ges_timeline_get_element (timeline, name); + REPORT_UNLESS (GES_IS_CLIP (clip), beach, "Couldn't find clip: %s", name); + + layer = ges_clip_get_layer (GES_CLIP (clip)); + REPORT_UNLESS (layer, beach, "Clip %s not in a layer", name); + + res = ges_layer_remove_clip (layer, GES_CLIP (clip)); + +beach: + gst_clear_object (&layer); + gst_clear_object (&clip); +} + +GST_END_VALIDATE_ACTION; + +GES_START_VALIDATE_ACTION (_edit) +{ + GList *layers = NULL; + GESTimelineElement *element; + GESFrameNumber fposition = GES_FRAME_NUMBER_NONE; + GstClockTime position; + GError *err = NULL; + gboolean source_position = FALSE; + + gint new_layer_priority = -1; + guint edge = GES_EDGE_NONE; + guint mode = GES_EDIT_MODE_NORMAL; + + const gchar *edit_mode_str = NULL, *edge_str = NULL; + const gchar *element_name; + + res = GST_VALIDATE_EXECUTE_ACTION_ERROR; + element_name = gst_structure_get_string (action->structure, + gst_structure_has_name (action->structure, "edit-container") ? + "container-name" : "element-name"); + + element = ges_timeline_get_element (timeline, element_name); + REPORT_UNLESS (element, beach, "Could not find element %s", element_name); + + if (!_get_clocktime (action->structure, "position", &position, &fposition)) { + fposition = 0; + if (!gst_structure_get_int (action->structure, "source-frame", + (gint *) & fposition) + && !gst_structure_get_int64 (action->structure, "source-frame", + &fposition)) { + gchar *structstr = gst_structure_to_string (action->structure); + + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "could not find `position` or `source-frame` in %s", structstr); + g_free (structstr); + goto beach; + } + + source_position = TRUE; + position = GST_CLOCK_TIME_NONE; + } + + if ((edit_mode_str = + gst_structure_get_string (action->structure, "edit-mode"))) { + REPORT_UNLESS (gst_validate_utils_enum_from_str (GES_TYPE_EDIT_MODE, + edit_mode_str, &mode), beach, + "Could not get enum from %s", edit_mode_str); + } + + if ((edge_str = gst_structure_get_string (action->structure, "edge"))) { + REPORT_UNLESS (gst_validate_utils_enum_from_str (GES_TYPE_EDGE, edge_str, + &edge), beach, "Could not get enum from %s", edge_str); + } + + if (GES_FRAME_NUMBER_IS_VALID (fposition)) { + if (source_position) { + GESClip *clip = NULL; + + if (GES_IS_CLIP (element)) + clip = GES_CLIP (element); + else if (GES_IS_TRACK_ELEMENT (element)) + clip = GES_CLIP (element->parent); + + REPORT_UNLESS (clip, beach, + "Could not get find element to edit using source frame for %" + GST_PTR_FORMAT, action->structure); + position = + ges_clip_get_timeline_time_from_source_frame (clip, fposition, &err); + } else { + position = ges_timeline_get_frame_time (timeline, fposition); + } + + REPORT_UNLESS (GST_CLOCK_TIME_IS_VALID (position), beach, + "Invalid frame number '%" G_GINT64_FORMAT "': %s", fposition, + err ? err->message : "Unknown"); + } + + gst_structure_get_int (action->structure, "new-layer-priority", + &new_layer_priority); + + if (!(res = ges_timeline_element_edit (element, layers, + new_layer_priority, mode, edge, position))) { + + gchar *fpositionstr = GES_FRAME_NUMBER_IS_VALID (fposition) + ? g_strdup_printf ("(%" G_GINT64_FORMAT ")", fposition) + : NULL; + + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not edit '%s' to %" GST_TIME_FORMAT + "%s in %s mode, edge: %s " + "with new layer prio: %d", + element_name, GST_TIME_ARGS (position), + fpositionstr ? fpositionstr : "", + edit_mode_str ? edit_mode_str : "normal", + edge_str ? edge_str : "None", new_layer_priority); + g_free (fpositionstr); + goto beach; + } + +beach: + gst_clear_object (&element); + g_clear_error (&err); +} + +GST_END_VALIDATE_ACTION; + + +static void +_state_changed_cb (GstBus * bus, GstMessage * message, + GstValidateAction * action) +{ + GstState next_state; + + if (!GST_IS_PIPELINE (GST_MESSAGE_SRC (message))) + return; + + gst_message_parse_state_changed (message, NULL, NULL, &next_state); + + if (next_state == GST_STATE_VOID_PENDING) { + gst_validate_action_set_done (action); + + g_signal_handlers_disconnect_by_func (bus, _state_changed_cb, action); + } +} + +GES_START_VALIDATE_ACTION (_commit) +{ + GstBus *bus; + GstState state; + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + + gst_validate_printf (action, "Committing timeline %s\n", + GST_OBJECT_NAME (timeline)); + + g_signal_connect (bus, "message::state-changed", + G_CALLBACK (_state_changed_cb), action); + + gst_element_get_state (pipeline, &state, NULL, 0); + if (!ges_timeline_commit (timeline) || state < GST_STATE_PAUSED) { + g_signal_handlers_disconnect_by_func (bus, G_CALLBACK (_state_changed_cb), + action); + gst_object_unref (bus); + goto done; + } + + gst_object_unref (bus); + + res = GST_VALIDATE_EXECUTE_ACTION_ASYNC; +} + +GST_END_VALIDATE_ACTION; + +GES_START_VALIDATE_ACTION (_split_clip) +{ + const gchar *clip_name; + GESTimelineElement *element; + GstClockTime position; + + clip_name = gst_structure_get_string (action->structure, "clip-name"); + + element = ges_timeline_get_element (timeline, clip_name); + REPORT_UNLESS (GES_IS_CLIP (element), beach, "Could not find clip: %s", + clip_name); + REPORT_UNLESS (gst_validate_action_get_clocktime (scenario, action, + "position", &position), beach, + "Could not find position in %" GST_PTR_FORMAT, action->structure); + res = (ges_clip_split (GES_CLIP (element), position) != NULL); + +beach: + gst_clear_object (&element); +} + +GST_END_VALIDATE_ACTION; + +typedef struct +{ + GstValidateScenario *scenario; + GESTimelineElement *element; + GstValidateActionReturn res; + GstClockTime time; + gboolean on_children; + GstValidateAction *action; +} PropertyData; + +static gboolean +check_property (GQuark field_id, GValue * expected_value, PropertyData * data) +{ + GValue cvalue = G_VALUE_INIT, *tvalue = NULL, comparable_value = G_VALUE_INIT, + *observed_value; + const gchar *property = g_quark_to_string (field_id); + GstControlBinding *binding = NULL; + + if (!data->on_children) { + GObject *tmpobject, *object = g_object_ref (G_OBJECT (data->element)); + gchar **object_prop_name = g_strsplit (property, "::", 2); + gint i = 0; + GParamSpec *pspec = NULL; + + while (TRUE) { + pspec = + g_object_class_find_property (G_OBJECT_GET_CLASS (object), + object_prop_name[i]); + + if (!pspec) { + GST_VALIDATE_REPORT_ACTION (data->scenario, data->action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not get property %s on %" GES_FORMAT, + object_prop_name[i], GES_ARGS (data->element)); + data->res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + g_strfreev (object_prop_name); + + return FALSE; + } + + if (!object_prop_name[++i]) + break; + + tmpobject = object; + g_object_get (tmpobject, pspec->name, &object, NULL); + g_object_unref (tmpobject); + } + + g_strfreev (object_prop_name); + g_value_init (&cvalue, pspec->value_type); + g_object_get_property (object, pspec->name, &cvalue); + g_object_unref (object); + goto compare; + } + + if (GST_CLOCK_TIME_IS_VALID (data->time)) { + if (!GES_IS_TRACK_ELEMENT (data->element)) { + GST_VALIDATE_REPORT_ACTION (data->scenario, data->action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not get property at time for type %s - only GESTrackElement supported", + G_OBJECT_TYPE_NAME (data->element)); + data->res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + + return FALSE; + } + + binding = + ges_track_element_get_control_binding (GES_TRACK_ELEMENT + (data->element), property); + if (binding) { + tvalue = gst_control_binding_get_value (binding, data->time); + + if (!tvalue) { + GST_VALIDATE_REPORT_ACTION (data->scenario, data->action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not get property: %s at %" GST_TIME_FORMAT, property, + GST_TIME_ARGS (data->time)); + data->res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + + return FALSE; + } + } + } + + if (!tvalue + && !ges_timeline_element_get_child_property (data->element, property, + &cvalue)) { + GST_VALIDATE_REPORT_ACTION (data->scenario, data->action, + SCENARIO_ACTION_EXECUTION_ERROR, "Could not get child property: %s:", + property); + data->res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + + return FALSE; + } + +compare: + observed_value = tvalue ? tvalue : &cvalue; + + if (G_VALUE_TYPE (observed_value) != G_VALUE_TYPE (expected_value)) { + g_value_init (&comparable_value, G_VALUE_TYPE (observed_value)); + + if (G_VALUE_TYPE (observed_value) == GST_TYPE_CLOCK_TIME) { + GstClockTime t; + + if (gst_validate_utils_get_clocktime (data->action->structure, property, + &t)) { + g_value_set_uint64 (&comparable_value, t); + expected_value = &comparable_value; + } + } else if (g_value_transform (expected_value, &comparable_value)) { + expected_value = &comparable_value; + } + } + + if (gst_value_compare (observed_value, expected_value) != GST_VALUE_EQUAL) { + gchar *expected = gst_value_serialize (expected_value), *observed = + gst_value_serialize (observed_value); + + GST_VALIDATE_REPORT_ACTION (data->scenario, data->action, + SCENARIO_ACTION_CHECK_ERROR, + "%s::%s expected value: '(%s)%s' different than observed: '(%s)%s'", + GES_TIMELINE_ELEMENT_NAME (data->element), property, + G_VALUE_TYPE_NAME (observed_value), expected, + G_VALUE_TYPE_NAME (expected_value), observed); + + g_free (expected); + g_free (observed); + data->res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + + if (G_VALUE_TYPE (&comparable_value) != G_TYPE_NONE) + g_value_unset (&comparable_value); + + if (tvalue) { + g_value_unset (tvalue); + g_free (tvalue); + } else + g_value_reset (&cvalue); + return TRUE; +} + +static gboolean +set_property (GQuark field_id, const GValue * value, PropertyData * data) +{ + const gchar *property = g_quark_to_string (field_id); + + if (data->on_children) { + if (!ges_timeline_element_set_child_property (data->element, property, + value)) { + gchar *v = gst_value_serialize (value); + + GST_VALIDATE_REPORT_ACTION (data->scenario, data->action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not set %s child property %s to %s", + GES_TIMELINE_ELEMENT_NAME (data->element), property, v); + + data->res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + g_free (v); + + return FALSE; + } + } else { + data->res = + gst_validate_object_set_property (GST_VALIDATE_REPORTER + (data->scenario), G_OBJECT (data->element), property, value, FALSE); + } + + return TRUE; +} + +GES_START_VALIDATE_ACTION (set_or_check_properties) +{ + GESTimelineElement *element; + GstStructure *structure; + const gchar *element_name; + gboolean is_setting = FALSE; + PropertyData data = { + .scenario = scenario, + .element = NULL, + .res = GST_VALIDATE_EXECUTE_ACTION_OK, + .time = GST_CLOCK_TIME_NONE, + .on_children = + !gst_structure_has_name (action->structure, "check-ges-properties") + && !gst_structure_has_name (action->structure, "set-ges-properties"), + .action = action, + }; + + is_setting = gst_structure_has_name (action->structure, "set-ges-properties") + || gst_structure_has_name (action->structure, "set-child-properties"); + gst_validate_action_get_clocktime (scenario, action, "at-time", &data.time); + + structure = gst_structure_copy (action->structure); + element_name = gst_structure_get_string (structure, "element-name"); + element = ges_timeline_get_element (timeline, element_name); + if (!element) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Can not find element: %s", element_name); + + data.res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto local_done; + } + + data.element = element; + gst_structure_remove_fields (structure, "element-name", "at-time", + "project-uri", NULL); + gst_structure_foreach (structure, + is_setting ? (GstStructureForeachFunc) set_property + : (GstStructureForeachFunc) check_property, &data); + gst_object_unref (element); + +local_done: + gst_structure_free (structure); + res = data.res; +} + +GST_END_VALIDATE_ACTION; + +GES_START_VALIDATE_ACTION (_set_track_restriction_caps) +{ + GList *tmp; + GstCaps *caps; + GESTrackType track_types; + + const gchar *track_type_str = + gst_structure_get_string (action->structure, "track-type"); + const gchar *caps_str = gst_structure_get_string (action->structure, "caps"); + + REPORT_UNLESS (track_types = + gst_validate_utils_flags_from_str (GES_TYPE_TRACK_TYPE, track_type_str), + done, "Invalid track types: %s", track_type_str); + + REPORT_UNLESS (caps = gst_caps_from_string (caps_str), + done, "Invalid track restriction caps: %s", caps_str); + + res = GST_VALIDATE_EXECUTE_ACTION_ERROR; + for (tmp = timeline->tracks; tmp; tmp = tmp->next) { + GESTrack *track = tmp->data; + + if (track->type & track_types) { + gchar *str; + + str = gst_caps_to_string (caps); + g_free (str); + + ges_track_set_restriction_caps (track, caps); + + res = GST_VALIDATE_EXECUTE_ACTION_OK; + } + } + gst_caps_unref (caps); +} + +GST_END_VALIDATE_ACTION; + +GES_START_VALIDATE_ACTION (_set_asset_on_element) +{ + GESAsset *asset; + GESTimelineElement *element; + const gchar *element_name, *id; + + element_name = gst_structure_get_string (action->structure, "element-name"); + element = ges_timeline_get_element (timeline, element_name); + REPORT_UNLESS (element, done, "Can't find %s", element_name); + + id = gst_structure_get_string (action->structure, "asset-id"); + + gst_validate_printf (action, "Setting asset %s on element %s\n", + id, element_name); + + asset = _ges_get_asset_from_timeline (timeline, G_OBJECT_TYPE (element), id, + NULL); + REPORT_UNLESS (asset, beach, "Could not find asset: %s", id); + + res = ges_extractable_set_asset (GES_EXTRACTABLE (element), asset); + +beach: + gst_clear_object (&asset); + gst_clear_object (&element); +} + +GST_END_VALIDATE_ACTION; + +GES_START_VALIDATE_ACTION (_container_remove_child) +{ + GESContainer *container = NULL; + GESTimelineElement *child = NULL; + const gchar *container_name, *child_name; + + container_name = + gst_structure_get_string (action->structure, "container-name"); + container = + (GESContainer *) ges_timeline_get_element (timeline, container_name); + REPORT_UNLESS (GES_IS_CONTAINER (container), beach, + "Could not find container: %s", container_name); + + child_name = gst_structure_get_string (action->structure, "child-name"); + child = ges_timeline_get_element (timeline, child_name); + REPORT_UNLESS (GES_IS_TIMELINE_ELEMENT (child), beach, "Could not find %s", + child_name); + + res = ges_container_remove (container, child); + +beach: + gst_clear_object (&container); + gst_clear_object (&child); +} + +GST_END_VALIDATE_ACTION; + +GES_START_VALIDATE_ACTION (_ungroup) +{ + GESContainer *container; + gboolean recursive = FALSE; + const gchar *container_name; + + container_name = + gst_structure_get_string (action->structure, "container-name"); + container = + (GESContainer *) ges_timeline_get_element (timeline, container_name); + REPORT_UNLESS (GES_IS_CONTAINER (container), beach, "Could not find %s", + container_name); + + gst_structure_get_boolean (action->structure, "recursive", &recursive); + + g_list_free (ges_container_ungroup (container, recursive)); + +beach: + gst_clear_object (&container); +} + +GST_END_VALIDATE_ACTION; + +GES_START_VALIDATE_ACTION (_copy_element) +{ + GESTimelineElement *element = NULL, *copied = NULL, *pasted = NULL; + gboolean recursive = FALSE; + const gchar *element_name, *paste_name; + GstClockTime position; + + element_name = gst_structure_get_string (action->structure, "element-name"); + element = ges_timeline_get_element (timeline, element_name); + + REPORT_UNLESS (GES_IS_CONTAINER (element), beach, "Could not find %s", + element_name); + + if (!gst_structure_get_boolean (action->structure, "recursive", &recursive)) + recursive = TRUE; + + REPORT_UNLESS (gst_validate_action_get_clocktime (scenario, action, + "position", &position), beach, "Could not find position"); + + copied = ges_timeline_element_copy (element, recursive); + pasted = ges_timeline_element_paste (copied, position); + + REPORT_UNLESS (pasted, beach, "Could not paste clip %s", element_name); + + paste_name = gst_structure_get_string (action->structure, "paste-name"); + if (paste_name) + REPORT_UNLESS (ges_timeline_element_set_name (pasted, paste_name), + beach, "Could not set element name %s", paste_name); + +beach: + gst_clear_object (&pasted); + gst_clear_object (&element); + + /* `copied` is only used for the single paste operation, and is not + * actually in any timeline. We own it (it is actually still floating). + * `pasted` is the actual new object in the timeline. We own a + * reference to it. */ + gst_clear_object (&copied); +} + +GST_END_VALIDATE_ACTION; + +GES_START_VALIDATE_ACTION (_validate_action_execute) +{ + GError *err = NULL; + ActionFromStructureFunc func; + + gst_structure_remove_field (action->structure, "playback-time"); + if (gst_structure_has_name (action->structure, "add-keyframe") || + gst_structure_has_name (action->structure, "remove-keyframe")) { + func = _ges_add_remove_keyframe_from_struct; + } else if (gst_structure_has_name (action->structure, "set-control-source")) { + func = _ges_set_control_source_from_struct; + } else if (gst_structure_has_name (action->structure, "add-clip")) { + func = _ges_add_clip_from_struct; + } else if (gst_structure_has_name (action->structure, "container-add-child")) { + func = _ges_container_add_child_from_struct; + } else if (gst_structure_has_name (action->structure, "set-child-property")) { + func = _ges_set_child_property_from_struct; + } else { + g_assert_not_reached (); + } + + if (!func (timeline, action->structure, &err)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + g_quark_from_string ("scenario::execution-error"), + "Could not execute %s (error: %s)", + gst_structure_get_name (action->structure), + err ? err->message : "None"); + + g_clear_error (&err); + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } +} + +GST_END_VALIDATE_ACTION; + +static void +_project_loaded_cb (GESProject * project, GESTimeline * timeline, + GstValidateAction * action) +{ + gst_validate_action_set_done (action); +} + +GES_START_VALIDATE_ACTION (_load_project) +{ + GstState state; + GESProject *project = NULL; + GList *tmp, *tmp_full; + + gchar *uri = NULL; + GError *error = NULL; + const gchar *content = NULL; + + gchar *tmpfile = g_strdup_printf ("%s%s%s", g_get_tmp_dir (), + G_DIR_SEPARATOR_S, "tmpxgesload.xges"); + + res = GST_VALIDATE_EXECUTE_ACTION_ASYNC; + REPORT_UNLESS (GES_IS_PIPELINE (pipeline), local_done, + "Not a GES pipeline, can't work with it"); + + gst_element_get_state (pipeline, &state, NULL, 0); + gst_element_set_state (pipeline, GST_STATE_NULL); + + content = gst_structure_get_string (action->structure, "serialized-content"); + if (content) { + + g_file_set_contents (tmpfile, content, -1, &error); + REPORT_UNLESS (!error, local_done, + "Could not set XML content: %s", error->message); + + uri = gst_filename_to_uri (tmpfile, &error); + REPORT_UNLESS (!error, local_done, + "Could not set filename to URI: %s", error->message); + } else { + uri = g_strdup (gst_structure_get_string (action->structure, "uri")); + REPORT_UNLESS (uri, local_done, + "None of 'uri' or 'content' passed as parameter" + " can't load any timeline!"); + } + + tmp_full = ges_timeline_get_layers (timeline); + for (tmp = tmp_full; tmp; tmp = tmp->next) + ges_timeline_remove_layer (timeline, tmp->data); + g_list_free_full (tmp_full, gst_object_unref); + + tmp_full = ges_timeline_get_tracks (timeline); + for (tmp = tmp_full; tmp; tmp = tmp->next) + ges_timeline_remove_track (timeline, tmp->data); + g_list_free_full (tmp_full, gst_object_unref); + + project = ges_project_new (uri); + g_signal_connect (project, "loaded", G_CALLBACK (_project_loaded_cb), action); + ges_project_load (project, gst_object_ref (timeline), &error); + REPORT_UNLESS (!error, local_done, + "Could not load timeline: %s", error->message); + + gst_element_set_state (pipeline, state); + +local_done: + gst_clear_object (&project); + g_clear_error (&error); + g_free (uri); + g_free (tmpfile); +} + +GST_END_VALIDATE_ACTION; + +static gint +prepare_seek_action (GstValidateAction * action) +{ + gint res = GST_VALIDATE_EXECUTE_ACTION_ERROR; + GESFrameNumber fstart, fstop; + GstValidateScenario *scenario = gst_validate_action_get_scenario (action); + GstValidateActionType *type = gst_validate_get_action_type (action->type); + GError *err = NULL; + + DECLARE_AND_GET_TIMELINE (scenario, action); + + if (timeline + && ges_util_structure_get_clocktime (action->structure, "start", NULL, + &fstart)) { + GstClockTime start = ges_timeline_get_frame_time (timeline, fstart); + + if (err) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Invalid seeking frame number '%" G_GINT64_FORMAT "': %s", fstart, + err->message); + goto done; + } + gst_structure_set (action->structure, "start", G_TYPE_UINT64, start, NULL); + } + + if (timeline + && ges_util_structure_get_clocktime (action->structure, "stop", NULL, + &fstop)) { + GstClockTime stop = ges_timeline_get_frame_time (timeline, fstop); + + if (err) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Invalid seeking frame number '%" G_GINT64_FORMAT "': %s", fstop, + err->message); + goto done; + } + gst_structure_set (action->structure, "stop", G_TYPE_UINT64, stop, NULL); + } + + gst_object_unref (scenario); + gst_object_unref (timeline); + return type->overriden_type->prepare (action); + +done: + gst_object_unref (scenario); + gst_object_unref (timeline); + return res; +} + +static gint +set_layer_active (GstValidateScenario * scenario, GstValidateAction * action) +{ + gboolean active; + gint i, layer_prio; + GESLayer *layer; + GList *tracks = NULL; + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; + gchar **track_names = + gst_validate_utils_get_strv (action->structure, "tracks"); + + DECLARE_AND_GET_TIMELINE (scenario, action); + + for (i = 0; track_names[i]; i++) { + GESTrack *track = + (GESTrack *) gst_bin_get_by_name (GST_BIN (timeline), track_names[i]); + + if (!track) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not find track %s", track_names[i]); + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto done; + } + + tracks = g_list_prepend (tracks, track); + } + + if (!gst_structure_get_int (action->structure, "layer-priority", &layer_prio)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not find layer from %" GST_PTR_FORMAT, action->structure); + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto done; + } + if (!(layer = g_list_nth_data (timeline->layers, layer_prio))) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, "Could not find layer %d", layer_prio); + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto done; + } + + if (!gst_structure_get_boolean (action->structure, "active", &active)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not find 'active' boolean in %" GST_PTR_FORMAT, + action->structure); + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto done; + } + + if (!ges_layer_set_active_for_tracks (layer, active, tracks)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not set active for track defined in %" GST_PTR_FORMAT, + action->structure); + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto done; + } + +done: + g_strfreev (track_names); + gst_object_unref (timeline); + g_list_free_full (tracks, gst_object_unref); + + return res; +} + +#endif + +gboolean +ges_validate_register_action_types (void) +{ +#ifdef HAVE_GST_VALIDATE + GstValidateActionType *validate_seek, *seek_override; + + + gst_validate_init (); + validate_seek = gst_validate_get_action_type ("seek"); + + /* *INDENT-OFF* */ + seek_override = gst_validate_register_action_type("seek", "ges", validate_seek->execute, + validate_seek->parameters, validate_seek->description, + validate_seek->flags); + gst_mini_object_unref(GST_MINI_OBJECT(validate_seek)); + seek_override->prepare = prepare_seek_action; + + gst_validate_register_action_type ("edit-container", "ges", _edit, + (GstValidateActionParameter []) { + { + .name = "container-name", + .description = "The name of the GESContainer to edit", + .mandatory = TRUE, + .types = "string", + }, + { + .name = "position", + .description = "The new position of the GESContainer", + .mandatory = FALSE, + .types = "double or string", + .possible_variables = "position: The current position in the stream\n" + "duration: The duration of the stream", + NULL + }, + { + .name = "edit-mode", + .description = "The GESEditMode to use to edit @container-name", + .mandatory = FALSE, + .types = "string", + .def = "normal", + }, + { + .name = "edge", + .description = "The GESEdge to use to edit @container-name\n" + "should be in [ start, end, none ] ", + .mandatory = FALSE, + .types = "string", + .def = "none", + }, + { + .name = "new-layer-priority", + .description = "The priority of the layer @container should land in.\n" + "If the layer you're trying to move the container to doesn't exist, it will\n" + "be created automatically. -1 means no move.", + .mandatory = FALSE, + .types = "int", + .def = "-1", + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, + "Allows to edit a container (like a GESClip), for more details, have a look at:\n" + "ges_timeline_element_edit documentation, Note that the timeline will\n" + "be committed, and flushed so that the edition is taken into account", + GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("edit", "ges", _edit, + (GstValidateActionParameter []) { + { + .name = "element-name", + .description = "The name of the element to edit", + .mandatory = TRUE, + .types = "string", + }, + { + .name = "position", + .description = "The new position of the element", + .mandatory = FALSE, + .types = "double or string", + .possible_variables = "position: The current position in the stream\n" + "duration: The duration of the stream", + NULL + }, + { + .name = "source-frame", + .description = "The new frame of the element, computed from the @element-name" + "clip's source frame.", + .mandatory = FALSE, + .types = "double or string", + NULL + }, + { + .name = "edit-mode", + .description = "The GESEditMode to use to edit @element-name", + .mandatory = FALSE, + .types = "string", + .def = "normal", + }, + { + .name = "edge", + .description = "The GESEdge to use to edit @element-name\n" + "should be in [ start, end, none ] ", + .mandatory = FALSE, + .types = "string", + .def = "none", + }, + { + .name = "new-layer-priority", + .description = "The priority of the layer @element should land in.\n" + "If the layer you're trying to move the element to doesn't exist, it will\n" + "be created automatically. -1 means no move.", + .mandatory = FALSE, + .types = "int", + .def = "-1", + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, + "Allows to edit a element (like a GESClip), for more details, have a look at:\n" + "ges_timeline_element_edit documentation, Note that the timeline will\n" + "be committed, and flushed so that the edition is taken into account", + GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("add-asset", "ges", _add_asset, + (GstValidateActionParameter []) { + { + .name = "id", + .description = "Adds an asset to a project.", + .mandatory = TRUE, + NULL + }, + { + .name = "type", + .description = "The type of asset to add", + .mandatory = TRUE, + NULL + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, + "Allows to add an asset to the current project", GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("remove-asset", "ges", _remove_asset, + (GstValidateActionParameter []) { + { + .name = "id", + .description = "The ID of the clip to remove", + .mandatory = TRUE, + NULL + }, + { + .name = "type", + .description = "The type of asset to remove", + .mandatory = TRUE, + NULL + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + { NULL } + }, + "Allows to remove an asset from the current project", GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("add-layer", "ges", _add_layer, + (GstValidateActionParameter []) { + { + .name = "priority", + .description = "The priority of the new layer to add," + "if not specified, the new layer will be" + " appended to the timeline", + .mandatory = FALSE, + NULL + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + { NULL } + }, + "Allows to add a layer to the current timeline", GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("remove-layer", "ges", _remove_layer, + (GstValidateActionParameter []) { + { + .name = "priority", + .description = "The priority of the layer to remove", + .mandatory = TRUE, + NULL + }, + { + .name = "auto-transition", + .description = "Whether auto-transition is activated on the new layer.", + .mandatory = FALSE, + .types="boolean", + .def = "False" + }, + { + .name = "project-uri", + .description = "The nested timeline to add clip to", + .types = "string", + .mandatory = FALSE, + }, + { NULL } + }, + "Allows to remove a layer from the current timeline", GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("add-clip", "ges", _validate_action_execute, + (GstValidateActionParameter []) { + { + .name = "name", + .description = "The name of the clip to add", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "layer-priority", + .description = "The priority of the clip to add", + .types = "int", + .mandatory = TRUE, + }, + { + .name = "asset-id", + .description = "The id of the asset from which to extract the clip", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "type", + .description = "The type of the clip to create", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "start", + .description = "The start value to set on the new GESClip.", + .types = "double or string", + .mandatory = FALSE, + }, + { + .name = "inpoint", + .description = "The inpoint value to set on the new GESClip", + .types = "double or string", + .mandatory = FALSE, + }, + { + .name = "duration", + .description = "The duration value to set on the new GESClip", + .types = "double or string", + .mandatory = FALSE, + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, "Allows to add a clip to a given layer", GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("remove-clip", "ges", _remove_clip, + (GstValidateActionParameter []) { + { + .name = "name", + .description = "The name of the clip to remove", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, "Allows to remove a clip from a given layer", GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("serialize-project", "ges", _serialize_project, + (GstValidateActionParameter []) { + { + .name = "uri", + .description = "The uri where to store the serialized project", + .types = "string", + .mandatory = TRUE, + }, + {NULL} + }, "serializes a project", GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("set-child-property", "ges", _validate_action_execute, + (GstValidateActionParameter []) { + { + .name = "element-name", + .description = "The name of the element on which to modify the property", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "property", + .description = "The name of the property to modify", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "value", + .description = "The value of the property", + .types = "gvalue", + .mandatory = TRUE, + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, "Allows to change child property of an object", GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("set-layer-active", "ges", set_layer_active, + (GstValidateActionParameter []) { + { + .name = "layer-priority", + .description = "The priority of the layer to set activness on", + .types = "gint", + .mandatory = TRUE, + }, + { + .name = "active", + .description = "The activness of the layer", + .types = "gboolean", + .mandatory = TRUE, + }, + { + .name = "tracks", + .description = "tracks", + .types = "{string, }", + .mandatory = FALSE, + }, + {NULL} + }, "Set activness of a layer (on optional tracks).", + GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("set-ges-properties", "ges", set_or_check_properties, + (GstValidateActionParameter []) { + { + .name = "element-name", + .description = "The name of the element on which to set properties", + .types = "string", + .mandatory = TRUE, + }, + {NULL} + }, "Set `element-name` properties values defined by the" + " fields in the following format: `property_name=expected-value`", + GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("check-ges-properties", "ges", set_or_check_properties, + (GstValidateActionParameter []) { + { + .name = "element-name", + .description = "The name of the element on which to check properties", + .types = "string", + .mandatory = TRUE, + }, + {NULL} + }, "Check `element-name` properties values defined by the" + " fields in the following format: `property_name=expected-value`", + GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("check-child-properties", "ges", set_or_check_properties, + (GstValidateActionParameter []) { + { + .name = "element-name", + .description = "The name of the element on which to check children properties", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "at-time", + .description = "The time at which to check the values, taking into" + " account the ControlBinding if any set.", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, "Check `element-name` children properties values defined by the" + " fields in the following format: `property_name=expected-value`", + GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("set-child-properties", "ges", set_or_check_properties, + (GstValidateActionParameter []) { + { + .name = "element-name", + .description = "The name of the element on which to modify child properties", + .types = "string", + .mandatory = TRUE, + }, + {NULL} + }, "Sets `element-name` children properties values defined by the" + " fields in the following format: `property-name=new-value`", + GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("split-clip", "ges", _split_clip, + (GstValidateActionParameter []) { + { + .name = "clip-name", + .description = "The name of the clip to split", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "position", + .description = "The position at which to split the clip", + .types = "double or string", + .mandatory = TRUE, + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, "Split a clip at a specified position.", GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("set-track-restriction-caps", "ges", _set_track_restriction_caps, + (GstValidateActionParameter []) { + { + .name = "track-type", + .description = "The type of track to set restriction caps on", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "caps", + .description = "The caps to set on the track", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, "Sets restriction caps on tracks of a specific type.", GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("element-set-asset", "ges", _set_asset_on_element, + (GstValidateActionParameter []) { + { + .name = "element-name", + .description = "The name of the TimelineElement to set an asset on", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "asset-id", + .description = "The id of the asset from which to extract the clip", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, "Sets restriction caps on tracks of a specific type.", GST_VALIDATE_ACTION_TYPE_NONE); + + + gst_validate_register_action_type ("container-add-child", "ges", _validate_action_execute, + (GstValidateActionParameter []) { + { + .name = "container-name", + .description = "The name of the GESContainer to add a child to", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "child-name", + .description = "The name of the child to add to @container-name", + .types = "string", + .mandatory = FALSE, + .def = "NULL" + }, + { + .name = "asset-id", + .description = "The id of the asset from which to extract the child", + .types = "string", + .mandatory = TRUE, + .def = "NULL" + }, + { + .name = "child-type", + .description = "The type of the child to create", + .types = "string", + .mandatory = FALSE, + .def = "NULL" + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, "Add a child to @container-name. If asset-id and child-type are specified," + " the child will be created and added. Otherwise @child-name has to be specified" + " and will be added to the container.", GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("container-remove-child", "ges", _container_remove_child, + (GstValidateActionParameter []) { + { + .name = "container-name", + .description = "The name of the GESContainer to remove a child from", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "child-name", + .description = "The name of the child to reomve from @container-name", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, "Remove a child from @container-name.", FALSE); + + gst_validate_register_action_type ("ungroup-container", "ges", _ungroup, + (GstValidateActionParameter []) { + { + .name = "container-name", + .description = "The name of the GESContainer to ungroup children from", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "recursive", + .description = "Whether to recurse ungrouping or not.", + .types = "boolean", + .mandatory = FALSE, + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, "Ungroup children of @container-name.", FALSE); + + gst_validate_register_action_type ("set-control-source", "ges", _validate_action_execute, + (GstValidateActionParameter []) { + { + .name = "element-name", + .description = "The name of the GESTrackElement to set the control source on", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "property-name", + .description = "The name of the property for which to set a control source", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "binding-type", + .description = "The name of the type of binding to use", + .types = "string", + .mandatory = FALSE, + .def = "direct", + }, + { + .name = "source-type", + .description = "The name of the type of ControlSource to use", + .types = "string", + .mandatory = FALSE, + .def = "interpolation", + }, + { + .name = "interpolation-mode", + .description = "The name of the GstInterpolationMode to on the source", + .types = "string", + .mandatory = FALSE, + .def = "linear", + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, "Adds a GstControlSource on @element-name::@property-name" + " allowing you to then add keyframes on that property.", GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("add-keyframe", "ges", _validate_action_execute, + (GstValidateActionParameter []) { + { + .name = "element-name", + .description = "The name of the GESTrackElement to add a keyframe on", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "property-name", + .description = "The name of the property for which to add a keyframe on", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "timestamp", + .description = "The timestamp of the keyframe", + .types = "string or float", + .mandatory = TRUE, + }, + { + .name = "value", + .description = "The value of the keyframe", + .types = "float", + .mandatory = TRUE, + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, "Set a keyframe on @element-name:property-name.", GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("copy-element", "ges", _copy_element, + (GstValidateActionParameter []) { + { + .name = "element-name", + .description = "The name of the GESTtimelineElement to copy", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "recurse", + .description = "Copy recursively or not", + .types = "boolean", + .def = "true", + .mandatory = FALSE, + }, + { + .name = "position", + .description = "The time where to paste the element", + .types = "string or float", + .mandatory = TRUE, + }, + { + .name = "paste-name", + .description = "The name of the copied element", + .types = "string", + .mandatory = FALSE, + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, "Remove a child from @container-name.", GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("remove-keyframe", "ges", _validate_action_execute, + (GstValidateActionParameter []) { + { + .name = "element-name", + .description = "The name of the GESTrackElement to add a keyframe on", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "property-name", + .description = "The name of the property for which to add a keyframe on", + .types = "string", + .mandatory = TRUE, + }, + { + .name = "timestamp", + .description = "The timestamp of the keyframe", + .types = "string or float", + .mandatory = TRUE, + }, + { + .name = "project-uri", + .description = "The project URI with the serialized timeline to execute the action on", + .types = "string", + .mandatory = FALSE, + }, + {NULL} + }, "Remove a keyframe on @element-name:property-name.", GST_VALIDATE_ACTION_TYPE_NONE); + + gst_validate_register_action_type ("load-project", "ges", _load_project, + (GstValidateActionParameter []) { + { + .name = "serialized-content", + .description = "The full content of the XML describing project in XGES format.", + .mandatory = FALSE, + .types = "string", + NULL + }, + { + .name = "uri", + .description = "The uri of the project to load (used only if serialized-content is not provided)", + .mandatory = FALSE, + .types = "string", + NULL + }, + {NULL} + }, + "Loads a project either from its content passed in the 'serialized-content' field or using the provided 'uri'.\n" + "Note that it will completely clean the previous timeline", + GST_VALIDATE_ACTION_TYPE_NONE); + + + gst_validate_register_action_type ("commit", "ges", _commit, NULL, + "Commit the timeline.", GST_VALIDATE_ACTION_TYPE_ASYNC); + /* *INDENT-ON* */ + + return TRUE; +#else + return FALSE; +#endif +} diff --git a/ges/ges-version.h.in b/ges/ges-version.h.in new file mode 100644 index 0000000000..60ed5f9874 --- /dev/null +++ b/ges/ges-version.h.in @@ -0,0 +1,29 @@ +/* GStreamer Editing Services + * Copyright (C) 2014 Thibault Saunier <tsaunier@gnome.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +G_BEGIN_DECLS + +#define GES_VERSION_MAJOR (@GES_VERSION_MAJOR@) +#define GES_VERSION_MINOR (@GES_VERSION_MINOR@) +#define GES_VERSION_MICRO (@GES_VERSION_MICRO@) +#define GES_VERSION_NANO (@GES_VERSION_NANO@) + +G_END_DECLS diff --git a/ges/ges-video-source.c b/ges/ges-video-source.c new file mode 100644 index 0000000000..ce8394100f --- /dev/null +++ b/ges/ges-video-source.c @@ -0,0 +1,291 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesvideosource + * @title: GESVideoSource + * @short_description: Base Class for video sources + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/pbutils/missing-plugins.h> +#include <gst/video/video.h> + +#include "ges-internal.h" +#include "ges/ges-meta-container.h" +#include "ges-track-element.h" +#include "ges-video-source.h" +#include "ges-layer.h" +#include "gstframepositioner.h" +#include "ges-extractable.h" + +#define parent_class ges_video_source_parent_class +static GESExtractableInterface *parent_extractable_iface = NULL; + +struct _GESVideoSourcePrivate +{ + GstFramePositioner *positioner; + GstElement *capsfilter; +}; + +static void +ges_video_source_set_asset (GESExtractable * extractable, GESAsset * asset) +{ + GESVideoSource *self = GES_VIDEO_SOURCE (extractable); + + parent_extractable_iface->set_asset (extractable, asset); + + ges_video_source_get_natural_size (self, + &self->priv->positioner->natural_width, + &self->priv->positioner->natural_height); +} + +static void +ges_extractable_interface_init (GESExtractableInterface * iface) +{ + iface->set_asset = ges_video_source_set_asset; + + parent_extractable_iface = g_type_interface_peek_parent (iface); +} + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESVideoSource, ges_video_source, + GES_TYPE_SOURCE, G_ADD_PRIVATE (GESVideoSource) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, + ges_extractable_interface_init)); + +/* TrackElement VMethods */ + +static gboolean +_set_priority (GESTimelineElement * element, guint32 priority) +{ + gboolean res; + GESVideoSource *self = GES_VIDEO_SOURCE (element); + + res = GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_priority (element, + priority); + + if (res && self->priv->positioner) + g_object_set (self->priv->positioner, "zorder", G_MAXUINT - priority, NULL); + + return res; +} + + +static gboolean +_set_parent (GESTimelineElement * element, GESTimelineElement * parent) +{ + GESVideoSource *self = GES_VIDEO_SOURCE (element); + + if (!parent) + return TRUE; + + /* Some subclass might have different access to its natural size only + * once it knows its parent */ + ges_video_source_get_natural_size (GES_VIDEO_SOURCE (self), + &self->priv->positioner->natural_width, + &self->priv->positioner->natural_height); + + return TRUE; +} + + +static gboolean +ges_video_source_create_filters (GESVideoSource * self, GPtrArray * elements, + gboolean needs_converters) +{ + GESTrackElement *trksrc = GES_TRACK_ELEMENT (self); + GstElement *positioner, *videoflip, *capsfilter; + const gchar *positioner_props[] + = { "alpha", "posx", "posy", "width", "height", "operator", NULL }; + const gchar *videoflip_props[] = { "video-direction", NULL }; + + g_ptr_array_add (elements, gst_element_factory_make ("queue", NULL)); + + /* That positioner will add metadata to buffers according to its + properties, acting like a proxy for our smart-mixer dynamic pads. */ + positioner = gst_element_factory_make ("framepositioner", NULL); + g_object_set (positioner, "zorder", + G_MAXUINT - GES_TIMELINE_ELEMENT_PRIORITY (self), NULL); + g_ptr_array_add (elements, positioner); + + if (needs_converters) + g_ptr_array_add (elements, gst_element_factory_make ("videoconvert", NULL)); + + /* If there's image-orientation tag, make sure the image is correctly oriented + * before we scale it. */ + videoflip = gst_element_factory_make ("videoflip", "track-element-videoflip"); + g_object_set (videoflip, "video-direction", GST_VIDEO_ORIENTATION_AUTO, NULL); + g_ptr_array_add (elements, videoflip); + + if (needs_converters) { + g_ptr_array_add (elements, gst_element_factory_make ("videoscale", + "track-element-videoscale")); + g_ptr_array_add (elements, gst_element_factory_make ("videoconvert", + "track-element-videoconvert")); + } + g_ptr_array_add (elements, gst_element_factory_make ("videorate", + "track-element-videorate")); + + capsfilter = + gst_element_factory_make ("capsfilter", "track-element-capsfilter"); + g_ptr_array_add (elements, capsfilter); + + ges_frame_positioner_set_source_and_filter (GST_FRAME_POSITIONNER + (positioner), trksrc, capsfilter); + + ges_track_element_add_children_props (trksrc, positioner, NULL, NULL, + positioner_props); + ges_track_element_add_children_props (trksrc, videoflip, NULL, NULL, + videoflip_props); + + self->priv->positioner = GST_FRAME_POSITIONNER (positioner); + self->priv->positioner->scale_in_compositor = + !GES_VIDEO_SOURCE_GET_CLASS (self)->ABI.abi.disable_scale_in_compositor; + ges_video_source_get_natural_size (self, + &self->priv->positioner->natural_width, + &self->priv->positioner->natural_height); + + self->priv->capsfilter = capsfilter; + + return TRUE; +} + +static GstElement * +ges_video_source_create_element (GESTrackElement * trksrc) +{ + GstElement *topbin; + GstElement *sub_element; + GESVideoSourceClass *vsource_class = GES_VIDEO_SOURCE_GET_CLASS (trksrc); + GESSourceClass *source_class = GES_SOURCE_GET_CLASS (trksrc); + GESVideoSource *self; + gboolean needs_converters = TRUE; + GPtrArray *elements; + + if (!source_class->create_source) + return NULL; + + sub_element = source_class->create_source (GES_SOURCE (trksrc)); + + self = (GESVideoSource *) trksrc; + if (vsource_class->ABI.abi.needs_converters) + needs_converters = vsource_class->ABI.abi.needs_converters (self); + + elements = g_ptr_array_new (); + g_assert (vsource_class->ABI.abi.create_filters); + if (!vsource_class->ABI.abi.create_filters (self, elements, needs_converters)) { + g_ptr_array_free (elements, TRUE); + + return NULL; + } + + topbin = ges_source_create_topbin (GES_SOURCE (trksrc), "videosrcbin", + sub_element, elements); + + return topbin; +} + +static gboolean +_lookup_child (GESTimelineElement * object, + const gchar * prop_name, GObject ** element, GParamSpec ** pspec) +{ + gboolean res; + + gchar *clean_name; + + if (!g_strcmp0 (prop_name, "deinterlace-fields")) + clean_name = g_strdup ("GstDeinterlace::fields"); + else if (!g_strcmp0 (prop_name, "deinterlace-mode")) + clean_name = g_strdup ("GstDeinterlace::mode"); + else if (!g_strcmp0 (prop_name, "deinterlace-tff")) + clean_name = g_strdup ("GstDeinterlace::tff"); + else if (!g_strcmp0 (prop_name, "tff") || + !g_strcmp0 (prop_name, "fields") || !g_strcmp0 (prop_name, "mode")) { + GST_DEBUG_OBJECT (object, "Not allowed to use GstDeinterlace %s" + " property without prefixing its name", prop_name); + return FALSE; + } else + clean_name = g_strdup (prop_name); + + res = + GES_TIMELINE_ELEMENT_CLASS (ges_video_source_parent_class)->lookup_child + (object, clean_name, element, pspec); + + g_free (clean_name); + + return res; +} + +static void +ges_video_source_class_init (GESVideoSourceClass * klass) +{ + GESTrackElementClass *track_element_class = GES_TRACK_ELEMENT_CLASS (klass); + GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass); + GESVideoSourceClass *video_source_class = GES_VIDEO_SOURCE_CLASS (klass); + + element_class->set_priority = _set_priority; + element_class->lookup_child = _lookup_child; + element_class->set_parent = _set_parent; + + track_element_class->nleobject_factorytype = "nlesource"; + track_element_class->create_element = ges_video_source_create_element; + track_element_class->ABI.abi.default_track_type = GES_TRACK_TYPE_VIDEO; + + video_source_class->ABI.abi.create_filters = ges_video_source_create_filters; +} + +static void +ges_video_source_init (GESVideoSource * self) +{ + self->priv = ges_video_source_get_instance_private (self); + self->priv->positioner = NULL; + self->priv->capsfilter = NULL; +} + +/** + * ges_video_source_get_natural_size: + * @self: A #GESVideoSource + * @width: (out): The natural width of the underlying source + * @height: (out): The natural height of the underlying source + * + * Retrieves the natural size of the video stream. The natural size, is + * the size at which it will be displayed if no scaling is being applied. + * + * NOTE: The sources take into account the potential video rotation applied + * by the #videoflip element that is inside the source, effects applied on + * the clip which potentially also rotate the element are not taken into + * account. + * + * Returns: %TRUE if the object has a natural size, %FALSE otherwise. + * + * Since: 1.18 + */ +gboolean +ges_video_source_get_natural_size (GESVideoSource * self, gint * width, + gint * height) +{ + GESVideoSourceClass *klass = GES_VIDEO_SOURCE_GET_CLASS (self); + + if (!klass->ABI.abi.get_natural_size) + return FALSE; + + return klass->ABI.abi.get_natural_size (self, width, height); +} diff --git a/ges/ges-video-source.h b/ges/ges-video-source.h new file mode 100644 index 0000000000..d2a895dfe4 --- /dev/null +++ b/ges/ges-video-source.h @@ -0,0 +1,89 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <gst/gst.h> +#include <ges/ges-types.h> +#include <ges/ges-track-element.h> +#include <ges/ges-source.h> + +G_BEGIN_DECLS + +#define GES_TYPE_VIDEO_SOURCE ges_video_source_get_type() +GES_DECLARE_TYPE(VideoSource, video_source, VIDEO_SOURCE); + +/** + * GESVideoSource: + * + * Base class for video sources + */ + +struct _GESVideoSource { + /*< private >*/ + GESSource parent; + + GESVideoSourcePrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESVideoSourceClass: + * @create_source: method to return the GstElement to put in the source topbin. + * Other elements will be queued, like a videoscale. + * In the case of a VideoUriSource, for example, the subclass will return a decodebin, + * and we will append a videoscale. + */ +struct _GESVideoSourceClass { + /*< private >*/ + GESSourceClass parent_class; + + /*< public >*/ + /** + * GESVideoSource::create_element: + * @object: The #GESTrackElement + * + * Returns: (transfer floating): the #GstElement that the underlying nleobject + * controls. + * + * Deprecated: 1.20: Use #GESSourceClass::create_element instead. + */ + GstElement* (*create_source) (GESTrackElement * object); + + /*< private >*/ + /* Padding for API extension */ + union { + gpointer _ges_reserved[GES_PADDING]; + struct { + gboolean disable_scale_in_compositor; + gboolean (*needs_converters)(GESVideoSource *self); + gboolean (*get_natural_size)(GESVideoSource* self, gint* width, gint* height); + gboolean (*create_filters)(GESVideoSource *self, GPtrArray *filters, gboolean needs_converters); + } abi; + } ABI; +}; + +GES_API +gboolean ges_video_source_get_natural_size(GESVideoSource* self, gint* width, gint* height); + +G_END_DECLS diff --git a/ges/ges-video-test-source.c b/ges/ges-video-test-source.c new file mode 100644 index 0000000000..7d0ae580f2 --- /dev/null +++ b/ges/ges-video-test-source.c @@ -0,0 +1,333 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2010 Nokia Corporation + * Copyright (C) 2020 Igalia S.L + * Author: 2020 Thibault Saunier <tsaunier@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesvideotestsource + * @title: GESVideoTestSource + * @short_description: produce solid colors and patterns, possibly with a time + * overlay. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-internal.h" +#include "ges-track-element.h" +#include "ges-video-test-source.h" + +#define DEFAULT_VPATTERN GES_VIDEO_TEST_PATTERN_SMPTE + +struct _GESVideoTestSourcePrivate +{ + GESVideoTestPattern pattern; + + GstElement *capsfilter; +}; + +static void +ges_extractable_interface_init (GESExtractableInterface * iface) +{ + iface->check_id = ges_test_source_asset_check_id; + iface->asset_type = GES_TYPE_TRACK_ELEMENT_ASSET; +} + +static GstStructure * +ges_video_test_source_asset_get_config (GESAsset * asset) +{ + const gchar *id = ges_asset_get_id (asset); + if (g_strcmp0 (id, g_type_name (ges_asset_get_extractable_type (asset)))) + return gst_structure_from_string (ges_asset_get_id (asset), NULL); + + return NULL; +} + +G_DEFINE_TYPE_WITH_CODE (GESVideoTestSource, ges_video_test_source, + GES_TYPE_VIDEO_SOURCE, G_ADD_PRIVATE (GESVideoTestSource) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, + ges_extractable_interface_init)); + +static GstElement *ges_video_test_source_create_source (GESSource * source); + +static gboolean +get_natural_size (GESVideoSource * source, gint * width, gint * height) +{ + gboolean res = FALSE; + GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (source); + + if (parent) { + GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (parent)); + + if (asset) + res = ges_test_clip_asset_get_natural_size (asset, width, height); + } + + if (!res) { + *width = DEFAULT_WIDTH; + *height = DEFAULT_HEIGHT; + } + + return TRUE; +} + +static gboolean +_set_parent (GESTimelineElement * element, GESTimelineElement * parent) +{ + gint width, height, fps_n, fps_d; + GstCaps *caps; + GESVideoTestSource *self = GES_VIDEO_TEST_SOURCE (element); + + + if (!parent) + goto done; + + g_assert (self->priv->capsfilter); + /* Setting the parent ourself as we need it to get the natural size */ + element->parent = parent; + if (!ges_video_source_get_natural_size (GES_VIDEO_SOURCE (self), &width, + &height)) { + width = DEFAULT_WIDTH; + height = DEFAULT_HEIGHT; + } + + if (!ges_timeline_element_get_natural_framerate (parent, &fps_n, &fps_d)) { + fps_n = DEFAULT_FRAMERATE_N; + fps_d = DEFAULT_FRAMERATE_D; + } + + caps = gst_caps_new_simple ("video/x-raw", + "width", G_TYPE_INT, width, + "height", G_TYPE_INT, height, + "framerate", GST_TYPE_FRACTION, fps_n, fps_d, NULL); + g_object_set (self->priv->capsfilter, "caps", caps, NULL); + gst_caps_unref (caps); + +done: + return + GES_TIMELINE_ELEMENT_CLASS + (ges_video_test_source_parent_class)->set_parent (element, parent); +} + +static gboolean +_get_natural_framerate (GESTimelineElement * element, gint * fps_n, + gint * fps_d) +{ + gboolean res = FALSE; + GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (element); + + if (parent) { + GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (parent)); + + if (asset) { + res = + ges_clip_asset_get_natural_framerate (GES_CLIP_ASSET (asset), fps_n, + fps_d); + } + } + + if (!res) { + *fps_n = DEFAULT_FRAMERATE_N; + *fps_d = DEFAULT_FRAMERATE_D; + } + + return TRUE; +} + +static void +dispose (GObject * object) +{ + G_OBJECT_CLASS (ges_video_test_source_parent_class)->dispose (object); +} + +static void +ges_video_test_source_class_init (GESVideoTestSourceClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESVideoSourceClass *vsource_class = GES_VIDEO_SOURCE_CLASS (klass); + GESSourceClass *source_class = GES_SOURCE_CLASS (klass); + + source_class->create_source = ges_video_test_source_create_source; + vsource_class->ABI.abi.get_natural_size = get_natural_size; + + object_class->dispose = dispose; + + GES_TIMELINE_ELEMENT_CLASS (klass)->set_parent = _set_parent; + GES_TIMELINE_ELEMENT_CLASS (klass)->get_natural_framerate = + _get_natural_framerate; +} + +static void +ges_video_test_source_init (GESVideoTestSource * self) +{ + self->priv = ges_video_test_source_get_instance_private (self); + + self->priv->pattern = DEFAULT_VPATTERN; +} + +static GstStructure * +ges_video_test_source_get_config (GESVideoTestSource * self) +{ + GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (self)); + if (asset) + return ges_video_test_source_asset_get_config (asset); + + return NULL; +} + +static GstElement * +ges_video_test_source_create_overlay (GESVideoTestSource * self) +{ + const gchar *bindesc = NULL; + GstStructure *config = ges_video_test_source_get_config (self); + + if (!config) + return NULL; + + if (gst_structure_has_name (config, "time-overlay")) { + gboolean disable_timecodestamper; + + if (gst_structure_get_boolean (config, "disable-timecodestamper", + &disable_timecodestamper)) + bindesc = "timeoverlay"; + else + bindesc = "timecodestamper ! timeoverlay"; + } + gst_structure_free (config); + + if (!bindesc) + return NULL; + + return gst_parse_bin_from_description (bindesc, TRUE, NULL); +} + +static GstElement * +ges_video_test_source_create_source (GESSource * source) +{ + GstCaps *caps; + gint pattern; + GstElement *testsrc, *res; + GstElement *overlay; + const gchar *props[] = + { "pattern", "background-color", "foreground-color", NULL }; + GPtrArray *elements; + GESVideoTestSource *self = GES_VIDEO_TEST_SOURCE (source); + GESTrackElement *element = GES_TRACK_ELEMENT (source); + + g_assert (!GES_TIMELINE_ELEMENT_PARENT (source)); + testsrc = gst_element_factory_make ("videotestsrc", NULL); + self->priv->capsfilter = gst_element_factory_make ("capsfilter", NULL); + pattern = self->priv->pattern; + + g_object_set (testsrc, "pattern", pattern, NULL); + + elements = g_ptr_array_new (); + g_ptr_array_add (elements, self->priv->capsfilter); + caps = gst_caps_new_simple ("video/x-raw", + "width", G_TYPE_INT, DEFAULT_WIDTH, + "height", G_TYPE_INT, DEFAULT_HEIGHT, + "framerate", GST_TYPE_FRACTION, DEFAULT_FRAMERATE_N, DEFAULT_FRAMERATE_D, + NULL); + g_object_set (self->priv->capsfilter, "caps", caps, NULL); + gst_caps_unref (caps); + + overlay = ges_video_test_source_create_overlay (self); + if (overlay) { + const gchar *overlay_props[] = + { "time-mode", "text-y", "text-x", "text-width", "test-height", + "halignment", "valignment", "font-desc", NULL + }; + + ges_track_element_add_children_props (element, overlay, NULL, + NULL, overlay_props); + g_ptr_array_add (elements, overlay); + } + + ges_track_element_add_children_props (element, testsrc, NULL, NULL, props); + + res = ges_source_create_topbin (GES_SOURCE (element), "videotestsrc", testsrc, + elements); + + return res; +} + +/** + * ges_video_test_source_set_pattern: + * @self: a #GESVideoTestSource + * @pattern: a #GESVideoTestPattern + * + * Sets the source to use the given @pattern. + */ +void +ges_video_test_source_set_pattern (GESVideoTestSource + * self, GESVideoTestPattern pattern) +{ + GstElement *element = + ges_track_element_get_element (GES_TRACK_ELEMENT (self)); + + self->priv->pattern = pattern; + + if (element) { + GValue val = { 0 }; + + g_value_init (&val, GES_VIDEO_TEST_PATTERN_TYPE); + g_value_set_enum (&val, pattern); + ges_track_element_set_child_property (GES_TRACK_ELEMENT (self), "pattern", + &val); + } +} + +/** + * ges_video_test_source_get_pattern: + * @source: a #GESVideoTestPattern + * + * Get the video pattern used by the @source. + * + * Returns: The video pattern used by the @source. + */ +GESVideoTestPattern +ges_video_test_source_get_pattern (GESVideoTestSource * source) +{ + GValue val = { 0 }; + + ges_track_element_get_child_property (GES_TRACK_ELEMENT (source), "pattern", + &val); + return g_value_get_enum (&val); +} + +/** + * ges_video_test_source_new: + * + * Creates a new #GESVideoTestSource. + * + * Returns: (transfer floating) (nullable): The newly created + * #GESVideoTestSource, or %NULL if there was an error. + */ +GESVideoTestSource * +ges_video_test_source_new (void) +{ + GESVideoTestSource *res; + GESAsset *asset = ges_asset_request (GES_TYPE_VIDEO_TEST_SOURCE, NULL, NULL); + + res = GES_VIDEO_TEST_SOURCE (ges_asset_extract (asset, NULL)); + gst_object_unref (asset); + + return res; +} diff --git a/ges/ges-video-test-source.h b/ges/ges-video-test-source.h new file mode 100644 index 0000000000..e6355afb50 --- /dev/null +++ b/ges/ges-video-test-source.h @@ -0,0 +1,65 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2010 Nokia Corporation + * Copyright (C) 2020 Igalia S.L + * Author: 2020 Thibault Saunier <tsaunier@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-enums.h> +#include <ges/ges-types.h> +#include <ges/ges-video-source.h> + +G_BEGIN_DECLS + +#define GES_TYPE_VIDEO_TEST_SOURCE ges_video_test_source_get_type() +GES_DECLARE_TYPE(VideoTestSource, video_test_source, VIDEO_TEST_SOURCE); + +/** + * GESVideoTestSource: + * + * ### Children Properties + * + * {{ libs/GESVideoTestSource-children-props.md }} + */ +struct _GESVideoTestSource { + /*< private >*/ + GESVideoSource parent; + + GESVideoTestSourcePrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESVideoTestSourceClass { + GESVideoSourceClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API void +ges_video_test_source_set_pattern(GESVideoTestSource *self, + GESVideoTestPattern pattern); +GES_API GESVideoTestPattern +ges_video_test_source_get_pattern (GESVideoTestSource *source); + +G_END_DECLS diff --git a/ges/ges-video-track.c b/ges/ges-video-track.c new file mode 100644 index 0000000000..1cfe9c7b44 --- /dev/null +++ b/ges/ges-video-track.c @@ -0,0 +1,189 @@ +/* GStreamer Editing Services + * Copyright (C) <2013> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION: gesvideotrack + * @title: GESVideoTrack + * @short_description: A standard GESTrack for raw video + * + * A #GESVideoTrack is a default video #GESTrack, with a + * #GES_TRACK_TYPE_VIDEO #GESTrack:track-type and "video/x-raw(ANY)" + * #GESTrack:caps. + * + * By default, a video track will have its #GESTrack:restriction-caps + * set to "video/x-raw" with the following properties: + * + * - width: 1280 + * - height: 720 + * - framerate: 30/1 + * + * These fields are needed for negotiation purposes, but you can change + * their values if you wish. It is advised that you do so using + * ges_track_update_restriction_caps() with new values for the fields you + * wish to change, and any additional fields you may want to add. Unlike + * using ges_track_set_restriction_caps(), this will ensure that these + * default fields will at least have some value set. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-video-track.h" +#include "ges-smart-video-mixer.h" +#include "ges-internal.h" + +#define DEFAULT_RESTRICTION_CAPS "video/x-raw(ANY), framerate=" G_STRINGIFY(DEFAULT_FRAMERATE_N) "/" G_STRINGIFY(DEFAULT_FRAMERATE_D) ", width=" G_STRINGIFY(DEFAULT_WIDTH) ", height=" G_STRINGIFY(DEFAULT_HEIGHT) + +struct _GESVideoTrackPrivate +{ + gpointer nothing; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GESVideoTrack, ges_video_track, GES_TYPE_TRACK); + +static void +_sync_capsfilter_with_track (GESTrack * track, GstElement * capsfilter) +{ + GstCaps *restriction, *caps; + gint fps_n, fps_d; + GstStructure *structure; + + g_object_get (track, "restriction-caps", &restriction, NULL); + if (restriction == NULL) + return; + + if (gst_caps_get_size (restriction) == 0) + goto done; + + structure = gst_caps_get_structure (restriction, 0); + if (!gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d)) + goto done; + + caps = + gst_caps_new_simple ("video/x-raw", "framerate", GST_TYPE_FRACTION, fps_n, + fps_d, NULL); + + g_object_set (capsfilter, "caps", caps, NULL); + gst_caps_unref (caps); + +done: + gst_caps_unref (restriction); +} + +static void +_track_restriction_changed_cb (GESTrack * track, GParamSpec * arg G_GNUC_UNUSED, + GstElement * capsfilter) +{ + _sync_capsfilter_with_track (track, capsfilter); +} + +static void +_weak_notify_cb (GESTrack * track, GstElement * capsfilter) +{ + g_signal_handlers_disconnect_by_func (track, + (GCallback) _track_restriction_changed_cb, capsfilter); +} + +static GstElement * +create_element_for_raw_video_gap (GESTrack * track) +{ + GstElement *bin; + GstElement *capsfilter; + + bin = gst_parse_bin_from_description + ("videotestsrc pattern=2 name=src ! videorate ! capsfilter name=gapfilter caps=video/x-raw", + TRUE, NULL); + + capsfilter = gst_bin_get_by_name (GST_BIN (bin), "gapfilter"); + g_object_weak_ref (G_OBJECT (capsfilter), (GWeakNotify) _weak_notify_cb, + track); + g_signal_connect (track, "notify::restriction-caps", + (GCallback) _track_restriction_changed_cb, capsfilter); + + _sync_capsfilter_with_track (track, capsfilter); + + gst_object_unref (capsfilter); + + return bin; +} + +static void +ges_video_track_init (GESVideoTrack * ges_video_track) +{ +/* GESVideoTrackPrivate *priv = GES_VIDEO_TRACK_GET_PRIVATE (ges_video_track); + */ + + /* TODO: Add initialization code here */ +} + +static void +ges_video_track_finalize (GObject * object) +{ + /* TODO: Add deinitalization code here */ + + G_OBJECT_CLASS (ges_video_track_parent_class)->finalize (object); +} + +static void +ges_video_track_class_init (GESVideoTrackClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); +/* GESTrackClass *parent_class = GES_TRACK_CLASS (klass); + */ + + object_class->finalize = ges_video_track_finalize; + + GES_TRACK_CLASS (klass)->get_mixing_element = ges_smart_mixer_new; +} + +/** + * ges_video_track_new: + * + * Creates a new video track, with a #GES_TRACK_TYPE_VIDEO + * #GESTrack:track-type and "video/x-raw(ANY)" #GESTrack:caps, and + * "video/x-raw" #GESTrack:restriction-caps with the properties: + * + * - width: 1280 + * - height: 720 + * - framerate: 30/1 + * + * You should use ges_track_update_restriction_caps() if you wish to + * modify these fields, or add additional ones. + * + * Returns: (transfer floating): The newly created video track. + */ +GESVideoTrack * +ges_video_track_new (void) +{ + GESVideoTrack *ret; + GstCaps *caps = gst_caps_new_empty_simple ("video/x-raw"); + GstCaps *restriction_caps = gst_caps_from_string (DEFAULT_RESTRICTION_CAPS); + + ret = g_object_new (GES_TYPE_VIDEO_TRACK, "track-type", GES_TRACK_TYPE_VIDEO, + "caps", caps, NULL); + + ges_track_set_create_element_for_gap_func (GES_TRACK (ret), + create_element_for_raw_video_gap); + ges_track_set_restriction_caps (GES_TRACK (ret), restriction_caps); + + gst_caps_unref (caps); + gst_caps_unref (restriction_caps); + + return ret; +} diff --git a/ges/ges-video-track.h b/ges/ges-video-track.h new file mode 100644 index 0000000000..1f0e9256f0 --- /dev/null +++ b/ges/ges-video-track.h @@ -0,0 +1,53 @@ +/* GStreamer Editing Services + * Copyright (C) <2013> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> + +#include "ges-track.h" +#include "ges-types.h" + +G_BEGIN_DECLS +#define GES_TYPE_VIDEO_TRACK (ges_video_track_get_type ()) +GES_DECLARE_TYPE(VideoTrack, video_track, VIDEO_TRACK); + +struct _GESVideoTrackClass +{ + GESTrackClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESVideoTrack +{ + GESTrack parent_instance; + + /*< private >*/ + GESVideoTrackPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_API +GESVideoTrack * ges_video_track_new (void); + +G_END_DECLS diff --git a/ges/ges-video-transition.c b/ges/ges-video-transition.c new file mode 100644 index 0000000000..e84af2569f --- /dev/null +++ b/ges/ges-video-transition.c @@ -0,0 +1,709 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesvideotransition + * @title: GESVideoTransition + * @short_description: implements video crossfade transition + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <ges/ges.h> +#include "ges-internal.h" +#include "ges-smart-video-mixer.h" + +#include <gst/controller/gstdirectcontrolbinding.h> + +#define parent_class ges_video_transition_parent_class + +static inline void +ges_video_transition_set_border_internal (GESVideoTransition * self, + guint border); +static inline void +ges_video_transition_set_inverted_internal (GESVideoTransition * + self, gboolean inverted); +static inline gboolean +ges_video_transition_set_transition_type_internal (GESVideoTransition + * self, GESVideoStandardTransitionType type); +struct _GESVideoTransitionPrivate +{ + GESVideoStandardTransitionType type; + + /* prevents cases where the transitions have not been created yet */ + GESVideoStandardTransitionType pending_type; + + /* these enable video interpolation */ + GstTimedValueControlSource *fade_in_control_source; + GstTimedValueControlSource *fade_out_control_source; + GstTimedValueControlSource *smpte_control_source; + + /* so we can support changing between wipes */ + GstElement *smpte; + + GstPad *mixer_sink; + + GstElement *mixer; + GstPad *mixer_sinka; + GstPad *mixer_sinkb; + + GstPad *mixer_ghosta; + GstPad *mixer_ghostb; + + /* This is in case the smpte doesn't exist yet */ + gint pending_border_value; + gboolean pending_inverted; + + GstElement *positioner; +}; + +enum +{ + PROP_0, + PROP_BORDER, + PROP_TRANSITION_TYPE, + PROP_INVERT, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +G_DEFINE_TYPE_WITH_PRIVATE (GESVideoTransition, ges_video_transition, + GES_TYPE_TRANSITION); + +#define fast_element_link(a,b) gst_element_link_pads_full((a),"src",(b),"sink",GST_PAD_LINK_CHECK_NOTHING) + +static GObject *link_element_to_mixer_with_smpte (GstBin * bin, + GstElement * element, GstElement * mixer, gint type, + GstElement ** smpteref, GESVideoTransitionPrivate * priv, GstPad ** ghost); + +static void +ges_video_transition_duration_changed (GESTrackElement * self, + guint64 duration); + +static GstElement *ges_video_transition_create_element (GESTrackElement * self); + +static void ges_video_transition_dispose (GObject * object); + +static void ges_video_transition_finalize (GObject * object); + +static void ges_video_transition_get_property (GObject * object, guint + property_id, GValue * value, GParamSpec * pspec); + +static void ges_video_transition_set_property (GObject * object, guint + property_id, const GValue * value, GParamSpec * pspec); + +static void +duration_changed_cb (GESTrackElement * self, GParamSpec * arg G_GNUC_UNUSED) +{ + ges_video_transition_duration_changed (self, + ges_timeline_element_get_duration (GES_TIMELINE_ELEMENT (self))); +} + +static gboolean +_set_priority (GESTimelineElement * element, guint32 priority) +{ + gboolean res; + GESVideoTransition *self = GES_VIDEO_TRANSITION (element); + + res = GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_priority (element, + priority); + + if (res && self->priv->positioner) + g_object_set (self->priv->positioner, "zorder", G_MAXUINT - priority, NULL); + + return res; +} + +static void +ges_video_transition_class_init (GESVideoTransitionClass * klass) +{ + GObjectClass *object_class; + GESTrackElementClass *toclass; + GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass); + GESTrackElementClass *track_element_class = GES_TRACK_ELEMENT_CLASS (klass); + + object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = ges_video_transition_get_property; + object_class->set_property = ges_video_transition_set_property; + object_class->dispose = ges_video_transition_dispose; + object_class->finalize = ges_video_transition_finalize; + + track_element_class->ABI.abi.default_track_type = GES_TRACK_TYPE_VIDEO; + + /** + * GESVideoTransition:border: + * + * This value represents the border width of the transition. + * + */ + properties[PROP_BORDER] = + g_param_spec_uint ("border", "Border", "The border width", 0, + G_MAXUINT, 0, G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_BORDER, + properties[PROP_BORDER]); + + /** + * GESVideoTransition:type: + * + * The #GESVideoStandardTransitionType currently applied on the object + * + * Deprecated:1.20: Use ges_timeline_element_[sg]et_child_property instead. + */ + properties[PROP_TRANSITION_TYPE] = + g_param_spec_enum ("transition-type", "Transition type", + "The type of the transition", GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE, + GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE, G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_TRANSITION_TYPE, + properties[PROP_TRANSITION_TYPE]); + + /** + * GESVideoTransition:invert: + * + * This value represents the direction of the transition. + * + * Deprecated:1.20: Use ges_timeline_element_[sg]et_child_property instead. + */ + properties[PROP_INVERT] = + g_param_spec_boolean ("invert", "Invert", + "Whether the transition is inverted", FALSE, G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_INVERT, + properties[PROP_INVERT]); + + toclass = GES_TRACK_ELEMENT_CLASS (klass); + toclass->create_element = ges_video_transition_create_element; + + element_class->set_priority = _set_priority; +} + +static void +ges_video_transition_init (GESVideoTransition * self) +{ + self->priv = ges_video_transition_get_instance_private (self); + + self->priv->fade_in_control_source = NULL; + self->priv->fade_out_control_source = NULL; + self->priv->smpte_control_source = NULL; + self->priv->smpte = NULL; + self->priv->mixer_sink = NULL; + self->priv->mixer = NULL; + self->priv->mixer_sinka = NULL; + self->priv->mixer_sinkb = NULL; + self->priv->pending_type = GES_VIDEO_STANDARD_TRANSITION_TYPE_NONE; + self->priv->pending_border_value = 0; + self->priv->pending_inverted = TRUE; +} + +static void +ges_video_transition_release_mixer (GESVideoTransition * self) +{ + GESVideoTransitionPrivate *priv = self->priv; + + if (priv->mixer_ghosta && priv->mixer_ghostb) { + gst_element_release_request_pad (priv->mixer, priv->mixer_ghosta); + gst_element_release_request_pad (priv->mixer, priv->mixer_ghostb); + gst_clear_object (&priv->mixer_ghosta); + gst_clear_object (&priv->mixer_ghostb); + } + + gst_clear_object (&priv->mixer_sinka); + gst_clear_object (&priv->mixer_sinkb); + gst_clear_object (&priv->mixer); +} + +static void +ges_video_transition_dispose (GObject * object) +{ + GESVideoTransition *self = GES_VIDEO_TRANSITION (object); + GESVideoTransitionPrivate *priv = self->priv; + + GST_DEBUG ("disposing"); + + if (priv->fade_in_control_source) { + gst_object_unref (priv->fade_in_control_source); + priv->fade_in_control_source = NULL; + } + + if (priv->fade_out_control_source) { + gst_object_unref (priv->fade_out_control_source); + priv->fade_out_control_source = NULL; + } + + if (priv->smpte_control_source) { + gst_object_unref (priv->smpte_control_source); + priv->smpte_control_source = NULL; + } + + ges_video_transition_release_mixer (self); + + g_signal_handlers_disconnect_by_func (GES_TRACK_ELEMENT (self), + duration_changed_cb, NULL); + + G_OBJECT_CLASS (ges_video_transition_parent_class)->dispose (object); +} + +static void +ges_video_transition_finalize (GObject * object) +{ + G_OBJECT_CLASS (ges_video_transition_parent_class)->finalize (object); +} + +static void +ges_video_transition_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec) +{ + GESVideoTransition *tr = GES_VIDEO_TRANSITION (object); + + switch (property_id) { + case PROP_BORDER: + g_value_set_uint (value, ges_video_transition_get_border (tr)); + break; + case PROP_TRANSITION_TYPE: + g_value_set_enum (value, ges_video_transition_get_transition_type (tr)); + break; + case PROP_INVERT: + g_value_set_boolean (value, ges_video_transition_is_inverted (tr)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_video_transition_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec) +{ + GESVideoTransition *tr = GES_VIDEO_TRANSITION (object); + + switch (property_id) { + case PROP_BORDER: + ges_video_transition_set_border_internal (tr, g_value_get_uint (value)); + break; + case PROP_TRANSITION_TYPE: + ges_video_transition_set_transition_type_internal (tr, + g_value_get_enum (value)); + break; + case PROP_INVERT: + ges_video_transition_set_inverted_internal (tr, + g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static GstTimedValueControlSource * +set_interpolation (GstObject * element, GESVideoTransitionPrivate * priv, + const gchar * propname) +{ + GstControlSource *control_source; + + g_object_set (element, propname, (gfloat) 0.0, NULL); + + control_source = gst_interpolation_control_source_new (); + gst_object_add_control_binding (GST_OBJECT (element), + gst_direct_control_binding_new (GST_OBJECT (element), propname, + control_source)); + g_object_set (control_source, "mode", GST_INTERPOLATION_MODE_LINEAR, NULL); + + return GST_TIMED_VALUE_CONTROL_SOURCE (control_source); +} + +static GstElement * +ges_video_transition_create_element (GESTrackElement * object) +{ + GstElement *topbin, *iconva, *iconvb; + GstElement *mixer = NULL; + GstPad *sinka_target, *sinkb_target, *src_target, *sinka, *sinkb, *src; + GESVideoTransition *self; + GESVideoTransitionPrivate *priv; + const gchar *smpte_properties[] = { "invert", "border", NULL }; + + self = GES_VIDEO_TRANSITION (object); + priv = self->priv; + + GST_LOG ("creating a video bin"); + + topbin = gst_bin_new ("transition-bin"); + + iconva = gst_element_factory_make ("videoconvert", "tr-csp-a"); + iconvb = gst_element_factory_make ("videoconvert", "tr-csp-b"); + priv->positioner = + gst_element_factory_make ("framepositioner", "frame_tagger"); + g_object_set (priv->positioner, "zorder", + G_MAXUINT - GES_TIMELINE_ELEMENT_PRIORITY (self), NULL); + + gst_bin_add_many (GST_BIN (topbin), iconva, iconvb, priv->positioner, NULL); + + mixer = + g_object_new (GES_TYPE_SMART_MIXER, "name", + GES_TIMELINE_ELEMENT_NAME (object), NULL); + GES_SMART_MIXER (mixer)->is_transition = TRUE; + gst_util_set_object_arg (G_OBJECT (GES_SMART_MIXER (mixer)->mixer), + "background", "transparent"); + gst_bin_add (GST_BIN (topbin), mixer); + + priv->mixer_sinka = + (GstPad *) link_element_to_mixer_with_smpte (GST_BIN (topbin), iconva, + mixer, GES_VIDEO_STANDARD_TRANSITION_TYPE_BAR_WIPE_LR, NULL, priv, + &priv->mixer_ghosta); + priv->mixer_sinkb = + (GstPad *) link_element_to_mixer_with_smpte (GST_BIN (topbin), iconvb, + mixer, GES_VIDEO_STANDARD_TRANSITION_TYPE_BAR_WIPE_LR, &priv->smpte, + priv, &priv->mixer_ghostb); + g_object_set (priv->mixer_sinka, "zorder", 0, NULL); + g_object_set (priv->mixer_sinkb, "zorder", 1, NULL); + gst_util_set_object_arg (G_OBJECT (priv->mixer_sinka), "operator", + priv->type == + GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE ? "source" : "over"); + gst_util_set_object_arg (G_OBJECT (priv->mixer_sinkb), "operator", + priv->type == + GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE ? "add" : "over"); + + fast_element_link (mixer, priv->positioner); + + sinka_target = gst_element_get_static_pad (iconva, "sink"); + sinkb_target = gst_element_get_static_pad (iconvb, "sink"); + src_target = gst_element_get_static_pad (priv->positioner, "src"); + + sinka = gst_ghost_pad_new ("sinka", sinka_target); + sinkb = gst_ghost_pad_new ("sinkb", sinkb_target); + src = gst_ghost_pad_new ("src", src_target); + + gst_element_add_pad (topbin, src); + gst_element_add_pad (topbin, sinka); + gst_element_add_pad (topbin, sinkb); + + gst_object_unref (sinka_target); + gst_object_unref (sinkb_target); + gst_object_unref (src_target); + + /* set up interpolation */ + + priv->fade_out_control_source = + set_interpolation (GST_OBJECT (priv->mixer_ghosta), priv, "alpha"); + priv->fade_in_control_source = + set_interpolation (GST_OBJECT (priv->mixer_ghostb), priv, "alpha"); + priv->smpte_control_source = + set_interpolation (GST_OBJECT (priv->smpte), priv, "position"); + priv->mixer = gst_object_ref (mixer); + + if (priv->pending_type) + ges_video_transition_set_transition_type_internal (self, + priv->pending_type); + else + ges_video_transition_set_transition_type_internal (self, priv->type); + + ges_video_transition_duration_changed (object, + ges_timeline_element_get_duration (GES_TIMELINE_ELEMENT (object))); + + g_signal_connect (object, "notify::duration", + G_CALLBACK (duration_changed_cb), NULL); + + priv->pending_type = GES_VIDEO_STANDARD_TRANSITION_TYPE_NONE; + + ges_track_element_add_children_props (GES_TRACK_ELEMENT (self), + priv->smpte, NULL, NULL, smpte_properties); + + return topbin; +} + +static GObject * +link_element_to_mixer_with_smpte (GstBin * bin, GstElement * element, + GstElement * mixer, gint type, GstElement ** smpteref, + GESVideoTransitionPrivate * priv, GstPad ** ghost) +{ + GstPad *srcpad, *mixerpad; + GstElement *smptealpha = gst_element_factory_make ("smptealpha", NULL); + + g_object_set (G_OBJECT (smptealpha), + "type", (gint) type, "invert", (gboolean) priv->pending_inverted, + "border", priv->pending_border_value, NULL); + gst_bin_add (bin, smptealpha); + + fast_element_link (element, smptealpha); + + /* crack */ + if (smpteref) { + *smpteref = smptealpha; + } + + srcpad = gst_element_get_static_pad (smptealpha, "src"); + *ghost = ges_smart_mixer_get_mixer_pad (GES_SMART_MIXER (mixer), &mixerpad); + gst_pad_link_full (srcpad, *ghost, GST_PAD_LINK_CHECK_NOTHING); + gst_object_unref (srcpad); + + return G_OBJECT (mixerpad); +} + +static void +ges_video_transition_update_control_source (GstTimedValueControlSource * ts, + guint64 duration, gdouble start_value, gdouble end_value) +{ + gst_timed_value_control_source_unset_all (ts); + gst_timed_value_control_source_set (ts, 0, start_value); + gst_timed_value_control_source_set (ts, duration, end_value); +} + +static void +ges_video_transition_update_control_sources (GESVideoTransition * self, + GESVideoStandardTransitionType type) +{ + GESVideoTransitionPrivate *priv = self->priv; + guint64 duration = + ges_timeline_element_get_duration (GES_TIMELINE_ELEMENT (self)); + + GST_LOG ("updating controller"); + if (type == GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE) { + ges_video_transition_update_control_source + (priv->fade_in_control_source, duration, 0.0, 1.0); + ges_video_transition_update_control_source + (priv->fade_out_control_source, duration, 1.0, 0.0); + ges_video_transition_update_control_source (priv->smpte_control_source, + duration, 0.0, 0.0); + } else { + ges_video_transition_update_control_source + (priv->fade_in_control_source, duration, 1.0, 1.0); + ges_video_transition_update_control_source + (priv->fade_out_control_source, duration, 1.0, 1.0); + ges_video_transition_update_control_source (priv->smpte_control_source, + duration, 1.0, 0.0); + } + GST_LOG ("done updating controller"); +} + +static void +ges_video_transition_duration_changed (GESTrackElement * object, + guint64 duration) +{ + GESVideoTransition *self = GES_VIDEO_TRANSITION (object); + + ges_video_transition_update_control_sources (self, self->priv->type); +} + +static inline void +ges_video_transition_set_border_internal (GESVideoTransition * self, + guint value) +{ + GESVideoTransitionPrivate *priv = self->priv; + + if (!priv->smpte) { + priv->pending_border_value = value; + return; + } + g_object_set (priv->smpte, "border", value, NULL); +} + +static inline void +ges_video_transition_set_inverted_internal (GESVideoTransition * + self, gboolean inverted) +{ + GESVideoTransitionPrivate *priv = self->priv; + + if (!priv->smpte) { + priv->pending_inverted = !inverted; + return; + } + g_object_set (priv->smpte, "invert", !inverted, NULL); +} + +static inline gboolean +ges_video_transition_set_transition_type_internal (GESVideoTransition + * self, GESVideoStandardTransitionType type) +{ + GESVideoTransitionPrivate *priv = self->priv; + + GST_DEBUG ("%p %d => %d", self, priv->type, type); + + if (!priv->mixer) { + priv->pending_type = type; + return TRUE; + } + + if (type == priv->type) { + GST_DEBUG ("%d type is already set on this transition\n", type); + return TRUE; + } + + ges_video_transition_update_control_sources (self, type); + + priv->type = type; + + if (type != GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE) { + g_object_set (priv->smpte, "type", (gint) type, NULL); + } + + gst_util_set_object_arg (G_OBJECT (priv->mixer_sinka), "operator", + priv->type == + GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE ? "source" : "over"); + gst_util_set_object_arg (G_OBJECT (priv->mixer_sinkb), "operator", + priv->type == + GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE ? "add" : "over"); + + return TRUE; +} + +/** + * ges_video_transition_set_border: + * @self: The #GESVideoTransition to set the border to + * @value: The value of the border to set on @object + * + * Set the border property of @self, this value represents + * the border width of the transition. In case this value does + * not make sense for the current transition type, it is cached + * for later use. + * + * Deprecated:1.20: Use ges_timeline_element_set_child_property instead. + */ +void +ges_video_transition_set_border (GESVideoTransition * self, guint value) +{ + ges_video_transition_set_border_internal (self, value); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BORDER]); +} + +/** + * ges_video_transition_get_border: + * @self: The #GESVideoTransition to get the border from + * + * Get the border property of @self, this value represents + * the border width of the transition. + * + * Returns: The border values of @self or -1 if not meaningful + * (this will happen when not using a smpte transition). + * + * Deprecated:1.20: Use ges_timeline_element_get_child_property instead. + */ +gint +ges_video_transition_get_border (GESVideoTransition * self) +{ + gint value; + + if (!self->priv->smpte) { + return -1; + } + + g_object_get (self->priv->smpte, "border", &value, NULL); + + return value; +} + +/** + * ges_video_transition_set_inverted: + * @self: The #GESVideoTransition to set invert on + * @inverted: %TRUE if the transition should be inverted %FALSE otherwise + * + * Set the invert property of @self, this value represents + * the direction of the transition. In case this value does + * not make sense for the current transition type, it is cached + * for later use. + * + * Deprecated:1.20: Use ges_timeline_element_set_child_property instead. + */ +void +ges_video_transition_set_inverted (GESVideoTransition * self, gboolean inverted) +{ + ges_video_transition_set_inverted_internal (self, inverted); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INVERT]); +} + +/** + * ges_video_transition_is_inverted: + * @self: The #GESVideoTransition to get the inversion from + * + * Get the invert property of @self, this value represents + * the direction of the transition. + * + * Returns: The invert value of @self + * + * Deprecated:1.20: Use ges_timeline_element_get_child_property instead. + */ +gboolean +ges_video_transition_is_inverted (GESVideoTransition * self) +{ + gboolean inverted; + + if (!self->priv->smpte) { + return FALSE; + } + + g_object_get (self->priv->smpte, "invert", &inverted, NULL); + + return !inverted; +} + +/** + * ges_video_transition_set_transition_type: + * @self: a #GESVideoTransition + * @type: a #GESVideoStandardTransitionType + * + * Sets the transition being used to @type. + * + * Returns: %TRUE if the transition type was properly changed, else %FALSE. + */ +gboolean +ges_video_transition_set_transition_type (GESVideoTransition * self, + GESVideoStandardTransitionType type) +{ + gboolean ret = ges_video_transition_set_transition_type_internal (self, type); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TRANSITION_TYPE]); + + return ret; +} + +/** + * ges_video_transition_get_transition_type: + * @trans: a #GESVideoTransition + * + * Get the transition type used by @trans. + * + * Returns: The transition type used by @trans. + */ +GESVideoStandardTransitionType +ges_video_transition_get_transition_type (GESVideoTransition * trans) +{ + if (trans->priv->pending_type) + return trans->priv->pending_type; + return trans->priv->type; +} + +/* ges_video_transition_new: + * + * Creates a new #GESVideoTransition. + * + * Returns: (transfer floating) (nullable): The newly created + * #GESVideoTransition, or %NULL if there was an error. + */ +GESVideoTransition * +ges_video_transition_new (void) +{ + GESVideoTransition *res; + GESAsset *asset = ges_asset_request (GES_TYPE_VIDEO_TRANSITION, NULL, NULL); + + res = GES_VIDEO_TRANSITION (ges_asset_extract (asset, NULL)); + gst_object_unref (asset); + + return res; +} diff --git a/ges/ges-video-transition.h b/ges/ges-video-transition.h new file mode 100644 index 0000000000..398f17dea9 --- /dev/null +++ b/ges/ges-video-transition.h @@ -0,0 +1,82 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> +#include <ges/ges-transition.h> + +G_BEGIN_DECLS + +#define GES_TYPE_VIDEO_TRANSITION ges_video_transition_get_type() +GES_DECLARE_TYPE(VideoTransition, video_transition, VIDEO_TRANSITION); + +/** + * GESVideoTransition: + */ + +struct _GESVideoTransition { + GESTransition parent; + + /*< private >*/ + + GESVideoTransitionPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +/** + * GESVideoTransitionClass: + * @parent_class: parent class + * + */ + +struct _GESVideoTransitionClass { + GESTransitionClass parent_class; + + /*< private >*/ + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GES_DEPRECATED +GESVideoTransition* ges_video_transition_new (void); + +GES_API +gboolean ges_video_transition_set_transition_type (GESVideoTransition * self, + GESVideoStandardTransitionType type); +GES_API GESVideoStandardTransitionType +ges_video_transition_get_transition_type (GESVideoTransition * trans); + +GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) +void ges_video_transition_set_border (GESVideoTransition * self, + guint value); +GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) +gint ges_video_transition_get_border (GESVideoTransition * self); + +GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) +void ges_video_transition_set_inverted (GESVideoTransition * self, + gboolean inverted); +GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) +gboolean ges_video_transition_is_inverted (GESVideoTransition * self); + +G_END_DECLS diff --git a/ges/ges-video-uri-source.c b/ges/ges-video-uri-source.c new file mode 100644 index 0000000000..3eef67bb0d --- /dev/null +++ b/ges/ges-video-uri-source.c @@ -0,0 +1,346 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gesvideourisource + * @title: GESVideoUriSource + * @short_description: outputs a single video stream from a given file + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> + +#include <gst/pbutils/missing-plugins.h> +#include "ges-utils.h" +#include "ges-internal.h" +#include "ges-track-element.h" +#include "ges-uri-source.h" +#include "ges-video-uri-source.h" +#include "ges-uri-asset.h" +#include "ges-extractable.h" + +struct _GESVideoUriSourcePrivate +{ + GESUriSource parent; +}; + +enum +{ + PROP_0, + PROP_URI +}; + +/* GESSource VMethod */ +static GstElement * +ges_video_uri_source_create_source (GESSource * element) +{ + return ges_uri_source_create_source (GES_VIDEO_URI_SOURCE (element)->priv); +} + +static gboolean +ges_video_uri_source_needs_converters (GESVideoSource * source) +{ + GESTrack *track = ges_track_element_get_track (GES_TRACK_ELEMENT (source)); + + if (!track || ges_track_get_mixing (track)) { + GESAsset *asset = ges_asset_request (GES_TYPE_URI_CLIP, + GES_VIDEO_URI_SOURCE (source)->uri, NULL); + gboolean is_nested = FALSE; + + g_assert (asset); + + g_object_get (asset, "is-nested-timeline", &is_nested, NULL); + gst_object_unref (asset); + + return !is_nested; + } + + + return FALSE; +} + +static GstDiscovererVideoInfo * +_get_video_stream_info (GESVideoUriSource * self) +{ + GstDiscovererStreamInfo *info; + GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (self)); + + if (!asset) { + GST_DEBUG_OBJECT (self, "No asset set yet"); + return NULL; + } + + info = ges_uri_source_asset_get_stream_info (GES_URI_SOURCE_ASSET (asset)); + + if (!GST_IS_DISCOVERER_VIDEO_INFO (info)) { + GST_ERROR_OBJECT (self, "Doesn't have a video info (%" GST_PTR_FORMAT + ")", info); + return NULL; + } + + return GST_DISCOVERER_VIDEO_INFO (info); +} + + +gboolean +ges_video_uri_source_get_natural_size (GESVideoSource * source, gint * width, + gint * height) +{ + const GstTagList *tags = NULL; + gchar *rotation_info = NULL; + gint videoflip_method, rotate_angle; + GstDiscovererVideoInfo *info = + _get_video_stream_info (GES_VIDEO_URI_SOURCE (source)); + + if (!info) + return FALSE; + + *width = gst_discoverer_video_info_get_width (info); + *height = gst_discoverer_video_info_get_height (info); + if (!ges_timeline_element_lookup_child (GES_TIMELINE_ELEMENT (source), + "GstVideoFlip::video-direction", NULL, NULL)) + goto done; + + ges_timeline_element_get_child_properties (GES_TIMELINE_ELEMENT (source), + "GstVideoFlip::video-direction", &videoflip_method, NULL); + + /* Rotating 90 degrees, either way, rotate */ + if (videoflip_method == 1 || videoflip_method == 3) + goto rotate; + + if (videoflip_method != 8) + goto done; + + /* Rotation is automatic, we need to check if the media file is naturally + rotated */ + tags = + gst_discoverer_stream_info_get_tags (GST_DISCOVERER_STREAM_INFO (info)); + if (!tags) + goto done; + + if (!gst_tag_list_get_string (tags, GST_TAG_IMAGE_ORIENTATION, + &rotation_info)) + goto done; + + if (sscanf (rotation_info, "rotate-%d", &rotate_angle) == 1) { + if (rotate_angle == 90 || rotate_angle == 270) + goto rotate; + } + +done: + g_free (rotation_info); + return TRUE; + +rotate: + GST_INFO_OBJECT (source, "Stream is rotated, taking that into account"); + *width = + gst_discoverer_video_info_get_height (GST_DISCOVERER_VIDEO_INFO (info)); + *height = + gst_discoverer_video_info_get_width (GST_DISCOVERER_VIDEO_INFO (info)); + + goto done; +} + +/* Extractable interface implementation */ + +static gchar * +ges_extractable_check_id (GType type, const gchar * id, GError ** error) +{ + return g_strdup (id); +} + +static void +ges_extractable_interface_init (GESExtractableInterface * iface) +{ + iface->asset_type = GES_TYPE_URI_SOURCE_ASSET; + iface->check_id = ges_extractable_check_id; +} + +G_DEFINE_TYPE_WITH_CODE (GESVideoUriSource, ges_video_uri_source, + GES_TYPE_VIDEO_SOURCE, G_ADD_PRIVATE (GESVideoUriSource) + G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, + ges_extractable_interface_init)); + +static gboolean +_find_positioner (GstElement * a, GstElement * b) +{ + return !g_strcmp0 (GST_OBJECT_NAME (gst_element_get_factory (a)), + "framepositioner"); +} + +static void +post_missing_element_message (GstElement * element, const gchar * name) +{ + GstMessage *msg; + + msg = gst_missing_element_message_new (element, name); + gst_element_post_message (element, msg); +} + +/* GObject VMethods */ +static gboolean +ges_video_uri_source_create_filters (GESVideoSource * source, + GPtrArray * elements, gboolean needs_converters) +{ + GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (source)); + GstDiscovererVideoInfo *info = + GST_DISCOVERER_VIDEO_INFO (ges_uri_source_asset_get_stream_info + (GES_URI_SOURCE_ASSET (asset))); + + g_assert (GES_IS_URI_SOURCE_ASSET (asset)); + if (!GES_VIDEO_SOURCE_CLASS (ges_video_uri_source_parent_class) + ->ABI.abi.create_filters (source, elements, needs_converters)) + return FALSE; + + if (gst_discoverer_video_info_is_interlaced (info)) { + const gchar *deinterlace_props[] = { "mode", "fields", "tff", NULL }; + GstElement *deinterlace = + gst_element_factory_make ("deinterlace", "deinterlace"); + + if (deinterlace == NULL) { + post_missing_element_message (ges_track_element_get_nleobject + (GES_TRACK_ELEMENT (source)), "deinterlace"); + + GST_ELEMENT_WARNING (ges_track_element_get_nleobject (GES_TRACK_ELEMENT + (source)), CORE, MISSING_PLUGIN, + ("Missing element '%s' - check your GStreamer installation.", + "deinterlace"), ("deinterlacing won't work")); + } else { + /* Right after the queue */ + g_ptr_array_insert (elements, 1, gst_element_factory_make ("videoconvert", + NULL)); + g_ptr_array_insert (elements, 2, deinterlace); + ges_track_element_add_children_props (GES_TRACK_ELEMENT (source), + deinterlace, NULL, NULL, deinterlace_props); + } + + } + + if (ges_uri_source_asset_is_image (GES_URI_SOURCE_ASSET (asset))) { + guint i; + + g_ptr_array_find_with_equal_func (elements, NULL, + (GEqualFunc) _find_positioner, &i); + /* Adding the imagefreeze right before the positionner so positioning can happen + * properly */ + g_ptr_array_insert (elements, i, + gst_element_factory_make ("imagefreeze", NULL)); + } + + return TRUE; +} + +static void +ges_video_uri_source_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESVideoUriSource *urisource = GES_VIDEO_URI_SOURCE (object); + + switch (property_id) { + case PROP_URI: + g_value_set_string (value, urisource->uri); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_video_uri_source_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESVideoUriSource *urisource = GES_VIDEO_URI_SOURCE (object); + + switch (property_id) { + case PROP_URI: + if (urisource->uri) { + GST_WARNING_OBJECT (object, "Uri already set to %s", urisource->uri); + return; + } + urisource->priv->uri = urisource->uri = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_video_uri_source_finalize (GObject * object) +{ + GESVideoUriSource *urisource = GES_VIDEO_URI_SOURCE (object); + + g_free (urisource->uri); + + G_OBJECT_CLASS (ges_video_uri_source_parent_class)->finalize (object); +} + +static void +ges_video_uri_source_class_init (GESVideoUriSourceClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESSourceClass *src_class = GES_SOURCE_CLASS (klass); + GESVideoSourceClass *video_src_class = GES_VIDEO_SOURCE_CLASS (klass); + + object_class->get_property = ges_video_uri_source_get_property; + object_class->set_property = ges_video_uri_source_set_property; + object_class->finalize = ges_video_uri_source_finalize; + + /** + * GESVideoUriSource:uri: + * + * The location of the file/resource to use. + */ + g_object_class_install_property (object_class, PROP_URI, + g_param_spec_string ("uri", "URI", "uri of the resource", + NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + src_class->select_pad = ges_uri_source_select_pad; + + src_class->create_source = ges_video_uri_source_create_source; + video_src_class->ABI.abi.needs_converters = + ges_video_uri_source_needs_converters; + video_src_class->ABI.abi.get_natural_size = + ges_video_uri_source_get_natural_size; + video_src_class->ABI.abi.create_filters = ges_video_uri_source_create_filters; +} + +static void +ges_video_uri_source_init (GESVideoUriSource * self) +{ + self->priv = ges_video_uri_source_get_instance_private (self); + ges_uri_source_init (GES_TRACK_ELEMENT (self), self->priv); +} + +/** + * ges_video_uri_source_new: + * @uri: the URI the source should control + * + * Creates a new #GESVideoUriSource for the provided @uri. + * + * Returns: (transfer floating) (nullable): The newly created #GESVideoUriSource, + * or %NULL if there was an error. + */ +GESVideoUriSource * +ges_video_uri_source_new (gchar * uri) +{ + return g_object_new (GES_TYPE_VIDEO_URI_SOURCE, "uri", uri, NULL); +} diff --git a/ges/ges-video-uri-source.h b/ges/ges-video-uri-source.h new file mode 100644 index 0000000000..d024160988 --- /dev/null +++ b/ges/ges-video-uri-source.h @@ -0,0 +1,63 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib-object.h> +#include <ges/ges-types.h> +#include <ges/ges-video-source.h> + +G_BEGIN_DECLS + +/** + * GESUriSource: (attributes doc.skip=true): + */ +typedef struct _GESUriSource GESUriSource; +#define GES_TYPE_VIDEO_URI_SOURCE ges_video_uri_source_get_type() +GES_DECLARE_TYPE(VideoUriSource, video_uri_source, VIDEO_URI_SOURCE); + +/** + * GESVideoUriSource: + * + * ### Children Properties + * + * {{ libs/GESVideoUriSource-children-props.md }} + */ +struct _GESVideoUriSource { + /*< private >*/ + GESVideoSource parent; + + gchar *uri; + + GESUriSource *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESVideoUriSourceClass { + /*< private >*/ + GESVideoSourceClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +G_END_DECLS diff --git a/ges/ges-xml-formatter.c b/ges/ges-xml-formatter.c new file mode 100644 index 0000000000..63660f1f59 --- /dev/null +++ b/ges/ges-xml-formatter.c @@ -0,0 +1,2225 @@ +/* Gstreamer Editing Services + * + * Copyright (C) <2012> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#undef VERSION +#endif + + +/* TODO Determine error codes numbers */ + +#include <string.h> +#include <errno.h> +#include <locale.h> + +#include "ges.h" +#include <glib/gstdio.h> +#include "ges-internal.h" + +#define parent_class ges_xml_formatter_parent_class +#define API_VERSION 0 +#define MINOR_VERSION 8 +#define VERSION 0.8 + +#define COLLECT_STR_OPT (G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL) + +#define _GET_PRIV(o) (((GESXmlFormatter*)o)->priv) + +typedef struct +{ + const gchar *id; + gint start_line; + gint start_char; + gint fd; + gchar *filename; + GError *error; + GMainLoop *ml; +} SubprojectData; + +struct _GESXmlFormatterPrivate +{ + gboolean ges_opened; + gboolean project_opened; + + GString *str; + + GHashTable *element_id; + GHashTable *subprojects_map; + SubprojectData *subproject; + gint subproject_depth; + + guint nbelements; + + guint min_version; +}; + +G_LOCK_DEFINE_STATIC (uri_subprojects_map_lock); +/* { project_uri: { subproject_uri: new_suproject_uri}} */ +static GHashTable *uri_subprojects_map = NULL; + +G_DEFINE_TYPE_WITH_PRIVATE (GESXmlFormatter, ges_xml_formatter, + GES_TYPE_BASE_XML_FORMATTER); + +static GString *_save_project (GESFormatter * formatter, GString * str, + GESProject * project, GESTimeline * timeline, GError ** error, guint depth); + +static inline void +_parse_ges_element (GMarkupParseContext * context, const gchar * element_name, + const gchar ** attribute_names, const gchar ** attribute_values, + GESXmlFormatter * self, GError ** error) +{ + guint api_version; + const gchar *version, *properties; + + gchar **split_version = NULL; + + if (g_strcmp0 (element_name, "ges")) { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Found element '%s', Missing '<ges>' element'", element_name); + return; + } + + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, G_MARKUP_COLLECT_STRING, "version", &version, + COLLECT_STR_OPT, "properties", &properties, + G_MARKUP_COLLECT_INVALID)) { + return; + } + + split_version = g_strsplit (version, ".", 2); + if (split_version[1] == NULL) + goto failed; + + errno = 0; + api_version = g_ascii_strtoull (split_version[0], NULL, 10); + if (errno || api_version != API_VERSION) + goto stroull_failed; + + self->priv->min_version = g_ascii_strtoull (split_version[1], NULL, 10); + if (self->priv->min_version > MINOR_VERSION) + goto failed; + + _GET_PRIV (self)->ges_opened = TRUE; + g_strfreev (split_version); + return; + +failed: + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', %s wrong version'", element_name, version); + if (split_version) + g_strfreev (split_version); + + return; + +stroull_failed: + GST_WARNING_OBJECT (self, "Error while strtoull: %s", g_strerror (errno)); + goto failed; +} + +static inline void +_parse_project (GMarkupParseContext * context, const gchar * element_name, + const gchar ** attribute_names, const gchar ** attribute_values, + GESXmlFormatter * self, GError ** error) +{ + const gchar *metadatas = NULL, *properties; + GESXmlFormatterPrivate *priv = _GET_PRIV (self); + + if (g_strcmp0 (element_name, "project")) { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Found element '%s', Missing '<project>' element'", element_name); + } else { + priv->project_opened = TRUE; + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, + COLLECT_STR_OPT, "properties", &properties, + COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID)) + return; + + if (GES_FORMATTER (self)->project && metadatas) + ges_meta_container_add_metas_from_string (GES_META_CONTAINER + (GES_FORMATTER (self)->project), metadatas); + + } +} + +static inline void +_parse_encoding_profile (GMarkupParseContext * context, + const gchar * element_name, const gchar ** attribute_names, + const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) +{ + GstCaps *capsformat = NULL; + GstStructure *preset_properties = NULL; + const gchar *name, *description, *type, *preset = NULL, + *str_preset_properties = NULL, *preset_name = NULL, *format; + + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, + G_MARKUP_COLLECT_STRING, "name", &name, + G_MARKUP_COLLECT_STRING, "description", &description, + G_MARKUP_COLLECT_STRING, "type", &type, + COLLECT_STR_OPT, "preset", &preset, + COLLECT_STR_OPT, "preset-properties", &str_preset_properties, + COLLECT_STR_OPT, "preset-name", &preset_name, + COLLECT_STR_OPT, "format", &format, G_MARKUP_COLLECT_INVALID)) + return; + + if (format) + capsformat = gst_caps_from_string (format); + + if (str_preset_properties) { + preset_properties = gst_structure_from_string (str_preset_properties, NULL); + if (preset_properties == NULL) { + gst_caps_unref (capsformat); + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', Wrong preset-properties format.", element_name); + return; + } + } + + ges_base_xml_formatter_add_encoding_profile (GES_BASE_XML_FORMATTER (self), + type, NULL, name, description, capsformat, preset, preset_properties, + preset_name, 0, 0, NULL, 0, FALSE, NULL, TRUE, error); + + if (preset_properties) + gst_structure_free (preset_properties); +} + +static inline void +_parse_stream_profile (GMarkupParseContext * context, + const gchar * element_name, const gchar ** attribute_names, + const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) +{ + gboolean variableframerate = FALSE, enabled = TRUE; + guint id = 0, presence = 0, pass = 0; + GstCaps *format_caps = NULL, *restriction_caps = NULL; + GstStructure *preset_properties = NULL; + const gchar *parent, *strid, *type, *strpresence, *format = NULL, + *name = NULL, *description = NULL, *preset, + *str_preset_properties = NULL, *preset_name = NULL, *restriction = NULL, + *strpass = NULL, *strvariableframerate = NULL, *strenabled = NULL; + + /* FIXME Looks like there is a bug in that function, if we put the parent + * at the beginning it set %NULL and not the real value... :/ */ + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, + G_MARKUP_COLLECT_STRING, "id", &strid, + G_MARKUP_COLLECT_STRING, "type", &type, + G_MARKUP_COLLECT_STRING, "presence", &strpresence, + COLLECT_STR_OPT, "format", &format, + COLLECT_STR_OPT, "name", &name, + COLLECT_STR_OPT, "description", &description, + COLLECT_STR_OPT, "preset", &preset, + COLLECT_STR_OPT, "preset-properties", &str_preset_properties, + COLLECT_STR_OPT, "preset-name", &preset_name, + COLLECT_STR_OPT, "restriction", &restriction, + COLLECT_STR_OPT, "pass", &strpass, + COLLECT_STR_OPT, "variableframerate", &strvariableframerate, + COLLECT_STR_OPT, "enabled", &strenabled, + G_MARKUP_COLLECT_STRING, "parent", &parent, G_MARKUP_COLLECT_INVALID)) + return; + + errno = 0; + id = g_ascii_strtoll (strid, NULL, 10); + if (errno) + goto convertion_failed; + + if (strpresence) { + presence = g_ascii_strtoll (strpresence, NULL, 10); + if (errno) + goto convertion_failed; + } + + if (str_preset_properties) { + preset_properties = gst_structure_from_string (str_preset_properties, NULL); + if (preset_properties == NULL) + goto convertion_failed; + } + + if (strpass) { + pass = g_ascii_strtoll (strpass, NULL, 10); + if (errno) + goto convertion_failed; + } + + if (strvariableframerate) { + variableframerate = g_ascii_strtoll (strvariableframerate, NULL, 10); + if (errno) + goto convertion_failed; + } + + if (strenabled) { + enabled = g_ascii_strtoll (strenabled, NULL, 10); + if (errno) + goto convertion_failed; + } + + if (format) + format_caps = gst_caps_from_string (format); + + if (restriction) + restriction_caps = gst_caps_from_string (restriction); + + ges_base_xml_formatter_add_encoding_profile (GES_BASE_XML_FORMATTER (self), + type, parent, name, description, format_caps, preset, preset_properties, + preset_name, id, presence, restriction_caps, pass, variableframerate, + NULL, enabled, error); + + if (preset_properties) + gst_structure_free (preset_properties); + + return; + +convertion_failed: + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', Wrong property type, error: %s'", element_name, + g_strerror (errno)); + return; +} + +static inline void +_parse_timeline (GMarkupParseContext * context, const gchar * element_name, + const gchar ** attribute_names, const gchar ** attribute_values, + GESXmlFormatter * self, GError ** error) +{ + const gchar *metadatas = NULL, *properties = NULL; + GESTimeline *timeline = GES_FORMATTER (self)->timeline; + + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, + COLLECT_STR_OPT, "properties", &properties, + COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID)) + return; + + if (timeline == NULL) + return; + + ges_base_xml_formatter_set_timeline_properties (GES_BASE_XML_FORMATTER (self), + timeline, properties, metadatas); +} + +static inline void +_parse_asset (GMarkupParseContext * context, const gchar * element_name, + const gchar ** attribute_names, const gchar ** attribute_values, + GESXmlFormatter * self, GError ** error) +{ + GType extractable_type; + const gchar *id, *extractable_type_name, *metadatas = NULL, *properties = + NULL, *proxy_id = NULL; + GESXmlFormatterPrivate *priv = _GET_PRIV (self); + + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, G_MARKUP_COLLECT_STRING, "id", &id, + G_MARKUP_COLLECT_STRING, "extractable-type-name", + &extractable_type_name, + COLLECT_STR_OPT, "properties", &properties, + COLLECT_STR_OPT, "metadatas", &metadatas, + COLLECT_STR_OPT, "proxy-id", &proxy_id, G_MARKUP_COLLECT_INVALID)) + return; + + extractable_type = g_type_from_name (extractable_type_name); + if (extractable_type == GES_TYPE_TIMELINE) { + SubprojectData *subproj_data = g_malloc0 (sizeof (SubprojectData)); + const gchar *nid; + + priv->subproject = subproj_data; + G_LOCK (uri_subprojects_map_lock); + nid = g_hash_table_lookup (priv->subprojects_map, id); + G_UNLOCK (uri_subprojects_map_lock); + + if (!nid) { + subproj_data->id = id; + subproj_data->fd = + g_file_open_tmp ("XXXXXX.xges", &subproj_data->filename, error); + if (subproj_data->fd == -1) { + GST_ERROR_OBJECT (self, "Could not create subproject file for %s", id); + return; + } + g_markup_parse_context_get_position (context, &subproj_data->start_line, + &subproj_data->start_char); + id = g_filename_to_uri (subproj_data->filename, NULL, NULL); + G_LOCK (uri_subprojects_map_lock); + g_hash_table_insert (priv->subprojects_map, g_strdup (subproj_data->id), + (gchar *) id); + G_UNLOCK (uri_subprojects_map_lock); + GST_INFO_OBJECT (self, "Serialized subproject %sis now at: %s", + subproj_data->id, id); + } else { + GST_DEBUG_OBJECT (self, "Subproject already exists: %s -> %s", id, nid); + id = nid; + subproj_data->start_line = -1; + } + } + + if (extractable_type == G_TYPE_NONE) + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s' invalid extractable_type %s'", + element_name, extractable_type_name); + else if (!g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE)) + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', %s not an extractable_type'", + element_name, extractable_type_name); + else { + GstStructure *props = NULL; + if (properties) + props = gst_structure_from_string (properties, NULL); + + if (extractable_type == GES_TYPE_URI_CLIP) { + G_LOCK (uri_subprojects_map_lock); + if (g_hash_table_contains (priv->subprojects_map, id)) { + id = g_hash_table_lookup (priv->subprojects_map, id); + + GST_DEBUG_OBJECT (self, "Using subproject %s", id); + } + G_UNLOCK (uri_subprojects_map_lock); + } + + ges_base_xml_formatter_add_asset (GES_BASE_XML_FORMATTER (self), id, + extractable_type, props, metadatas, proxy_id, error); + if (props) + gst_structure_free (props); + } +} + + +static inline void +_parse_track (GMarkupParseContext * context, const gchar * element_name, + const gchar ** attribute_names, const gchar ** attribute_values, + GESXmlFormatter * self, GError ** error) +{ + GstCaps *caps; + GESTrackType track_type; + GstStructure *props = NULL; + const gchar *strtrack_type, *strcaps, *strtrack_id, *metadatas = + NULL, *properties = NULL; + + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, + G_MARKUP_COLLECT_STRING, "track-type", &strtrack_type, + G_MARKUP_COLLECT_STRING, "track-id", &strtrack_id, + COLLECT_STR_OPT, "properties", &properties, + COLLECT_STR_OPT, "metadatas", &metadatas, + G_MARKUP_COLLECT_STRING, "caps", &strcaps, G_MARKUP_COLLECT_INVALID)) + return; + + if ((caps = gst_caps_from_string (strcaps)) == NULL) + goto wrong_caps; + + errno = 0; + track_type = g_ascii_strtoll (strtrack_type, NULL, 10); + if (errno) + goto convertion_failed; + + if (properties) { + props = gst_structure_from_string (properties, NULL); + if (!props) + goto wrong_properties; + } + + ges_base_xml_formatter_add_track (GES_BASE_XML_FORMATTER (self), track_type, + caps, strtrack_id, props, metadatas, error); + + if (props) + gst_structure_free (props); + + gst_caps_unref (caps); + + return; + +wrong_caps: + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', Can not create caps: %s'", element_name, strcaps); + return; + +convertion_failed: + gst_caps_unref (caps); + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', Wrong property type, error: %s'", element_name, + g_strerror (errno)); + return; + +wrong_properties: + gst_clear_caps (&caps); + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', Can not create properties: %s'", element_name, properties); + return; + +} + +static inline void +_parse_layer (GMarkupParseContext * context, const gchar * element_name, + const gchar ** attribute_names, const gchar ** attribute_values, + GESXmlFormatter * self, GError ** error) +{ + GstStructure *props = NULL; + guint priority; + GType extractable_type = G_TYPE_NONE; + const gchar *metadatas = NULL, *properties = NULL, *strprio = NULL, + *extractable_type_name, *deactivated_tracks_str; + + gchar **deactivated_tracks = NULL; + + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, + G_MARKUP_COLLECT_STRING, "priority", &strprio, + COLLECT_STR_OPT, "extractable-type-name", &extractable_type_name, + COLLECT_STR_OPT, "properties", &properties, + COLLECT_STR_OPT, "deactivated-tracks", &deactivated_tracks_str, + COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID)) + return; + + if (extractable_type_name) { + extractable_type = g_type_from_name (extractable_type_name); + if (extractable_type == G_TYPE_NONE) { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s' invalid extractable_type %s'", + element_name, extractable_type_name); + + return; + } else if (!g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE)) { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', %s not an extractable_type'", + element_name, extractable_type_name); + + return; + } + } + + if (properties) { + props = gst_structure_from_string (properties, NULL); + if (props == NULL) + goto wrong_properties; + } + + errno = 0; + priority = g_ascii_strtoll (strprio, NULL, 10); + if (errno) + goto convertion_failed; + + if (deactivated_tracks_str) + deactivated_tracks = g_strsplit (deactivated_tracks_str, " ", -1); + + ges_base_xml_formatter_add_layer (GES_BASE_XML_FORMATTER (self), + extractable_type, priority, props, metadatas, deactivated_tracks, error); + + g_strfreev (deactivated_tracks); + +done: + if (props) + gst_structure_free (props); + + return; + +convertion_failed: + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', Wrong property type, error: %s'", element_name, + g_strerror (errno)); + goto done; + +wrong_properties: + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', wrong layer properties '%s', could no be deserialized", + element_name, properties); +} + +static inline void +_parse_clip (GMarkupParseContext * context, + const gchar * element_name, const gchar ** attribute_names, + const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) +{ + GType type; + GstStructure *props = NULL, *children_props = NULL; + GESTrackType track_types; + GstClockTime start, inpoint = 0, duration, layer_prio; + GESXmlFormatterPrivate *priv = _GET_PRIV (self); + + const gchar *strid, *asset_id, *strstart, *strin, *strduration, *strrate, + *strtrack_types, *strtype, *metadatas = NULL, *properties = + NULL, *children_properties = NULL, *strlayer_prio; + + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, + G_MARKUP_COLLECT_STRING, "id", &strid, + G_MARKUP_COLLECT_STRING, "type-name", &strtype, + G_MARKUP_COLLECT_STRING, "start", &strstart, + G_MARKUP_COLLECT_STRING, "duration", &strduration, + G_MARKUP_COLLECT_STRING, "asset-id", &asset_id, + G_MARKUP_COLLECT_STRING, "track-types", &strtrack_types, + G_MARKUP_COLLECT_STRING, "layer-priority", &strlayer_prio, + COLLECT_STR_OPT, "properties", &properties, + COLLECT_STR_OPT, "children-properties", &children_properties, + COLLECT_STR_OPT, "metadatas", &metadatas, + COLLECT_STR_OPT, "rate", &strrate, + COLLECT_STR_OPT, "inpoint", &strin, G_MARKUP_COLLECT_INVALID)) { + return; + } + type = g_type_from_name (strtype); + if (!g_type_is_a (type, GES_TYPE_CLIP)) + goto wrong_type; + + errno = 0; + track_types = g_ascii_strtoll (strtrack_types, NULL, 10); + if (errno) + goto convertion_failed; + + layer_prio = g_ascii_strtoll (strlayer_prio, NULL, 10); + if (errno) + goto convertion_failed; + + if (strin) { + inpoint = g_ascii_strtoull (strin, NULL, 10); + if (errno) + goto convertion_failed; + } + + start = g_ascii_strtoull (strstart, NULL, 10); + if (errno) + goto convertion_failed; + + duration = g_ascii_strtoull (strduration, NULL, 10); + if (errno) + goto convertion_failed; + + if (properties) { + props = gst_structure_from_string (properties, NULL); + if (props == NULL) + goto wrong_properties; + } + + if (children_properties) { + children_props = gst_structure_from_string (children_properties, NULL); + if (children_props == NULL) + goto wrong_children_properties; + } + + G_LOCK (uri_subprojects_map_lock); + if (g_hash_table_contains (priv->subprojects_map, asset_id)) { + asset_id = g_hash_table_lookup (priv->subprojects_map, asset_id); + GST_DEBUG_OBJECT (self, "Using subproject %s", asset_id); + } + G_UNLOCK (uri_subprojects_map_lock); + ges_base_xml_formatter_add_clip (GES_BASE_XML_FORMATTER (self), + strid, asset_id, type, start, inpoint, duration, layer_prio, + track_types, props, children_props, metadatas, error); + if (props) + gst_structure_free (props); + if (children_props) + gst_structure_free (children_props); + + return; + +wrong_properties: + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', Clip %s properties '%s', could no be deserialized", + element_name, asset_id, properties); + return; + +wrong_children_properties: + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', Clip %s children properties '%s', could no be deserialized", + element_name, asset_id, children_properties); + if (props) + gst_structure_free (props); + return; + +convertion_failed: + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', Wrong property type, error: %s'", element_name, + g_strerror (errno)); + return; + +wrong_type: + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', %s not a GESClip'", element_name, strtype); +} + +static inline void +_parse_binding (GMarkupParseContext * context, const gchar * element_name, + const gchar ** attribute_names, const gchar ** attribute_values, + GESXmlFormatter * self, GError ** error) +{ + const gchar *type = NULL, *source_type = NULL, *timed_values = + NULL, *property_name = NULL, *mode = NULL, *track_id = NULL; + gchar **pairs, **tmp; + gchar *pair; + GSList *list = NULL; + + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, + G_MARKUP_COLLECT_STRING, "type", &type, + G_MARKUP_COLLECT_STRING, "source_type", &source_type, + G_MARKUP_COLLECT_STRING, "property", &property_name, + G_MARKUP_COLLECT_STRING, "mode", &mode, + G_MARKUP_COLLECT_STRING, "track_id", &track_id, + G_MARKUP_COLLECT_STRING, "values", &timed_values, + G_MARKUP_COLLECT_INVALID)) { + return; + } + + pairs = g_strsplit (timed_values, " ", 0); + for (tmp = pairs; tmp != NULL; tmp += 1) { + gchar **value_pair; + + pair = *tmp; + if (pair == NULL) + break; + if (strlen (pair)) { + GstTimedValue *value; + + value = g_new0 (GstTimedValue, 1); + value_pair = g_strsplit (pair, ":", 0); + value->timestamp = g_ascii_strtoull (value_pair[0], NULL, 10); + value->value = g_ascii_strtod (value_pair[1], NULL); + list = g_slist_append (list, value); + g_strfreev (value_pair); + } + } + + g_strfreev (pairs); + + ges_base_xml_formatter_add_control_binding (GES_BASE_XML_FORMATTER (self), + type, + source_type, + property_name, (gint) g_ascii_strtoll (mode, NULL, 10), track_id, list); +} + +static inline void +_parse_source (GMarkupParseContext * context, const gchar * element_name, + const gchar ** attribute_names, const gchar ** attribute_values, + GESXmlFormatter * self, GError ** error) +{ + GstStructure *children_props = NULL, *props = NULL; + const gchar *track_id = NULL, *children_properties = NULL, *properties = + NULL, *metadatas = NULL; + + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, + G_MARKUP_COLLECT_STRING, "track-id", &track_id, + COLLECT_STR_OPT, "children-properties", &children_properties, + COLLECT_STR_OPT, "properties", &properties, + COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID)) { + return; + } + + if (children_properties) { + children_props = gst_structure_from_string (children_properties, NULL); + if (children_props == NULL) + goto wrong_children_properties; + } + + if (properties) { + props = gst_structure_from_string (properties, NULL); + if (props == NULL) + goto wrong_properties; + } + + ges_base_xml_formatter_add_source (GES_BASE_XML_FORMATTER (self), track_id, + children_props, props, metadatas); + +done: + if (children_props) + gst_structure_free (children_props); + + if (props) + gst_structure_free (props); + + return; + +wrong_children_properties: + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', children properties '%s', could no be deserialized", + element_name, children_properties); + goto done; + +wrong_properties: + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', properties '%s', could no be deserialized", + element_name, properties); + goto done; +} + +static inline void +_parse_effect (GMarkupParseContext * context, const gchar * element_name, + const gchar ** attribute_names, const gchar ** attribute_values, + GESXmlFormatter * self, GError ** error) +{ + GType type; + + GstStructure *children_props = NULL, *props = NULL; + const gchar *asset_id = NULL, *strtype = NULL, *track_id = + NULL, *metadatas = NULL, *properties = NULL, *track_type = NULL, + *children_properties = NULL, *clip_id; + + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, + COLLECT_STR_OPT, "metadatas", &metadatas, + G_MARKUP_COLLECT_STRING, "asset-id", &asset_id, + G_MARKUP_COLLECT_STRING, "clip-id", &clip_id, + G_MARKUP_COLLECT_STRING, "type-name", &strtype, + G_MARKUP_COLLECT_STRING, "track-id", &track_id, + COLLECT_STR_OPT, "children-properties", &children_properties, + COLLECT_STR_OPT, "track-type", &track_type, + COLLECT_STR_OPT, "properties", &properties, + G_MARKUP_COLLECT_INVALID)) { + return; + } + + type = g_type_from_name (strtype); + if (!g_type_is_a (type, GES_TYPE_BASE_EFFECT)) + goto wrong_type; + + if (children_properties) { + children_props = gst_structure_from_string (children_properties, NULL); + if (children_props == NULL) + goto wrong_children_properties; + } + + if (properties) { + props = gst_structure_from_string (properties, NULL); + if (props == NULL) + goto wrong_properties; + } + + ges_base_xml_formatter_add_track_element (GES_BASE_XML_FORMATTER (self), + type, asset_id, track_id, clip_id, children_props, props, metadatas, + error); + +out: + + if (props) + gst_structure_free (props); + if (children_props) + gst_structure_free (children_props); + + return; + +wrong_properties: + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', Effect %s properties '%s', could no be deserialized", + element_name, asset_id, properties); + goto out; + +wrong_children_properties: + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', Effect %s children properties '%s', could no be deserialized", + element_name, asset_id, children_properties); + goto out; + +wrong_type: + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "element '%s', %s not a GESBaseEffect'", element_name, strtype); +} + + +static inline void +_parse_group (GMarkupParseContext * context, const gchar * element_name, + const gchar ** attribute_names, const gchar ** attribute_values, + GESXmlFormatter * self, GError ** error) +{ + const gchar *id, *properties, *metadatas = NULL; + + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, + G_MARKUP_COLLECT_STRING, "id", &id, + G_MARKUP_COLLECT_STRING, "properties", &properties, + COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID)) { + return; + } + + ges_base_xml_formatter_add_group (GES_BASE_XML_FORMATTER (self), id, + properties, metadatas); +} + +static inline void +_parse_group_child (GMarkupParseContext * context, const gchar * element_name, + const gchar ** attribute_names, const gchar ** attribute_values, + GESXmlFormatter * self, GError ** error) +{ + const gchar *child_id, *name; + + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, + G_MARKUP_COLLECT_STRING, "id", &child_id, + G_MARKUP_COLLECT_STRING, "name", &name, G_MARKUP_COLLECT_INVALID)) { + return; + } + + ges_base_xml_formatter_last_group_add_child (GES_BASE_XML_FORMATTER (self), + child_id, name); +} + +static void +_parse_element_start (GMarkupParseContext * context, const gchar * element_name, + const gchar ** attribute_names, const gchar ** attribute_values, + gpointer self, GError ** error) +{ + GESXmlFormatterPrivate *priv = _GET_PRIV (self); + + if (priv->subproject) { + if (g_strcmp0 (element_name, "ges") == 0) { + priv->subproject_depth += 1; + } + return; + } + + if (!G_UNLIKELY (priv->ges_opened)) { + _parse_ges_element (context, element_name, attribute_names, + attribute_values, self, error); + } else if (!G_UNLIKELY (priv->project_opened)) + _parse_project (context, element_name, attribute_names, attribute_values, + self, error); + else if (g_strcmp0 (element_name, "ges") == 0) { + } else if (g_strcmp0 (element_name, "encoding-profile") == 0) + _parse_encoding_profile (context, element_name, attribute_names, + attribute_values, self, error); + else if (g_strcmp0 (element_name, "stream-profile") == 0) + _parse_stream_profile (context, element_name, attribute_names, + attribute_values, self, error); + else if (g_strcmp0 (element_name, "timeline") == 0) + _parse_timeline (context, element_name, attribute_names, attribute_values, + self, error); + else if (g_strcmp0 (element_name, "asset") == 0) + _parse_asset (context, element_name, attribute_names, attribute_values, + self, error); + else if (g_strcmp0 (element_name, "track") == 0) + _parse_track (context, element_name, attribute_names, + attribute_values, self, error); + else if (g_strcmp0 (element_name, "layer") == 0) + _parse_layer (context, element_name, attribute_names, + attribute_values, self, error); + else if (g_strcmp0 (element_name, "clip") == 0) + _parse_clip (context, element_name, attribute_names, + attribute_values, self, error); + else if (g_strcmp0 (element_name, "source") == 0) + _parse_source (context, element_name, attribute_names, + attribute_values, self, error); + else if (g_strcmp0 (element_name, "effect") == 0) + _parse_effect (context, element_name, attribute_names, + attribute_values, self, error); + else if (g_strcmp0 (element_name, "binding") == 0) + _parse_binding (context, element_name, attribute_names, + attribute_values, self, error); + else if (g_strcmp0 (element_name, "group") == 0) + _parse_group (context, element_name, attribute_names, + attribute_values, self, error); + else if (g_strcmp0 (element_name, "child") == 0) + _parse_group_child (context, element_name, attribute_names, + attribute_values, self, error); + else + GST_LOG_OBJECT (self, "Element %s not handled", element_name); +} + +static gboolean +_save_subproject_data (GESXmlFormatter * self, SubprojectData * subproj_data, + gint subproject_end_line, gint subproject_end_char, GError ** error) +{ + gsize size; + gint line = 1, i; + gboolean res = FALSE; + gsize start = 0, end = 0; + gchar *xml = GES_BASE_XML_FORMATTER (self)->xmlcontent; + + for (i = 0; xml[i] != '\0'; i++) { + if (!start && line == subproj_data->start_line) { + i += subproj_data->start_char - 1; + start = i; + } + + if (line == subproject_end_line) { + end = i + subproject_end_char - 1; + break; + } + + if (xml[i] == '\n') + line++; + } + g_assert (start && end); + size = (end - start); + + GST_INFO_OBJECT (self, "Saving subproject %s from %d:%d(%" G_GSIZE_FORMAT + ") to %d:%d(%" G_GSIZE_FORMAT ")", + subproj_data->id, subproj_data->start_line, subproj_data->start_char, + start, subproject_end_line, subproject_end_char, end); + + res = g_file_set_contents (subproj_data->filename, &xml[start], size, error); + + return res; +} + +static void +_parse_element_end (GMarkupParseContext * context, + const gchar * element_name, gpointer self, GError ** error) +{ + GESXmlFormatterPrivate *priv = _GET_PRIV (self); + SubprojectData *subproj_data = priv->subproject; + + /*GESXmlFormatterPrivate *priv = _GET_PRIV (self); */ + + if (!g_strcmp0 (element_name, "ges")) { + gint subproject_end_line, subproject_end_char; + + if (priv->subproject_depth) + priv->subproject_depth -= 1; + + if (!subproj_data) { + if (GES_FORMATTER (self)->project) { + gchar *version = g_strdup_printf ("%d.%d", + API_VERSION, GES_XML_FORMATTER (self)->priv->min_version); + + ges_meta_container_set_string (GES_META_CONTAINER (GES_FORMATTER + (self)->project), GES_META_FORMAT_VERSION, version); + + g_free (version); + _GET_PRIV (self)->ges_opened = FALSE; + } + } else if (subproj_data->start_line != -1 && !priv->subproject_depth) { + g_markup_parse_context_get_position (context, &subproject_end_line, + &subproject_end_char); + _save_subproject_data (GES_XML_FORMATTER (self), subproj_data, + subproject_end_line, subproject_end_char, error); + + subproj_data->filename = NULL; + g_close (subproj_data->fd, error); + subproj_data->id = NULL; + subproj_data->start_line = 0; + subproj_data->start_char = 0; + } + + if (!priv->subproject_depth) { + g_clear_pointer (&priv->subproject, g_free); + } + } else if (!g_strcmp0 (element_name, "clip")) { + if (!priv->subproject) + ges_base_xml_formatter_end_current_clip (GES_BASE_XML_FORMATTER (self)); + } +} + +static void +_error_parsing (GMarkupParseContext * context, GError * error, + gpointer user_data) +{ + GST_WARNING ("Error occurred when parsing %s", error->message); +} + +/*********************************************** + * * + * Saving implementation * + * * + ***********************************************/ + +/* XML writting utils */ +static inline void +string_add_indents (GString * str, guint depth, gboolean prepend) +{ + gint i; + for (i = 0; i < depth; i++) + prepend ? g_string_prepend (str, " ") : g_string_append (str, " "); +} + +static inline void +string_append_with_depth (GString * str, const gchar * string, guint depth) +{ + string_add_indents (str, depth, FALSE); + g_string_append (str, string); +} + +static inline void +append_escaped (GString * str, gchar * tmpstr, guint depth) +{ + string_append_with_depth (str, tmpstr, depth); + g_free (tmpstr); +} + +gboolean +ges_util_can_serialize_spec (GParamSpec * spec) +{ + if (!(spec->flags & G_PARAM_WRITABLE)) { + GST_LOG ("%s from %s is not writable", + spec->name, g_type_name (spec->owner_type)); + + return FALSE; + } else if (spec->flags & G_PARAM_CONSTRUCT_ONLY) { + GST_LOG ("%s from %s is construct only", + spec->name, g_type_name (spec->owner_type)); + + return FALSE; + } else if (spec->flags & GES_PARAM_NO_SERIALIZATION && + g_type_is_a (spec->owner_type, GES_TYPE_TIMELINE_ELEMENT)) { + GST_LOG ("%s from %s is set as GES_PARAM_NO_SERIALIZATION", + spec->name, g_type_name (spec->owner_type)); + + return FALSE; + } else if (g_type_is_a (G_PARAM_SPEC_VALUE_TYPE (spec), G_TYPE_OBJECT)) { + GST_LOG ("%s from %s contains GObject, can't serialize that.", + spec->name, g_type_name (spec->owner_type)); + + return FALSE; + } else if ((g_type_is_a (spec->owner_type, GST_TYPE_OBJECT) && + !g_strcmp0 (spec->name, "name"))) { + + GST_LOG ("We do not want to serialize the name of GstObjects."); + return FALSE; + } else if (G_PARAM_SPEC_VALUE_TYPE (spec) == G_TYPE_GTYPE) { + GST_LOG ("%s from %s contains a GType, can't serialize.", + spec->name, g_type_name (spec->owner_type)); + return FALSE; + } + + return TRUE; +} + +static inline void +_init_value_from_spec_for_serialization (GValue * value, GParamSpec * spec) +{ + + if (g_type_is_a (spec->value_type, G_TYPE_ENUM) || + g_type_is_a (spec->value_type, G_TYPE_FLAGS)) + g_value_init (value, G_TYPE_INT); + else + g_value_init (value, spec->value_type); +} + +static gchar * +_serialize_properties (GObject * object, gint * ret_n_props, + const gchar * fieldname, ...) +{ + gchar *ret; + guint n_props, j; + GParamSpec *spec, **pspecs; + GObjectClass *class = G_OBJECT_GET_CLASS (object); + GstStructure *structure = gst_structure_new_empty ("properties"); + + pspecs = g_object_class_list_properties (class, &n_props); + for (j = 0; j < n_props; j++) { + GValue val = { 0 }; + + spec = pspecs[j]; + if (!ges_util_can_serialize_spec (spec)) + continue; + + _init_value_from_spec_for_serialization (&val, spec); + g_object_get_property (object, spec->name, &val); + if (gst_value_compare (g_param_spec_get_default_value (spec), + &val) == GST_VALUE_EQUAL) { + GST_INFO ("Ignoring %s as it is using the default value", spec->name); + goto next; + } + + if (spec->value_type == GST_TYPE_CAPS) { + gchar *caps_str; + const GstCaps *caps = gst_value_get_caps (&val); + + caps_str = gst_caps_to_string (caps); + gst_structure_set (structure, spec->name, G_TYPE_STRING, caps_str, NULL); + g_free (caps_str); + goto next; + } + + gst_structure_set_value (structure, spec->name, &val); + + next: + g_value_unset (&val); + } + g_free (pspecs); + + if (fieldname) { + va_list varargs; + va_start (varargs, fieldname); + gst_structure_remove_fields_valist (structure, fieldname, varargs); + va_end (varargs); + } + + ret = gst_structure_to_string (structure); + if (ret_n_props) + *ret_n_props = gst_structure_n_fields (structure); + gst_structure_free (structure); + + return ret; +} + +static void +project_loaded_cb (GESProject * project, GESTimeline * timeline, + SubprojectData * data) +{ + g_main_loop_quit (data->ml); +} + +static void +error_loading_asset_cb (GESProject * project, GError * err, + const gchar * unused_id, GType extractable_type, SubprojectData * data) +{ + data->error = g_error_copy (err); + g_main_loop_quit (data->ml); +} + +static gboolean +_save_subproject (GESXmlFormatter * self, GString * str, GESProject * project, + GESAsset * subproject, GError ** error, guint depth) +{ + GString *substr; + GESTimeline *timeline; + gchar *properties, *metas; + GESXmlFormatterPrivate *priv = self->priv; + GMainContext *context = g_main_context_get_thread_default (); + const gchar *id = ges_asset_get_id (subproject); + SubprojectData data = { 0, }; + + if (!g_strcmp0 (ges_asset_get_id (GES_ASSET (project)), id)) { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Project %s trying to recurse into itself", id); + return FALSE; + } + + G_LOCK (uri_subprojects_map_lock); + g_hash_table_insert (priv->subprojects_map, g_strdup (id), g_strdup (id)); + G_UNLOCK (uri_subprojects_map_lock); + timeline = GES_TIMELINE (ges_asset_extract (subproject, error)); + if (!timeline) { + return FALSE; + } + + if (!context) + context = g_main_context_default (); + + data.ml = g_main_loop_new (context, TRUE); + g_signal_connect (subproject, "loaded", (GCallback) project_loaded_cb, &data); + g_signal_connect (subproject, "error-loading-asset", + (GCallback) error_loading_asset_cb, &data); + g_main_loop_run (data.ml); + + g_signal_handlers_disconnect_by_func (subproject, project_loaded_cb, &data); + g_signal_handlers_disconnect_by_func (subproject, error_loading_asset_cb, + &data); + if (data.error) { + g_propagate_error (error, data.error); + return FALSE; + } + + subproject = ges_extractable_get_asset (GES_EXTRACTABLE (timeline)); + substr = g_string_new (NULL); + properties = _serialize_properties (G_OBJECT (subproject), NULL, NULL); + metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (subproject)); + append_escaped (str, + g_markup_printf_escaped + (" <asset id='%s' extractable-type-name='%s' properties='%s' metadatas='%s'>\n", + ges_asset_get_id (subproject), + g_type_name (ges_asset_get_extractable_type (subproject)), properties, + metas), depth); + self->priv->min_version = MAX (self->priv->min_version, 6); + + depth += 4; + GST_DEBUG_OBJECT (self, "Saving subproject %s (depth: %d)", + ges_asset_get_id (subproject), depth / 4); + if (!_save_project (GES_FORMATTER (self), substr, GES_PROJECT (subproject), + timeline, error, depth)) { + g_string_free (substr, TRUE); + g_object_unref (subproject); + goto err; + } + GST_DEBUG_OBJECT (self, "DONE Saving subproject %s", + ges_asset_get_id (subproject)); + depth -= 4; + + g_string_append (str, substr->str); + g_string_free (substr, TRUE); + string_append_with_depth (str, " </asset>\n", depth); + +err: + g_object_unref (subproject); + + return TRUE; +} + +static gint +sort_assets (GESAsset * a, GESAsset * b) +{ + if (GES_IS_PROJECT (a)) + return -1; + + if (GES_IS_PROJECT (b)) + return 1; + + return 0; +} + +static void +_serialize_streams (GESXmlFormatter * self, GString * str, + GESUriClipAsset * asset, GError ** error, guint depth) +{ + const GList *tmp, *streams = ges_uri_clip_asset_get_stream_assets (asset); + + for (tmp = streams; tmp; tmp = tmp->next) { + gchar *properties, *metas, *capsstr; + const gchar *id = ges_asset_get_id (tmp->data); + GstDiscovererStreamInfo *sinfo = + ges_uri_source_asset_get_stream_info (tmp->data); + GstCaps *caps = gst_discoverer_stream_info_get_caps (sinfo); + + properties = _serialize_properties (tmp->data, NULL, NULL); + metas = ges_meta_container_metas_to_string (tmp->data); + capsstr = gst_caps_to_string (caps); + + append_escaped (str, + g_markup_printf_escaped + (" <stream-info id='%s' extractable-type-name='%s' properties='%s' metadatas='%s' caps='%s'/>\n", + id, g_type_name (ges_asset_get_extractable_type (tmp->data)), + properties, metas, capsstr), depth); + self->priv->min_version = MAX (self->priv->min_version, 6); + g_free (metas); + g_free (properties); + g_free (capsstr); + gst_caps_unref (caps); + } + +} + +static inline gboolean +_save_assets (GESXmlFormatter * self, GString * str, GESProject * project, + GError ** error, guint depth) +{ + gchar *properties, *metas; + GESAsset *asset, *proxy; + GList *assets, *tmp; + const gchar *id; + GESXmlFormatterPrivate *priv = self->priv; + + assets = ges_project_list_assets (project, GES_TYPE_EXTRACTABLE); + for (tmp = g_list_sort (assets, (GCompareFunc) sort_assets); tmp; + tmp = tmp->next) { + asset = GES_ASSET (tmp->data); + id = ges_asset_get_id (asset); + + if (GES_IS_PROJECT (asset)) { + if (!_save_subproject (self, str, project, asset, error, depth)) + return FALSE; + + continue; + } + + if (ges_asset_get_extractable_type (asset) == GES_TYPE_URI_CLIP) { + G_LOCK (uri_subprojects_map_lock); + if (g_hash_table_contains (priv->subprojects_map, id)) { + id = g_hash_table_lookup (priv->subprojects_map, id); + + GST_DEBUG_OBJECT (self, "Using subproject %s", id); + } + G_UNLOCK (uri_subprojects_map_lock); + } + + properties = _serialize_properties (G_OBJECT (asset), NULL, NULL); + metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (asset)); + append_escaped (str, + g_markup_printf_escaped + (" <asset id='%s' extractable-type-name='%s' properties='%s' metadatas='%s' ", + id, g_type_name (ges_asset_get_extractable_type (asset)), + properties, metas), depth); + + /*TODO Save the whole list of proxies */ + proxy = ges_asset_get_proxy (asset); + if (proxy) { + const gchar *proxy_id = ges_asset_get_id (proxy); + + if (ges_asset_get_extractable_type (asset) == GES_TYPE_URI_CLIP) { + G_LOCK (uri_subprojects_map_lock); + if (g_hash_table_contains (priv->subprojects_map, proxy_id)) { + proxy_id = g_hash_table_lookup (priv->subprojects_map, proxy_id); + + GST_DEBUG_OBJECT (self, "Using subproject %s", id); + } + G_UNLOCK (uri_subprojects_map_lock); + } + append_escaped (str, g_markup_printf_escaped (" proxy-id='%s' ", + proxy_id), depth); + + if (!g_list_find (assets, proxy)) { + assets = g_list_append (assets, gst_object_ref (proxy)); + + if (!tmp->next) + tmp->next = g_list_last (assets); + } + + self->priv->min_version = MAX (self->priv->min_version, 3); + } + g_string_append (str, ">\n"); + + if (GES_IS_URI_CLIP_ASSET (asset)) { + _serialize_streams (self, str, GES_URI_CLIP_ASSET (asset), error, depth); + } + + string_append_with_depth (str, " </asset>\n", depth); + g_free (properties); + g_free (metas); + } + + g_list_free_full (assets, gst_object_unref); + + return TRUE; +} + +static inline void +_save_tracks (GESXmlFormatter * self, GString * str, GESTimeline * timeline, + guint depth) +{ + gchar *strtmp, *metas; + GESTrack *track; + GList *tmp, *tracks; + char *properties; + + guint nb_tracks = 0; + + tracks = ges_timeline_get_tracks (timeline); + for (tmp = tracks; tmp; tmp = tmp->next) { + track = GES_TRACK (tmp->data); + properties = _serialize_properties (G_OBJECT (track), NULL, "caps", NULL); + strtmp = gst_caps_to_string (ges_track_get_caps (track)); + metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (track)); + append_escaped (str, + g_markup_printf_escaped + (" <track caps='%s' track-type='%i' track-id='%i' properties='%s' metadatas='%s'/>\n", + strtmp, track->type, nb_tracks++, properties, metas), depth); + g_free (strtmp); + g_free (metas); + g_free (properties); + } + g_list_free_full (tracks, gst_object_unref); +} + +static inline void +_save_children_properties (GString * str, GESTimelineElement * element, + guint depth) +{ + GstStructure *structure; + GParamSpec **pspecs, *spec; + guint i, n_props; + gchar *struct_str; + + pspecs = ges_timeline_element_list_children_properties (element, &n_props); + + structure = gst_structure_new_empty ("properties"); + for (i = 0; i < n_props; i++) { + GValue val = { 0 }; + spec = pspecs[i]; + + if (ges_util_can_serialize_spec (spec)) { + gchar *spec_name = + g_strdup_printf ("%s::%s", g_type_name (spec->owner_type), + spec->name); + + _init_value_from_spec_for_serialization (&val, spec); + ges_timeline_element_get_child_property_by_pspec (element, spec, &val); + gst_structure_set_value (structure, spec_name, &val); + + g_free (spec_name); + g_value_unset (&val); + } + g_param_spec_unref (spec); + } + g_free (pspecs); + + struct_str = gst_structure_to_string (structure); + append_escaped (str, + g_markup_printf_escaped (" children-properties='%s'", struct_str), 0); + gst_structure_free (structure); + g_free (struct_str); +} + +/* TODO : Use this function for every track element with controllable properties */ +static inline void +_save_keyframes (GString * str, GESTrackElement * trackelement, gint index, + guint depth) +{ + GHashTable *bindings_hashtable; + GHashTableIter iter; + gpointer key, value; + + bindings_hashtable = + ges_track_element_get_all_control_bindings (trackelement); + + g_hash_table_iter_init (&iter, bindings_hashtable); + + /* We iterate over the bindings, and save the timed values */ + while (g_hash_table_iter_next (&iter, &key, &value)) { + if (GST_IS_DIRECT_CONTROL_BINDING ((GstControlBinding *) value)) { + GstControlSource *source; + gboolean absolute = FALSE; + GstDirectControlBinding *binding; + + binding = (GstDirectControlBinding *) value; + + g_object_get (binding, "control-source", &source, + "absolute", &absolute, NULL); + + if (GST_IS_INTERPOLATION_CONTROL_SOURCE (source)) { + GList *timed_values, *tmp; + GstInterpolationMode mode; + + append_escaped (str, + g_markup_printf_escaped + (" <binding type='%s' source_type='interpolation' property='%s'", + absolute ? "direct-absolute" : "direct", (gchar *) key), depth); + + g_object_get (source, "mode", &mode, NULL); + append_escaped (str, g_markup_printf_escaped (" mode='%d'", mode), + depth); + append_escaped (str, g_markup_printf_escaped (" track_id='%d'", index), + depth); + append_escaped (str, g_markup_printf_escaped (" values ='"), depth); + timed_values = + gst_timed_value_control_source_get_all + (GST_TIMED_VALUE_CONTROL_SOURCE (source)); + for (tmp = timed_values; tmp; tmp = tmp->next) { + gchar strbuf[G_ASCII_DTOSTR_BUF_SIZE]; + GstTimedValue *value; + + value = (GstTimedValue *) tmp->data; + append_escaped (str, g_markup_printf_escaped (" %" G_GUINT64_FORMAT + ":%s ", value->timestamp, g_ascii_dtostr (strbuf, + G_ASCII_DTOSTR_BUF_SIZE, value->value)), depth); + } + g_list_free (timed_values); + append_escaped (str, g_markup_printf_escaped ("'/>\n"), depth); + } else + GST_DEBUG ("control source not in [interpolation]"); + + gst_object_unref (source); + } else + GST_DEBUG ("Binding type not in [direct, direct-absolute]"); + } +} + +static inline void +_save_effect (GString * str, guint clip_id, GESTrackElement * trackelement, + GESTimeline * timeline, guint depth) +{ + GESTrack *tck; + GList *tmp, *tracks; + gchar *properties, *metas; + guint track_id = 0; + gboolean serialize; + gchar *extractable_id; + + g_object_get (trackelement, "serialize", &serialize, NULL); + if (!serialize) { + + GST_DEBUG_OBJECT (trackelement, "Should not be serialized"); + + return; + } + + tck = ges_track_element_get_track (trackelement); + if (tck == NULL) { + GST_WARNING_OBJECT (trackelement, " Not in any track, can not save it"); + + return; + } + + tracks = ges_timeline_get_tracks (timeline); + for (tmp = tracks; tmp; tmp = tmp->next) { + if (tmp->data == tck) + break; + track_id++; + } + g_list_free_full (tracks, gst_object_unref); + + properties = _serialize_properties (G_OBJECT (trackelement), NULL, "start", + "duration", "locked", "name", "priority", NULL); + metas = + ges_meta_container_metas_to_string (GES_META_CONTAINER (trackelement)); + extractable_id = ges_extractable_get_id (GES_EXTRACTABLE (trackelement)); + append_escaped (str, + g_markup_printf_escaped (" <effect asset-id='%s' clip-id='%u'" + " type-name='%s' track-type='%i' track-id='%i' properties='%s' metadatas='%s'", + extractable_id, clip_id, + g_type_name (G_OBJECT_TYPE (trackelement)), tck->type, track_id, + properties, metas), depth); + g_free (extractable_id); + g_free (properties); + g_free (metas); + + _save_children_properties (str, GES_TIMELINE_ELEMENT (trackelement), depth); + append_escaped (str, g_markup_printf_escaped (">\n"), depth); + + _save_keyframes (str, trackelement, -1, depth); + + append_escaped (str, g_markup_printf_escaped (" </effect>\n"), + depth); +} + +static inline void +_save_layer_track_activness (GESXmlFormatter * self, GESLayer * layer, + GString * str, GESTimeline * timeline, guint depth) +{ + guint nb_tracks = 0, i; + GList *tmp, *tracks = ges_timeline_get_tracks (timeline); + GArray *deactivated_tracks = g_array_new (TRUE, FALSE, sizeof (gint32)); + + for (tmp = tracks; tmp; tmp = tmp->next, nb_tracks++) { + if (!ges_layer_get_active_for_track (layer, tmp->data)) + g_array_append_val (deactivated_tracks, nb_tracks); + } + + if (!deactivated_tracks->len) { + g_string_append (str, ">\n"); + goto done; + } + + self->priv->min_version = MAX (self->priv->min_version, 7); + g_string_append (str, " deactivated-tracks='"); + for (i = 0; i < deactivated_tracks->len; i++) + g_string_append_printf (str, "%d ", g_array_index (deactivated_tracks, gint, + i)); + g_string_append (str, "'>\n"); + +done: + g_array_free (deactivated_tracks, TRUE); + g_list_free_full (tracks, gst_object_unref); +} + +static void +_save_source (GESXmlFormatter * self, GString * str, + GESTimelineElement * element, GESTimeline * timeline, GList * tracks, + guint depth) +{ + gint index, n_props; + gboolean serialize; + gchar *properties, *metas; + + if (!GES_IS_SOURCE (element)) + return; + + g_object_get (element, "serialize", &serialize, NULL); + if (!serialize) { + GST_DEBUG_OBJECT (element, "Should not be serialized"); + return; + } + + index = + g_list_index (tracks, + ges_track_element_get_track (GES_TRACK_ELEMENT (element))); + append_escaped (str, + g_markup_printf_escaped + (" <source track-id='%i' ", index), depth); + + properties = _serialize_properties (G_OBJECT (element), &n_props, + "in-point", "priority", "start", "duration", "track", "track-type" + "uri", "name", "max-duration", NULL); + + /* Try as possible to allow older versions of GES to load the files */ + if (n_props) { + self->priv->min_version = MAX (self->priv->min_version, 7); + g_string_append_printf (str, "properties='%s' ", properties); + } + g_free (properties); + + metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (element)); + g_string_append_printf (str, "metadatas='%s' ", metas); + g_free (metas); + + _save_children_properties (str, element, depth); + append_escaped (str, g_markup_printf_escaped (">\n"), depth); + _save_keyframes (str, GES_TRACK_ELEMENT (element), index, depth); + append_escaped (str, g_markup_printf_escaped (" </source>\n"), + depth); +} + +static inline void +_save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline, + guint depth) +{ + gchar *properties, *metas; + GESLayer *layer; + GESClip *clip; + GList *tmplayer, *tmpclip, *clips; + GESXmlFormatterPrivate *priv = self->priv; + + for (tmplayer = timeline->layers; tmplayer; tmplayer = tmplayer->next) { + guint priority; + layer = GES_LAYER (tmplayer->data); + + priority = ges_layer_get_priority (layer); + properties = + _serialize_properties (G_OBJECT (layer), NULL, "priority", NULL); + metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (layer)); + append_escaped (str, + g_markup_printf_escaped + (" <layer priority='%i' properties='%s' metadatas='%s'", + priority, properties, metas), depth); + g_free (properties); + g_free (metas); + + _save_layer_track_activness (self, layer, str, timeline, depth); + + clips = ges_layer_get_clips (layer); + for (tmpclip = clips; tmpclip; tmpclip = tmpclip->next) { + GList *effects, *tmpeffect; + GList *tmptrackelement; + GList *tracks; + gboolean serialize; + gchar *extractable_id; + + clip = GES_CLIP (tmpclip->data); + + g_object_get (clip, "serialize", &serialize, NULL); + if (!serialize) { + GST_DEBUG_OBJECT (clip, "Should not be serialized"); + continue; + } + + /* We escape all mandatrorry properties that are handled sparetely + * and vtype for StandarTransition as it is the asset ID */ + properties = _serialize_properties (G_OBJECT (clip), NULL, + "supported-formats", "rate", "in-point", "start", "duration", + "max-duration", "priority", "vtype", "uri", NULL); + extractable_id = ges_extractable_get_id (GES_EXTRACTABLE (clip)); + if (GES_IS_URI_CLIP (clip)) { + G_LOCK (uri_subprojects_map_lock); + if (g_hash_table_contains (priv->subprojects_map, extractable_id)) + extractable_id = + g_strdup (g_hash_table_lookup (priv->subprojects_map, + extractable_id)); + G_UNLOCK (uri_subprojects_map_lock); + } + metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (clip)); + append_escaped (str, + g_markup_printf_escaped (" <clip id='%i' asset-id='%s'" + " type-name='%s' layer-priority='%i' track-types='%i' start='%" + G_GUINT64_FORMAT "' duration='%" G_GUINT64_FORMAT "' inpoint='%" + G_GUINT64_FORMAT "' rate='%d' properties='%s' metadatas='%s'", + priv->nbelements, extractable_id, + g_type_name (G_OBJECT_TYPE (clip)), priority, + ges_clip_get_supported_formats (clip), _START (clip), + _DURATION (clip), _INPOINT (clip), 0, properties, metas), depth); + g_free (metas); + + if (GES_IS_TRANSITION_CLIP (clip)) { + _save_children_properties (str, GES_TIMELINE_ELEMENT (clip), depth); + self->priv->min_version = MAX (self->priv->min_version, 4); + } + g_string_append (str, ">\n"); + + g_free (extractable_id); + g_free (properties); + + g_hash_table_insert (self->priv->element_id, clip, + GINT_TO_POINTER (priv->nbelements)); + + + /* Effects must always be serialized in the right priority order. + * List order is guaranteed by the fact that ges_clip_get_top_effects + * sorts the effects. */ + effects = ges_clip_get_top_effects (clip); + for (tmpeffect = effects; tmpeffect; tmpeffect = tmpeffect->next) { + _save_effect (str, priv->nbelements, + GES_TRACK_ELEMENT (tmpeffect->data), timeline, depth); + } + g_list_free (effects); + tracks = ges_timeline_get_tracks (timeline); + + for (tmptrackelement = GES_CONTAINER_CHILDREN (clip); tmptrackelement; + tmptrackelement = tmptrackelement->next) { + _save_source (self, str, tmptrackelement->data, timeline, tracks, + depth); + } + g_list_free_full (tracks, gst_object_unref); + + string_append_with_depth (str, " </clip>\n", depth); + + priv->nbelements++; + } + g_list_free_full (clips, (GDestroyNotify) gst_object_unref); + string_append_with_depth (str, " </layer>\n", depth); + } +} + +static void +_save_group (GESXmlFormatter * self, GString * str, GList ** seen_groups, + GESGroup * group, guint depth) +{ + GList *tmp; + gboolean serialize; + gchar *properties, *metadatas; + + g_object_get (group, "serialize", &serialize, NULL); + if (!serialize) { + + GST_DEBUG_OBJECT (group, "Should not be serialized"); + + return; + } + + if (g_list_find (*seen_groups, group)) { + GST_DEBUG_OBJECT (group, "Already serialized"); + + return; + } + + *seen_groups = g_list_prepend (*seen_groups, group); + for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) { + if (GES_IS_GROUP (tmp->data)) { + _save_group (self, str, seen_groups, + GES_GROUP (GES_TIMELINE_ELEMENT (tmp->data)), depth); + } + } + + properties = _serialize_properties (G_OBJECT (group), NULL, NULL); + + metadatas = ges_meta_container_metas_to_string (GES_META_CONTAINER (group)); + self->priv->min_version = MAX (self->priv->min_version, 5); + + string_add_indents (str, depth, FALSE); + g_string_append_printf (str, + " <group id='%d' properties='%s' metadatas='%s'>\n", + self->priv->nbelements, properties, metadatas); + g_free (properties); + g_free (metadatas); + g_hash_table_insert (self->priv->element_id, group, + GINT_TO_POINTER (self->priv->nbelements)); + self->priv->nbelements++; + + for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) { + gint id = GPOINTER_TO_INT (g_hash_table_lookup (self->priv->element_id, + tmp->data)); + + string_add_indents (str, depth, FALSE); + g_string_append_printf (str, " <child id='%d' name='%s'/>\n", id, + GES_TIMELINE_ELEMENT_NAME (tmp->data)); + } + string_append_with_depth (str, " </group>\n", depth); +} + +static void +_save_groups (GESXmlFormatter * self, GString * str, GESTimeline * timeline, + guint depth) +{ + GList *tmp; + GList *seen_groups = NULL; + + string_append_with_depth (str, " <groups>\n", depth); + for (tmp = ges_timeline_get_groups (timeline); tmp; tmp = tmp->next) { + _save_group (self, str, &seen_groups, tmp->data, depth); + } + g_list_free (seen_groups); + string_append_with_depth (str, " </groups>\n", depth); +} + +static inline void +_save_timeline (GESXmlFormatter * self, GString * str, GESTimeline * timeline, + guint depth) +{ + gchar *properties = NULL, *metas = NULL; + + properties = + _serialize_properties (G_OBJECT (timeline), NULL, "update", "name", + "async-handling", "message-forward", NULL); + + ges_meta_container_set_uint64 (GES_META_CONTAINER (timeline), "duration", + ges_timeline_get_duration (timeline)); + metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (timeline)); + append_escaped (str, + g_markup_printf_escaped + (" <timeline properties='%s' metadatas='%s'>\n", properties, metas), + depth); + + _save_tracks (self, str, timeline, depth); + _save_layers (self, str, timeline, depth); + _save_groups (self, str, timeline, depth); + + string_append_with_depth (str, " </timeline>\n", depth); + + g_free (properties); + g_free (metas); +} + +static void +_save_stream_profiles (GESXmlFormatter * self, GString * str, + GstEncodingProfile * sprof, const gchar * profilename, guint id, + guint depth) +{ + gchar *tmpc; + GstCaps *tmpcaps; + GstStructure *properties; + const gchar *preset, *preset_name, *name, *description; + + append_escaped (str, + g_markup_printf_escaped + (" <stream-profile parent='%s' id='%d' type='%s' " + "presence='%d' ", profilename, id, + gst_encoding_profile_get_type_nick (sprof), + gst_encoding_profile_get_presence (sprof)), depth); + + if (!gst_encoding_profile_is_enabled (sprof)) { + append_escaped (str, g_strdup ("enabled='0' "), depth); + + self->priv->min_version = MAX (self->priv->min_version, 2); + } + + tmpcaps = gst_encoding_profile_get_format (sprof); + if (tmpcaps) { + tmpc = gst_caps_to_string (tmpcaps); + append_escaped (str, g_markup_printf_escaped ("format='%s' ", tmpc), depth); + gst_caps_unref (tmpcaps); + g_free (tmpc); + } + + name = gst_encoding_profile_get_name (sprof); + if (name) + append_escaped (str, g_markup_printf_escaped ("name='%s' ", name), depth); + + description = gst_encoding_profile_get_description (sprof); + if (description) + append_escaped (str, g_markup_printf_escaped ("description='%s' ", + description), depth); + + preset = gst_encoding_profile_get_preset (sprof); + if (preset) { + append_escaped (str, g_markup_printf_escaped ("preset='%s' ", preset), + depth); + } + + properties = gst_encoding_profile_get_element_properties (sprof); + if (properties) { + gchar *props_str = gst_structure_to_string (properties); + + append_escaped (str, + g_markup_printf_escaped ("preset-properties='%s' ", props_str), depth); + g_free (props_str); + gst_structure_free (properties); + } + + preset_name = gst_encoding_profile_get_preset_name (sprof); + if (preset_name) + append_escaped (str, g_markup_printf_escaped ("preset-name='%s' ", + preset_name), depth); + + tmpcaps = gst_encoding_profile_get_restriction (sprof); + if (tmpcaps) { + tmpc = gst_caps_to_string (tmpcaps); + append_escaped (str, g_markup_printf_escaped ("restriction='%s' ", tmpc), + depth); + gst_caps_unref (tmpcaps); + g_free (tmpc); + } + + if (GST_IS_ENCODING_VIDEO_PROFILE (sprof)) { + GstEncodingVideoProfile *vp = (GstEncodingVideoProfile *) sprof; + + append_escaped (str, + g_markup_printf_escaped ("pass='%d' variableframerate='%i' ", + gst_encoding_video_profile_get_pass (vp), + gst_encoding_video_profile_get_variableframerate (vp)), depth); + } + + g_string_append (str, "/>\n"); +} + +static inline void +_save_encoding_profiles (GESXmlFormatter * self, GString * str, + GESProject * project, guint depth) +{ + GstCaps *profformat; + GstStructure *properties; + const gchar *profname, *profdesc, *profpreset, *proftype, *profpresetname; + + const GList *tmp; + GList *profiles = g_list_reverse (g_list_copy ((GList *) + ges_project_list_encoding_profiles (project))); + + for (tmp = profiles; tmp; tmp = tmp->next) { + GstEncodingProfile *prof = GST_ENCODING_PROFILE (tmp->data); + + profname = gst_encoding_profile_get_name (prof); + profdesc = gst_encoding_profile_get_description (prof); + profpreset = gst_encoding_profile_get_preset (prof); + profpresetname = gst_encoding_profile_get_preset_name (prof); + proftype = gst_encoding_profile_get_type_nick (prof); + + append_escaped (str, + g_markup_printf_escaped + (" <encoding-profile name='%s' description='%s' type='%s' ", + profname, profdesc, proftype), depth); + + if (profpreset) { + append_escaped (str, g_markup_printf_escaped ("preset='%s' ", + profpreset), depth); + } + + properties = gst_encoding_profile_get_element_properties (prof); + if (properties) { + gchar *props_str = gst_structure_to_string (properties); + + append_escaped (str, + g_markup_printf_escaped ("preset-properties='%s' ", props_str), + depth); + g_free (props_str); + gst_structure_free (properties); + } + + if (profpresetname) + append_escaped (str, g_markup_printf_escaped ("preset-name='%s' ", + profpresetname), depth); + + profformat = gst_encoding_profile_get_format (prof); + if (profformat) { + gchar *format = gst_caps_to_string (profformat); + append_escaped (str, g_markup_printf_escaped ("format='%s' ", format), + depth); + g_free (format); + gst_caps_unref (profformat); + } + + g_string_append (str, ">\n"); + + if (GST_IS_ENCODING_CONTAINER_PROFILE (prof)) { + guint i = 0; + const GList *tmp2; + GstEncodingContainerProfile *container_prof; + + container_prof = GST_ENCODING_CONTAINER_PROFILE (prof); + for (tmp2 = gst_encoding_container_profile_get_profiles (container_prof); + tmp2; tmp2 = tmp2->next, i++) { + GstEncodingProfile *sprof = (GstEncodingProfile *) tmp2->data; + _save_stream_profiles (self, str, sprof, profname, i, depth); + } + } + append_escaped (str, + g_markup_printf_escaped (" </encoding-profile>\n"), depth); + } + g_list_free (profiles); +} + +static GString * +_save (GESFormatter * formatter, GESTimeline * timeline, GError ** error) +{ + GString *str; + GESProject *project; + GESXmlFormatterPrivate *priv = _GET_PRIV (formatter); + + priv->min_version = 1; + project = formatter->project; + str = priv->str = g_string_new (NULL); + + return _save_project (formatter, str, project, timeline, error, 0); +} + +static GString * +_save_project (GESFormatter * formatter, GString * str, GESProject * project, + GESTimeline * timeline, GError ** error, guint depth) +{ + gchar *projstr = NULL, *version; + gchar *properties = NULL, *metas = NULL; + GESXmlFormatter *self = GES_XML_FORMATTER (formatter); + GESXmlFormatterPrivate *priv = _GET_PRIV (formatter); + + properties = _serialize_properties (G_OBJECT (project), NULL, NULL); + metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (project)); + append_escaped (str, + g_markup_printf_escaped (" <project properties='%s' metadatas='%s'>\n", + properties, metas), depth); + g_free (properties); + g_free (metas); + + string_append_with_depth (str, " <encoding-profiles>\n", depth); + _save_encoding_profiles (GES_XML_FORMATTER (formatter), str, project, depth); + string_append_with_depth (str, " </encoding-profiles>\n", depth); + + string_append_with_depth (str, " <ressources>\n", depth); + if (!_save_assets (self, str, project, error, depth)) { + g_string_free (str, TRUE); + return NULL; + } + string_append_with_depth (str, " </ressources>\n", depth); + + _save_timeline (self, str, timeline, depth); + string_append_with_depth (str, " </project>\n", depth); + string_append_with_depth (str, "</ges>\n", depth); + + projstr = g_strdup_printf ("<ges version='%i.%i'>\n", API_VERSION, + priv->min_version); + g_string_prepend (str, projstr); + string_add_indents (str, depth, TRUE); + g_free (projstr); + + ges_meta_container_set_int (GES_META_CONTAINER (project), + GES_META_FORMAT_VERSION, priv->min_version); + + version = g_strdup_printf ("%d.%d", API_VERSION, + GES_XML_FORMATTER (formatter)->priv->min_version); + + ges_meta_container_set_string (GES_META_CONTAINER (project), + GES_META_FORMAT_VERSION, version); + + g_free (version); + + priv->str = NULL; + + return str; +} + +static void +_setup_subprojects_map (GESXmlFormatterPrivate * priv, const gchar * uri) +{ + GHashTable *subprojects_map; + + G_LOCK (uri_subprojects_map_lock); + if (!uri_subprojects_map) + uri_subprojects_map = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) g_hash_table_unref); + + subprojects_map = g_hash_table_lookup (uri_subprojects_map, uri); + if (!subprojects_map) { + subprojects_map = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_insert (uri_subprojects_map, g_strdup (uri), subprojects_map); + } + priv->subprojects_map = subprojects_map; + G_UNLOCK (uri_subprojects_map_lock); + +} + +void +ges_xml_formatter_deinit (void) +{ + GST_DEBUG ("Deinit"); + G_LOCK (uri_subprojects_map_lock); + if (uri_subprojects_map) { + g_hash_table_unref (uri_subprojects_map); + uri_subprojects_map = NULL; + } + G_UNLOCK (uri_subprojects_map_lock); +} + +static gboolean +_save_to_uri (GESFormatter * formatter, GESTimeline * timeline, + const gchar * uri, gboolean overwrite, GError ** error) +{ + _setup_subprojects_map (_GET_PRIV (formatter), uri); + return GES_FORMATTER_CLASS (parent_class)->save_to_uri (formatter, timeline, + uri, overwrite, error); +} + +static gboolean +_can_load_uri (GESFormatter * formatter, const gchar * uri, GError ** error) +{ + _setup_subprojects_map (_GET_PRIV (formatter), uri); + return GES_FORMATTER_CLASS (parent_class)->can_load_uri (formatter, uri, + error); +} + +static gboolean +_load_from_uri (GESFormatter * formatter, GESTimeline * timeline, + const gchar * uri, GError ** error) +{ + _setup_subprojects_map (_GET_PRIV (formatter), uri); + return GES_FORMATTER_CLASS (parent_class)->load_from_uri (formatter, timeline, + uri, error); +} + +/*********************************************** + * * + * GObject virtual methods implementation * + * * + ***********************************************/ + +static void +_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ +} + +static void +_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ +} + +static void +ges_xml_formatter_init (GESXmlFormatter * self) +{ + GESXmlFormatterPrivate *priv = ges_xml_formatter_get_instance_private (self); + + priv->project_opened = FALSE; + priv->element_id = g_hash_table_new (g_direct_hash, g_direct_equal); + + self->priv = priv; + self->priv->min_version = 1; +} + +static void +_dispose (GObject * object) +{ + g_clear_pointer (&GES_XML_FORMATTER (object)->priv->element_id, + g_hash_table_unref); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +ges_xml_formatter_class_init (GESXmlFormatterClass * self_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (self_class); + GESBaseXmlFormatterClass *basexmlformatter_class; + GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (self_class); + + basexmlformatter_class = GES_BASE_XML_FORMATTER_CLASS (self_class); + + formatter_klass->save_to_uri = _save_to_uri; + formatter_klass->can_load_uri = _can_load_uri; + formatter_klass->load_from_uri = _load_from_uri; + + object_class->get_property = _get_property; + object_class->set_property = _set_property; + object_class->dispose = _dispose; + + basexmlformatter_class->content_parser.start_element = _parse_element_start; + basexmlformatter_class->content_parser.end_element = _parse_element_end; + basexmlformatter_class->content_parser.text = NULL; + basexmlformatter_class->content_parser.passthrough = NULL; + basexmlformatter_class->content_parser.error = _error_parsing; + + ges_formatter_class_register_metas (GES_FORMATTER_CLASS (self_class), + "ges", "GStreamer Editing Services project files", + "xges", "application/xges", VERSION, GST_RANK_PRIMARY); + + basexmlformatter_class->save = _save; +} + +#undef COLLECT_STR_OPT diff --git a/ges/ges-xml-formatter.h b/ges/ges-xml-formatter.h new file mode 100644 index 0000000000..a88f35554f --- /dev/null +++ b/ges/ges-xml-formatter.h @@ -0,0 +1,44 @@ +/* Gstreamer Editing Services + * + * Copyright (C) <2012> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#pragma once + +#include "ges-base-xml-formatter.h" + +G_BEGIN_DECLS +#define GES_TYPE_XML_FORMATTER (ges_xml_formatter_get_type ()) +GES_DECLARE_TYPE(XmlFormatter, xml_formatter, XML_FORMATTER); + +struct _GESXmlFormatter +{ + GESBaseXmlFormatter parent; + + GESXmlFormatterPrivate *priv; + + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESXmlFormatterClass +{ + GESBaseXmlFormatterClass parent; + + gpointer _ges_reserved[GES_PADDING]; +}; + +G_END_DECLS diff --git a/ges/ges.c b/ges/ges.c new file mode 100644 index 0000000000..d34c4efd79 --- /dev/null +++ b/ges/ges.c @@ -0,0 +1,424 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION: ges.h + * @title: Initialization + * @short_description: GStreamer editing services initialization functions + * + * GES needs to be initialized after GStreamer itself. This section + * contains the various functions to do so. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <ges/ges.h> +#include "ges/gstframepositioner.h" +#include "ges/ges-smart-adder.h" +#include "ges/ges-smart-video-mixer.h" +#include "ges-internal.h" + +#ifndef DISABLE_XPTV +#include <ges/ges-pitivi-formatter.h> +#endif + +#define GES_GNONLIN_VERSION_NEEDED_MAJOR 1 +#define GES_GNONLIN_VERSION_NEEDED_MINOR 2 +#define GES_GNONLIN_VERSION_NEEDED_MICRO 0 + +GST_DEBUG_CATEGORY (_ges_debug_category); + +G_LOCK_DEFINE_STATIC (init_lock); + +/* (without holding ref) thread object for thread_self() validation + * between init/deinit + */ +static GThread *initialized_thread = NULL; + +#ifndef GST_DISABLE_GST_DEBUG +static gpointer +init_debug_category (gpointer data) +{ + /* initialize debugging category */ + GST_DEBUG_CATEGORY_INIT (_ges_debug_category, "ges", GST_DEBUG_FG_YELLOW, + "GStreamer Editing Services"); + + return _ges_debug_category; +} + +GstDebugCategory * +_ges_debug (void) +{ + static GOnce my_once = G_ONCE_INIT; + + g_once (&my_once, init_debug_category, NULL); + + return my_once.retval; +} +#else +GstDebugCategory * +_ges_debug (void) +{ + return NULL; +} + +#endif + +static gboolean +ges_init_pre (GOptionContext * context, GOptionGroup * group, gpointer data, + GError ** error) +{ + if (initialized_thread) { + GST_DEBUG ("already initialized"); + return TRUE; + } + + return TRUE; +} + +static gboolean +ges_init_post (GOptionContext * context, GOptionGroup * group, gpointer data, + GError ** error) +{ + GESUriClipAssetClass *uriasset_klass = NULL; + GstElementFactory *nlecomposition_factory = NULL; + static GstValueTable gstvtable = { + G_TYPE_NONE, + (GstValueCompareFunc) NULL, + (GstValueSerializeFunc) ges_marker_list_serialize, + (GstValueDeserializeFunc) ges_marker_list_deserialize + }; + static gboolean marker_list_registered = FALSE; + + if (initialized_thread) { + GST_DEBUG ("already initialized ges"); + return TRUE; + } + + uriasset_klass = g_type_class_ref (GES_TYPE_URI_CLIP_ASSET); + + _init_formatter_assets (); + if (!_ges_uri_asset_ensure_setup (uriasset_klass)) { + GST_ERROR ("cannot setup uri asset"); + if (error) + *error = g_error_new (GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN, + "Cannot initialize URI asset class."); + goto failed; + } + + nlecomposition_factory = gst_element_factory_find ("nlecomposition"); + if (!nlecomposition_factory) { + GST_ERROR ("The `nlecomposition` object was not found."); + if (error) + *error = g_error_new (GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN, + "The `nle` plugin is missing."); + + goto failed; + } + gst_object_unref (nlecomposition_factory); + + /* register clip classes with the system */ + + g_type_class_ref (GES_TYPE_TEST_CLIP); + g_type_class_ref (GES_TYPE_URI_CLIP); + g_type_class_ref (GES_TYPE_TITLE_CLIP); + g_type_class_ref (GES_TYPE_TRANSITION_CLIP); + g_type_class_ref (GES_TYPE_OVERLAY_CLIP); + g_type_class_ref (GES_TYPE_OVERLAY_TEXT_CLIP); + g_type_class_ref (GES_TYPE_EFFECT_CLIP); + + g_type_class_ref (GES_TYPE_GROUP); + + /* Register track elements */ + g_type_class_ref (GES_TYPE_EFFECT); + + ges_asset_cache_init (); + + gst_element_register (NULL, "gesaudiomixer", 0, GES_TYPE_SMART_ADDER); + gst_element_register (NULL, "gescompositor", 0, GES_TYPE_SMART_MIXER); + gst_element_register (NULL, "framepositioner", 0, GST_TYPE_FRAME_POSITIONNER); + gst_element_register (NULL, "gespipeline", 0, GES_TYPE_PIPELINE); + + /* TODO: user-defined types? */ + initialized_thread = g_thread_self (); + g_type_class_unref (uriasset_klass); + + if (!marker_list_registered) { + gstvtable.type = GES_TYPE_MARKER_LIST; + gst_value_register (&gstvtable); + marker_list_registered = TRUE; + } + + GST_DEBUG ("GStreamer Editing Services initialized"); + + return TRUE; + +failed: + if (uriasset_klass) + g_type_class_unref (uriasset_klass); + + GST_ERROR ("Could not initialize GES."); + + return FALSE; +} + +/** + * ges_init: + * + * Initialize the GStreamer Editing Service. Call this before any usage of + * GES. You should take care of initilizing GStreamer before calling this + * function. + * + * MT safety. + * GStreamer Editing Services do not guarantee MT safety. + * An application is required to use GES APIs (including ges_deinit()) + * in the thread where ges_init() was called. + */ + +gboolean +ges_init (void) +{ + gboolean ret; + + G_LOCK (init_lock); + ges_init_pre (NULL, NULL, NULL, NULL); + + ret = ges_init_post (NULL, NULL, NULL, NULL); + G_UNLOCK (init_lock); + + return ret; +} + +/** + * ges_deinit: + * + * Clean up any resources created by GES in ges_init(). + * + * It is normally not needed to call this function in a normal application as the + * resources will automatically be freed when the program terminates. + * This function is therefore mostly used by testsuites and other memory profiling tools. + * This function should be called from the thread where ges_init() was called. + * + * After this call GES should not be used until another ges_init() call. + */ +void +ges_deinit (void) +{ + G_LOCK (init_lock); + + GST_INFO ("deinitializing GES"); + + if (!initialized_thread) { + GST_DEBUG ("nothing to deinitialize"); + G_UNLOCK (init_lock); + return; + } + + /* Allow deinit only from a thread where ges_init() was called */ + g_assert (initialized_thread == g_thread_self ()); + + _ges_uri_asset_cleanup (); + + g_type_class_unref (g_type_class_peek (GES_TYPE_TEST_CLIP)); + g_type_class_unref (g_type_class_peek (GES_TYPE_URI_CLIP)); + g_type_class_unref (g_type_class_peek (GES_TYPE_TITLE_CLIP)); + g_type_class_unref (g_type_class_peek (GES_TYPE_TRANSITION_CLIP)); + g_type_class_unref (g_type_class_peek (GES_TYPE_OVERLAY_CLIP)); + g_type_class_unref (g_type_class_peek (GES_TYPE_OVERLAY_TEXT_CLIP)); + g_type_class_unref (g_type_class_peek (GES_TYPE_EFFECT_CLIP)); + + g_type_class_unref (g_type_class_peek (GES_TYPE_GROUP)); + /* Register track elements */ + g_type_class_unref (g_type_class_peek (GES_TYPE_EFFECT)); + + ges_asset_cache_deinit (); + ges_xml_formatter_deinit (); + + initialized_thread = NULL; + G_UNLOCK (init_lock); + + GST_INFO ("deinitialized GES"); + + return; +} + +#ifndef GST_DISABLE_OPTION_PARSING +static gboolean +parse_goption_arg (const gchar * s_opt, + const gchar * arg, gpointer data, GError ** err) +{ + if (g_strcmp0 (s_opt, "--ges-version") == 0) { + gst_print ("GStreamer Editing Services version %s\n", PACKAGE_VERSION); + exit (0); + } else if (g_strcmp0 (s_opt, "--ges-sample-paths") == 0) { + ges_add_missing_uri_relocation_uri (arg, FALSE); + } else if (g_strcmp0 (s_opt, "--ges-sample-path-recurse") == 0) { + ges_add_missing_uri_relocation_uri (arg, TRUE); + } + + return TRUE; +} +#endif + +/** + * ges_init_get_option_group: (skip) + * + * Returns a #GOptionGroup with GES's argument specifications. The + * group is set up to use standard GOption callbacks, so when using this + * group in combination with GOption parsing methods, all argument parsing + * and initialization is automated. + * + * This function is useful if you want to integrate GES with other + * libraries that use GOption (see g_option_context_add_group() ). + * + * If you use this function, you should make sure you initialise the GStreamer + * as one of the very first things in your program. That means you need to + * use gst_init_get_option_group() and add it to the option context before + * using the ges_init_get_option_group() result. + * + * Returns: (transfer full): a pointer to GES's option group. + */ +GOptionGroup * +ges_init_get_option_group (void) +{ +#ifndef GST_DISABLE_OPTION_PARSING + + GOptionGroup *group; + static const GOptionEntry ges_args[] = { + {"ges-version", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + (gpointer) parse_goption_arg, + "Print the GStreamer Editing Services version", + NULL}, + {"ges-sample-paths", 0, 0, G_OPTION_ARG_CALLBACK, + (gpointer) parse_goption_arg, + "List of pathes to look assets in if they were moved"}, + {"ges-sample-path-recurse", 0, 0, G_OPTION_ARG_CALLBACK, + (gpointer) parse_goption_arg, + "Same as above, but recursing into the folder"}, + {NULL} + }; + + group = g_option_group_new ("GES", "GStreamer Editing Services Options", + "Show GES Options", NULL, NULL); + g_option_group_set_parse_hooks (group, (GOptionParseFunc) ges_init_pre, + (GOptionParseFunc) ges_init_post); + g_option_group_add_entries (group, ges_args); + + return group; + +#else + return NULL; +#endif +} + +/** + * ges_version: + * @major: (out): pointer to a guint to store the major version number + * @minor: (out): pointer to a guint to store the minor version number + * @micro: (out): pointer to a guint to store the micro version number + * @nano: (out): pointer to a guint to store the nano version number + * + * Gets the version number of the GStreamer Editing Services library. + */ +void +ges_version (guint * major, guint * minor, guint * micro, guint * nano) +{ + g_return_if_fail (major); + g_return_if_fail (minor); + g_return_if_fail (micro); + g_return_if_fail (nano); + + *major = GES_VERSION_MAJOR; + *minor = GES_VERSION_MINOR; + *micro = GES_VERSION_MICRO; + *nano = GES_VERSION_NANO; +} + +/** + * ges_init_check: + * @argc: (inout) (allow-none): pointer to application's argc + * @argv: (inout) (array length=argc) (allow-none): pointer to application's argv + * @err: pointer to a #GError to which a message will be posted on error + * + * Initializes the GStreamer Editing Services library, setting up internal path lists, + * and loading evrything needed. + * + * This function will return %FALSE if GES could not be initialized + * for some reason. + * + * Returns: %TRUE if GES could be initialized. + */ +gboolean +ges_init_check (int *argc, char **argv[], GError ** err) +{ +#ifndef GST_DISABLE_OPTION_PARSING + GOptionGroup *group; + GOptionContext *ctx; +#endif + gboolean res; + + G_LOCK (init_lock); + + if (initialized_thread) { + GST_DEBUG ("already initialized ges"); + G_UNLOCK (init_lock); + return TRUE; + } +#ifndef GST_DISABLE_OPTION_PARSING + ctx = g_option_context_new ("- GStreamer Editing Services initialization"); + g_option_context_set_ignore_unknown_options (ctx, TRUE); + g_option_context_set_help_enabled (ctx, FALSE); + group = ges_init_get_option_group (); + g_option_context_add_group (ctx, group); + res = g_option_context_parse (ctx, argc, argv, err); + g_option_context_free (ctx); +#endif + + if (!res) { + G_UNLOCK (init_lock); + return res; + } + + ges_init_pre (NULL, NULL, NULL, NULL); + res = ges_init_post (NULL, NULL, NULL, NULL); + + G_UNLOCK (init_lock); + + return res; +} + +/** + * ges_is_initialized: + * + * Use this function to check if GES has been initialized with ges_init() + * or ges_init_check(). + * + * Returns: %TRUE if initialization has been done, %FALSE otherwise. + * + * Since: 1.16 + */ +gboolean +ges_is_initialized (void) +{ + return initialized_thread ? TRUE : FALSE; +} diff --git a/ges/ges.h b/ges/ges.h new file mode 100644 index 0000000000..0fcded9c21 --- /dev/null +++ b/ges/ges.h @@ -0,0 +1,114 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <glib.h> +#include <gst/gst.h> + +#include <ges/ges-prelude.h> +#include <ges/ges-types.h> +#include <ges/ges-enums.h> + +#include <ges/ges-timeline.h> +#include <ges/ges-layer.h> +#include <ges/ges-timeline-element.h> +#include <ges/ges-clip.h> +#include <ges/ges-pipeline.h> +#include <ges/ges-source-clip.h> +#include <ges/ges-time-overlay-clip.h> +#include <ges/ges-test-clip.h> +#include <ges/ges-title-clip.h> +#include <ges/ges-operation-clip.h> +#include <ges/ges-base-effect-clip.h> +#include <ges/ges-overlay-clip.h> +#include <ges/ges-text-overlay-clip.h> +#include <ges/ges-base-transition-clip.h> +#include <ges/ges-transition-clip.h> +#include <ges/ges-effect-clip.h> +#include <ges/ges-base-effect-clip.h> +#include <ges/ges-uri-clip.h> +#include <ges/ges-group.h> +#include <ges/ges-screenshot.h> +#include <ges/ges-asset.h> +#include <ges/ges-clip-asset.h> +#include <ges/ges-track-element-asset.h> +#include <ges/ges-uri-asset.h> +#include <ges/ges-project.h> +#include <ges/ges-extractable.h> +#include <ges/ges-base-xml-formatter.h> +#include <ges/ges-xml-formatter.h> + +#include <ges/ges-track.h> +#include <ges/ges-track-element.h> +#include <ges/ges-source.h> +#include <ges/ges-operation.h> + +#include <ges/ges-video-uri-source.h> +#include <ges/ges-audio-uri-source.h> +#include <ges/ges-image-source.h> +#include <ges/ges-multi-file-source.h> +#include <ges/ges-video-test-source.h> +#include <ges/ges-audio-test-source.h> +#include <ges/ges-title-source.h> +#include <ges/ges-text-overlay.h> +#include <ges/ges-transition.h> +#include <ges/ges-video-transition.h> +#include <ges/ges-audio-transition.h> +#include <ges/ges-base-effect.h> +#include <ges/ges-effect-asset.h> +#include <ges/ges-effect.h> +#include <ges/ges-formatter.h> +#include <ges/ges-command-line-formatter.h> +#include <ges/ges-utils.h> +#include <ges/ges-meta-container.h> +#include <ges/ges-gerror.h> +#include <ges/ges-audio-track.h> +#include <ges/ges-video-track.h> +#include <ges/ges-version.h> +#include <ges/ges-marker-list.h> + +G_BEGIN_DECLS + + +GES_API +gboolean ges_init (void); + +GES_API +gboolean ges_init_check (int *argc, char **argv[], GError ** err); + +GES_API +void ges_deinit (void); + +GES_API +void ges_version (guint * major, + guint * minor, + guint * micro, + guint * nano); +GES_API GOptionGroup * +ges_init_get_option_group (void); + +GES_API +gboolean ges_validate_register_action_types (void); + +GES_API +gboolean ges_is_initialized (void); + +G_END_DECLS diff --git a/ges/ges.resource b/ges/ges.resource new file mode 100644 index 0000000000..638794ea3d --- /dev/null +++ b/ges/ges.resource @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/ges/"> + <file>python/gesotioformatter.py</file> + </gresource> +</gresources> diff --git a/ges/gesmarshal.list b/ges/gesmarshal.list new file mode 100644 index 0000000000..1c28a896c1 --- /dev/null +++ b/ges/gesmarshal.list @@ -0,0 +1,2 @@ +VOID:OBJECT +VOID:OBJECT,INT,INT diff --git a/ges/gstframepositioner.c b/ges/gstframepositioner.c new file mode 100644 index 0000000000..b332d82743 --- /dev/null +++ b/ges/gstframepositioner.c @@ -0,0 +1,788 @@ +/* GStreamer + * Copyright (C) 2013 Mathieu Duponchelle <mduponchelle1@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Suite 500, + * Boston, MA 02110-1335, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <math.h> +#include <gst/gst.h> +#include <gst/video/video.h> + +#include "gstframepositioner.h" +#include "ges-internal.h" + +GST_DEBUG_CATEGORY_STATIC (_framepositioner); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT _framepositioner + +/* We need to define a max number of pixel so we can interpolate them */ +#define MAX_PIXELS 100000 +#define MIN_PIXELS -100000 + +static void gst_frame_positioner_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec); +static void gst_frame_positioner_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec); +static GstFlowReturn gst_frame_positioner_transform_ip (GstBaseTransform * + trans, GstBuffer * buf); + +static gboolean +gst_frame_positioner_meta_init (GstMeta * meta, gpointer params, + GstBuffer * buffer); +static gboolean gst_frame_positioner_meta_transform (GstBuffer * dest, + GstMeta * meta, GstBuffer * buffer, GQuark type, gpointer data); + +enum +{ + PROP_0, + PROP_ALPHA, + PROP_POSX, + PROP_POSY, + PROP_ZORDER, + PROP_WIDTH, + PROP_HEIGHT, + PROP_OPERATOR, + PROP_LAST, +}; + +static GParamSpec *properties[PROP_LAST]; + +static GstStaticPadTemplate gst_frame_positioner_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw(ANY)") + ); + +static GstStaticPadTemplate gst_frame_positioner_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw(ANY)") + ); + +G_DEFINE_TYPE (GstFramePositioner, gst_frame_positioner, + GST_TYPE_BASE_TRANSFORM); + +static GType +gst_compositor_operator_get_type_and_default_value (int *default_operator_value) +{ + GstElement *compositor = + gst_element_factory_create (ges_get_compositor_factory (), NULL); + + GstPad *compositorPad = + gst_element_request_pad_simple (compositor, "sink_%u"); + + GParamSpec *pspec = + g_object_class_find_property (G_OBJECT_GET_CLASS (compositorPad), + "operator"); + GType ret = 0; + + if (pspec) { + *default_operator_value = + g_value_get_enum (g_param_spec_get_default_value (pspec)); + g_return_val_if_fail (pspec, G_TYPE_NONE); + + ret = pspec->value_type; + } + + gst_element_release_request_pad (compositor, compositorPad); + gst_object_unref (compositorPad); + gst_object_unref (compositor); + + return ret; +} + +static void +_weak_notify_cb (GstFramePositioner * pos, GObject * old) +{ + pos->current_track = NULL; +} + +static gboolean +is_user_positionned (GstFramePositioner * self) +{ + gint i; + GParamSpec *positioning_props[] = { + properties[PROP_WIDTH], + properties[PROP_HEIGHT], + properties[PROP_POSX], + properties[PROP_POSY], + }; + + if (self->user_positioned) + return TRUE; + + for (i = 0; i < G_N_ELEMENTS (positioning_props); i++) { + GstControlBinding *b = gst_object_get_control_binding (GST_OBJECT (self), + positioning_props[i]->name); + + if (b) { + gst_object_unref (b); + return TRUE; + } + } + + return FALSE; +} + +static gboolean +auto_position (GstFramePositioner * self) +{ + gdouble scaled_width = -1, scaled_height = -1, x, y; + + if (is_user_positionned (self)) { + GST_DEBUG_OBJECT (self, "Was positioned by the user, not auto positioning"); + return FALSE; + } + + if (!self->natural_width || !self->natural_height) + return FALSE; + + if (self->track_width == self->natural_width && + self->track_height == self->natural_height) + return TRUE; + + scaled_height = + gst_util_uint64_scale_int (self->natural_height, self->track_width, + self->natural_width); + scaled_width = self->track_width; + if (scaled_height > self->track_height) { + scaled_height = self->track_height; + scaled_width = + gst_util_uint64_scale_int (self->natural_width, self->track_height, + self->natural_height); + } + + x = MAX (0, (self->track_width - scaled_width) / 2.f); + y = MAX (0, (self->track_height - scaled_height) / 2.f); + + GST_INFO_OBJECT (self, "Scalling video to match track size from " + "%dx%d to %fx%f", + self->natural_width, self->natural_height, scaled_width, scaled_height); + self->width = scaled_width; + self->height = scaled_height; + self->posx = x; + self->posy = y; + + return TRUE; +} + +typedef struct +{ + gdouble *value; + gint old_track_value; + gint track_value; + GParamSpec *pspec; +} RepositionPropertyData; + +static void +reposition_properties (GstFramePositioner * pos, gint old_track_width, + gint old_track_height) +{ + gint i; + RepositionPropertyData props_data[] = { + {&pos->width, old_track_width, pos->track_width, properties[PROP_WIDTH]}, + {&pos->height, old_track_height, pos->track_height, + properties[PROP_HEIGHT]}, + {&pos->posx, old_track_width, pos->track_width, properties[PROP_POSX]}, + {&pos->posy, old_track_height, pos->track_height, properties[PROP_POSY]}, + }; + + for (i = 0; i < G_N_ELEMENTS (props_data); i++) { + GList *values, *tmp; + gboolean absolute; + GstTimedValueControlSource *source = NULL; + + RepositionPropertyData d = props_data[i]; + GstControlBinding *binding = + gst_object_get_control_binding (GST_OBJECT (pos), d.pspec->name); + + *(d.value) = + *(d.value) * (gdouble) d.track_value / (gdouble) d.old_track_value; + + if (!binding) + continue; + + if (!GST_IS_DIRECT_CONTROL_BINDING (binding)) { + GST_FIXME_OBJECT (pos, "Implement support for control binding type: %s", + G_OBJECT_TYPE_NAME (binding)); + + goto next; + } + + g_object_get (binding, "control_source", &source, NULL); + if (!source || !GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) { + GST_FIXME_OBJECT (pos, "Implement support for control source type: %s", + source ? G_OBJECT_TYPE_NAME (source) : "NULL"); + + goto next; + } + + values = + gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE + (source)); + + if (!values) + goto next; + + g_object_get (binding, "absolute", &absolute, NULL); + for (tmp = values; tmp; tmp = tmp->next) { + GstTimedValue *value = tmp->data; + + gst_timed_value_control_source_set (source, value->timestamp, + value->value * d.track_value / d.old_track_value); + } + + g_list_free (values); + + next: + gst_clear_object (&source); + gst_object_unref (binding); + } +} + +static void +gst_frame_positioner_update_properties (GstFramePositioner * pos, + gboolean track_mixing, gint old_track_width, gint old_track_height) +{ + GstCaps *caps; + + if (pos->capsfilter == NULL) + return; + + caps = gst_caps_from_string ("video/x-raw(ANY)"); + + if (pos->track_width && pos->track_height && + (!track_mixing || !pos->scale_in_compositor)) { + gst_caps_set_simple (caps, "width", G_TYPE_INT, + pos->track_width, "height", G_TYPE_INT, pos->track_height, NULL); + } + + if (pos->fps_n != -1) + gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION, pos->fps_n, + pos->fps_d, NULL); + + if (pos->par_n != -1) + gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION, + pos->par_n, pos->par_d, NULL); + + if (!pos->track_width || !pos->track_height) { + GST_INFO_OBJECT (pos, "Track doesn't have a proper size, not " + "positioning the source"); + goto done; + } else if (auto_position (pos)) + goto done; + + if (!old_track_height || !old_track_width) { + GST_DEBUG_OBJECT (pos, "No old track size, can not properly reposition"); + goto done; + } + + if ((!pos->natural_width || !pos->natural_height) && + (!pos->width || !pos->height)) { + GST_DEBUG_OBJECT (pos, "No natural aspect ratio and no user set " + " image size, can't not reposition."); + goto done; + } + + if (gst_util_fraction_compare (old_track_width, old_track_height, + pos->track_width, pos->track_height)) { + GST_INFO_OBJECT (pos, "Not repositioning as track size change didn't" + " keep the same aspect ratio (previous %dx%d(" + "ratio=%f), new: %dx%d(ratio=%f)", + old_track_width, old_track_height, + (gdouble) old_track_width / (gdouble) old_track_height, + pos->track_width, pos->track_height, + (gdouble) pos->track_width / (gdouble) pos->track_height); + goto done; + } + + reposition_properties (pos, old_track_width, old_track_height); + +done: + GST_DEBUG_OBJECT (pos, "setting caps %" GST_PTR_FORMAT, caps); + + g_object_set (pos->capsfilter, "caps", caps, NULL); + + gst_caps_unref (caps); +} + +static void +sync_properties_from_track (GstFramePositioner * pos, GESTrack * track) +{ + gint width, height; + gint old_track_width, old_track_height; + GstCaps *caps; + + g_object_get (track, "restriction-caps", &caps, NULL); + + width = height = 0; + + if (caps && gst_caps_get_size (caps) > 0) { + GstStructure *structure; + + structure = gst_caps_get_structure (caps, 0); + if (!gst_structure_get_int (structure, "width", &width)) + width = 0; + if (!gst_structure_get_int (structure, "height", &height)) + height = 0; + if (!gst_structure_get_fraction (structure, "framerate", &(pos->fps_n), + &(pos->fps_d))) + pos->fps_n = -1; + + if (!gst_structure_get_fraction (structure, "pixel-aspect-ratio", + &(pos->par_n), &(pos->par_d))) + pos->par_n = -1; + } + + old_track_width = pos->track_width; + old_track_height = pos->track_height; + + pos->track_width = width; + pos->track_height = height; + + GST_DEBUG_OBJECT (pos, "syncing framerate from caps : %d/%d", pos->fps_n, + pos->fps_d); + if (caps) + gst_caps_unref (caps); + + gst_frame_positioner_update_properties (pos, ges_track_get_mixing (track), + old_track_width, old_track_height); +} + +static void +_track_restriction_changed_cb (GESTrack * track, GParamSpec * arg G_GNUC_UNUSED, + GstFramePositioner * pos) +{ + sync_properties_from_track (pos, track); +} + +static void +set_track (GstFramePositioner * pos) +{ + GESTrack *new_track; + + if (pos->current_track) { + g_signal_handlers_disconnect_by_func (pos->current_track, + (GCallback) _track_restriction_changed_cb, pos); + g_object_weak_unref (G_OBJECT (pos->current_track), + (GWeakNotify) _weak_notify_cb, pos); + } + + new_track = ges_track_element_get_track (pos->track_source); + if (new_track) { + pos->current_track = new_track; + g_object_weak_ref (G_OBJECT (new_track), (GWeakNotify) _weak_notify_cb, + pos); + GST_DEBUG_OBJECT (pos, "connecting to track : %p", pos->current_track); + + g_signal_connect (pos->current_track, "notify::restriction-caps", + (GCallback) _track_restriction_changed_cb, pos); + sync_properties_from_track (pos, pos->current_track); + } else { + pos->current_track = NULL; + } +} + +static void +_track_changed_cb (GESTrackElement * trksrc, GParamSpec * arg G_GNUC_UNUSED, + GstFramePositioner * pos) +{ + set_track (pos); +} + +static void +_trk_element_weak_notify_cb (GstFramePositioner * pos, GObject * old) +{ + pos->track_source = NULL; + gst_object_unref (pos); +} + +void +ges_frame_positioner_set_source_and_filter (GstFramePositioner * pos, + GESTrackElement * trksrc, GstElement * capsfilter) +{ + pos->track_source = trksrc; + pos->capsfilter = capsfilter; + pos->current_track = ges_track_element_get_track (trksrc); + + g_object_weak_ref (G_OBJECT (trksrc), + (GWeakNotify) _trk_element_weak_notify_cb, gst_object_ref (pos)); + g_signal_connect (trksrc, "notify::track", (GCallback) _track_changed_cb, + pos); + set_track (pos); +} + +static void +gst_frame_positioner_dispose (GObject * object) +{ + GstFramePositioner *pos = GST_FRAME_POSITIONNER (object); + + if (pos->track_source) { + g_signal_handlers_disconnect_by_func (pos->track_source, _track_changed_cb, + pos); + pos->track_source = NULL; + } + + if (pos->current_track) { + g_signal_handlers_disconnect_by_func (pos->current_track, + _track_restriction_changed_cb, pos); + g_object_weak_unref (G_OBJECT (pos->current_track), + (GWeakNotify) _weak_notify_cb, pos); + pos->current_track = NULL; + } + + G_OBJECT_CLASS (gst_frame_positioner_parent_class)->dispose (object); +} + +static void +gst_frame_positioner_class_init (GstFramePositionerClass * klass) +{ + int default_operator_value = 0; + GType operator_gtype = + gst_compositor_operator_get_type_and_default_value + (&default_operator_value); + guint n_pspecs = PROP_LAST; + + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstBaseTransformClass *base_transform_class = + GST_BASE_TRANSFORM_CLASS (klass); + + GST_DEBUG_CATEGORY_INIT (_framepositioner, "framepositioner", + GST_DEBUG_FG_YELLOW, "ges frame positioner"); + + gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass), + &gst_frame_positioner_src_template); + gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass), + &gst_frame_positioner_sink_template); + + gobject_class->set_property = gst_frame_positioner_set_property; + gobject_class->get_property = gst_frame_positioner_get_property; + gobject_class->dispose = gst_frame_positioner_dispose; + base_transform_class->transform_ip = + GST_DEBUG_FUNCPTR (gst_frame_positioner_transform_ip); + + /** + * gstframepositioner:alpha: + * + * The desired alpha for the stream. + */ + properties[PROP_ALPHA] = + g_param_spec_double ("alpha", "alpha", "alpha of the stream", 0.0, 1.0, + 1.0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE); + + /** + * gstframepositioner:posx: + * + * The desired x position for the stream. + */ + properties[PROP_POSX] = + g_param_spec_int ("posx", "posx", "x position of the stream", MIN_PIXELS, + MAX_PIXELS, 0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE); + + + /** + * gstframepositioner:posy: + * + * The desired y position for the stream. + */ + properties[PROP_POSY] = + g_param_spec_int ("posy", "posy", "y position of the stream", MIN_PIXELS, + MAX_PIXELS, 0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE); + + /** + * gstframepositioner:zorder: + * + * The desired z order for the stream. + */ + properties[PROP_ZORDER] = + g_param_spec_uint ("zorder", "zorder", "z order of the stream", 0, + G_MAXUINT, 0, G_PARAM_READWRITE); + + /** + * gesframepositioner:width: + * + * The desired width for that source. + * Set to 0 if size is not mandatory, will be set to width of the current track. + */ + properties[PROP_WIDTH] = + g_param_spec_int ("width", "width", "width of the source", 0, MAX_PIXELS, + 0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE); + + /** + * gesframepositioner:height: + * + * The desired height for that source. + * Set to 0 if size is not mandatory, will be set to height of the current track. + */ + properties[PROP_HEIGHT] = + g_param_spec_int ("height", "height", "height of the source", 0, + MAX_PIXELS, 0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE); + + /** + * gesframepositioner:operator: + * + * The blending operator for the source. + */ + if (operator_gtype) { + properties[PROP_OPERATOR] = + g_param_spec_enum ("operator", "Operator", + "Blending operator to use for blending this pad over the previous ones", + operator_gtype, default_operator_value, + (G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | + GST_PARAM_CONDITIONALLY_AVAILABLE | G_PARAM_STATIC_STRINGS)); + } else { + n_pspecs--; + } + + g_object_class_install_properties (gobject_class, n_pspecs, properties); + + gst_element_class_set_static_metadata (GST_ELEMENT_CLASS (klass), + "frame positioner", "Metadata", + "This element provides with tagging facilities", + "mduponchelle1@gmail.com"); +} + +static void +gst_frame_positioner_init (GstFramePositioner * framepositioner) +{ + int default_operator_value; + gst_compositor_operator_get_type_and_default_value (&default_operator_value); + + framepositioner->alpha = 1.0; + framepositioner->posx = 0.0; + framepositioner->posy = 0.0; + framepositioner->zorder = 0; + framepositioner->width = 0; + framepositioner->height = 0; + framepositioner->operator = default_operator_value; + framepositioner->fps_n = -1; + framepositioner->fps_d = -1; + framepositioner->track_width = 0; + framepositioner->track_height = 0; + framepositioner->capsfilter = NULL; + framepositioner->track_source = NULL; + framepositioner->current_track = NULL; + framepositioner->scale_in_compositor = TRUE; + + framepositioner->par_n = -1; + framepositioner->par_d = 1; +} + +void +gst_frame_positioner_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GstFramePositioner *framepositioner = GST_FRAME_POSITIONNER (object); + gboolean track_mixing = TRUE; + + if (framepositioner->current_track) + track_mixing = ges_track_get_mixing (framepositioner->current_track); + + + GST_OBJECT_LOCK (framepositioner); + switch (property_id) { + case PROP_ALPHA: + framepositioner->alpha = g_value_get_double (value); + break; + case PROP_POSX: + framepositioner->posx = g_value_get_int (value); + framepositioner->user_positioned = TRUE; + break; + case PROP_POSY: + framepositioner->posy = g_value_get_int (value); + framepositioner->user_positioned = TRUE; + break; + case PROP_ZORDER: + framepositioner->zorder = g_value_get_uint (value); + break; + case PROP_WIDTH: + framepositioner->user_positioned = TRUE; + framepositioner->width = g_value_get_int (value); + gst_frame_positioner_update_properties (framepositioner, track_mixing, + 0, 0); + break; + case PROP_HEIGHT: + framepositioner->user_positioned = TRUE; + framepositioner->height = g_value_get_int (value); + gst_frame_positioner_update_properties (framepositioner, track_mixing, + 0, 0); + break; + case PROP_OPERATOR: + framepositioner->operator = g_value_get_enum (value); + gst_frame_positioner_update_properties (framepositioner, track_mixing, + 0, 0); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } + GST_OBJECT_UNLOCK (framepositioner); +} + +void +gst_frame_positioner_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GstFramePositioner *pos = GST_FRAME_POSITIONNER (object); + gint real_width, real_height; + + switch (property_id) { + case PROP_ALPHA: + g_value_set_double (value, pos->alpha); + break; + case PROP_POSX: + g_value_set_int (value, round (pos->posx)); + break; + case PROP_POSY: + g_value_set_int (value, round (pos->posy)); + break; + case PROP_ZORDER: + g_value_set_uint (value, pos->zorder); + break; + case PROP_WIDTH: + if (pos->scale_in_compositor) { + g_value_set_int (value, round (pos->width)); + } else { + real_width = + pos->width > 0 ? round (pos->width) : round (pos->track_width); + g_value_set_int (value, real_width); + } + break; + case PROP_HEIGHT: + if (pos->scale_in_compositor) { + g_value_set_int (value, round (pos->height)); + } else { + real_height = + pos->height > 0 ? round (pos->height) : round (pos->track_height); + g_value_set_int (value, real_height); + } + break; + case PROP_OPERATOR: + g_value_set_enum (value, pos->operator); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +GType +gst_frame_positioner_meta_api_get_type (void) +{ + static GType type; + static const gchar *tags[] = { "video", NULL }; + + if (g_once_init_enter (&type)) { + GType _type = gst_meta_api_type_register ("GstFramePositionerApi", tags); + g_once_init_leave (&type, _type); + } + return type; +} + +static const GstMetaInfo * +gst_frame_positioner_get_info (void) +{ + static const GstMetaInfo *meta_info = NULL; + + if (g_once_init_enter ((GstMetaInfo **) & meta_info)) { + const GstMetaInfo *meta = + gst_meta_register (gst_frame_positioner_meta_api_get_type (), + "GstFramePositionerMeta", + sizeof (GstFramePositionerMeta), gst_frame_positioner_meta_init, + NULL, + gst_frame_positioner_meta_transform); + g_once_init_leave ((GstMetaInfo **) & meta_info, (GstMetaInfo *) meta); + } + return meta_info; +} + +static gboolean +gst_frame_positioner_meta_init (GstMeta * meta, gpointer params, + GstBuffer * buffer) +{ + int default_operator_value = 0; + GstFramePositionerMeta *smeta; + + smeta = (GstFramePositionerMeta *) meta; + + gst_compositor_operator_get_type_and_default_value (&default_operator_value); + + smeta->alpha = 0.0; + smeta->posx = smeta->posy = smeta->height = smeta->width = 0; + smeta->zorder = 0; + smeta->operator = default_operator_value; + + return TRUE; +} + +static gboolean +gst_frame_positioner_meta_transform (GstBuffer * dest, GstMeta * meta, + GstBuffer * buffer, GQuark type, gpointer data) +{ + GstFramePositionerMeta *dmeta, *smeta; + + smeta = (GstFramePositionerMeta *) meta; + + if (GST_META_TRANSFORM_IS_COPY (type)) { + /* only copy if the complete data is copied as well */ + dmeta = + (GstFramePositionerMeta *) gst_buffer_add_meta (dest, + gst_frame_positioner_get_info (), NULL); + dmeta->alpha = smeta->alpha; + dmeta->posx = smeta->posx; + dmeta->posy = smeta->posy; + dmeta->width = smeta->width; + dmeta->height = smeta->height; + dmeta->zorder = smeta->zorder; + dmeta->operator = smeta->operator; + } + + return TRUE; +} + +static GstFlowReturn +gst_frame_positioner_transform_ip (GstBaseTransform * trans, GstBuffer * buf) +{ + GstFramePositionerMeta *meta; + GstFramePositioner *framepositioner = GST_FRAME_POSITIONNER (trans); + GstClockTime timestamp = GST_BUFFER_PTS (buf); + + if (GST_CLOCK_TIME_IS_VALID (timestamp)) { + gst_object_sync_values (GST_OBJECT (trans), timestamp); + } + + meta = + (GstFramePositionerMeta *) gst_buffer_add_meta (buf, + gst_frame_positioner_get_info (), NULL); + + GST_OBJECT_LOCK (framepositioner); + meta->alpha = framepositioner->alpha; + meta->posx = round (framepositioner->posx); + meta->posy = round (framepositioner->posy); + meta->width = round (framepositioner->width); + meta->height = round (framepositioner->height); + meta->zorder = framepositioner->zorder; + meta->operator = framepositioner->operator; + GST_OBJECT_UNLOCK (framepositioner); + + return GST_FLOW_OK; +} diff --git a/ges/gstframepositioner.h b/ges/gstframepositioner.h new file mode 100644 index 0000000000..0c1b28b1fb --- /dev/null +++ b/ges/gstframepositioner.h @@ -0,0 +1,97 @@ +/* GStreamer + * Copyright (C) 2013 Mathieu Duponchelle <mduponchelle1@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _GST_FRAME_POSITIONNER_H_ +#define _GST_FRAME_POSITIONNER_H_ + +#include <gst/base/gstbasetransform.h> +#include <ges/ges-track-element.h> +#include <ges/ges-track.h> + +G_BEGIN_DECLS + +#define GST_TYPE_FRAME_POSITIONNER (gst_frame_positioner_get_type()) +#define GST_FRAME_POSITIONNER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FRAME_POSITIONNER,GstFramePositioner)) +#define GST_FRAME_POSITIONNER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_FRAME_POSITIONNER,GstFramePositionerClass)) +#define GST_IS_FRAME_POSITIONNER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FRAME_POSITIONNER)) +#define GST_IS_FRAME_POSITIONNER_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_FRAME_POSITIONNER)) + +typedef struct _GstFramePositioner GstFramePositioner; +typedef struct _GstFramePositionerClass GstFramePositionerClass; +typedef struct _GstFramePositionerMeta GstFramePositionerMeta; + +struct _GstFramePositioner +{ + GstBaseTransform base_framepositioner; + + GstElement *capsfilter; + + GESTrackElement *track_source; + GESTrack *current_track; + + gboolean scale_in_compositor; + gdouble alpha; + gdouble posx; + gdouble posy; + guint zorder; + gdouble width; + gdouble height; + gint operator; + gint natural_width; + gint natural_height; + gint track_width; + gint track_height; + gint fps_n; + gint fps_d; + + gint par_n; + gint par_d; + + gboolean user_positioned; + + /* This should never be made public, no padding needed */ +}; + +struct _GstFramePositionerClass +{ + GstBaseTransformClass base_framepositioner_class; +}; + +struct _GstFramePositionerMeta { + GstMeta meta; + + gdouble alpha; + gint posx; + gint posy; + gint height; + gint width; + guint zorder; + gint operator; +}; + +G_GNUC_INTERNAL void ges_frame_positioner_set_source_and_filter (GstFramePositioner *pos, + GESTrackElement *trksrc, + GstElement *capsfilter); +G_GNUC_INTERNAL GType gst_frame_positioner_get_type (void); +G_GNUC_INTERNAL GType +gst_frame_positioner_meta_api_get_type (void); + +G_END_DECLS + +#endif diff --git a/ges/meson.build b/ges/meson.build new file mode 100644 index 0000000000..cf835ed3e5 --- /dev/null +++ b/ges/meson.build @@ -0,0 +1,226 @@ +ges_sources = files([ + 'ges.c', + 'ges-enums.c', + 'ges-meta-container.c', + 'ges-timeline.c', + 'ges-layer.c', + 'ges-clip.c', + 'ges-pipeline.c', + 'ges-source-clip.c', + 'ges-source-clip-asset.c', + 'ges-base-effect-clip.c', + 'ges-effect-clip.c', + 'ges-uri-clip.c', + 'ges-uri-source.c', + 'ges-operation-clip.c', + 'ges-base-transition-clip.c', + 'ges-transition-clip.c', + 'ges-time-overlay-clip.c', + 'ges-test-clip.c', + 'ges-title-clip.c', + 'ges-overlay-clip.c', + 'ges-text-overlay-clip.c', + 'ges-track.c', + 'ges-audio-track.c', + 'ges-video-track.c', + 'ges-track-element.c', + 'ges-source.c', + 'ges-operation.c', + 'ges-video-source.c', + 'ges-audio-source.c', + 'ges-video-uri-source.c', + 'ges-audio-uri-source.c', + 'ges-image-source.c', + 'ges-multi-file-source.c', + 'ges-transition.c', + 'ges-audio-transition.c', + 'ges-video-transition.c', + 'ges-video-test-source.c', + 'ges-audio-test-source.c', + 'ges-title-source.c', + 'ges-text-overlay.c', + 'ges-base-effect.c', + 'ges-effect.c', + 'ges-screenshot.c', + 'ges-formatter.c', + 'ges-asset.c', + 'ges-uri-asset.c', + 'ges-clip-asset.c', + 'ges-track-element-asset.c', + 'ges-extractable.c', + 'ges-project.c', + 'ges-base-xml-formatter.c', + 'ges-xml-formatter.c', + 'ges-command-line-formatter.c', + 'ges-auto-transition.c', + 'ges-timeline-element.c', + 'ges-timeline-tree.c', + 'ges-container.c', + 'ges-effect-asset.c', + 'ges-smart-adder.c', + 'ges-smart-video-mixer.c', + 'ges-utils.c', + 'ges-group.c', + 'ges-validate.c', + 'ges-structured-interface.c', + 'ges-structure-parser.c', + 'ges-marker-list.c', + 'gstframepositioner.c' +]) + +ges_headers = files([ + 'ges-types.h', + 'ges.h', + 'ges-prelude.h', + 'ges-enums.h', + 'ges-gerror.h', + 'ges-meta-container.h', + 'ges-timeline.h', + 'ges-layer.h', + 'ges-clip.h', + 'ges-pipeline.h', + 'ges-source-clip.h', + 'ges-source-clip-asset.h', + 'ges-uri-clip.h', + 'ges-base-effect-clip.h', + 'ges-effect-clip.h', + 'ges-operation-clip.h', + 'ges-base-transition-clip.h', + 'ges-transition-clip.h', + 'ges-test-clip.h', + 'ges-time-overlay-clip.h', + 'ges-title-clip.h', + 'ges-overlay-clip.h', + 'ges-text-overlay-clip.h', + 'ges-base-effect.h', + 'ges-effect.h', + 'ges-track.h', + 'ges-audio-track.h', + 'ges-video-track.h', + 'ges-track-element.h', + 'ges-track-element-deprecated.h', + 'ges-source.h', + 'ges-operation.h', + 'ges-video-source.h', + 'ges-audio-source.h', + 'ges-video-uri-source.h', + 'ges-audio-uri-source.h', + 'ges-image-source.h', + 'ges-multi-file-source.h', + 'ges-transition.h', + 'ges-audio-transition.h', + 'ges-video-transition.h', + 'ges-video-test-source.h', + 'ges-audio-test-source.h', + 'ges-title-source.h', + 'ges-text-overlay.h', + 'ges-screenshot.h', + 'ges-formatter.h', + 'ges-asset.h', + 'ges-uri-asset.h', + 'ges-clip-asset.h', + 'ges-track-element-asset.h', + 'ges-extractable.h', + 'ges-project.h', + 'ges-base-xml-formatter.h', + 'ges-xml-formatter.h', + 'ges-command-line-formatter.h', + 'ges-timeline-element.h', + 'ges-container.h', + 'ges-effect-asset.h', + 'ges-utils.h', + 'ges-group.h', + 'ges-marker-list.h' +]) + +if libxml_dep.found() + ges_sources += files(['ges-pitivi-formatter.c']) + ges_headers += files(['ges-pitivi-formatter.h']) +endif + +version_data = configuration_data() +version_data.set('GES_VERSION_MAJOR', gst_version_major) +version_data.set('GES_VERSION_MINOR', gst_version_minor) +version_data.set('GES_VERSION_MICRO', gst_version_micro) +version_data.set('GES_VERSION_NANO', gst_version_nano) + +ges_headers += [configure_file(input : 'ges-version.h.in', + output : 'ges-version.h', + install_dir : join_paths(get_option('includedir'), 'gstreamer-1.0/ges'), + configuration : version_data)] + +install_headers(ges_headers, subdir : 'gstreamer-1.0/ges') + +flex = find_program('flex', required : false) +if not flex.found() + flex = find_program('win_flex', required : false) + if not flex.found() + error('flex not found') + endif +endif + +parser = custom_target('gesparselex', + input : 'parse.l', + output : ['lex.priv_ges_parse_yy.c', 'ges-parse-lex.h'], + command : [flex, '-Ppriv_ges_parse_yy', '--header-file=@OUTPUT1@', '-o', '@OUTPUT0@', '@INPUT@'] +) + +ges_resources = [] +if has_python + ges_resources = gnome.compile_resources( + 'ges-resources', 'ges.resource', + source_dir: '.', + c_name: 'ges' + ) +endif + +libges = library('ges-1.0', ges_sources, parser, ges_resources, + version : libversion, + soversion : soversion, + darwin_versions : osxversion, + c_args : [ges_c_args] + ['-DBUILDING_GES'], + include_directories : [configinc], + install : true, + dependencies : libges_deps) + +pkgconfig.generate(libges, + libraries : [gst_dep, gstbase_dep, gstpbutils_dep, gstcontroller_dep], + subdirs : pkgconfig_subdirs, + name : 'gst-editing-services-1.0', + description : 'GStreamer Editing Services', +) + +ges_gen_sources = [] +if build_gir + ges_gir_extra_args = gir_init_section + [ '--c-include=ges/ges.h' ] + if meson.is_subproject() + # FIXME: There must be a better way to do this + # Need to pass the include path to find gst/gst.h and gst/gstenumtypes.h (built) + ges_gir_extra_args += ['--cflags-begin', + '-I' + meson.current_source_dir() + '/..', + '-I' + meson.current_build_dir() + '/..', + '--cflags-end'] + endif + ges_gir = gnome.generate_gir(libges, + sources : ges_sources + ges_headers, + namespace : 'GES', + nsversion : apiversion, + identifier_prefix : 'GES', + symbol_prefix : 'ges', + export_packages : 'gst-editing-services-1.0', + includes : ['Gst-1.0', 'GstPbutils-1.0', 'GstVideo-1.0', 'Gio-2.0', 'GObject-2.0'], + install : true, + dependencies : libges_deps, + extra_args : ges_gir_extra_args + ) + + ges_gen_sources += [ges_gir] +endif + +ges_dep = declare_dependency(link_with : libges, + include_directories : [configinc], + sources : ges_gen_sources, + dependencies : libges_deps, +) + +meson.override_dependency('gst-editing-services-1.0', ges_dep) diff --git a/ges/parse.l b/ges/parse.l new file mode 100644 index 0000000000..712d05d5d8 --- /dev/null +++ b/ges/parse.l @@ -0,0 +1,57 @@ +%{ +#include "ges-structure-parser.h" + +%} + +%option noyywrap +%option nounput +%option reentrant +%option extra-type="GESStructureParser *" +%option never-interactive +%option noinput +%option nounistd + +CLIP [ ]+\+clip[ ]+ +TEST_CLIP [ ]+\+test-clip[ ]+ +TRANSITION [ ]+\+transition[ ]+ +EFFECT [ ]+\+effect[ ]+ +TITLE [ ]+\+title[ ]+ +TRACK [ ]+\+track[ ]+ +KEYFRAME [ ]+\+keyframes[ ]+ + +SETTER [ ]+set-[^ ]+[ ]+ + +STRING \"(\\.|[^"])*\" +/* A value string, as understood by gst_structure_from_string + * Characters are from GST_ASCII_IS_STRING + * NOTE: character set is *not* supposed to be locale dependent */ +VALUE {STRING}|([abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_+/:.-]+) + +%% + +={VALUE} { + ges_structure_parser_parse_value (yyextra, yytext); +} + +{STRING} { + ges_structure_parser_parse_string (yyextra, yytext, FALSE); +} + +{KEYFRAME}|{TRACK}|{CLIP}|{TRANSITION}|{EFFECT}|{TEST_CLIP}|{TITLE} { + ges_structure_parser_parse_symbol (yyextra, yytext); +} + +{SETTER} { + ges_structure_parser_parse_setter (yyextra, yytext); +} + +[ \t\n]+ { + ges_structure_parser_parse_whitespace (yyextra); +} + +. { + /* add everything else */ + ges_structure_parser_parse_default (yyextra, yytext); +} + +%% diff --git a/ges/python/gesotioformatter.py b/ges/python/gesotioformatter.py new file mode 100644 index 0000000000..344e13bd46 --- /dev/null +++ b/ges/python/gesotioformatter.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 +# +# Copyright (C) 2019 Igalia S.L +# Authors: +# Thibault Saunier <tsaunier@igalia.com> +# + +import sys + +import gi +import tempfile +gi.require_version("GES", "1.0") +gi.require_version("Gst", "1.0") + +from gi.repository import GObject +from gi.repository import Gst +Gst.init(None) +from gi.repository import GES +from gi.repository import GLib +from collections import OrderedDict + +import opentimelineio as otio +otio.adapters.from_name('xges') + +class GESOtioFormatter(GES.Formatter): + def do_save_to_uri(self, timeline, uri, overwrite): + if not Gst.uri_is_valid(uri) or Gst.uri_get_protocol(uri) != "file": + Gst.error("Protocol not supported for file: %s" % uri) + return False + + with tempfile.NamedTemporaryFile(suffix=".xges") as tmpxges: + timeline.get_asset().save(timeline, "file://" + tmpxges.name, None, overwrite) + + linker = otio.media_linker.MediaLinkingPolicy.ForceDefaultLinker + otio_timeline = otio.adapters.read_from_file(tmpxges.name, "xges", media_linker_name=linker) + location = Gst.uri_get_location(uri) + out_adapter = otio.adapters.from_filepath(location) + otio.adapters.write_to_file(otio_timeline, Gst.uri_get_location(uri), out_adapter.name) + + return True + + def do_can_load_uri(self, uri): + try: + if not Gst.uri_is_valid(uri) or Gst.uri_get_protocol(uri) != "file": + return False + except GLib.Error as e: + Gst.error(str(e)) + return False + + if uri.endswith(".xges"): + return False + + try: + return otio.adapters.from_filepath(Gst.uri_get_location(uri)) is not None + except Exception as e: + Gst.info("Could not load %s -> %s" % (uri, e)) + return False + + + def do_load_from_uri(self, timeline, uri): + location = Gst.uri_get_location(uri) + in_adapter = otio.adapters.from_filepath(location) + assert(in_adapter) # can_load_uri should have ensured it is loadable + + linker = otio.media_linker.MediaLinkingPolicy.ForceDefaultLinker + otio_timeline = otio.adapters.read_from_file( + location, + in_adapter.name, + media_linker_name=linker + ) + + with tempfile.NamedTemporaryFile(suffix=".xges") as tmpxges: + otio.adapters.write_to_file(otio_timeline, tmpxges.name, "xges") + formatter = GES.Formatter.get_default().extract() + timeline.get_asset().add_formatter(formatter) + return formatter.load_from_uri(timeline, "file://" + tmpxges.name) + +GObject.type_register(GESOtioFormatter) +known_extensions_mimetype_map = [ + ("otio", "xml", "fcpxml"), + ("application/vnd.pixar.opentimelineio+json", "application/vnd.apple-xmeml+xml", "application/vnd.apple-fcp+xml") +] + +extensions = [] +for adapter in otio.plugins.ActiveManifest().adapters: + if adapter.name != 'xges': + extensions.extend(adapter.suffixes) + +extensions_mimetype_map = [[], []] +for i, ext in enumerate(known_extensions_mimetype_map[0]): + if ext in extensions: + extensions_mimetype_map[0].append(ext) + extensions_mimetype_map[1].append(known_extensions_mimetype_map[1][i]) + extensions.remove(ext) +extensions_mimetype_map[0].extend(extensions) + +GES.FormatterClass.register_metas(GESOtioFormatter, "otioformatter", + "GES Formatter using OpenTimelineIO", + ','.join(extensions_mimetype_map[0]), + ';'.join(extensions_mimetype_map[1]), 0.1, Gst.Rank.SECONDARY) diff --git a/gst-editing-services.doap b/gst-editing-services.doap new file mode 100644 index 0000000000..93b5a309a4 --- /dev/null +++ b/gst-editing-services.doap @@ -0,0 +1,442 @@ +<Project + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" + xmlns="http://usefulinc.com/ns/doap#" + xmlns:foaf="http://xmlns.com/foaf/0.1/" + xmlns:admin="http://webns.net/mvcb/"> + + <name>GStreamer Editing Services Library</name> + <shortname>gst-editing-services</shortname> + <homepage rdf:resource="http://gstreamer.freedesktop.org/modules/gst-editing-services.html" /> + <created>2004-02-26</created> + <shortdesc xml:lang="en"> +a library for creating audio and video editors +</shortdesc> + <description xml:lang="en"> +GStreamer library for creating audio and video editors + </description> + <category></category> + <bug-database rdf:resource="https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/" /> + <screenshots></screenshots> + <mailing-list rdf:resource="http://lists.freedesktop.org/mailman/listinfo/gstreamer-devel" /> + <programming-language>C</programming-language> + <license rdf:resource="http://usefulinc.com/doap/licenses/lgpl"/> + <download-page rdf:resource="http://gstreamer.freedesktop.org/download/" /> + + <repository> + <GitRepository> + <location rdf:resource="https://gitlab.freedesktop.org/gstreamer/gst-editing-services"/> + <browse rdf:resource="http://gitlab.freedesktop.org/gstreamer/gst-editing-services"/> + </GitRepository> + </repository> + + <release> + <Version> + <revision>1.19.2</revision> + <branch>master</branch> + <name></name> + <created>2021-09-23</created> + <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.19.2.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.19.1</revision> + <branch>master</branch> + <name></name> + <created>2021-06-01</created> + <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.19.1.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.18.0</revision> + <branch>master</branch> + <name></name> + <created>2020-09-08</created> + <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.18.0.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.17.90</revision> + <branch>master</branch> + <name></name> + <created>2020-08-20</created> + <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.17.90.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.17.2</revision> + <branch>master</branch> + <name></name> + <created>2020-07-03</created> + <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.17.2.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.17.1</revision> + <branch>master</branch> + <name></name> + <created>2020-06-19</created> + <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.17.1.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.16.0</revision> + <branch>master</branch> + <name></name> + <created>2019-04-19</created> + <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.16.0.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.15.90</revision> + <branch>master</branch> + <name></name> + <created>2019-04-11</created> + <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.15.90.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.15.2</revision> + <branch>master</branch> + <name></name> + <created>2019-02-26</created> + <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.15.2.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.15.1</revision> + <branch>master</branch> + <name></name> + <created>2019-01-17</created> + <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.15.1.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.14.0</revision> + <branch>master</branch> + <name></name> + <created>2018-03-19</created> + <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.14.0.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.13.91</revision> + <branch>master</branch> + <name></name> + <created>2018-03-13</created> + <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.13.91.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.13.90</revision> + <branch>master</branch> + <name></name> + <created>2018-03-03</created> + <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.13.90.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.13.1</revision> + <branch>master</branch> + <name></name> + <created>2018-02-15</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.13.1.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.12.4</revision> + <branch>1.12</branch> + <name></name> + <created>2017-12-07</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.12.4.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.12.3</revision> + <branch>1.12</branch> + <name></name> + <created>2017-09-18</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.12.3.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.12.2</revision> + <branch>1.12</branch> + <name></name> + <created>2017-07-14</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.12.2.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.12.1</revision> + <branch>1.12</branch> + <name></name> + <created>2017-06-20</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.12.1.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.12.0</revision> + <branch>master</branch> + <name></name> + <created>2017-05-04</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.12.0.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.11.91</revision> + <branch>master</branch> + <name></name> + <created>2017-04-27</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.11.91.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.11.90</revision> + <branch>master</branch> + <name></name> + <created>2017-04-07</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.11.90.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.11.2</revision> + <branch>master</branch> + <name></name> + <created>2017-02-24</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.11.2.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.11.1</revision> + <branch>master</branch> + <name></name> + <created>2017-01-12</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.11.1.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.10.0</revision> + <branch>master</branch> + <created>2016-11-01</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.10.0.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.9.90</revision> + <branch>master</branch> + <created>2016-09-30</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.9.90.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.9.2</revision> + <branch>master</branch> + <created>2016-09-01</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.9.2.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.9.1</revision> + <branch>master</branch> + <created>2016-06-06</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.9.1.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.8.0</revision> + <branch>master</branch> + <created>2016-03-24</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.8.0.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.7.91</revision> + <branch>master</branch> + <created>2016-03-15</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.7.91.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.7.90</revision> + <branch>master</branch> + <created>2016-03-01</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.7.90.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.7.2</revision> + <branch>master</branch> + <created>2016-02-19</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.7.2.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.7.1</revision> + <branch>master</branch> + <created>2015-12-24</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.7.1.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.6.2</revision> + <branch>1.6</branch> + <created>2015-12-14</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.6.2.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.6.1</revision> + <branch>1.6</branch> + <created>2015-10-30</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.6.1.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.6.0</revision> + <branch>1.6</branch> + <created>2015-09-25</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.6.0.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.5.91</revision> + <branch>1.5</branch> + <created>2015-09-18</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.5.91.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.5.90</revision> + <branch>1.5</branch> + <created>2015-08-20</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.5.90.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.5.2</revision> + <branch>1.5</branch> + <created>2015-06-24</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.5.2.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.4.0</revision> + <branch>1.4</branch> + <created>2014-10-20</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.4.0.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.3.90</revision> + <branch>1.0</branch> + <created>2014-09-23</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.3.90.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>1.2.0</revision> + <branch>1.0</branch> + <created>2011-01-20</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.2.0.tar.xz" /> + </Version> + </release> + + <release> + <Version> + <revision>0.10.1</revision> + <branch>0.10</branch> + <created>2011-01-20</created> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-0.10.1.tar.bz2" /> + <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-0.10.1.tar.gz" /> + </Version> + </release> + + + <maintainer> + <foaf:Person> + <foaf:name>Thibault Saunier</foaf:name> + </foaf:Person> + </maintainer> + +</Project> diff --git a/hooks/pre-commit.hook b/hooks/pre-commit.hook new file mode 100755 index 0000000000..3c1062b9e0 --- /dev/null +++ b/hooks/pre-commit.hook @@ -0,0 +1,83 @@ +#!/bin/sh +# +# Check that the code follows a consistant code style +# + +# Check for existence of indent, and error out if not present. +# On some *bsd systems the binary seems to be called gnunindent, +# so check for that first. + +version=`gnuindent --version 2>/dev/null` +if test "x$version" = "x"; then + version=`gindent --version 2>/dev/null` + if test "x$version" = "x"; then + version=`indent --version 2>/dev/null` + if test "x$version" = "x"; then + echo "GStreamer git pre-commit hook:" + echo "Did not find GNU indent, please install it before continuing." + exit 1 + else + INDENT=indent + fi + else + INDENT=gindent + fi +else + INDENT=gnuindent +fi + +case `$INDENT --version` in + GNU*) + ;; + default) + echo "GStreamer git pre-commit hook:" + echo "Did not find GNU indent, please install it before continuing." + echo "(Found $INDENT, but it doesn't seem to be GNU indent)" + exit 1 + ;; +esac + +INDENT_PARAMETERS="--braces-on-if-line \ + --case-brace-indentation0 \ + --case-indentation2 \ + --braces-after-struct-decl-line \ + --line-length80 \ + --no-tabs \ + --cuddle-else \ + --dont-line-up-parentheses \ + --continuation-indentation4 \ + --honour-newlines \ + --tab-size8 \ + --indent-level2 \ + --leave-preprocessor-space" + +echo "--Checking style--" +for file in `git diff-index --cached --name-only HEAD --diff-filter=ACMR| grep "\.c$"` ; do + # nf is the temporary checkout. This makes sure we check against the + # revision in the index (and not the checked out version). + nf=`git checkout-index --temp ${file} | cut -f 1` + newfile=`mktemp /tmp/${nf}.XXXXXX` || exit 1 + $INDENT ${INDENT_PARAMETERS} \ + $nf -o $newfile 2>> /dev/null + # FIXME: Call indent twice as it tends to do line-breaks + # different for every second call. + $INDENT ${INDENT_PARAMETERS} \ + $newfile 2>> /dev/null + diff -u -p "${nf}" "${newfile}" + r=$? + rm "${newfile}" + rm "${nf}" + if [ $r != 0 ] ; then +echo "=================================================================================================" +echo " Code style error in: $file " +echo " " +echo " Please fix before committing. Don't forget to run git add before trying to commit again. " +echo " If the whole file is to be committed, this should work (run from the top-level directory): " +echo " " +echo " gst-indent $file; git add $file; git commit" +echo " " +echo "=================================================================================================" + exit 1 + fi +done +echo "--Checking style pass--" diff --git a/meson.build b/meson.build new file mode 100644 index 0000000000..81fc109d5b --- /dev/null +++ b/meson.build @@ -0,0 +1,331 @@ +project('gst-editing-services', 'c', + version : '1.19.2', + meson_version : '>= 0.54', + default_options : [ 'warning_level=1', + 'buildtype=debugoptimized' ]) + +gst_version = meson.project_version() +version_arr = gst_version.split('.') +gst_version = meson.project_version() +version_arr = gst_version.split('.') +gst_version_major = version_arr[0].to_int() +gst_version_minor = version_arr[1].to_int() +gst_version_micro = version_arr[2].to_int() + if version_arr.length() == 4 + gst_version_nano = version_arr[3].to_int() +else + gst_version_nano = 0 +endif + +apiversion = '1.0' +soversion = 0 +# maintaining compatibility with the previous libtool versioning +# current = minor * 100 + micro +curversion = gst_version_minor * 100 + gst_version_micro +libversion = '@0@.@1@.0'.format(soversion, curversion) +osxversion = curversion + 1 + +glib_req = '>= 2.56.0' +gst_req = '>= @0@.@1@.0'.format(gst_version_major, gst_version_minor) + +cc = meson.get_compiler('c') +mathlib = cc.find_library('m', required : false) + +cdata = configuration_data() + +prefix = get_option('prefix') +datadir = prefix / get_option('datadir') + +if cc.get_id() == 'msvc' + msvc_args = [ + # Ignore several spurious warnings for things gstreamer does very commonly + # If a warning is completely useless and spammy, use '/wdXXXX' to suppress it + # If a warning is harmless but hard to fix, use '/woXXXX' so it's shown once + # NOTE: Only add warnings here if you are sure they're spurious + '/wd4018', # implicit signed/unsigned conversion + '/wd4146', # unary minus on unsigned (beware INT_MIN) + '/wd4244', # lossy type conversion (e.g. double -> int) + '/wd4305', # truncating type conversion (e.g. double -> float) + cc.get_supported_arguments(['/utf-8']), # set the input encoding to utf-8 + + # Enable some warnings on MSVC to match GCC/Clang behaviour + '/w14062', # enumerator 'identifier' in switch of enum 'enumeration' is not handled + '/w14101', # 'identifier' : unreferenced local variable + '/w14189', # 'identifier' : local variable is initialized but not referenced + ] + add_project_arguments(msvc_args, language: 'c') +endif + +if cc.has_link_argument('-Wl,-Bsymbolic-functions') + add_project_link_arguments('-Wl,-Bsymbolic-functions', language : 'c') +endif + +# Symbol visibility +if cc.get_id() == 'msvc' + export_define = '__declspec(dllexport) extern' +elif cc.has_argument('-fvisibility=hidden') + add_project_arguments('-fvisibility=hidden', language: 'c') + export_define = 'extern __attribute__ ((visibility ("default")))' +else + export_define = 'extern' +endif + +# Passing this through the command line would be too messy +cdata.set('GST_API_EXPORT', export_define) + +# Disable strict aliasing +if cc.has_argument('-fno-strict-aliasing') + add_project_arguments('-fno-strict-aliasing', language: 'c') +endif + +cdata.set('VERSION', '"@0@"'.format(gst_version)) +cdata.set('PACKAGE', '"gst-editing-services"') +cdata.set('PACKAGE_VERSION', '"@0@"'.format(gst_version)) +cdata.set('PACKAGE_BUGREPORT', '"https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/new"') +cdata.set('PACKAGE_NAME', '"GStreamer Editing Services"') +cdata.set('GST_PACKAGE_NAME', '"GStreamer Editing Services"') +cdata.set('GST_PACKAGE_ORIGIN', '"Unknown package origin"') +cdata.set('GST_LICENSE', '"LGPL"') + +# Mandatory GST deps +gst_dep = dependency('gstreamer-' + apiversion, version : gst_req, + fallback : ['gstreamer', 'gst_dep']) +gstpbutils_dep = dependency('gstreamer-pbutils-' + apiversion, version : gst_req, + fallback : ['gst-plugins-base', 'pbutils_dep']) +gstvideo_dep = dependency('gstreamer-video-' + apiversion, version : gst_req, + fallback : ['gst-plugins-base', 'video_dep']) +gstaudio_dep = dependency('gstreamer-audio-' + apiversion, version : gst_req, + fallback : ['gst-plugins-base', 'audio_dep']) +gstbase_dep = dependency('gstreamer-base-1.0', version : gst_req, + fallback : ['gstreamer', 'gst_base_dep']) +if host_machine.system() != 'windows' + gstcheck_dep = dependency('gstreamer-check-1.0', version : gst_req, + required : get_option('tests'), + fallback : ['gstreamer', 'gst_check_dep']) +endif +gstcontroller_dep = dependency('gstreamer-controller-1.0', version : gst_req, + fallback : ['gstreamer', 'gst_controller_dep']) +gstvalidate_dep = dependency('gst-validate-1.0', version : gst_req, required : get_option('validate'), + fallback : ['gst-devtools', 'validate_dep']) + +gio_dep = dependency('gio-2.0', version: glib_req, fallback: ['glib', 'libgio_dep']) +libxml_dep = dependency('libxml-2.0', required: get_option('xptv')) +cdata.set('DISABLE_XPTV', not libxml_dep.found()) + +# TODO Properly port to Gtk 3 +# gtk_dep = dependency('gtk+-3.0', required : false) + +libges_deps = [gst_dep, gstbase_dep, gstvideo_dep, gstpbutils_dep, + gstcontroller_dep, gio_dep, libxml_dep, mathlib] + +if gstvalidate_dep.found() + libges_deps = libges_deps + [gstvalidate_dep] + cdata.set('HAVE_GST_VALIDATE', 1) +endif + +gir = find_program('g-ir-scanner', required : get_option('introspection')) +gnome = import('gnome') + +# Fixme, not very elegant. +build_gir = gir.found() and (not meson.is_cross_build() or get_option('introspection').enabled()) +gir_init_section = [ '--add-init-section=' + \ + 'extern void gst_init(gint*,gchar**);' + \ + 'extern void ges_init(void);' + \ + 'g_setenv("GST_REGISTRY_1.0", "/no/way/this/exists.reg", TRUE);' + \ + 'g_setenv("GST_PLUGIN_PATH_1_0", "", TRUE);' + \ + 'g_setenv("GST_PLUGIN_SYSTEM_PATH_1_0", "", TRUE);' + \ + 'g_setenv("GST_DEBUG", "0", TRUE);' + \ + 'gst_init(NULL,NULL);' + \ + 'ges_init();', '--quiet'] + +has_python = false +if build_gir + pymod = import('python') + python = pymod.find_installation(required: get_option('python')) + if python.found() + # Workaround for https://github.com/mesonbuild/meson/issues/5629 + pythonver = python.language_version() + python_dep = dependency('python-@0@-embed'.format(pythonver), version: '>=3', required: false) + if not python_dep.found() + python_dep = python.dependency(required : get_option('python')) + endif + else + python_dep = dependency('', required: false) + endif + if python_dep.found() + python_abi_flags = python.get_variable('ABIFLAGS', '') + pylib_loc = get_option('libpython-dir') + + error_msg = '' + if not cc.compiles('#include <Python.h>', dependencies: [python_dep]) + error_msg = 'Could not compile a simple program against python' + elif pylib_loc == '' + check_path_exists = 'import os, sys; assert(os.path.exists(sys.argv[1]))' + pylib_loc = python.get_variable('LIBPL', '') + if host_machine.system() != 'windows' and host_machine.system() != 'darwin' + pylib_ldlibrary = python.get_variable('LDLIBRARY', '') + if run_command(python, '-c', check_path_exists, join_paths(pylib_loc, pylib_ldlibrary)).returncode() != 0 + # Workaround for Fedora + pylib_loc = python.get_variable('LIBDIR', '') + message('pylib_loc = @0@'.format(pylib_loc)) + endif + + res = run_command(python, '-c', check_path_exists, join_paths(pylib_loc, pylib_ldlibrary)) + if res.returncode() != 0 + error_msg = '@0@ doesn\' exist, can\'t use python'.format(join_paths(pylib_loc, pylib_ldlibrary)) + endif + endif + if error_msg == '' + pylib_suffix = 'so' + if host_machine.system() == 'windows' + pylib_suffix = 'dll' + elif host_machine.system() == 'darwin' + pylib_suffix = 'dylib' + endif + + gmodule_dep = dependency('gmodule-2.0') + libges_deps = libges_deps + [python_dep, gmodule_dep] + has_python = true + message('python_abi_flags = @0@'.format(python_abi_flags)) + message('pylib_loc = @0@'.format(pylib_loc)) + cdata.set('HAS_PYTHON', true) + cdata.set('PY_LIB_LOC', '"@0@"'.format(pylib_loc)) + cdata.set('PY_ABI_FLAGS', '"@0@"'.format(python_abi_flags)) + cdata.set('PY_LIB_SUFFIX', '"@0@"'.format(pylib_suffix)) + cdata.set('PYTHON_VERSION', '"@0@"'.format(python_dep.version())) + else + if get_option('python').enabled() + error(error_msg) + else + message(error_msg) + endif + endif + endif + endif +endif + +ges_c_args = ['-DHAVE_CONFIG_H', '-DG_LOG_DOMAIN="GES"'] +plugins_install_dir = '@0@/gstreamer-1.0'.format(get_option('libdir')) + +pkgconfig = import('pkgconfig') +plugins_pkgconfig_install_dir = join_paths(plugins_install_dir, 'pkgconfig') +if get_option('default_library') == 'shared' + # If we don't build static plugins there is no need to generate pc files + plugins_pkgconfig_install_dir = disabler() +endif + +if gst_dep.type_name() == 'internal' + gst_debug_disabled = not subproject('gstreamer').get_variable('gst_debug') +else + # We can't check that in the case of subprojects as we won't + # be able to build against an internal dependency (which is not built yet) + gst_debug_disabled = cc.has_header_symbol('gst/gstconfig.h', 'GST_DISABLE_GST_DEBUG', dependencies: gst_dep) +endif + +if gst_debug_disabled and cc.has_argument('-Wno-unused') + add_project_arguments('-Wno-unused', language: 'c') +endif + +warning_flags = [ + '-Wmissing-declarations', + '-Wmissing-prototypes', + '-Wredundant-decls', + '-Wundef', + '-Wwrite-strings', + '-Wformat', + '-Wformat-security', + '-Winit-self', + '-Wmissing-include-dirs', + '-Waddress', + '-Wno-multichar', + '-Wdeclaration-after-statement', + '-Wvla', + '-Wpointer-arith', +] + +foreach extra_arg : warning_flags + if cc.has_argument (extra_arg) + add_project_arguments([extra_arg], language: 'c') + endif +endforeach + +python3 = import('python').find_installation() +pkgconfig = import('pkgconfig') +pkgconfig_subdirs = ['gstreamer-1.0'] + +configinc = include_directories('.') +subdir('ges') +subdir('plugins') +if not get_option('tools').disabled() + subdir('tools') +endif +subdir('tests') +if not get_option('examples').disabled() + subdir('examples') +endif +subdir('docs') + +override_detector = ''' +import sys +import os + +prefix = sys.argv[1] +version = sys.version_info + +# If we are installing in the same prefix as PyGobject +# make sure to install in the right place. +import gi.overrides + +overrides_path = os.path.dirname(gi.overrides.__file__) +if os.path.commonprefix([overrides_path, prefix]) == prefix: + print(overrides_path) + exit(0) + +# Otherwise follow python's way of install site packages inside +# the provided prefix +if os.name == 'posix': + print(os.path.join( + prefix, 'lib', 'python%d.%d' % (version.major, version.minor), + 'site-packages', 'gi', 'overrides')) +else: + print(os.path.join( + prefix, 'Lib', 'Python%d%d' % (version.major, version.minor), + 'site-packages', 'gi', 'overrides')) +''' +pygi_override_dir = get_option('pygi-overrides-dir') +if pygi_override_dir == '' + cres = run_command(python3, '-c', override_detector, get_option('prefix')) + if cres.returncode() == 0 + pygi_override_dir = cres.stdout().strip() + endif + if cres.stderr() != '' + message(cres.stderr()) + endif +endif + +if pygi_override_dir != '' + message('pygobject overrides directory ' + pygi_override_dir) + subdir('bindings/python') +endif + +# Set release date +if gst_version_nano == 0 + extract_release_date = find_program('scripts/extract-release-date-from-doap-file.py') + run_result = run_command(extract_release_date, gst_version, files('gst-editing-services.doap')) + if run_result.returncode() == 0 + release_date = run_result.stdout().strip() + cdata.set_quoted('GST_PACKAGE_RELEASE_DATETIME', release_date) + message('Package release date: ' + release_date) + else + # Error out if our release can't be found in the .doap file + error(run_result.stderr()) + endif +endif + +if gio_dep.version().version_compare('< 2.67.4') + cdata.set('g_memdup2(ptr,sz)', '(G_LIKELY(((guint64)(sz)) < G_MAXUINT)) ? g_memdup(ptr,sz) : (g_abort(),NULL)') +endif + +configure_file(output: 'config.h', configuration: cdata) + +run_command(python3, '-c', 'import shutil; shutil.copy("hooks/pre-commit.hook", ".git/hooks/pre-commit")') diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000000..5f15a784da --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,26 @@ +# Common feature options +option('doc', type : 'feature', value : 'auto', yield: true, + description: 'Enable documentation.') +option('examples', type : 'feature', value : 'auto', yield : true, + description : 'Build examples') +option('introspection', type : 'feature', value : 'auto', yield : true, + description : 'Generate gobject-introspection bindings') +option('tests', type : 'feature', value : 'auto', yield : true, + description : 'Build and enable unit tests') +option('tools', type : 'feature', value : 'auto', yield : true, + description : 'Build ges-launch command line tool') + +# GES options +option('bash-completion', type : 'feature', value : 'auto', + description : 'Install bash completion files') +option('pygi-overrides-dir', type : 'string', value : '', + description: 'Path to pygobject overrides directory') +option('xptv', type : 'feature', value : 'auto', + description : 'Build the deprecated xptv formater') +option('python', type : 'feature', value : 'auto', yield: true, + description: 'Enable python formatters.') +option('libpython-dir', type : 'string', value : '', + description: 'Path to find libpythonXX.so') +option('validate', type : 'feature', value : 'auto', yield: true, + description: 'Enable GstValidate integration.') +option('examples', type : 'feature', value : 'auto', yield : true) \ No newline at end of file diff --git a/plugins/ges/gesbasebin.c b/plugins/ges/gesbasebin.c new file mode 100644 index 0000000000..5e1c6ca519 --- /dev/null +++ b/plugins/ges/gesbasebin.c @@ -0,0 +1,310 @@ +/* GStreamer GES plugin + * + * Copyright (C) 2019 Thibault Saunier <tsaunier@igalia.com> + * + * gesbasebin.h + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "gesbasebin.h" + +static GstStaticPadTemplate video_src_template = +GST_STATIC_PAD_TEMPLATE ("video_src", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("video/x-raw(ANY)")); + +static GstStaticPadTemplate audio_src_template = + GST_STATIC_PAD_TEMPLATE ("audio_src", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("audio/x-raw(ANY);")); + +typedef struct +{ + GESTimeline *timeline; + GstFlowCombiner *flow_combiner; +} GESBaseBinPrivate; + +enum +{ + PROP_0, + PROP_TIMELINE, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +G_DEFINE_TYPE_WITH_PRIVATE (GESBaseBin, ges_base_bin, GST_TYPE_BIN); + +GST_DEBUG_CATEGORY_STATIC (gesbasebin); +#define GST_CAT_DEFAULT gesbasebin + +static void +ges_base_bin_dispose (GObject * object) +{ + GESBaseBin *self = GES_BASE_BIN (object); + GESBaseBinPrivate *priv = ges_base_bin_get_instance_private (self); + + if (priv->timeline) + gst_clear_object (&priv->timeline); +} + +static void +ges_base_bin_finalize (GObject * object) +{ + GESBaseBin *self = GES_BASE_BIN (object); + GESBaseBinPrivate *priv = ges_base_bin_get_instance_private (self); + + gst_flow_combiner_free (priv->flow_combiner); +} + +static void +ges_base_bin_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESBaseBin *self = GES_BASE_BIN (object); + GESBaseBinPrivate *priv = ges_base_bin_get_instance_private (self); + + switch (property_id) { + case PROP_TIMELINE: + g_value_set_object (value, priv->timeline); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_base_bin_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESBaseBin *self = GES_BASE_BIN (object); + + switch (property_id) { + case PROP_TIMELINE: + ges_base_bin_set_timeline (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_base_bin_class_init (GESBaseBinClass * self_class) +{ + GObjectClass *gclass = G_OBJECT_CLASS (self_class); + GstElementClass *gstelement_klass = GST_ELEMENT_CLASS (self_class); + + GST_DEBUG_CATEGORY_INIT (gesbasebin, "gesbasebin", 0, "ges bin element"); + + gst_tag_register ("is-ges-timeline", GST_TAG_FLAG_META, G_TYPE_BOOLEAN, + "is-ges-timeline", "The stream is a ges timeline.", NULL); + + gclass->get_property = ges_base_bin_get_property; + gclass->set_property = ges_base_bin_set_property; + gclass->dispose = ges_base_bin_dispose; + gclass->finalize = ges_base_bin_finalize; + + /** + * GESBaseBin:timeline: + * + * Timeline to use in this bin. + * + * Since: 1.16 + */ + properties[PROP_TIMELINE] = g_param_spec_object ("timeline", "Timeline", + "Timeline to use in this src.", + GES_TYPE_TIMELINE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gclass, PROP_LAST, properties); + + gst_element_class_add_pad_template (gstelement_klass, + gst_static_pad_template_get (&video_src_template)); + gst_element_class_add_pad_template (gstelement_klass, + gst_static_pad_template_get (&audio_src_template)); + + gst_type_mark_as_plugin_api (ges_base_bin_get_type (), 0); +} + +static void +ges_base_bin_init (GESBaseBin * self) +{ + GESBaseBinPrivate *priv = ges_base_bin_get_instance_private (self); + + ges_init (); + + priv->flow_combiner = gst_flow_combiner_new (); +} + +static gboolean +ges_base_bin_event (GstPad * pad, GstObject * parent, GstEvent * event) +{ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_STREAM_START: + { + const gchar *stream_id; + gchar *new_stream_id; + guint stream_group; + GstTagList *tlist = gst_tag_list_new ("is-ges-timeline", TRUE, NULL); + GstPad *peer = gst_pad_get_peer (pad); + GstEvent *new_event; + + gst_event_parse_stream_start (event, &stream_id); + new_stream_id = + gst_pad_create_stream_id (peer, + GST_ELEMENT (GST_OBJECT_PARENT (parent)), stream_id); + gst_object_unref (peer); + + new_event = gst_event_new_stream_start (new_stream_id); + if (gst_event_parse_group_id (event, &stream_group)) + gst_event_set_group_id (new_event, stream_group); + gst_event_unref (event); + g_free (new_stream_id); + + gst_pad_event_default (pad, parent, new_event); + + gst_tag_list_set_scope (tlist, GST_TAG_SCOPE_GLOBAL); + + return gst_pad_send_event (pad, gst_event_new_tag (tlist)); + } + default: + break; + } + + return gst_pad_event_default (pad, parent, event); +} + +static GstFlowReturn +ges_base_bin_src_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) +{ + GstFlowReturn result, chain_result; + GESBaseBin *self = GES_BASE_BIN (GST_OBJECT_PARENT (parent)); + GESBaseBinPrivate *priv = ges_base_bin_get_instance_private (self); + + chain_result = gst_proxy_pad_chain_default (pad, GST_OBJECT (self), buffer); + result = + gst_flow_combiner_update_pad_flow (priv->flow_combiner, pad, + chain_result); + + if (result == GST_FLOW_FLUSHING) + return chain_result; + + return result; +} + + +gboolean +ges_base_bin_set_timeline (GESBaseBin * self, GESTimeline * timeline) +{ + GList *tmp; + guint naudiopad = 0, nvideopad = 0; + GstBin *sbin = GST_BIN (self); + GESBaseBinPrivate *priv = ges_base_bin_get_instance_private (self); + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + + if (priv->timeline) { + GST_ERROR_OBJECT (sbin, "Implement changing timeline support"); + + return FALSE; + } + + priv->timeline = gst_object_ref (timeline); + GST_INFO_OBJECT (sbin, "Setting timeline: %" GST_PTR_FORMAT, timeline); + gst_element_set_locked_state (GST_ELEMENT (timeline), TRUE); + if (!gst_bin_add (sbin, GST_ELEMENT (timeline))) { + GST_ERROR_OBJECT (sbin, "Could not add timeline to myself!"); + + return FALSE; + } + + ges_timeline_commit (timeline); + for (tmp = timeline->tracks; tmp; tmp = tmp->next) { + GstPad *gpad; + gchar *name = NULL; + GstElement *queue; + GESTrack *track = GES_TRACK (tmp->data); + GstPad *proxy_pad, *tmppad, *pad = + ges_timeline_get_pad_for_track (timeline, track); + GstStaticPadTemplate *template; + + if (!pad) { + GST_WARNING_OBJECT (sbin, "No pad for track: %" GST_PTR_FORMAT, track); + + continue; + } + + if (track->type == GES_TRACK_TYPE_AUDIO) { + name = g_strdup_printf ("audio_%u", naudiopad++); + template = &audio_src_template; + } else if (track->type == GES_TRACK_TYPE_VIDEO) { + name = g_strdup_printf ("video_%u", nvideopad++); + template = &video_src_template; + } else { + GST_INFO_OBJECT (sbin, "Track type not handled: %" GST_PTR_FORMAT, track); + continue; + } + + queue = gst_element_factory_make ("queue", NULL); + /* Add queues the same way as in GESPipeline */ + g_object_set (G_OBJECT (queue), "max-size-buffers", 0, + "max-size-bytes", 0, "max-size-time", (gint64) 2 * GST_SECOND, NULL); + gst_bin_add (sbin, queue); + gst_element_sync_state_with_parent (GST_ELEMENT (queue)); + + tmppad = gst_element_get_static_pad (queue, "sink"); + if (gst_pad_link (pad, tmppad) != GST_PAD_LINK_OK) { + GST_ERROR_OBJECT (sbin, "Could not link %s:%s and %s:%s", + GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (tmppad)); + + gst_object_unref (tmppad); + gst_object_unref (queue); + continue; + } + + tmppad = gst_element_get_static_pad (queue, "src"); + gpad = gst_ghost_pad_new_from_template (name, tmppad, + gst_static_pad_template_get (template)); + + gst_pad_set_active (gpad, TRUE); + gst_element_add_pad (GST_ELEMENT (sbin), gpad); + + proxy_pad = GST_PAD (gst_proxy_pad_get_internal (GST_PROXY_PAD (gpad))); + gst_flow_combiner_add_pad (priv->flow_combiner, proxy_pad); + gst_pad_set_chain_function (proxy_pad, ges_base_bin_src_chain); + gst_pad_set_event_function (proxy_pad, ges_base_bin_event); + gst_object_unref (proxy_pad); + GST_DEBUG_OBJECT (sbin, "Adding pad: %" GST_PTR_FORMAT, gpad); + } + + gst_element_set_locked_state (GST_ELEMENT (timeline), FALSE); + + gst_element_no_more_pads (GST_ELEMENT (sbin)); + gst_element_sync_state_with_parent (GST_ELEMENT (timeline)); + + return TRUE; +} + +GESTimeline * +ges_base_bin_get_timeline (GESBaseBin * self) +{ + GESBaseBinPrivate *priv = ges_base_bin_get_instance_private (self); + + return priv->timeline; +} diff --git a/plugins/ges/gesbasebin.h b/plugins/ges/gesbasebin.h new file mode 100644 index 0000000000..7320452dc6 --- /dev/null +++ b/plugins/ges/gesbasebin.h @@ -0,0 +1,43 @@ +/* GStreamer GES plugin + * + * Copyright (C) 2019 Thibault Saunier <tsaunier@igalia.com> + * + * gesbasebin.h + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#pragma once + +#include <gst/gst.h> +#include <gst/base/gstflowcombiner.h> +#include <ges/ges.h> + +G_BEGIN_DECLS + +#define SUPRESS_UNUSED_WARNING(a) (void)a + +G_DECLARE_DERIVABLE_TYPE(GESBaseBin, ges_base_bin, GES, BASE_BIN, GstBin) +struct _GESBaseBinClass +{ + GstBinClass parent_class; +}; + +gboolean ges_base_bin_set_timeline (GESBaseBin * self, GESTimeline * timeline); +GESTimeline * ges_base_bin_get_timeline (GESBaseBin * self); + +G_END_DECLS diff --git a/plugins/ges/gesdemux.c b/plugins/ges/gesdemux.c new file mode 100644 index 0000000000..ed13a067d6 --- /dev/null +++ b/plugins/ges/gesdemux.c @@ -0,0 +1,626 @@ +/* GStreamer GES plugin + * + * Copyright (C) 2019 Igalia S.L + * Author: 2019 Thibault Saunier <tsaunier@igalia.com> + * + * gesdemux.c + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + /** + * SECTION:gstdemux + * @short_description: A GstBin subclasses use to use GESTimeline + * as demux inside any GstPipeline. + * @see_also: #GESTimeline + * + * The gstdemux is a bin that will simply expose the track source pads + * and implements the GstUriHandler interface using a custom ges://0Xpointer + * uri scheme. + **/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gesbasebin.h" + +#include <gst/gst.h> +#include <glib/gstdio.h> +#include <gst/pbutils/pbutils.h> +#include <gst/base/gstadapter.h> +#include <ges/ges.h> + +GST_DEBUG_CATEGORY_STATIC (gesdemux); +#define GST_CAT_DEFAULT gesdemux + +G_DECLARE_FINAL_TYPE (GESDemux, ges_demux, GES, DEMUX, GESBaseBin); +#define GES_DEMUX_DOC_CAPS \ + "application/xges;" \ + "text/x-xptv;" \ + "application/vnd.pixar.opentimelineio+json;" \ + "application/vnd.apple-xmeml+xml;" \ + "application/vnd.apple-fcp+xml;" \ + +struct _GESDemux +{ + GESBaseBin parent; + + GESTimeline *timeline; + GstPad *sinkpad; + + GstAdapter *input_adapter; + + gchar *upstream_uri; + GStatBuf stats; +}; + +G_DEFINE_TYPE (GESDemux, ges_demux, ges_base_bin_get_type ()); +#define GES_DEMUX(obj) ((GESDemux*)obj) + +enum +{ + PROP_0, + PROP_TIMELINE, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +static GstCaps * +ges_demux_get_sinkpad_caps () +{ + GList *tmp, *formatters; + GstCaps *sinkpad_caps = gst_caps_new_empty (); + + formatters = ges_list_assets (GES_TYPE_FORMATTER); + for (tmp = formatters; tmp; tmp = tmp->next) { + GstCaps *caps; + const gchar *mimetype = + ges_meta_container_get_string (GES_META_CONTAINER (tmp->data), + GES_META_FORMATTER_MIMETYPE); + if (!mimetype) + continue; + + caps = gst_caps_from_string (mimetype); + + if (!caps) { + GST_INFO_OBJECT (tmp->data, + "%s - could not create caps from mimetype: %s", + ges_meta_container_get_string (GES_META_CONTAINER (tmp->data), + GES_META_FORMATTER_NAME), mimetype); + + continue; + } + + gst_caps_append (sinkpad_caps, caps); + } + g_list_free (formatters); + + return sinkpad_caps; +} + +static gchar * +ges_demux_get_extension (GstStructure * _struct) +{ + GList *tmp, *formatters; + gchar *ext = NULL; + + formatters = ges_list_assets (GES_TYPE_FORMATTER); + for (tmp = formatters; tmp; tmp = tmp->next) { + gchar **extensions_a; + gint i, n_exts; + GstCaps *caps; + const gchar *mimetype = + ges_meta_container_get_string (GES_META_CONTAINER (tmp->data), + GES_META_FORMATTER_MIMETYPE); + const gchar *extensions = + ges_meta_container_get_string (GES_META_CONTAINER (tmp->data), + GES_META_FORMATTER_EXTENSION); + if (!mimetype) + continue; + + if (!extensions) + continue; + + caps = gst_caps_from_string (mimetype); + if (!caps) { + GST_INFO_OBJECT (tmp->data, + "%s - could not create caps from mimetype: %s", + ges_meta_container_get_string (GES_META_CONTAINER (tmp->data), + GES_META_FORMATTER_NAME), mimetype); + + continue; + } + + extensions_a = g_strsplit (extensions, ",", -1); + n_exts = g_strv_length (extensions_a); + for (i = 0; i < gst_caps_get_size (caps) && i < n_exts; i++) { + GstStructure *structure = gst_caps_get_structure (caps, i); + + if (gst_structure_has_name (_struct, gst_structure_get_name (structure))) { + ext = g_strdup (extensions_a[i]); + g_strfreev (extensions_a); + gst_caps_unref (caps); + goto done; + } + } + g_strfreev (extensions_a); + } +done: + g_list_free (formatters); + + return ext; +} + +static void +ges_demux_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESDemux *self = GES_DEMUX (object); + + switch (property_id) { + case PROP_TIMELINE: + g_value_set_object (value, + ges_base_bin_get_timeline (GES_BASE_BIN (self))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_demux_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_demux_class_init (GESDemuxClass * self_class) +{ + GstPadTemplate *pad_template; + GObjectClass *gclass = G_OBJECT_CLASS (self_class); + GstElementClass *gstelement_klass = GST_ELEMENT_CLASS (self_class); + GstCaps *sinkpad_caps, *doc_caps; + + GST_DEBUG_CATEGORY_INIT (gesdemux, "gesdemux", 0, "ges demux element"); + + sinkpad_caps = ges_demux_get_sinkpad_caps (); + + gclass->get_property = ges_demux_get_property; + gclass->set_property = ges_demux_set_property; + + /** + * GESDemux:timeline: + * + * Timeline to use in this source. + */ + properties[PROP_TIMELINE] = g_param_spec_object ("timeline", "Timeline", + "Timeline to use in this source.", + GES_TYPE_TIMELINE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_override_property (gclass, PROP_TIMELINE, "timeline"); + + gst_element_class_set_static_metadata (gstelement_klass, + "GStreamer Editing Services based 'demuxer'", + "Codec/Demux/Editing", + "Demuxer for complex timeline file formats using GES.", + "Thibault Saunier <tsaunier@igalia.com"); + + pad_template = + gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, sinkpad_caps); + doc_caps = gst_caps_from_string (GES_DEMUX_DOC_CAPS); + gst_pad_template_set_documentation_caps (pad_template, doc_caps); + gst_clear_caps (&doc_caps); + gst_element_class_add_pad_template (gstelement_klass, pad_template); + gst_caps_unref (sinkpad_caps); +} + +typedef struct +{ + GESTimeline *timeline; + GMainLoop *ml; + GError *error; + gulong loaded_sigid; + gulong error_sigid; + gulong error_asset_sigid; +} TimelineConstructionData; + +static void +project_loaded_cb (GESProject * project, GESTimeline * timeline, + TimelineConstructionData * data) +{ + data->timeline = timeline; + g_signal_handler_disconnect (project, data->loaded_sigid); + data->loaded_sigid = 0; + + g_main_loop_quit (data->ml); +} + +static void +error_loading_cb (GESProject * project, GESTimeline * timeline, + GError * error, TimelineConstructionData * data) +{ + data->error = g_error_copy (error); + g_signal_handler_disconnect (project, data->error_sigid); + data->error_sigid = 0; + + g_main_loop_quit (data->ml); +} + +static void +error_loading_asset_cb (GESProject * project, GError * error, gchar * id, + GType extractable_type, TimelineConstructionData * data) +{ + data->error = g_error_copy (error); + g_signal_handler_disconnect (project, data->error_asset_sigid); + data->error_asset_sigid = 0; + + g_main_loop_quit (data->ml); +} + +static gboolean +ges_demux_src_probe (GstPad * pad, GstPadProbeInfo * info, GstElement * parent) +{ + GESDemux *self = GES_DEMUX (parent); + GstStructure *structure = + (GstStructure *) gst_query_get_structure (info->data); + + if (gst_structure_has_name (structure, "NleCompositionQueryNeedsTearDown")) { + GstQuery *uri_query = gst_query_new_uri (); + + if (gst_pad_peer_query (self->sinkpad, uri_query)) { + gchar *upstream_uri = NULL; + GStatBuf stats; + gst_query_parse_uri (uri_query, &upstream_uri); + + if (gst_uri_has_protocol (upstream_uri, "file")) { + gchar *location = gst_uri_get_location (upstream_uri); + + if (g_stat (location, &stats) < 0) { + GST_INFO_OBJECT (self, "Could not stat %s - not updating", location); + + g_free (location); + g_free (upstream_uri); + goto done; + } + + g_free (location); + GST_OBJECT_LOCK (self); + if (g_strcmp0 (upstream_uri, self->upstream_uri) + || stats.st_mtime != self->stats.st_mtime + || stats.st_size != self->stats.st_size) { + GST_INFO_OBJECT (self, + "Underlying file changed, asking for an update"); + gst_structure_set (structure, "result", G_TYPE_BOOLEAN, TRUE, NULL); + g_free (self->upstream_uri); + self->upstream_uri = upstream_uri; + self->stats = stats; + } else { + g_free (upstream_uri); + } + GST_OBJECT_UNLOCK (self); + } + } + done: + gst_query_unref (uri_query); + } + + return GST_PAD_PROBE_OK; +} + +static gboolean +ges_demux_set_srcpad_probe (GstElement * element, GstPad * pad, + gpointer user_data) +{ + gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_QUERY_UPSTREAM, + (GstPadProbeCallback) ges_demux_src_probe, element, NULL); + return TRUE; +} + +static void +ges_demux_adapt_timeline_duration (GESDemux * self, GESTimeline * timeline) +{ + GType nleobject_type = g_type_from_name ("NleObject"); + GstObject *parent, *tmpparent; + + parent = gst_object_get_parent (GST_OBJECT (self)); + while (parent) { + if (g_type_is_a (G_OBJECT_TYPE (parent), nleobject_type)) { + GstClockTime duration, inpoint, timeline_duration; + + g_object_get (parent, "duration", &duration, "inpoint", &inpoint, NULL); + g_object_get (timeline, "duration", &timeline_duration, NULL); + + if (inpoint + duration > timeline_duration) { + GESLayer *layer = ges_timeline_get_layer (timeline, 0); + + if (layer) { + GESClip *clip = GES_CLIP (ges_test_clip_new ()); + GList *tmp, *tracks = ges_timeline_get_tracks (timeline); + + g_object_set (clip, "start", timeline_duration, "duration", + inpoint + duration, "vpattern", GES_VIDEO_TEST_PATTERN_SMPTE75, + NULL); + ges_layer_add_clip (layer, clip); + for (tmp = tracks; tmp; tmp = tmp->next) { + if (GES_IS_VIDEO_TRACK (tmp->data)) { + GESEffect *text; + GstCaps *caps; + gchar *effect_str_full = NULL; + const gchar *effect_str = + "textoverlay text=\"Nested timeline too short, please FIX!\" halignment=center valignment=center"; + + g_object_get (tmp->data, "restriction-caps", &caps, NULL); + if (caps) { + gchar *caps_str = gst_caps_to_string (caps); + effect_str = effect_str_full = + g_strdup_printf ("capsfilter caps=\"%s\" ! %s", caps_str, + effect_str); + g_free (caps_str); + gst_caps_unref (caps); + } + text = ges_effect_new (effect_str); + g_free (effect_str_full); + + if (!ges_container_add (GES_CONTAINER (clip), + GES_TIMELINE_ELEMENT (text))) { + GST_ERROR ("Could not add text overlay to ending clip!"); + } + } + + } + g_list_free_full (tracks, gst_object_unref); + GST_INFO_OBJECT (timeline, + "Added test clip with duration: %" GST_TIME_FORMAT " - %" + GST_TIME_FORMAT " to match parent nleobject duration", + GST_TIME_ARGS (timeline_duration), + GST_TIME_ARGS (inpoint + duration - timeline_duration)); + } + } + gst_object_unref (parent); + + return; + } + + tmpparent = parent; + parent = gst_object_get_parent (GST_OBJECT (parent)); + gst_object_unref (tmpparent); + } +} + +static gboolean +ges_demux_create_timeline (GESDemux * self, gchar * uri, GError ** error) +{ + GESProject *project = ges_project_new (uri); + G_GNUC_UNUSED void *unused; + TimelineConstructionData data = { 0, }; + GMainContext *ctx = g_main_context_new (); + GstQuery *query; + + g_main_context_push_thread_default (ctx); + data.ml = g_main_loop_new (ctx, TRUE); + + data.loaded_sigid = + g_signal_connect (project, "loaded", G_CALLBACK (project_loaded_cb), + &data); + data.error_asset_sigid = + g_signal_connect_after (project, "error-loading-asset", + G_CALLBACK (error_loading_asset_cb), &data); + data.error_sigid = + g_signal_connect_after (project, "error-loading", + G_CALLBACK (error_loading_cb), &data); + + unused = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), &data.error)); + if (data.error) { + *error = data.error; + + goto done; + } + + g_main_loop_run (data.ml); + g_main_loop_unref (data.ml); + if (data.error) + goto done; + + ges_demux_adapt_timeline_duration (self, data.timeline); + + query = gst_query_new_uri (); + if (gst_pad_peer_query (self->sinkpad, query)) { + GList *assets, *tmp; + + GST_OBJECT_LOCK (self); + g_free (self->upstream_uri); + gst_query_parse_uri (query, &self->upstream_uri); + if (gst_uri_has_protocol (self->upstream_uri, "file")) { + gchar *location = gst_uri_get_location (self->upstream_uri); + + if (g_stat (location, &self->stats) < 0) + GST_INFO_OBJECT (self, "Could not stat file: %s", location); + g_free (location); + } + + assets = ges_project_list_assets (project, GES_TYPE_URI_CLIP); + for (tmp = assets; tmp; tmp = tmp->next) { + const gchar *id = ges_asset_get_id (tmp->data); + + if (!g_strcmp0 (id, self->upstream_uri)) { + g_set_error (error, GST_STREAM_ERROR, GST_STREAM_ERROR_DEMUX, + "Recursively loading uri: %s", self->upstream_uri); + break; + } + } + GST_OBJECT_UNLOCK (self); + g_list_free_full (assets, g_object_unref); + } + +done: + if (data.loaded_sigid) + g_signal_handler_disconnect (project, data.loaded_sigid); + + if (data.error_sigid) + g_signal_handler_disconnect (project, data.error_sigid); + + if (data.error_asset_sigid) + g_signal_handler_disconnect (project, data.error_asset_sigid); + + g_clear_object (&project); + + GST_INFO_OBJECT (self, "Timeline properly loaded: %" GST_PTR_FORMAT, + data.timeline); + + if (!data.error) { + ges_base_bin_set_timeline (GES_BASE_BIN (self), data.timeline); + gst_element_foreach_src_pad (GST_ELEMENT (self), ges_demux_set_srcpad_probe, + NULL); + } else { + *error = data.error; + } + + g_main_context_pop_thread_default (ctx); + + return G_SOURCE_REMOVE; +} + +static gboolean +ges_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) +{ + GESDemux *self = GES_DEMUX (parent); + + switch (event->type) { + case GST_EVENT_EOS:{ + GstMapInfo map; + GstBuffer *xges_buffer; + gboolean ret = TRUE; + gsize available; + + available = gst_adapter_available (self->input_adapter); + if (available == 0) { + GST_WARNING_OBJECT (self, + "Received EOS without any serialized timeline."); + + return gst_pad_event_default (pad, parent, event); + } + + xges_buffer = gst_adapter_take_buffer (self->input_adapter, available); + if (gst_buffer_map (xges_buffer, &map, GST_MAP_READ)) { + gint f; + GError *err = NULL; + gchar *template = NULL; + gchar *filename = NULL, *uri = NULL; + GstCaps *caps = gst_pad_get_current_caps (pad); + GstStructure *structure = gst_caps_get_structure (caps, 0); + gchar *ext = ges_demux_get_extension (structure); + + gst_caps_unref (caps); + if (ext) { + template = g_strdup_printf ("XXXXXX.%s", ext); + g_free (ext); + } + + f = g_file_open_tmp (template, &filename, &err); + g_free (template); + + if (err) { + GST_ELEMENT_ERROR (self, RESOURCE, OPEN_WRITE, + ("Could not open temporary file to write timeline description"), + ("%s", err->message)); + + goto error; + } + + g_file_set_contents (filename, (gchar *) map.data, map.size, &err); + if (err) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Could not write temporary timeline description file"), + ("%s", err->message)); + + goto error; + } + + uri = gst_filename_to_uri (filename, NULL); + GST_INFO_OBJECT (self, "Pre loading the timeline."); + + ges_demux_create_timeline (self, uri, &err); + if (err) + goto error; + + done: + g_free (filename); + g_free (uri); + g_close (f, NULL); + return ret; + + error: + ret = FALSE; + gst_element_post_message (GST_ELEMENT (self), + gst_message_new_error (parent, err, + "Could not create timeline from description")); + g_clear_error (&err); + + goto done; + } else { + GST_ELEMENT_ERROR (self, RESOURCE, READ, + ("Could not map buffer containing timeline description"), + ("Not info")); + } + } + default: + break; + } + + return gst_pad_event_default (pad, parent, event); +} + +static GstFlowReturn +ges_demux_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) +{ + GESDemux *self = GES_DEMUX (parent); + + gst_adapter_push (self->input_adapter, buffer); + + GST_INFO_OBJECT (self, "Received buffer, total size is %i bytes", + (gint) gst_adapter_available (self->input_adapter)); + + return GST_FLOW_OK; +} + +static void +ges_demux_init (GESDemux * self) +{ + SUPRESS_UNUSED_WARNING (GES_DEMUX); + SUPRESS_UNUSED_WARNING (GES_IS_DEMUX); +#if defined(g_autoptr) + SUPRESS_UNUSED_WARNING (glib_autoptr_cleanup_GESDemux); +#endif + + self->sinkpad = + gst_pad_new_from_template (gst_element_get_pad_template (GST_ELEMENT + (self), "sink"), "sink"); + gst_element_add_pad (GST_ELEMENT (self), self->sinkpad); + + self->input_adapter = gst_adapter_new (); + + gst_pad_set_chain_function (self->sinkpad, + GST_DEBUG_FUNCPTR (ges_demux_sink_chain)); + + gst_pad_set_event_function (self->sinkpad, + GST_DEBUG_FUNCPTR (ges_demux_sink_event)); +} diff --git a/plugins/ges/gesplugin.c b/plugins/ges/gesplugin.c new file mode 100644 index 0000000000..fe5d27e196 --- /dev/null +++ b/plugins/ges/gesplugin.c @@ -0,0 +1,53 @@ +/* GStreamer GES plugin + * + * Copyright (C) 2015 Thibault Saunier <thibault.saunier@collabora.com> + * + * gstges.c + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> + +extern GType ges_demux_get_type (); +extern GType ges_src_get_type (); + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean res = 1; + + res |= + gst_element_register (plugin, "gessrc", GST_RANK_NONE, + ges_src_get_type ()); + + res |= gst_element_register (plugin, "gesdemux", GST_RANK_PRIMARY, + ges_demux_get_type ()); + + return res; +} + +/* plugin export resolution */ +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + ges, + "GStreamer Editing Services Plugin", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/plugins/ges/gessrc.c b/plugins/ges/gessrc.c new file mode 100644 index 0000000000..3e5cae8cb0 --- /dev/null +++ b/plugins/ges/gessrc.c @@ -0,0 +1,150 @@ +/* GStreamer Editing Services GStreamer plugin + * Copyright (C) 2019 Thibault Saunier <tsaunier@igalia.com> + * + * gessrc.c + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + + ** + * SECTION:element-gessrc + * @short_description: A GstBin subclasses use to use GESTimeline + * as sources inside any GstPipeline. + * @see_also: #GESTimeline + * + * The gessrc is a bin that will simply expose the track src pads + * and implements the GstUriHandler interface using a custom `ges://` + * uri scheme. + * + * NOTE: That to use it inside playbin and friends you **need** to + * set gessrc::timeline property yourself. + * + * Example with #playbin: + * + * {{../../examples/c/gessrc.c}} + * + * Example with #GstPlayer: + * + * {{../../examples/python/gst-player.py}} + **/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> +#include <ges/ges.h> + +#include "gesbasebin.h" + +GST_DEBUG_CATEGORY_STATIC (gessrc); +#define GST_CAT_DEFAULT gessrc + +G_DECLARE_FINAL_TYPE (GESSrc, ges_src, GES, SRC, GESBaseBin); +struct _GESSrc +{ + GESBaseBin parent; +}; +#define GES_SRC(obj) ((GESSrc*) obj) + +/*** GSTURIHANDLER INTERFACE *************************************************/ + +static GstURIType +ges_src_uri_get_type (GType type) +{ + return GST_URI_SRC; +} + +static const gchar *const * +ges_src_uri_get_protocols (GType type) +{ + static const gchar *protocols[] = { "ges", NULL }; + + return protocols; +} + +static gchar * +ges_src_uri_get_uri (GstURIHandler * handler) +{ + GESSrc *self = GES_SRC (handler); + GESTimeline *timeline = ges_base_bin_get_timeline (GES_BASE_BIN (self)); + + return ges_command_line_formatter_get_timeline_uri (timeline); +} + +static gboolean +ges_src_uri_set_uri (GstURIHandler * handler, const gchar * uristr, + GError ** error) +{ + gboolean res = FALSE; + GstUri *uri = gst_uri_from_string (uristr); + GESProject *project = NULL; + GESTimeline *timeline = NULL; + + if (!gst_uri_get_path (uri)) { + GST_INFO_OBJECT (handler, "User need to specify the timeline"); + res = TRUE; + goto done; + } + + project = ges_project_new (uristr); + timeline = (GESTimeline *) ges_asset_extract (GES_ASSET (project), NULL); + + if (timeline) + res = ges_base_bin_set_timeline (GES_BASE_BIN (handler), timeline); + +done: + gst_uri_unref (uri); + gst_clear_object (&project); + + return res; +} + +static void +ges_src_uri_handler_init (gpointer g_iface, gpointer iface_data) +{ + GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; + + iface->get_type = ges_src_uri_get_type; + iface->get_protocols = ges_src_uri_get_protocols; + iface->get_uri = ges_src_uri_get_uri; + iface->set_uri = ges_src_uri_set_uri; +} + +G_DEFINE_TYPE_WITH_CODE (GESSrc, ges_src, ges_base_bin_get_type (), + G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, ges_src_uri_handler_init)); + +static void +ges_src_class_init (GESSrcClass * self_class) +{ + GstElementClass *gstelement_klass = GST_ELEMENT_CLASS (self_class); + + GST_DEBUG_CATEGORY_INIT (gessrc, "gessrc", 0, "ges src element"); + gst_element_class_set_static_metadata (gstelement_klass, + "GStreamer Editing Services based 'source'", + "Codec/Source/Editing", + "Source for GESTimeline.", "Thibault Saunier <tsaunier@igalia.com"); +} + +static void +ges_src_init (GESSrc * self) +{ + SUPRESS_UNUSED_WARNING (GES_SRC); + SUPRESS_UNUSED_WARNING (GES_IS_SRC); +#if defined(g_autoptr) + SUPRESS_UNUSED_WARNING (glib_autoptr_cleanup_GESSrc); +#endif +} diff --git a/plugins/ges/meson.build b/plugins/ges/meson.build new file mode 100644 index 0000000000..14a17129dd --- /dev/null +++ b/plugins/ges/meson.build @@ -0,0 +1,11 @@ +gstges_sources = ['gesplugin.c', 'gessrc.c', 'gesdemux.c', 'gesbasebin.c'] + +gstges = library('gstges', gstges_sources, + dependencies : [gst_dep, ges_dep], + include_directories: [configinc], + c_args : ges_c_args, + install : true, + install_dir : plugins_install_dir, +) +pkgconfig.generate(gstges, install_dir : plugins_pkgconfig_install_dir) +plugins += [gstges] \ No newline at end of file diff --git a/plugins/meson.build b/plugins/meson.build new file mode 100644 index 0000000000..7190450136 --- /dev/null +++ b/plugins/meson.build @@ -0,0 +1,3 @@ +plugins = [] +subdir('nle') +subdir('ges') \ No newline at end of file diff --git a/plugins/nle/gnlmarshal.list b/plugins/nle/gnlmarshal.list new file mode 100644 index 0000000000..67c639461a --- /dev/null +++ b/plugins/nle/gnlmarshal.list @@ -0,0 +1,2 @@ +VOID:OBJECT,UINT + diff --git a/plugins/nle/gstnle.c b/plugins/nle/gstnle.c new file mode 100644 index 0000000000..df32330ae3 --- /dev/null +++ b/plugins/nle/gstnle.c @@ -0,0 +1,66 @@ +/* Non Linear Engine plugin + * + * Copyright (C) 2015 Thibault Saunier <thibault.saunier@collabora.com> + * + * gstnle.c + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> +#include "nle.h" + +struct _elements_entry +{ + const gchar *name; + GType (*type) (void); +}; + +static struct _elements_entry _elements[] = { + {"nlesource", nle_source_get_type}, + {"nlecomposition", nle_composition_get_type}, + {"nleoperation", nle_operation_get_type}, + {"nleurisource", nle_urisource_get_type}, + {NULL, 0} +}; + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gint i = 0; + + for (; _elements[i].name; i++) + if (!(gst_element_register (plugin, + _elements[i].name, GST_RANK_NONE, (_elements[i].type) ()))) + return FALSE; + + nle_init_ghostpad_category (); + + return TRUE; +} + +/* plugin export resolution */ +GST_PLUGIN_DEFINE + (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + nle, + "GStreamer Non Linear Engine", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/plugins/nle/meson.build b/plugins/nle/meson.build new file mode 100644 index 0000000000..74f0938e83 --- /dev/null +++ b/plugins/nle/meson.build @@ -0,0 +1,18 @@ +nle_sources = ['nleobject.c', + 'nlecomposition.c', + 'nleghostpad.c', + 'nleoperation.c', + 'nlesource.c', + 'nleurisource.c', + 'gstnle.c' +] + +nle = library('gstnle', nle_sources, + dependencies : [gst_dep, gstbase_dep], + include_directories: [configinc], + c_args : ges_c_args, + install : true, + install_dir : plugins_install_dir, +) +pkgconfig.generate(nle, install_dir : plugins_pkgconfig_install_dir) +plugins += [nle] diff --git a/plugins/nle/nle.h b/plugins/nle/nle.h new file mode 100644 index 0000000000..6e00eae474 --- /dev/null +++ b/plugins/nle/nle.h @@ -0,0 +1,36 @@ +/* Gnonlin + * Copyright (C) <2001> Wim Taymans <wim.taymans@gmail.com> + * <2004-2008> Edward Hervey <bilboed@bilboed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __NLE_H__ +#define __NLE_H__ + +#include <gst/gst.h> + +#include "nletypes.h" + +#include "nleobject.h" +#include "nleghostpad.h" +#include "nlesource.h" +#include "nlecomposition.h" +#include "nleoperation.h" + +#include "nleurisource.h" + +#endif /* __GST_H__ */ diff --git a/plugins/nle/nlecomposition.c b/plugins/nle/nlecomposition.c new file mode 100644 index 0000000000..97fa1b9ec2 --- /dev/null +++ b/plugins/nle/nlecomposition.c @@ -0,0 +1,3598 @@ +/* GStreamer + * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com> + * 2004-2008 Edward Hervey <bilboed@bilboed.com> + * 2014 Mathieu Duponchelle <mathieu.duponchelle@opencreed.com> + * 2014 Thibault Saunier <tsaunier@gnome.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "nle.h" + +/** + * SECTION:element-nlecomposition + * + * A NleComposition contains NleObjects such as NleSources and NleOperations, + * and connects them dynamically to create a composition timeline. + */ + +static GstStaticPadTemplate nle_composition_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC (nlecomposition_debug); +#define GST_CAT_DEFAULT nlecomposition_debug + +#define _do_init \ + GST_DEBUG_CATEGORY_INIT (nlecomposition_debug,"nlecomposition", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "NLE Composition"); +#define nle_composition_parent_class parent_class + +enum +{ + PROP_0, + PROP_ID, + PROP_DROP_TAGS, + PROP_LAST, +}; + +#define DEFAULT_DROP_TAGS TRUE + +/* Properties from NleObject */ +enum +{ + NLEOBJECT_PROP_START, + NLEOBJECT_PROP_STOP, + NLEOBJECT_PROP_DURATION, + NLEOBJECT_PROP_LAST +}; + +enum +{ + COMMIT_SIGNAL, + COMMITED_SIGNAL, + LAST_SIGNAL +}; + +typedef enum +{ + COMP_UPDATE_STACK_INITIALIZE, + COMP_UPDATE_STACK_ON_COMMIT, + COMP_UPDATE_STACK_ON_EOS, + COMP_UPDATE_STACK_ON_SEEK, + COMP_UPDATE_STACK_NONE +} NleUpdateStackReason; + +static const char *UPDATE_PIPELINE_REASONS[] = { + "Initialize", "Commit", "EOS", "Seek", "None" +}; + +typedef struct +{ + NleComposition *comp; + GstEvent *event; +} SeekData; + +typedef struct +{ + NleComposition *comp; + NleObject *object; +} ChildIOData; + +typedef struct +{ + NleComposition *comp; + gint32 seqnum; + + NleUpdateStackReason reason; +} UpdateCompositionData; + +typedef struct _Action +{ + GCClosure closure; + gint priority; +} Action; + +struct _NleCompositionPrivate +{ + gboolean dispose_has_run; + + /* + Sorted List of NleObjects , ThreadSafe + objects_start : sorted by start-time then priority + objects_stop : sorted by stop-time then priority + objects_hash : contains all controlled objects + + Those list should be manipulated exclusively in the main context + or while the task is totally stopped. + */ + GList *objects_start; + GList *objects_stop; + GHashTable *objects_hash; + + /* List of NleObject to be inserted or removed from the composition on the + * next commit */ + GHashTable *pending_io; + + gulong ghosteventprobe; + + /* current stack, list of NleObject* */ + GNode *current; + + /* List of NleObject whose start/duration will be the same as the composition */ + GList *expandables; + + /* currently configured stack seek start/stop time. + * In forward playback: + * - current_stack_start: The start of the current stack or the start value + * of the seek if the stack has been seeked 'in the middle' + * - current_stack_stop: The stop time of the current stack + * + * Reconstruct pipeline ONLY if seeking outside of those values + * FIXME : current_stack_start isn't always the earliest time before which the + * timeline doesn't need to be modified + */ + GstClockTime current_stack_start; + GstClockTime current_stack_stop; + + /* Seek segment handler */ + /* Represents the current segment that is being played, + * In forwards playback (logic is the same but swapping start and + * stop in backward playback): + * - segment->start: start of the current segment being played, + * at each stack change it will advance to the newly configured + * stack start. + * - segment->stop is the final stop of the segment being played. + * if a seek with a stop time happened, it will be the stop time + * otherwise, it will be the composition duration. + */ + GstSegment *segment; + + /* Segment representing the last seek. Simply initialized + * segment if no seek occured. */ + GstSegment *seek_segment; + guint64 next_base_time; + + /* + OUR sync_handler on the child_bus + We are called before nle_object_sync_handler + */ + GstPadEventFunction nle_event_pad_func; + gboolean send_stream_start; + + /* Protect the actions list */ + GMutex actions_lock; + GCond actions_cond; + GList *actions; + Action *current_action; + + gboolean running; + gboolean initialized; + + GstElement *current_bin; + + gboolean seeking_itself; + gint real_eos_seqnum; + gint next_eos_seqnum; + guint32 flush_seqnum; + + /* 0 means that we already received the right caps or segment */ + gint seqnum_to_restart_task; + gboolean waiting_serialized_query_or_buffer; + GstEvent *stack_initialization_seek; + gboolean stack_initialization_seek_sent; + + gboolean tearing_down_stack; + gboolean suppress_child_error; + + NleUpdateStackReason updating_reason; + + guint seek_seqnum; + + /* Both protected with object lock */ + gchar *id; + gboolean drop_tags; +}; + +#define ACTION_CALLBACK(__action) (((GCClosure*) (__action))->callback) + +static guint _signals[LAST_SIGNAL] = { 0 }; + +static GParamSpec *nleobject_properties[NLEOBJECT_PROP_LAST]; +static GParamSpec *properties[PROP_LAST]; + +G_DEFINE_TYPE_WITH_CODE (NleComposition, nle_composition, NLE_TYPE_OBJECT, + G_ADD_PRIVATE (NleComposition) + _do_init); + +#define OBJECT_IN_ACTIVE_SEGMENT(comp,element) \ + ((NLE_OBJECT_START(element) < comp->priv->current_stack_stop) && \ + (NLE_OBJECT_STOP(element) >= comp->priv->current_stack_start)) + +static void nle_composition_dispose (GObject * object); +static void nle_composition_finalize (GObject * object); +static void nle_composition_reset (NleComposition * comp); + +static gboolean nle_composition_add_object (GstBin * bin, GstElement * element); + +static gboolean +nle_composition_remove_object (GstBin * bin, GstElement * element); + +static GstStateChangeReturn +nle_composition_change_state (GstElement * element, GstStateChange transition); + +static inline void nle_composition_reset_target_pad (NleComposition * comp); + +static gboolean +seek_handling (NleComposition * comp, gint32 seqnum, + NleUpdateStackReason update_stack_reason); +static gint objects_start_compare (NleObject * a, NleObject * b); +static gint objects_stop_compare (NleObject * a, NleObject * b); +static GstClockTime get_current_position (NleComposition * comp); + +static gboolean update_pipeline (NleComposition * comp, + GstClockTime currenttime, gint32 seqnum, + NleUpdateStackReason update_stack_reason); +static gboolean nle_composition_commit_func (NleObject * object, + gboolean recurse); +static void update_start_stop_duration (NleComposition * comp); + +static gboolean +nle_composition_event_handler (GstPad * ghostpad, GstObject * parent, + GstEvent * event); +static void _relink_single_node (NleComposition * comp, GNode * node, + GstEvent * toplevel_seek); +static void _update_pipeline_func (NleComposition * comp, + UpdateCompositionData * ucompo); +static void _commit_func (NleComposition * comp, + UpdateCompositionData * ucompo); +static GstEvent *get_new_seek_event (NleComposition * comp, gboolean initial, + gboolean updatestoponly, NleUpdateStackReason reason); +static gboolean _nle_composition_add_object (NleComposition * comp, + NleObject * object); +static gboolean _nle_composition_remove_object (NleComposition * comp, + NleObject * object); +static void _deactivate_stack (NleComposition * comp, + NleUpdateStackReason reason); +static void _set_real_eos_seqnum_from_seek (NleComposition * comp, + GstEvent * event); +static void _emit_commited_signal_func (NleComposition * comp, gpointer udata); +static void _restart_task (NleComposition * comp); +static void +_add_action (NleComposition * comp, GCallback func, gpointer data, + gint priority); +static gboolean +_is_ready_to_restart_task (NleComposition * comp, GstEvent * event); + + +/* COMP_REAL_START: actual position to start current playback at. */ +#define COMP_REAL_START(comp) \ + (MAX (comp->priv->segment->start, NLE_OBJECT_START (comp))) + +#define COMP_REAL_STOP(comp) \ + (GST_CLOCK_TIME_IS_VALID (comp->priv->segment->stop) ? \ + (MIN (comp->priv->segment->stop, NLE_OBJECT_STOP (comp))) : \ + NLE_OBJECT_STOP (comp)) + +#define ACTIONS_LOCK(comp) G_STMT_START { \ + GST_LOG_OBJECT (comp, "Getting ACTIONS_LOCK in thread %p", \ + g_thread_self()); \ + g_mutex_lock(&((NleComposition*)comp)->priv->actions_lock); \ + GST_LOG_OBJECT (comp, "Got ACTIONS_LOCK in thread %p", \ + g_thread_self()); \ +} G_STMT_END + +#define ACTIONS_UNLOCK(comp) G_STMT_START { \ + g_mutex_unlock(&((NleComposition*)comp)->priv->actions_lock); \ + GST_LOG_OBJECT (comp, "Unlocked ACTIONS_LOCK in thread %p", \ + g_thread_self()); \ +} G_STMT_END + +#define WAIT_FOR_AN_ACTION(comp) G_STMT_START { \ + GST_LOG_OBJECT (comp, "Waiting for an action in thread %p", \ + g_thread_self()); \ + g_cond_wait(&((NleComposition*)comp)->priv->actions_cond, \ + &((NleComposition*)comp)->priv->actions_lock); \ + GST_LOG_OBJECT (comp, "Done WAITING for an action in thread %p", \ + g_thread_self()); \ +} G_STMT_END + +#define SIGNAL_NEW_ACTION(comp) G_STMT_START { \ + GST_LOG_OBJECT (comp, "Signalling new action from thread %p", \ + g_thread_self()); \ + g_cond_signal(&((NleComposition*)comp)->priv->actions_cond); \ +} G_STMT_END + +#define GET_TASK_LOCK(comp) (&(NLE_COMPOSITION(comp)->task_rec_lock)) + +static inline gboolean +_have_to_flush_downstream (NleUpdateStackReason update_reason) +{ + if (update_reason == COMP_UPDATE_STACK_ON_COMMIT || + update_reason == COMP_UPDATE_STACK_ON_SEEK || + update_reason == COMP_UPDATE_STACK_INITIALIZE) + return TRUE; + + return FALSE; +} + +static void +_assert_proper_thread (NleComposition * comp) +{ + if (comp->task && gst_task_get_state (comp->task) != GST_TASK_STOPPED && + g_thread_self () != comp->task->thread) { + g_warning ("Trying to touch children in a thread different from" + " its dedicated thread!"); + } +} + +static void +_remove_actions_for_type (NleComposition * comp, GCallback callback) +{ + GList *tmp; + + ACTIONS_LOCK (comp); + + GST_LOG_OBJECT (comp, "finding action[callback=%s], action count = %d", + GST_DEBUG_FUNCPTR_NAME (callback), g_list_length (comp->priv->actions)); + tmp = g_list_first (comp->priv->actions); + while (tmp != NULL) { + Action *act = tmp->data; + GList *removed = NULL; + + if (ACTION_CALLBACK (act) == callback) { + GST_LOG_OBJECT (comp, "remove action for callback %s", + GST_DEBUG_FUNCPTR_NAME (callback)); + removed = tmp; + g_closure_unref ((GClosure *) act); + comp->priv->actions = g_list_remove_link (comp->priv->actions, removed); + } + + tmp = g_list_next (tmp); + if (removed) + g_list_free (removed); + } + + ACTIONS_UNLOCK (comp); +} + +static void +_execute_actions (NleComposition * comp) +{ + NleCompositionPrivate *priv = comp->priv; + + ACTIONS_LOCK (comp); + if (priv->running == FALSE) { + GST_DEBUG_OBJECT (comp, "Not running anymore"); + + ACTIONS_UNLOCK (comp); + return; + } + + if (priv->actions == NULL) + WAIT_FOR_AN_ACTION (comp); + + if (comp->priv->running == FALSE) { + GST_INFO_OBJECT (comp, "Done waiting but not running anymore"); + + ACTIONS_UNLOCK (comp); + return; + } + + if (priv->actions) { + GValue params[1] = { G_VALUE_INIT }; + GList *lact; + + GST_LOG_OBJECT (comp, "scheduled actions [%d]", + g_list_length (priv->actions)); + + g_value_init (¶ms[0], G_TYPE_OBJECT); + g_value_set_object (¶ms[0], comp); + + lact = g_list_first (priv->actions); + priv->actions = g_list_remove_link (priv->actions, lact); + priv->current_action = lact->data; + ACTIONS_UNLOCK (comp); + + GST_INFO_OBJECT (comp, "Invoking %p:%s", + lact->data, GST_DEBUG_FUNCPTR_NAME ((ACTION_CALLBACK (lact->data)))); + g_closure_invoke (lact->data, NULL, 1, params, NULL); + g_value_unset (¶ms[0]); + + ACTIONS_LOCK (comp); + g_closure_unref (lact->data); + g_list_free (lact); + priv->current_action = NULL; + ACTIONS_UNLOCK (comp); + + GST_LOG_OBJECT (comp, "remaining actions [%d]", + g_list_length (priv->actions)); + } else { + ACTIONS_UNLOCK (comp); + } +} + +static void +_start_task (NleComposition * comp) +{ + GstTask *task; + + ACTIONS_LOCK (comp); + comp->priv->running = TRUE; + ACTIONS_UNLOCK (comp); + + GST_OBJECT_LOCK (comp); + + task = comp->task; + if (task == NULL) { + gchar *taskname = + g_strdup_printf ("%s_update_management", GST_OBJECT_NAME (comp)); + + task = gst_task_new ((GstTaskFunction) _execute_actions, comp, NULL); + gst_object_set_name (GST_OBJECT_CAST (task), taskname); + gst_task_set_lock (task, GET_TASK_LOCK (comp)); + GST_DEBUG_OBJECT (comp, "created task %p", task); + comp->task = task; + gst_object_set_parent (GST_OBJECT (task), GST_OBJECT (comp)); + gst_object_unref (task); + g_free (taskname); + } + + gst_task_set_state (task, GST_TASK_STARTED); + GST_OBJECT_UNLOCK (comp); +} + +static gboolean +_pause_task (NleComposition * comp) +{ + GST_OBJECT_LOCK (comp); + if (comp->task == NULL) { + GST_INFO_OBJECT (comp, "No task set, it must have been stopped, returning"); + GST_OBJECT_UNLOCK (comp); + return FALSE; + } + + gst_task_pause (comp->task); + GST_OBJECT_UNLOCK (comp); + + return TRUE; +} + +static gboolean +_stop_task (NleComposition * comp) +{ + gboolean res = TRUE; + GstTask *task; + + GST_INFO_OBJECT (comp, "Stoping children management task"); + + ACTIONS_LOCK (comp); + comp->priv->running = FALSE; + + /* Make sure we do not stay blocked trying to execute an action */ + SIGNAL_NEW_ACTION (comp); + ACTIONS_UNLOCK (comp); + + GST_DEBUG_OBJECT (comp, "stop task"); + + GST_OBJECT_LOCK (comp); + task = comp->task; + if (task == NULL) + goto no_task; + comp->task = NULL; + res = gst_task_set_state (task, GST_TASK_STOPPED); + GST_OBJECT_UNLOCK (comp); + + if (!gst_task_join (task)) + goto join_failed; + + gst_object_unparent (GST_OBJECT (task)); + + return res; + +no_task: + { + GST_OBJECT_UNLOCK (comp); + + /* this is not an error */ + return TRUE; + } +join_failed: + { + /* this is bad, possibly the application tried to join the task from + * the task's thread. We install the task again so that it will be stopped + * again from the right thread next time hopefully. */ + GST_OBJECT_LOCK (comp); + GST_DEBUG_OBJECT (comp, "join failed"); + /* we can only install this task if there was no other task */ + if (comp->task == NULL) + comp->task = task; + GST_OBJECT_UNLOCK (comp); + + return FALSE; + } + + return res; +} + +static void +_post_start_composition_update (NleComposition * comp, + gint32 seqnum, NleUpdateStackReason reason) +{ + GstMessage *msg; + + msg = gst_message_new_element (GST_OBJECT (comp), + gst_structure_new ("NleCompositionStartUpdate", + "reason", G_TYPE_STRING, UPDATE_PIPELINE_REASONS[reason], NULL)); + + gst_message_set_seqnum (msg, seqnum); + gst_element_post_message (GST_ELEMENT (comp), msg); +} + +static void +_post_start_composition_update_done (NleComposition * comp, + gint32 seqnum, NleUpdateStackReason reason) +{ + GstMessage *msg = gst_message_new_element (GST_OBJECT (comp), + gst_structure_new ("NleCompositionUpdateDone", + "reason", G_TYPE_STRING, UPDATE_PIPELINE_REASONS[reason], + NULL)); + + gst_message_set_seqnum (msg, seqnum); + gst_element_post_message (GST_ELEMENT (comp), msg); +} + +static void +_seek_pipeline_func (NleComposition * comp, SeekData * seekd) +{ + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType cur_type, stop_type; + gint64 cur, stop; + NleCompositionPrivate *priv = comp->priv; + gboolean initializing_stack = priv->stack_initialization_seek == seekd->event; + NleUpdateStackReason reason = + initializing_stack ? COMP_UPDATE_STACK_NONE : COMP_UPDATE_STACK_ON_SEEK; + GstClockTime segment_start, segment_stop; + gboolean reverse; + + gst_event_parse_seek (seekd->event, &rate, &format, &flags, + &cur_type, &cur, &stop_type, &stop); + + reverse = rate < 0; + + GST_DEBUG_OBJECT (seekd->comp, + "start:%" GST_TIME_FORMAT " -- stop:%" GST_TIME_FORMAT " flags:%d", + GST_TIME_ARGS (cur), GST_TIME_ARGS (stop), flags); + + if (!initializing_stack) { + segment_start = cur; + segment_stop = stop; + } else { + /* During plain playback (no seek), the segment->stop doesn't + * evolve when going from stack to stack, only the start does + * (in reverse playback, the logic is reversed) */ + segment_start = reverse ? priv->segment->start : cur; + segment_stop = reverse ? stop : priv->segment->stop; + } + + gst_segment_do_seek (priv->segment, + rate, format, flags, cur_type, segment_start, stop_type, segment_stop, + NULL); + + gst_segment_do_seek (priv->seek_segment, + rate, format, flags, cur_type, cur, stop_type, stop, NULL); + + GST_DEBUG_OBJECT (seekd->comp, "Segment now has flags:%d", + priv->segment->flags); + + /* FIXME: The idea was to avoid seeking on a stack if we know we will endup + * passed the end, but then we loose the flush, wich leads to hangs. Long + * term, we should just flush the stack instead to avoid the double seek. */ +#if 0 + if (priv->segment->start >= NLE_OBJECT_STOP (seekd->comp)) { + GST_INFO_OBJECT (seekd->comp, + "Start %" GST_TIME_FORMAT " > comp->stop: %" GST_TIME_FORMAT + " Not seeking", GST_TIME_ARGS (priv->segment->start), + GST_TIME_ARGS (NLE_OBJECT_STOP (seekd->comp))); + GST_FIXME_OBJECT (seekd->comp, "HANDLE error async!"); + return; + } +#endif + + if (!initializing_stack) + _post_start_composition_update (seekd->comp, + gst_event_get_seqnum (seekd->event), COMP_UPDATE_STACK_ON_SEEK); + + /* crop the segment start/stop values */ + /* Only crop segment start value if we don't have a default object */ + if (priv->expandables == NULL) + priv->segment->start = + MAX (priv->segment->start, NLE_OBJECT_START (seekd->comp)); + priv->segment->stop = + MIN (priv->segment->stop, NLE_OBJECT_STOP (seekd->comp)); + + + if (initializing_stack) { + GST_INFO_OBJECT (seekd->comp, "Pausing task to run initializing seek."); + _pause_task (seekd->comp); + } else { + priv->next_base_time = 0; + comp->priv->flush_seqnum = comp->priv->seek_seqnum = + gst_event_get_seqnum (seekd->event); + } + + seek_handling (seekd->comp, gst_event_get_seqnum (seekd->event), reason); + + if (!initializing_stack) + _post_start_composition_update_done (seekd->comp, + gst_event_get_seqnum (seekd->event), COMP_UPDATE_STACK_ON_SEEK); +} + +/* Must be called with OBJECTS_LOCK taken */ +static void +_process_pending_entries (NleComposition * comp, NleUpdateStackReason reason) +{ + NleObject *object; + GHashTableIter iter; + gboolean deactivated_stack = FALSE; + + NleCompositionPrivate *priv = comp->priv; + + g_hash_table_iter_init (&iter, priv->pending_io); + while (g_hash_table_iter_next (&iter, (gpointer *) & object, NULL)) { + if (g_hash_table_contains (priv->objects_hash, object)) { + + if (GST_OBJECT_PARENT (object) == GST_OBJECT_CAST (priv->current_bin) && + deactivated_stack == FALSE) { + deactivated_stack = TRUE; + + _deactivate_stack (comp, reason); + } + + _nle_composition_remove_object (comp, object); + } else { + /* take a new ref on object as the current one will be released when + * object is removed from pending_io */ + _nle_composition_add_object (comp, gst_object_ref (object)); + } + } + + g_hash_table_remove_all (priv->pending_io); +} + + +static inline gboolean +_commit_values (NleComposition * comp) +{ + GList *tmp; + gboolean commited = FALSE; + NleCompositionPrivate *priv = comp->priv; + + for (tmp = priv->objects_start; tmp; tmp = tmp->next) { + if (nle_object_commit (tmp->data, TRUE)) + commited = TRUE; + } + + GST_DEBUG_OBJECT (comp, "Linking up commit vmethod"); + commited |= NLE_OBJECT_CLASS (parent_class)->commit (NLE_OBJECT (comp), TRUE); + + return commited; +} + +static gboolean +_commit_all_values (NleComposition * comp, NleUpdateStackReason reason) +{ + NleCompositionPrivate *priv = comp->priv; + + priv->next_base_time = 0; + + _process_pending_entries (comp, reason); + + if (_commit_values (comp) == FALSE) { + + return FALSE;; + } + + /* The topology of the composition might have changed, update the lists */ + priv->objects_start = g_list_sort + (priv->objects_start, (GCompareFunc) objects_start_compare); + priv->objects_stop = g_list_sort + (priv->objects_stop, (GCompareFunc) objects_stop_compare); + + return TRUE; +} + +static gboolean +_initialize_stack_func (NleComposition * comp, UpdateCompositionData * ucompo) +{ + NleCompositionPrivate *priv = comp->priv; + + + _post_start_composition_update (comp, ucompo->seqnum, ucompo->reason); + + _commit_all_values (comp, ucompo->reason); + update_start_stop_duration (comp); + comp->priv->next_base_time = 0; + /* set ghostpad target */ + if (!(update_pipeline (comp, COMP_REAL_START (comp), + ucompo->seqnum, COMP_UPDATE_STACK_INITIALIZE))) { + GST_FIXME_OBJECT (comp, "PLEASE signal state change failure ASYNC"); + } + + _post_start_composition_update_done (comp, ucompo->seqnum, ucompo->reason); + priv->initialized = TRUE; + + return G_SOURCE_REMOVE; +} + +static void +_remove_object_func (NleComposition * comp, ChildIOData * childio) +{ + NleObject *object = childio->object; + + NleCompositionPrivate *priv = comp->priv; + NleObject *in_pending_io; + + in_pending_io = g_hash_table_lookup (priv->pending_io, object); + + if (!g_hash_table_contains (priv->objects_hash, object)) { + if (in_pending_io) { + GST_INFO_OBJECT (comp, "Object %" GST_PTR_FORMAT " was marked" + " for addition, removing it from the addition list", object); + + g_hash_table_remove (priv->pending_io, object); + return; + } + + GST_ERROR_OBJECT (comp, "Object %" GST_PTR_FORMAT " is " + " not in the composition", object); + + return; + } + + if (in_pending_io) { + GST_WARNING_OBJECT (comp, "Object %" GST_PTR_FORMAT " is already marked" + " for removal", object); + + return; + } + + g_hash_table_add (priv->pending_io, gst_object_ref (object)); + + return; +} + +static void +_add_remove_object_action (NleComposition * comp, NleObject * object) +{ + ChildIOData *childio = g_slice_new0 (ChildIOData); + + GST_DEBUG_OBJECT (comp, "Adding Action"); + + childio->comp = comp; + childio->object = object; + + _add_action (comp, G_CALLBACK (_remove_object_func), + childio, G_PRIORITY_DEFAULT); +} + +static void +_add_object_func (NleComposition * comp, ChildIOData * childio) +{ + NleObject *object = childio->object; + NleCompositionPrivate *priv = comp->priv; + NleObject *in_pending_io; + + in_pending_io = g_hash_table_lookup (priv->pending_io, object); + + if (g_hash_table_contains (priv->objects_hash, object)) { + + if (in_pending_io) { + GST_INFO_OBJECT (comp, "Object already in but marked in pendings" + " removing from pendings"); + g_hash_table_remove (priv->pending_io, object); + + return; + } + GST_ERROR_OBJECT (comp, "Object %" GST_PTR_FORMAT " is " + " already in the composition", object); + + return; + } + + if (in_pending_io) { + GST_WARNING_OBJECT (comp, "Object %" GST_PTR_FORMAT " is already marked" + " for addition", object); + + return; + } + + /* current reference is hold by the action and will be released with it, + * so take a new one */ + g_hash_table_add (priv->pending_io, gst_object_ref (object)); +} + +static void +_add_add_object_action (NleComposition * comp, NleObject * object) +{ + ChildIOData *childio = g_slice_new0 (ChildIOData); + + GST_DEBUG_OBJECT (comp, "Adding Action"); + + childio->comp = comp; + childio->object = object; + + _add_action (comp, G_CALLBACK (_add_object_func), childio, + G_PRIORITY_DEFAULT); +} + +static void +_free_action (gpointer udata, Action * action) +{ + GST_LOG ("freeing %p action for %s", action, + GST_DEBUG_FUNCPTR_NAME (ACTION_CALLBACK (action))); + if (ACTION_CALLBACK (action) == _seek_pipeline_func) { + SeekData *seekd = (SeekData *) udata; + + gst_event_unref (seekd->event); + g_slice_free (SeekData, seekd); + } else if (ACTION_CALLBACK (action) == _add_object_func) { + ChildIOData *iodata = (ChildIOData *) udata; + + gst_object_unref (iodata->object); + g_slice_free (ChildIOData, iodata); + } else if (ACTION_CALLBACK (action) == _remove_object_func) { + g_slice_free (ChildIOData, udata); + } else if (ACTION_CALLBACK (action) == _update_pipeline_func || + ACTION_CALLBACK (action) == _commit_func || + ACTION_CALLBACK (action) == _initialize_stack_func) { + g_slice_free (UpdateCompositionData, udata); + } +} + +static void +_add_action_locked (NleComposition * comp, GCallback func, + gpointer data, gint priority) +{ + Action *action; + NleCompositionPrivate *priv = comp->priv; + + action = (Action *) g_closure_new_simple (sizeof (Action), data); + g_closure_add_finalize_notifier ((GClosure *) action, data, + (GClosureNotify) _free_action); + ACTION_CALLBACK (action) = func; + + action->priority = priority; + g_closure_set_marshal ((GClosure *) action, g_cclosure_marshal_VOID__VOID); + + GST_INFO_OBJECT (comp, "Adding Action for function: %p:%s", + action, GST_DEBUG_FUNCPTR_NAME (func)); + + if (priority == G_PRIORITY_HIGH) + priv->actions = g_list_prepend (priv->actions, action); + else + priv->actions = g_list_append (priv->actions, action); + + GST_LOG_OBJECT (comp, "the number of remaining actions: %d", + g_list_length (priv->actions)); + + SIGNAL_NEW_ACTION (comp); +} + +static void +_add_action (NleComposition * comp, GCallback func, + gpointer data, gint priority) +{ + ACTIONS_LOCK (comp); + _add_action_locked (comp, func, data, priority); + ACTIONS_UNLOCK (comp); +} + +static SeekData * +create_seek_data (NleComposition * comp, GstEvent * event) +{ + SeekData *seekd = g_slice_new0 (SeekData); + + seekd->comp = comp; + seekd->event = event; + + return seekd; +} + +static void +_add_seek_action (NleComposition * comp, GstEvent * event) +{ + SeekData *seekd; + GList *tmp; + guint32 seqnum = gst_event_get_seqnum (event); + + ACTIONS_LOCK (comp); + /* Check if this is our current seqnum */ + if (seqnum == comp->priv->next_eos_seqnum) { + GST_DEBUG_OBJECT (comp, "Not adding Action, same seqnum as previous seek"); + ACTIONS_UNLOCK (comp); + return; + } + + /* Check if this seqnum is already queued up but not handled yet */ + for (tmp = comp->priv->actions; tmp != NULL; tmp = tmp->next) { + Action *act = tmp->data; + + if (ACTION_CALLBACK (act) == G_CALLBACK (_seek_pipeline_func)) { + SeekData *tmp_data = ((GClosure *) act)->data; + + if (gst_event_get_seqnum (tmp_data->event) == seqnum) { + GST_DEBUG_OBJECT (comp, + "Not adding Action, same seqnum as previous seek"); + ACTIONS_UNLOCK (comp); + return; + } + } + } + + /* Check if this seqnum is currently being handled */ + if (comp->priv->current_action) { + Action *act = comp->priv->current_action; + if (ACTION_CALLBACK (act) == G_CALLBACK (_seek_pipeline_func)) { + SeekData *tmp_data = ((GClosure *) act)->data; + + if (gst_event_get_seqnum (tmp_data->event) == seqnum) { + GST_DEBUG_OBJECT (comp, + "Not adding Action, same seqnum as previous seek"); + ACTIONS_UNLOCK (comp); + return; + } + } + } + + GST_DEBUG_OBJECT (comp, "Adding seek Action"); + seekd = create_seek_data (comp, event); + + comp->priv->next_eos_seqnum = 0; + comp->priv->real_eos_seqnum = 0; + comp->priv->seek_seqnum = 0; + _add_action_locked (comp, G_CALLBACK (_seek_pipeline_func), seekd, + G_PRIORITY_DEFAULT); + + ACTIONS_UNLOCK (comp); +} + +static void +_remove_update_actions (NleComposition * comp) +{ + _remove_actions_for_type (comp, G_CALLBACK (_update_pipeline_func)); +} + +static void +_remove_seek_actions (NleComposition * comp) +{ + _remove_actions_for_type (comp, G_CALLBACK (_seek_pipeline_func)); +} + +static void +_add_update_compo_action (NleComposition * comp, + GCallback callback, NleUpdateStackReason reason) +{ + UpdateCompositionData *ucompo = g_slice_new0 (UpdateCompositionData); + + ucompo->comp = comp; + ucompo->reason = reason; + ucompo->seqnum = gst_util_seqnum_next (); + + GST_INFO_OBJECT (comp, "Updating because: %s -- Setting seqnum: %i", + UPDATE_PIPELINE_REASONS[reason], ucompo->seqnum); + + _add_action (comp, callback, ucompo, G_PRIORITY_DEFAULT); +} + +static void +nle_composition_handle_message (GstBin * bin, GstMessage * message) +{ + NleComposition *comp = (NleComposition *) bin; + NleCompositionPrivate *priv = comp->priv; + + if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR && + (priv->tearing_down_stack || priv->suppress_child_error)) { + GST_FIXME_OBJECT (comp, "Dropping %" GST_PTR_FORMAT " message from " + " %" GST_PTR_FORMAT " tearing down: %d, suppressing error: %d", + message, GST_MESSAGE_SRC (message), priv->tearing_down_stack, + priv->suppress_child_error); + goto drop; + } else if (comp->priv->tearing_down_stack) { + GST_DEBUG_OBJECT (comp, "Dropping message %" GST_PTR_FORMAT " from " + "object being teared down to READY!", message); + goto drop; + } + + GST_BIN_CLASS (parent_class)->handle_message (bin, message); + + return; + +drop: + gst_message_unref (message); + + return; +} + +static void +nle_composition_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + NleComposition *comp = (NleComposition *) object; + + switch (property_id) { + case PROP_ID: + GST_OBJECT_LOCK (comp); + g_value_set_string (value, comp->priv->id); + GST_OBJECT_UNLOCK (comp); + break; + case PROP_DROP_TAGS: + GST_OBJECT_LOCK (comp); + g_value_set_boolean (value, comp->priv->drop_tags); + GST_OBJECT_UNLOCK (comp); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (comp, property_id, pspec); + } +} + +static void +nle_composition_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + NleComposition *comp = (NleComposition *) object; + + switch (property_id) { + case PROP_ID: + GST_OBJECT_LOCK (comp); + g_free (comp->priv->id); + comp->priv->id = g_value_dup_string (value); + GST_OBJECT_UNLOCK (comp); + break; + case PROP_DROP_TAGS: + GST_OBJECT_LOCK (comp); + comp->priv->drop_tags = g_value_get_boolean (value); + GST_OBJECT_UNLOCK (comp); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (comp, property_id, pspec); + } +} + +static void +nle_composition_class_init (NleCompositionClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBinClass *gstbin_class; + NleObjectClass *nleobject_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbin_class = (GstBinClass *) klass; + nleobject_class = (NleObjectClass *) klass; + + gst_element_class_set_static_metadata (gstelement_class, + "GNonLin Composition", "Filter/Editor", "Combines NLE objects", + "Wim Taymans <wim.taymans@gmail.com>, Edward Hervey <bilboed@bilboed.com>," + " Mathieu Duponchelle <mathieu.duponchelle@opencreed.com>," + " Thibault Saunier <tsaunier@gnome.org>"); + + gobject_class->dispose = GST_DEBUG_FUNCPTR (nle_composition_dispose); + gobject_class->finalize = GST_DEBUG_FUNCPTR (nle_composition_finalize); + gobject_class->get_property = + GST_DEBUG_FUNCPTR (nle_composition_get_property); + gobject_class->set_property = + GST_DEBUG_FUNCPTR (nle_composition_set_property); + + gstelement_class->change_state = nle_composition_change_state; + + gstbin_class->add_element = GST_DEBUG_FUNCPTR (nle_composition_add_object); + gstbin_class->remove_element = + GST_DEBUG_FUNCPTR (nle_composition_remove_object); + gstbin_class->handle_message = + GST_DEBUG_FUNCPTR (nle_composition_handle_message); + + gst_element_class_add_static_pad_template (gstelement_class, + &nle_composition_src_template); + + /* Get the paramspec of the NleObject klass so we can do + * fast notifies */ + nleobject_properties[NLEOBJECT_PROP_START] = + g_object_class_find_property (gobject_class, "start"); + nleobject_properties[NLEOBJECT_PROP_STOP] = + g_object_class_find_property (gobject_class, "stop"); + nleobject_properties[NLEOBJECT_PROP_DURATION] = + g_object_class_find_property (gobject_class, "duration"); + + properties[PROP_ID] = + g_param_spec_string ("id", "Id", "The stream-id of the composition", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_DOC_SHOW_DEFAULT); + + /** + * NleComposition:drop-tags: + * + * Whether the composition should drop tags from its children + * + * Since: 1.20 + */ + properties[PROP_DROP_TAGS] = + g_param_spec_boolean ("drop-tags", "Drop tags", + "Whether the composition should drop tags from its children", + DEFAULT_DROP_TAGS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_DOC_SHOW_DEFAULT | + GST_PARAM_MUTABLE_PLAYING); + g_object_class_install_properties (gobject_class, PROP_LAST, properties); + + _signals[COMMITED_SIGNAL] = + g_signal_new ("commited", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + + GST_DEBUG_REGISTER_FUNCPTR (_seek_pipeline_func); + GST_DEBUG_REGISTER_FUNCPTR (_remove_object_func); + GST_DEBUG_REGISTER_FUNCPTR (_add_object_func); + GST_DEBUG_REGISTER_FUNCPTR (_update_pipeline_func); + GST_DEBUG_REGISTER_FUNCPTR (_commit_func); + GST_DEBUG_REGISTER_FUNCPTR (_emit_commited_signal_func); + GST_DEBUG_REGISTER_FUNCPTR (_initialize_stack_func); + + /* Just be useless, so the compiler does not warn us + * about our uselessness */ + nleobject_class->commit = nle_composition_commit_func; + +} + +static void +nle_composition_init (NleComposition * comp) +{ + NleCompositionPrivate *priv; + + GST_OBJECT_FLAG_SET (comp, NLE_OBJECT_SOURCE); + GST_OBJECT_FLAG_SET (comp, NLE_OBJECT_COMPOSITION); + + priv = nle_composition_get_instance_private (comp); + priv->objects_start = NULL; + priv->objects_stop = NULL; + + priv->segment = gst_segment_new (); + priv->seek_segment = gst_segment_new (); + + g_rec_mutex_init (&comp->task_rec_lock); + + priv->objects_hash = g_hash_table_new (g_direct_hash, g_direct_equal); + + g_mutex_init (&priv->actions_lock); + g_cond_init (&priv->actions_cond); + + priv->pending_io = g_hash_table_new_full (g_direct_hash, g_direct_equal, + gst_object_unref, NULL); + + comp->priv = priv; + + priv->current_bin = gst_bin_new ("current-bin"); + gst_bin_add (GST_BIN (comp), priv->current_bin); + + nle_composition_reset (comp); + + priv->id = gst_pad_create_stream_id (NLE_OBJECT_SRC (comp), + GST_ELEMENT (comp), NULL); + priv->drop_tags = DEFAULT_DROP_TAGS; + priv->nle_event_pad_func = GST_PAD_EVENTFUNC (NLE_OBJECT_SRC (comp)); + gst_pad_set_event_function (NLE_OBJECT_SRC (comp), + GST_DEBUG_FUNCPTR (nle_composition_event_handler)); +} + +static void +_remove_each_nleobj (gpointer data, gpointer udata) +{ + NleComposition *comp = NLE_COMPOSITION (udata); + NleObject *nleobj = NLE_OBJECT (data); + + _nle_composition_remove_object (NLE_COMPOSITION (comp), NLE_OBJECT (nleobj)); +} + +static void +_remove_each_action (gpointer data) +{ + Action *action = (Action *) (data); + + GST_LOG ("remove action %p for %s", action, + GST_DEBUG_FUNCPTR_NAME (ACTION_CALLBACK (action))); + g_closure_invalidate ((GClosure *) action); + g_closure_unref ((GClosure *) action); +} + +static void +nle_composition_dispose (GObject * object) +{ + NleComposition *comp = NLE_COMPOSITION (object); + NleCompositionPrivate *priv = comp->priv; + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + g_list_foreach (priv->objects_start, _remove_each_nleobj, comp); + g_list_free (priv->objects_start); + + g_list_foreach (priv->expandables, _remove_each_nleobj, comp); + g_list_free (priv->expandables); + + g_list_foreach (priv->objects_stop, _remove_each_nleobj, comp); + g_list_free (priv->objects_stop); + + g_list_free_full (priv->actions, (GDestroyNotify) _remove_each_action); + g_clear_object (&priv->stack_initialization_seek); + + nle_composition_reset_target_pad (comp); + + if (priv->pending_io) { + g_hash_table_unref (priv->pending_io); + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +nle_composition_finalize (GObject * object) +{ + NleComposition *comp = NLE_COMPOSITION (object); + NleCompositionPrivate *priv = comp->priv; + + _assert_proper_thread (comp); + + if (priv->current) { + g_node_destroy (priv->current); + priv->current = NULL; + } + + g_hash_table_destroy (priv->objects_hash); + + gst_segment_free (priv->segment); + gst_segment_free (priv->seek_segment); + + g_rec_mutex_clear (&comp->task_rec_lock); + + g_mutex_clear (&priv->actions_lock); + g_cond_clear (&priv->actions_cond); + g_free (priv->id); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/* signal_duration_change + * Creates a new GST_MESSAGE_DURATION_CHANGED with the currently configured + * composition duration and sends that on the bus. + */ +static inline void +signal_duration_change (NleComposition * comp) +{ + gst_element_post_message (GST_ELEMENT_CAST (comp), + gst_message_new_duration_changed (GST_OBJECT_CAST (comp))); +} + +static gboolean +_remove_child (GValue * item, GValue * ret G_GNUC_UNUSED, GstBin * bin) +{ + GstElement *child = g_value_get_object (item); + + if (NLE_IS_OPERATION (child)) + nle_operation_hard_cleanup (NLE_OPERATION (child)); + + + gst_bin_remove (bin, child); + + return TRUE; +} + +static void +_empty_bin (GstBin * bin) +{ + GstIterator *children; + + children = gst_bin_iterate_elements (bin); + + while (G_UNLIKELY (gst_iterator_fold (children, + (GstIteratorFoldFunction) _remove_child, NULL, + bin) == GST_ITERATOR_RESYNC)) { + gst_iterator_resync (children); + } + + gst_iterator_free (children); +} + +static void +nle_composition_reset (NleComposition * comp) +{ + NleCompositionPrivate *priv = comp->priv; + + GST_DEBUG_OBJECT (comp, "resetting"); + + _assert_proper_thread (comp); + + priv->current_stack_start = GST_CLOCK_TIME_NONE; + priv->current_stack_stop = GST_CLOCK_TIME_NONE; + priv->next_base_time = 0; + + gst_segment_init (priv->segment, GST_FORMAT_TIME); + gst_segment_init (priv->seek_segment, GST_FORMAT_TIME); + + if (priv->current) + g_node_destroy (priv->current); + priv->current = NULL; + + nle_composition_reset_target_pad (comp); + + priv->initialized = FALSE; + priv->real_eos_seqnum = 0; + priv->seek_seqnum = 0; + priv->next_eos_seqnum = 0; + priv->flush_seqnum = 0; + + _empty_bin (GST_BIN_CAST (priv->current_bin)); + + GST_DEBUG_OBJECT (comp, "Composition now resetted"); +} + +static GstPadProbeReturn +ghost_event_probe_handler (GstPad * ghostpad G_GNUC_UNUSED, + GstPadProbeInfo * info, NleComposition * comp) +{ + GstPadProbeReturn retval = GST_PAD_PROBE_OK; + NleCompositionPrivate *priv = comp->priv; + GstEvent *event; + + if (GST_IS_BUFFER (info->data) || (GST_IS_QUERY (info->data) + && GST_QUERY_IS_SERIALIZED (info->data))) { + + if (priv->stack_initialization_seek) { + if (g_atomic_int_compare_and_exchange + (&priv->stack_initialization_seek_sent, FALSE, TRUE)) { + _add_action (comp, G_CALLBACK (_seek_pipeline_func), + create_seek_data (comp, + gst_event_ref (priv->stack_initialization_seek)), + G_PRIORITY_HIGH); + + GST_OBJECT_LOCK (comp); + if (comp->task) + gst_task_start (comp->task); + GST_OBJECT_UNLOCK (comp); + + priv->send_stream_start = + priv->updating_reason == COMP_UPDATE_STACK_INITIALIZE; + } + + GST_DEBUG_OBJECT (comp, + "Dropping %" GST_PTR_FORMAT " while sending initializing stack seek", + info->data); + + return GST_PAD_PROBE_DROP; + } + + if (priv->waiting_serialized_query_or_buffer) { + GST_INFO_OBJECT (comp, "update_pipeline DONE"); + _restart_task (comp); + } + + return GST_PAD_PROBE_OK; + } + + event = GST_PAD_PROBE_INFO_EVENT (info); + + GST_LOG_OBJECT (comp, "event: %s", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_STOP: + if (_is_ready_to_restart_task (comp, event)) + _restart_task (comp); + + if (g_atomic_int_compare_and_exchange + (&priv->stack_initialization_seek_sent, TRUE, FALSE)) { + GST_INFO_OBJECT (comp, "Done seeking initialization stack."); + gst_clear_event (&priv->stack_initialization_seek); + } + + if (gst_event_get_seqnum (event) != comp->priv->flush_seqnum) { + GST_INFO_OBJECT (comp, "Dropping FLUSH_STOP %d -- %d", + gst_event_get_seqnum (event), priv->flush_seqnum); + retval = GST_PAD_PROBE_DROP; + } else { + GST_INFO_OBJECT (comp, "Forwarding FLUSH_STOP with seqnum %i", + comp->priv->flush_seqnum); + gst_event_unref (event); + event = gst_event_new_flush_stop (TRUE); + GST_PAD_PROBE_INFO_DATA (info) = event; + if (comp->priv->seek_seqnum) { + GST_EVENT_SEQNUM (event) = comp->priv->seek_seqnum; + } else { + GST_EVENT_SEQNUM (event) = comp->priv->flush_seqnum; + } + GST_INFO_OBJECT (comp, "Set FLUSH_STOP seqnum: %d", + GST_EVENT_SEQNUM (event)); + comp->priv->flush_seqnum = 0; + } + break; + case GST_EVENT_FLUSH_START: + if (gst_event_get_seqnum (event) != comp->priv->flush_seqnum) { + GST_INFO_OBJECT (comp, "Dropping FLUSH_START %d != %d", + gst_event_get_seqnum (event), comp->priv->flush_seqnum); + retval = GST_PAD_PROBE_DROP; + } else { + GST_INFO_OBJECT (comp, "Forwarding FLUSH_START with seqnum %d", + comp->priv->flush_seqnum); + if (comp->priv->seek_seqnum) { + GST_EVENT_SEQNUM (event) = comp->priv->seek_seqnum; + GST_INFO_OBJECT (comp, "Setting FLUSH_START seqnum: %d", + comp->priv->seek_seqnum); + } + } + break; + case GST_EVENT_STREAM_START: + if (g_atomic_int_compare_and_exchange (&priv->send_stream_start, TRUE, + FALSE)) { + + gst_event_unref (event); + event = info->data = gst_event_new_stream_start (priv->id); + GST_INFO_OBJECT (comp, "forward stream-start %p (%s)", event, priv->id); + } else { + GST_DEBUG_OBJECT (comp, "dropping stream-start %p", event); + retval = GST_PAD_PROBE_DROP; + } + break; + case GST_EVENT_STREAM_GROUP_DONE: + if (GST_EVENT_SEQNUM (event) != comp->priv->real_eos_seqnum) { + GST_INFO_OBJECT (comp, "Dropping STREAM_GROUP_DONE %d != %d", + GST_EVENT_SEQNUM (event), comp->priv->real_eos_seqnum); + retval = GST_PAD_PROBE_DROP; + } + break; + case GST_EVENT_CAPS: + { + if (priv->stack_initialization_seek) { + GST_INFO_OBJECT (comp, + "Waiting for preroll to send initializing seek, dropping caps."); + return GST_PAD_PROBE_DROP; + } + break; + } + case GST_EVENT_SEGMENT: + { + guint64 rstart, rstop; + const GstSegment *segment; + GstSegment copy; + GstEvent *event2; + /* next_base_time */ + + if (priv->stack_initialization_seek) { + GST_INFO_OBJECT (comp, "Waiting for preroll to send initializing seek"); + return GST_PAD_PROBE_DROP; + } + + if (_is_ready_to_restart_task (comp, event)) + _restart_task (comp); + + gst_event_parse_segment (event, &segment); + gst_segment_copy_into (segment, ©); + + rstart = + gst_segment_to_running_time (segment, GST_FORMAT_TIME, + segment->start); + rstop = + gst_segment_to_running_time (segment, GST_FORMAT_TIME, segment->stop); + copy.base = comp->priv->next_base_time; + GST_DEBUG_OBJECT (comp, + "Updating base time to %" GST_TIME_FORMAT ", next:%" GST_TIME_FORMAT, + GST_TIME_ARGS (comp->priv->next_base_time), + GST_TIME_ARGS (comp->priv->next_base_time + rstop - rstart)); + comp->priv->next_base_time += rstop - rstart; + + event2 = gst_event_new_segment (©); + if (comp->priv->seek_seqnum) + GST_EVENT_SEQNUM (event2) = comp->priv->seek_seqnum; + else + GST_EVENT_SEQNUM (event2) = GST_EVENT_SEQNUM (event); + + GST_PAD_PROBE_INFO_DATA (info) = event2; + gst_event_unref (event); + } + break; + case GST_EVENT_TAG: + GST_DEBUG_OBJECT (comp, "Dropping tag: %" GST_PTR_FORMAT, info->data); + GST_OBJECT_LOCK (comp); + if (comp->priv->drop_tags) + retval = GST_PAD_PROBE_DROP; + GST_OBJECT_UNLOCK (comp); + break; + case GST_EVENT_EOS: + { + gint seqnum = gst_event_get_seqnum (event); + + GST_INFO_OBJECT (comp, "Got EOS, last EOS seqnum id : %i current " + "seq num is: %i", comp->priv->real_eos_seqnum, seqnum); + + if (_is_ready_to_restart_task (comp, event)) { + GST_INFO_OBJECT (comp, "We got an EOS right after seeing the right" + " segment, restarting task"); + + _restart_task (comp); + } + + if (g_atomic_int_compare_and_exchange (&comp->priv->real_eos_seqnum, + seqnum, 1)) { + + GST_INFO_OBJECT (comp, "Got EOS for real, seq ID is %i, fowarding it", + seqnum); + + if (comp->priv->seek_seqnum) + GST_EVENT_SEQNUM (event) = comp->priv->seek_seqnum; + + return GST_PAD_PROBE_OK; + } + + if (priv->next_eos_seqnum == seqnum) + _add_update_compo_action (comp, G_CALLBACK (_update_pipeline_func), + COMP_UPDATE_STACK_ON_EOS); + else + GST_INFO_OBJECT (comp, + "Got an EOS but it seqnum %i != next eos seqnum %i", seqnum, + priv->next_eos_seqnum); + + retval = GST_PAD_PROBE_DROP; + } + break; + default: + break; + } + + return retval; +} + +static gint +priority_comp (NleObject * a, NleObject * b) +{ + if (a->priority < b->priority) + return -1; + + if (a->priority > b->priority) + return 1; + + return 0; +} + +static inline gboolean +have_to_update_pipeline (NleComposition * comp, + NleUpdateStackReason update_stack_reason) +{ + NleCompositionPrivate *priv = comp->priv; + + if (update_stack_reason == COMP_UPDATE_STACK_ON_EOS) + return TRUE; + + GST_DEBUG_OBJECT (comp, + "segment[%" GST_TIME_FORMAT "--%" GST_TIME_FORMAT "] current[%" + GST_TIME_FORMAT "--%" GST_TIME_FORMAT "]", + GST_TIME_ARGS (priv->segment->start), + GST_TIME_ARGS (priv->segment->stop), + GST_TIME_ARGS (priv->current_stack_start), + GST_TIME_ARGS (priv->current_stack_stop)); + + if (priv->segment->start < priv->current_stack_start) + return TRUE; + + if (priv->segment->start >= priv->current_stack_stop) + return TRUE; + + return FALSE; +} + +static gboolean +nle_composition_commit_func (NleObject * object, gboolean recurse) +{ + _add_update_compo_action (NLE_COMPOSITION (object), + G_CALLBACK (_commit_func), COMP_UPDATE_STACK_ON_COMMIT); + + return TRUE; +} + +/* + * get_new_seek_event: + * + * Returns a seek event for the currently configured segment + * and start/stop values + * + * The GstSegment and current_stack_start|stop must have been configured + * before calling this function. + */ +static GstEvent * +get_new_seek_event (NleComposition * comp, gboolean initial, + gboolean updatestoponly, NleUpdateStackReason reason) +{ + GstSeekFlags flags = GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH; + gint64 start, stop; + GstSeekType starttype = GST_SEEK_TYPE_SET; + NleCompositionPrivate *priv = comp->priv; + + GST_DEBUG_OBJECT (comp, "initial:%d", initial); + /* remove the seek flag */ + if (!initial) + flags |= (GstSeekFlags) priv->segment->flags; + + GST_DEBUG_OBJECT (comp, + "private->segment->start:%" GST_TIME_FORMAT " current_stack_start%" + GST_TIME_FORMAT, GST_TIME_ARGS (priv->segment->start), + GST_TIME_ARGS (priv->current_stack_start)); + + GST_DEBUG_OBJECT (comp, + "private->segment->stop:%" GST_TIME_FORMAT " current_stack_stop%" + GST_TIME_FORMAT, GST_TIME_ARGS (priv->segment->stop), + GST_TIME_ARGS (priv->current_stack_stop)); + + if (reason == COMP_UPDATE_STACK_INITIALIZE + || reason == COMP_UPDATE_STACK_ON_EOS) { + start = priv->current_stack_start; + stop = priv->current_stack_stop; + } else { + start = GST_CLOCK_TIME_IS_VALID (priv->segment->start) + ? MAX (priv->segment->start, priv->current_stack_start) + : priv->current_stack_start; + stop = GST_CLOCK_TIME_IS_VALID (priv->segment->stop) + ? MIN (priv->segment->stop, priv->current_stack_stop) + : priv->current_stack_stop; + } + + if (updatestoponly) { + starttype = GST_SEEK_TYPE_NONE; + start = GST_CLOCK_TIME_NONE; + } + + GST_DEBUG_OBJECT (comp, + "Created new seek event. Flags:%d, start:%" GST_TIME_FORMAT ", stop:%" + GST_TIME_FORMAT ", rate:%lf", flags, GST_TIME_ARGS (start), + GST_TIME_ARGS (stop), priv->segment->rate); + + return gst_event_new_seek (priv->segment->rate, + priv->segment->format, flags, starttype, start, GST_SEEK_TYPE_SET, stop); +} + +static gboolean +nle_composition_needs_topelevel_initializing_seek (NleComposition * comp) +{ + GstObject *parent; + + parent = gst_object_get_parent (GST_OBJECT (comp)); + while (parent) { + if (NLE_IS_COMPOSITION (parent) + && NLE_COMPOSITION (parent)->priv->stack_initialization_seek) { + gst_object_unref (parent); + GST_INFO_OBJECT (comp, + "Not sending an initializing seek as %" GST_PTR_FORMAT + "is gonna seek anyway!", parent); + return FALSE; + } + + gst_object_unref (parent); + parent = gst_object_get_parent (parent); + } + + return TRUE; +} + +static GstClockTime +get_current_position (NleComposition * comp) +{ + GstPad *pad; + NleObject *obj; + NleCompositionPrivate *priv = comp->priv; + gboolean res; + gint64 value = GST_CLOCK_TIME_NONE; + GstObject *parent, *tmp; + + GstPad *peer; + + parent = gst_object_get_parent (GST_OBJECT (comp)); + while ((tmp = parent)) { + if (NLE_IS_COMPOSITION (parent)) { + GstClockTime parent_position = + get_current_position (NLE_COMPOSITION (parent)); + + if (parent_position > NLE_OBJECT_STOP (comp) + || parent_position < NLE_OBJECT_START (comp)) { + GST_INFO_OBJECT (comp, + "Global position outside of subcomposition, returning TIME_NONE"); + + return GST_CLOCK_TIME_NONE; + } + + value = + parent_position - NLE_OBJECT_START (comp) + NLE_OBJECT_INPOINT (comp); + } + + if (GST_IS_PIPELINE (parent)) { + if (gst_element_query_position (GST_ELEMENT (parent), GST_FORMAT_TIME, + &value)) { + + gst_object_unref (parent); + return value; + } + } + + + parent = gst_object_get_parent (GST_OBJECT (parent)); + gst_object_unref (tmp); + } + + /* Try querying position downstream */ + peer = gst_pad_get_peer (NLE_OBJECT (comp)->srcpad); + if (peer) { + res = gst_pad_query_position (peer, GST_FORMAT_TIME, &value); + gst_object_unref (peer); + + if (res) { + GST_DEBUG_OBJECT (comp, + "Successfully got downstream position %" GST_TIME_FORMAT, + GST_TIME_ARGS ((guint64) value)); + goto beach; + } + } + + GST_DEBUG_OBJECT (comp, "Downstream position query failed"); + + /* resetting format/value */ + value = GST_CLOCK_TIME_NONE; + + /* If downstream fails , try within the current stack */ + if (!priv->current) { + GST_DEBUG_OBJECT (comp, "No current stack, can't send query"); + goto beach; + } + + obj = (NleObject *) priv->current->data; + + pad = NLE_OBJECT_SRC (obj); + res = gst_pad_query_position (pad, GST_FORMAT_TIME, &value); + + if (G_UNLIKELY (res == FALSE)) { + GST_WARNING_OBJECT (comp, "query position failed"); + value = GST_CLOCK_TIME_NONE; + } else { + GST_LOG_OBJECT (comp, "Query returned %" GST_TIME_FORMAT, + GST_TIME_ARGS ((guint64) value)); + } + +beach: + + if (!GST_CLOCK_TIME_IS_VALID (value)) { + if (GST_CLOCK_TIME_IS_VALID (comp->priv->current_stack_start)) { + value = comp->priv->current_stack_start; + } else { + GST_INFO_OBJECT (comp, "Current position is unknown, " "setting it to 0"); + + value = 0; + } + } + + return (guint64) value; +} + +/* WITH OBJECTS LOCK TAKEN */ +static gboolean +_seek_current_stack (NleComposition * comp, GstEvent * event, + gboolean flush_downstream) +{ + gboolean res; + NleCompositionPrivate *priv = comp->priv; + GstPad *peer = gst_pad_get_peer (NLE_OBJECT_SRC (comp)); + + GST_INFO_OBJECT (comp, "Seeking itself %" GST_PTR_FORMAT, event); + + if (!peer) { + gst_event_unref (event); + GST_ERROR_OBJECT (comp, "Can't seek because no pad available - " + "no children in the composition ready to be used, the duration is 0, " + "or not committed yet"); + return FALSE; + } + + if (flush_downstream) { + priv->flush_seqnum = gst_event_get_seqnum (event); + GST_INFO_OBJECT (comp, "sending flushes downstream with seqnum %d", + priv->flush_seqnum); + } + + priv->seeking_itself = TRUE; + res = gst_pad_push_event (peer, event); + priv->seeking_itself = FALSE; + gst_object_unref (peer); + + GST_DEBUG_OBJECT (comp, "Done seeking"); + + return res; +} + +/* + Figures out if pipeline needs updating. + Updates it and sends the seek event. + Sends flush events downstream if needed. + can be called by user_seek or segment_done + + update_stack_reason: The reason for which we need to handle 'seek' +*/ + +static gboolean +seek_handling (NleComposition * comp, gint32 seqnum, + NleUpdateStackReason update_stack_reason) +{ + GST_DEBUG_OBJECT (comp, "Seek handling update pipeline reason: %s", + UPDATE_PIPELINE_REASONS[update_stack_reason]); + + if (have_to_update_pipeline (comp, update_stack_reason)) { + if (comp->priv->segment->rate >= 0.0) + update_pipeline (comp, comp->priv->segment->start, seqnum, + update_stack_reason); + else + update_pipeline (comp, comp->priv->segment->stop, seqnum, + update_stack_reason); + } else { + GstEvent *toplevel_seek = get_new_seek_event (comp, FALSE, FALSE, + update_stack_reason); + + gst_event_set_seqnum (toplevel_seek, seqnum); + _set_real_eos_seqnum_from_seek (comp, toplevel_seek); + + _remove_update_actions (comp); + _seek_current_stack (comp, toplevel_seek, + _have_to_flush_downstream (update_stack_reason)); + } + + return TRUE; +} + +static gboolean +nle_composition_event_handler (GstPad * ghostpad, GstObject * parent, + GstEvent * event) +{ + NleComposition *comp = (NleComposition *) parent; + NleCompositionPrivate *priv = comp->priv; + gboolean res = TRUE; + + GST_DEBUG_OBJECT (comp, "event type:%s", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + { + /* Queue up a seek action if this seek event does not come from + * ourselves. Due to a possible race condition around the + * seeking_itself flag, we also check if the seek comes from + * our task thread. The seeking_itself flag only works as an + * optimization */ + GST_OBJECT_LOCK (comp); + if (!priv->seeking_itself || (comp->task + && gst_task_get_state (comp->task) != GST_TASK_STOPPED + && g_thread_self () != comp->task->thread)) { + GST_OBJECT_UNLOCK (comp); + _add_seek_action (comp, event); + event = NULL; + GST_FIXME_OBJECT (comp, "HANDLE seeking errors!"); + + return TRUE; + } + GST_OBJECT_UNLOCK (comp); + break; + } + case GST_EVENT_QOS: + { + gdouble prop; + GstQOSType qostype; + GstClockTimeDiff diff; + GstClockTime timestamp; + + gst_event_parse_qos (event, &qostype, &prop, &diff, ×tamp); + + GST_DEBUG_OBJECT (comp, + "timestamp:%" GST_TIME_FORMAT " segment.start:%" GST_TIME_FORMAT + " segment.stop:%" GST_TIME_FORMAT " current_stack_start%" + GST_TIME_FORMAT " current_stack_stop:%" GST_TIME_FORMAT, + GST_TIME_ARGS (timestamp), + GST_TIME_ARGS (priv->seek_segment->start), + GST_TIME_ARGS (priv->seek_segment->stop), + GST_TIME_ARGS (priv->current_stack_start), + GST_TIME_ARGS (priv->current_stack_stop)); + + /* The problem with QoS events is the following: + * At each new internal segment (i.e. when we re-arrange our internal + * elements) we send flushing seeks to those elements (to properly + * configure their playback range) but don't let the FLUSH events get + * downstream. + * + * The problem is that the QoS running timestamps we receive from + * downstream will not have taken into account those flush. + * + * What we need to do is to translate to our internal running timestamps + * which for each configured segment starts at 0 for those elements. + * + * The generic algorithm for the incoming running timestamp translation + * is therefore: + * (original_seek_time : original seek position received from usptream) + * (current_segment_start : Start position of the currently configured + * timeline segment) + * + * difference = original_seek_time - current_segment_start + * new_qos_position = upstream_qos_position - difference + * + * The new_qos_position is only valid when: + * * it applies to the current segment (difference > 0) + * * The QoS difference + timestamp is greater than the difference + * + */ + + if (GST_CLOCK_TIME_IS_VALID (priv->seek_segment->start)) { + GstClockTimeDiff curdiff; + + /* We'll either create a new event or discard it */ + gst_event_unref (event); + + if (priv->segment->rate < 0.0) + curdiff = priv->seek_segment->stop - priv->current_stack_stop; + else + curdiff = priv->current_stack_start - priv->seek_segment->start; + GST_DEBUG ("curdiff %" GST_TIME_FORMAT, GST_TIME_ARGS (curdiff)); + if ((curdiff != 0) && ((timestamp < curdiff) + || (curdiff > timestamp + diff))) { + GST_DEBUG_OBJECT (comp, + "QoS event outside of current segment, discarding"); + /* The QoS timestamp is before the currently set-up pipeline */ + goto beach; + } + + /* Substract the amount of running time we've already outputted + * until the currently configured pipeline from the QoS timestamp.*/ + timestamp -= curdiff; + GST_DEBUG_OBJECT (comp, + "Creating new QoS event with timestamp %" GST_TIME_FORMAT, + GST_TIME_ARGS (timestamp)); + event = gst_event_new_qos (qostype, prop, diff, timestamp); + } + break; + } + default: + break; + } + + if (res) { + GST_DEBUG_OBJECT (comp, "About to call nle_event_pad_func: %p", + priv->nle_event_pad_func); + res = priv->nle_event_pad_func (NLE_OBJECT (comp)->srcpad, parent, event); + GST_DEBUG_OBJECT (comp, "Done calling nle_event_pad_func() %d", res); + } + +beach: + return res; +} + +static inline void +nle_composition_reset_target_pad (NleComposition * comp) +{ + NleCompositionPrivate *priv = comp->priv; + + GST_DEBUG_OBJECT (comp, "Removing ghostpad"); + + if (priv->ghosteventprobe) { + GstPad *target; + + target = gst_ghost_pad_get_target ((GstGhostPad *) NLE_OBJECT_SRC (comp)); + if (target) + gst_pad_remove_probe (target, priv->ghosteventprobe); + priv->ghosteventprobe = 0; + } + + nle_object_ghost_pad_set_target (NLE_OBJECT (comp), + NLE_OBJECT_SRC (comp), NULL); +} + +/* nle_composition_ghost_pad_set_target: + * target: The target #GstPad. The refcount will be decremented (given to the ghostpad). + */ +static void +nle_composition_ghost_pad_set_target (NleComposition * comp, GstPad * target) +{ + GstPad *ptarget; + NleCompositionPrivate *priv = comp->priv; + + if (target) + GST_DEBUG_OBJECT (comp, "target:%s:%s", GST_DEBUG_PAD_NAME (target)); + else + GST_DEBUG_OBJECT (comp, "Removing target"); + + + ptarget = + gst_ghost_pad_get_target (GST_GHOST_PAD (NLE_OBJECT (comp)->srcpad)); + if (ptarget) { + gst_object_unref (ptarget); + + if (ptarget == target) { + GST_DEBUG_OBJECT (comp, + "Target of srcpad is the same as existing one, not changing"); + return; + } + } + + /* Actually set the target */ + nle_object_ghost_pad_set_target ((NleObject *) comp, + NLE_OBJECT (comp)->srcpad, target); + + if (target && (priv->ghosteventprobe == 0)) { + priv->ghosteventprobe = + gst_pad_add_probe (target, + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH | + GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM | + GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM, + (GstPadProbeCallback) ghost_event_probe_handler, comp, NULL); + GST_DEBUG_OBJECT (comp, "added event probe %lu", priv->ghosteventprobe); + } +} + +static void +refine_start_stop_in_region_above_priority (NleComposition * composition, + GstClockTime timestamp, GstClockTime start, + GstClockTime stop, + GstClockTime * rstart, GstClockTime * rstop, guint32 priority) +{ + GList *tmp; + NleObject *object; + GstClockTime nstart = start, nstop = stop; + + GST_DEBUG_OBJECT (composition, + "timestamp:%" GST_TIME_FORMAT " start: %" GST_TIME_FORMAT " stop: %" + GST_TIME_FORMAT " priority:%u", GST_TIME_ARGS (timestamp), + GST_TIME_ARGS (start), GST_TIME_ARGS (stop), priority); + + for (tmp = composition->priv->objects_start; tmp; tmp = tmp->next) { + object = (NleObject *) tmp->data; + + GST_LOG_OBJECT (object, "START %" GST_TIME_FORMAT "--%" GST_TIME_FORMAT, + GST_TIME_ARGS (object->start), GST_TIME_ARGS (object->stop)); + + if ((object->priority >= priority) || (!NLE_OBJECT_ACTIVE (object))) + continue; + + if (object->start <= timestamp) + continue; + + if (object->start >= nstop) + continue; + + nstop = object->start; + + GST_DEBUG_OBJECT (composition, + "START Found %s [prio:%u] at %" GST_TIME_FORMAT, + GST_OBJECT_NAME (object), object->priority, + GST_TIME_ARGS (object->start)); + + break; + } + + for (tmp = composition->priv->objects_stop; tmp; tmp = tmp->next) { + object = (NleObject *) tmp->data; + + GST_LOG_OBJECT (object, "STOP %" GST_TIME_FORMAT "--%" GST_TIME_FORMAT, + GST_TIME_ARGS (object->start), GST_TIME_ARGS (object->stop)); + + if ((object->priority >= priority) || (!NLE_OBJECT_ACTIVE (object))) + continue; + + if (object->stop >= timestamp) + continue; + + if (object->stop <= nstart) + continue; + + nstart = object->stop; + + GST_DEBUG_OBJECT (composition, + "STOP Found %s [prio:%u] at %" GST_TIME_FORMAT, + GST_OBJECT_NAME (object), object->priority, + GST_TIME_ARGS (object->start)); + + break; + } + + if (*rstart) + *rstart = nstart; + + if (*rstop) + *rstop = nstop; +} + + +/* + * Converts a sorted list to a tree + * Recursive + * + * stack will be set to the next item to use in the parent. + * If operations number of sinks is limited, it will only use that number. + */ + +static GNode * +convert_list_to_tree (GList ** stack, GstClockTime * start, + GstClockTime * stop, guint32 * highprio) +{ + GNode *ret; + guint nbsinks; + gboolean limit; + GList *tmp; + NleObject *object; + + if (!stack || !*stack) + return NULL; + + object = (NleObject *) (*stack)->data; + + GST_DEBUG ("object:%s , *start:%" GST_TIME_FORMAT ", *stop:%" + GST_TIME_FORMAT " highprio:%d", + GST_ELEMENT_NAME (object), GST_TIME_ARGS (*start), + GST_TIME_ARGS (*stop), *highprio); + + /* update earliest stop */ + if (GST_CLOCK_TIME_IS_VALID (*stop)) { + if (GST_CLOCK_TIME_IS_VALID (object->stop) && (*stop > object->stop)) + *stop = object->stop; + } else { + *stop = object->stop; + } + + if (GST_CLOCK_TIME_IS_VALID (*start)) { + if (GST_CLOCK_TIME_IS_VALID (object->start) && (*start < object->start)) + *start = object->start; + } else { + *start = object->start; + } + + if (NLE_OBJECT_IS_SOURCE (object)) { + *stack = g_list_next (*stack); + + /* update highest priority. + * We do this here, since it's only used with sources (leafs of the tree) */ + if (object->priority > *highprio) + *highprio = object->priority; + + ret = g_node_new (object); + + goto beach; + } else { + /* NleOperation */ + NleOperation *oper = (NleOperation *) object; + + GST_LOG_OBJECT (oper, "operation, num_sinks:%d", oper->num_sinks); + + ret = g_node_new (object); + limit = (oper->dynamicsinks == FALSE); + nbsinks = oper->num_sinks; + + /* FIXME : if num_sinks == -1 : request the proper number of pads */ + for (tmp = g_list_next (*stack); tmp && (!limit || nbsinks);) { + g_node_append (ret, convert_list_to_tree (&tmp, start, stop, highprio)); + if (limit) + nbsinks--; + } + + *stack = tmp; + } + +beach: + GST_DEBUG_OBJECT (object, + "*start:%" GST_TIME_FORMAT " *stop:%" GST_TIME_FORMAT + " priority:%u", GST_TIME_ARGS (*start), GST_TIME_ARGS (*stop), *highprio); + + return ret; +} + +/* + * get_stack_list: + * @comp: The #NleComposition + * @timestamp: The #GstClockTime to look at + * @priority: The priority level to start looking from + * @activeonly: Only look for active elements if TRUE + * @start: The biggest start time of the objects in the stack + * @stop: The smallest stop time of the objects in the stack + * @highprio: The highest priority in the stack + * + * Not MT-safe, you should take the objects lock before calling it. + * Returns: A tree of #GNode sorted in priority order, corresponding + * to the given search arguments. The returned value can be #NULL. + * + * WITH OBJECTS LOCK TAKEN + */ +static GNode * +get_stack_list (NleComposition * comp, GstClockTime timestamp, + guint32 priority, gboolean activeonly, GstClockTime * start, + GstClockTime * stop, guint * highprio) +{ + GList *tmp; + GList *stack = NULL; + GNode *ret = NULL; + GstClockTime nstart = GST_CLOCK_TIME_NONE; + GstClockTime nstop = GST_CLOCK_TIME_NONE; + GstClockTime first_out_of_stack = GST_CLOCK_TIME_NONE; + guint32 highest = 0; + gboolean reverse = (comp->priv->segment->rate < 0.0); + + GST_DEBUG_OBJECT (comp, + "timestamp:%" GST_TIME_FORMAT ", priority:%u, activeonly:%d", + GST_TIME_ARGS (timestamp), priority, activeonly); + + GST_LOG ("objects_start:%p objects_stop:%p", comp->priv->objects_start, + comp->priv->objects_stop); + + if (reverse) { + for (tmp = comp->priv->objects_stop; tmp; tmp = g_list_next (tmp)) { + NleObject *object = (NleObject *) tmp->data; + + GST_LOG_OBJECT (object, + "start: %" GST_TIME_FORMAT ", stop:%" GST_TIME_FORMAT " , duration:%" + GST_TIME_FORMAT ", priority:%u, active:%d", + GST_TIME_ARGS (object->start), GST_TIME_ARGS (object->stop), + GST_TIME_ARGS (object->duration), object->priority, object->active); + + if (object->stop >= timestamp) { + if ((object->start < timestamp) && + (object->priority >= priority) && + ((!activeonly) || (NLE_OBJECT_ACTIVE (object)))) { + GST_LOG_OBJECT (comp, "adding %s: sorted to the stack", + GST_OBJECT_NAME (object)); + stack = g_list_insert_sorted (stack, object, + (GCompareFunc) priority_comp); + } + } else { + GST_LOG_OBJECT (comp, "too far, stopping iteration"); + first_out_of_stack = object->stop; + break; + } + } + } else { + for (tmp = comp->priv->objects_start; tmp; tmp = g_list_next (tmp)) { + NleObject *object = (NleObject *) tmp->data; + + GST_LOG_OBJECT (object, + "start: %" GST_TIME_FORMAT " , stop:%" GST_TIME_FORMAT " , duration:%" + GST_TIME_FORMAT ", priority:%u", GST_TIME_ARGS (object->start), + GST_TIME_ARGS (object->stop), GST_TIME_ARGS (object->duration), + object->priority); + + if (object->start <= timestamp) { + if ((object->stop > timestamp) && + (object->priority >= priority) && + ((!activeonly) || (NLE_OBJECT_ACTIVE (object)))) { + GST_LOG_OBJECT (comp, "adding %s: sorted to the stack", + GST_OBJECT_NAME (object)); + stack = g_list_insert_sorted (stack, object, + (GCompareFunc) priority_comp); + } + } else { + GST_LOG_OBJECT (comp, "too far, stopping iteration"); + first_out_of_stack = object->start; + break; + } + } + } + + /* Insert the expandables */ + if (G_LIKELY (timestamp < NLE_OBJECT_STOP (comp))) + for (tmp = comp->priv->expandables; tmp; tmp = tmp->next) { + GST_DEBUG_OBJECT (comp, "Adding expandable %s sorted to the list", + GST_OBJECT_NAME (tmp->data)); + stack = g_list_insert_sorted (stack, tmp->data, + (GCompareFunc) priority_comp); + } + + /* convert that list to a stack */ + tmp = stack; + ret = convert_list_to_tree (&tmp, &nstart, &nstop, &highest); + if (GST_CLOCK_TIME_IS_VALID (first_out_of_stack)) { + if (reverse && nstart < first_out_of_stack) + nstart = first_out_of_stack; + else if (!reverse && nstop > first_out_of_stack) + nstop = first_out_of_stack; + } + + GST_DEBUG ("nstart:%" GST_TIME_FORMAT ", nstop:%" GST_TIME_FORMAT, + GST_TIME_ARGS (nstart), GST_TIME_ARGS (nstop)); + + if (*stop) + *stop = nstop; + if (*start) + *start = nstart; + if (highprio) + *highprio = highest; + + g_list_free (stack); + + return ret; +} + +/* + * get_clean_toplevel_stack: + * @comp: The #NleComposition + * @timestamp: The #GstClockTime to look at + * @stop_time: Pointer to a #GstClockTime for min stop time of returned stack + * @start_time: Pointer to a #GstClockTime for greatest start time of returned stack + * + * Returns: The new current stack for the given #NleComposition and @timestamp. + * + * WITH OBJECTS LOCK TAKEN + */ +static GNode * +get_clean_toplevel_stack (NleComposition * comp, GstClockTime * timestamp, + GstClockTime * start_time, GstClockTime * stop_time) +{ + GNode *stack = NULL; + GstClockTime start = G_MAXUINT64; + GstClockTime stop = G_MAXUINT64; + guint highprio; + gboolean reverse = (comp->priv->segment->rate < 0.0); + + GST_DEBUG_OBJECT (comp, "timestamp:%" GST_TIME_FORMAT, + GST_TIME_ARGS (*timestamp)); + GST_DEBUG ("start:%" GST_TIME_FORMAT ", stop:%" GST_TIME_FORMAT, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); + + stack = get_stack_list (comp, *timestamp, 0, TRUE, &start, &stop, &highprio); + + if (!stack && + ((reverse && (*timestamp > COMP_REAL_START (comp))) || + (!reverse && (*timestamp < COMP_REAL_STOP (comp))))) { + GST_ELEMENT_ERROR (comp, STREAM, WRONG_TYPE, + ("Gaps ( at %" GST_TIME_FORMAT + ") in the stream is not supported, the application is responsible" + " for filling them", GST_TIME_ARGS (*timestamp)), + ("Gap in the composition this should never" + "append, make sure to fill them")); + + return NULL; + } + + GST_DEBUG_OBJECT (comp, "start:%" GST_TIME_FORMAT ", stop:%" GST_TIME_FORMAT, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); + + if (stack) { + guint32 top_priority = NLE_OBJECT_PRIORITY (stack->data); + + /* Figure out if there's anything blocking us with smaller priority */ + refine_start_stop_in_region_above_priority (comp, *timestamp, start, + stop, &start, &stop, (highprio == 0) ? top_priority : highprio); + } + + if (*stop_time) { + if (stack) + *stop_time = stop; + else + *stop_time = 0; + } + + if (*start_time) { + if (stack) + *start_time = start; + else + *start_time = 0; + } + + GST_DEBUG_OBJECT (comp, + "Returning timestamp:%" GST_TIME_FORMAT " , start_time:%" + GST_TIME_FORMAT " , stop_time:%" GST_TIME_FORMAT, + GST_TIME_ARGS (*timestamp), GST_TIME_ARGS (*start_time), + GST_TIME_ARGS (*stop_time)); + + return stack; +} + +static GstPadProbeReturn +_drop_all_cb (GstPad * pad G_GNUC_UNUSED, + GstPadProbeInfo * info, NleComposition * comp) +{ + return GST_PAD_PROBE_DROP; +} + +/* Must be called with OBJECTS_LOCK taken */ +static void +_set_current_bin_to_ready (NleComposition * comp, NleUpdateStackReason reason) +{ + gint probe_id = -1; + GstPad *ptarget = NULL; + NleCompositionPrivate *priv = comp->priv; + GstEvent *flush_event; + + comp->priv->tearing_down_stack = TRUE; + if (_have_to_flush_downstream (reason)) { + ptarget = gst_ghost_pad_get_target (GST_GHOST_PAD (NLE_OBJECT_SRC (comp))); + if (ptarget) { + + /* Make sure that between the flush_start/flush_stop + * and the time we set the current_bin to READY, no + * buffer can ever get prerolled which would lead to + * a deadlock */ + probe_id = gst_pad_add_probe (ptarget, + GST_PAD_PROBE_TYPE_DATA_BOTH | GST_PAD_PROBE_TYPE_EVENT_BOTH, + (GstPadProbeCallback) _drop_all_cb, comp, NULL); + + GST_DEBUG_OBJECT (comp, "added event probe %lu", priv->ghosteventprobe); + + flush_event = gst_event_new_flush_start (); + if (reason != COMP_UPDATE_STACK_ON_SEEK) + priv->flush_seqnum = gst_event_get_seqnum (flush_event); + else + gst_event_set_seqnum (flush_event, priv->seek_seqnum); + + GST_INFO_OBJECT (comp, "sending flushes downstream with seqnum %d", + priv->flush_seqnum); + gst_pad_push_event (ptarget, flush_event); + + } + + } + + gst_element_set_locked_state (priv->current_bin, TRUE); + gst_element_set_state (priv->current_bin, GST_STATE_READY); + + if (ptarget) { + if (_have_to_flush_downstream (reason)) { + flush_event = gst_event_new_flush_stop (TRUE); + + gst_event_set_seqnum (flush_event, priv->flush_seqnum); + + /* Force ad activation so that the event can actually travel. + * Not doing that would lead to the event being discarded. + */ + gst_pad_set_active (ptarget, TRUE); + gst_pad_push_event (ptarget, flush_event); + gst_pad_set_active (ptarget, FALSE); + } + + gst_pad_remove_probe (ptarget, probe_id); + gst_object_unref (ptarget); + } + + comp->priv->tearing_down_stack = FALSE; +} + +static void +_emit_commited_signal_func (NleComposition * comp, gpointer udata) +{ + GST_INFO_OBJECT (comp, "Emiting COMMITED now that the stack " "is ready"); + + g_signal_emit (comp, _signals[COMMITED_SIGNAL], 0, TRUE); +} + +static void +_restart_task (NleComposition * comp) +{ + GST_INFO_OBJECT (comp, "Restarting task! after %s DONE", + UPDATE_PIPELINE_REASONS[comp->priv->updating_reason]); + + if (comp->priv->updating_reason == COMP_UPDATE_STACK_ON_COMMIT) + _add_action (comp, G_CALLBACK (_emit_commited_signal_func), comp, + G_PRIORITY_HIGH); + + comp->priv->seqnum_to_restart_task = 0; + comp->priv->waiting_serialized_query_or_buffer = FALSE; + gst_clear_event (&comp->priv->stack_initialization_seek); + + comp->priv->updating_reason = COMP_UPDATE_STACK_NONE; + GST_OBJECT_LOCK (comp); + if (comp->task) + gst_task_start (comp->task); + GST_OBJECT_UNLOCK (comp); +} + +static gboolean +_is_ready_to_restart_task (NleComposition * comp, GstEvent * event) +{ + NleCompositionPrivate *priv = comp->priv; + gint seqnum = gst_event_get_seqnum (event); + + + if (comp->priv->seqnum_to_restart_task == seqnum) { + gchar *name = g_strdup_printf ("%s-new-stack__%" GST_TIME_FORMAT "--%" + GST_TIME_FORMAT "", GST_OBJECT_NAME (comp), + GST_TIME_ARGS (comp->priv->current_stack_start), + GST_TIME_ARGS (comp->priv->current_stack_stop)); + + GST_INFO_OBJECT (comp, "Got %s with proper seqnum" + " done with stack reconfiguration %" GST_PTR_FORMAT, + GST_EVENT_TYPE_NAME (event), event); + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (comp), + GST_DEBUG_GRAPH_SHOW_ALL, name); + g_free (name); + + if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { + GST_INFO_OBJECT (comp, "update_pipeline DONE"); + return TRUE; + } + + priv->waiting_serialized_query_or_buffer = TRUE; + return FALSE; + + } else if (comp->priv->seqnum_to_restart_task) { + GST_INFO_OBJECT (comp, "WARNING: %s seqnum %i != wanted %i", + GST_EVENT_TYPE_NAME (event), seqnum, + comp->priv->seqnum_to_restart_task); + } + + return FALSE; +} + +static void +_commit_func (NleComposition * comp, UpdateCompositionData * ucompo) +{ + GstClockTime curpos; + NleCompositionPrivate *priv = comp->priv; + + _post_start_composition_update (comp, ucompo->seqnum, ucompo->reason); + + /* Get current so that it represent the duration it was + * before commiting children */ + curpos = get_current_position (comp); + + if (!_commit_all_values (comp, ucompo->reason)) { + GST_DEBUG_OBJECT (comp, "Nothing to commit, leaving"); + + g_signal_emit (comp, _signals[COMMITED_SIGNAL], 0, FALSE); + _post_start_composition_update_done (comp, ucompo->seqnum, ucompo->reason); + + return; + } + + if (priv->initialized == FALSE) { + GST_DEBUG_OBJECT (comp, "Not initialized yet, just updating values"); + + update_start_stop_duration (comp); + + g_signal_emit (comp, _signals[COMMITED_SIGNAL], 0, TRUE); + + } else { + gboolean reverse; + + /* And update the pipeline at current position if needed */ + update_start_stop_duration (comp); + + reverse = (priv->segment->rate < 0.0); + if (!reverse) { + GST_DEBUG_OBJECT (comp, + "Setting segment->start to curpos:%" GST_TIME_FORMAT, + GST_TIME_ARGS (curpos)); + priv->segment->start = curpos; + } else { + GST_DEBUG_OBJECT (comp, + "Setting segment->stop to curpos:%" GST_TIME_FORMAT, + GST_TIME_ARGS (curpos)); + priv->segment->stop = curpos; + } + update_pipeline (comp, curpos, ucompo->seqnum, COMP_UPDATE_STACK_ON_COMMIT); + + if (!priv->current) { + GST_INFO_OBJECT (comp, "No new stack set, we can go and keep acting on" + " our children"); + + g_signal_emit (comp, _signals[COMMITED_SIGNAL], 0, TRUE); + } + } + + _post_start_composition_update_done (comp, ucompo->seqnum, ucompo->reason); +} + +static void +_update_pipeline_func (NleComposition * comp, UpdateCompositionData * ucompo) +{ + gboolean reverse; + NleCompositionPrivate *priv = comp->priv; + + _post_start_composition_update (comp, ucompo->seqnum, ucompo->reason); + + /* Set up a non-initial seek on current_stack_stop */ + reverse = (priv->segment->rate < 0.0); + if (!reverse) { + GST_DEBUG_OBJECT (comp, + "Setting segment->start to current_stack_stop:%" GST_TIME_FORMAT, + GST_TIME_ARGS (priv->current_stack_stop)); + priv->segment->start = priv->current_stack_stop; + } else { + GST_DEBUG_OBJECT (comp, + "Setting segment->stop to current_stack_start:%" GST_TIME_FORMAT, + GST_TIME_ARGS (priv->current_stack_start)); + priv->segment->stop = priv->current_stack_start; + } + + seek_handling (comp, ucompo->seqnum, COMP_UPDATE_STACK_ON_EOS); + + /* Post segment done if last seek was a segment seek */ + if (!priv->current && (priv->segment->flags & GST_SEEK_FLAG_SEGMENT)) { + gint64 epos; + + if (GST_CLOCK_TIME_IS_VALID (priv->segment->stop)) + epos = (MIN (priv->segment->stop, NLE_OBJECT_STOP (comp))); + else + epos = NLE_OBJECT_STOP (comp); + + GST_LOG_OBJECT (comp, "Emitting segment done pos %" GST_TIME_FORMAT, + GST_TIME_ARGS (epos)); + gst_element_post_message (GST_ELEMENT_CAST (comp), + gst_message_new_segment_done (GST_OBJECT (comp), + priv->segment->format, epos)); + gst_pad_push_event (NLE_OBJECT (comp)->srcpad, + gst_event_new_segment_done (priv->segment->format, epos)); + } + + _post_start_composition_update_done (comp, ucompo->seqnum, ucompo->reason); +} + +/* Never call when ->task runs! */ +static void +_set_all_children_state (NleComposition * comp, GstState state) +{ + GList *tmp; + + for (tmp = comp->priv->objects_start; tmp; tmp = tmp->next) + gst_element_set_state (tmp->data, state); +} + +static GstStateChangeReturn +nle_composition_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn res; + NleComposition *comp = (NleComposition *) element; + + GST_DEBUG_OBJECT (comp, "%s => %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + _set_all_children_state (comp, GST_STATE_READY); + _start_task (comp); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + _stop_task (comp); + + _remove_update_actions (comp); + _remove_seek_actions (comp); + _deactivate_stack (comp, TRUE); + comp->priv->tearing_down_stack = TRUE; + break; + case GST_STATE_CHANGE_READY_TO_NULL: + _stop_task (comp); + + _remove_update_actions (comp); + _remove_seek_actions (comp); + _set_all_children_state (comp, GST_STATE_NULL); + comp->priv->tearing_down_stack = TRUE; + break; + default: + break; + } + + res = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + if (res == GST_STATE_CHANGE_FAILURE) { + GST_ERROR_OBJECT (comp, "state change failure %s => %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + comp->priv->tearing_down_stack = TRUE; + _stop_task (comp); + nle_composition_reset (comp); + gst_element_set_state (comp->priv->current_bin, GST_STATE_NULL); + comp->priv->tearing_down_stack = FALSE; + + return res; + } + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + /* state-lock all elements */ + GST_DEBUG_OBJECT (comp, + "Setting all children to READY and locking their state"); + + _add_update_compo_action (comp, G_CALLBACK (_initialize_stack_func), + COMP_UPDATE_STACK_INITIALIZE); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + comp->priv->tearing_down_stack = FALSE; + nle_composition_reset (comp); + + /* In READY we are still able to process actions. */ + _start_task (comp); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_element_set_state (comp->priv->current_bin, GST_STATE_NULL); + comp->priv->tearing_down_stack = FALSE; + break; + default: + break; + } + + return res; +} + +static gint +objects_start_compare (NleObject * a, NleObject * b) +{ + if (a->start == b->start) { + if (a->priority < b->priority) + return -1; + if (a->priority > b->priority) + return 1; + return 0; + } + if (a->start < b->start) + return -1; + if (a->start > b->start) + return 1; + return 0; +} + +static gint +objects_stop_compare (NleObject * a, NleObject * b) +{ + if (a->stop == b->stop) { + if (a->priority < b->priority) + return -1; + if (a->priority > b->priority) + return 1; + return 0; + } + if (b->stop < a->stop) + return -1; + if (b->stop > a->stop) + return 1; + return 0; +} + +/* WITH OBJECTS LOCK TAKEN */ +static void +update_start_stop_duration (NleComposition * comp) +{ + NleObject *obj; + NleObject *cobj = (NleObject *) comp; + gboolean reverse = (comp->priv->segment->rate < 0); + GstClockTime prev_stop = NLE_OBJECT_STOP (comp); + NleCompositionPrivate *priv = comp->priv; + + _assert_proper_thread (comp); + + if (!priv->objects_start) { + GST_INFO_OBJECT (comp, "no objects, resetting everything to 0"); + + if (cobj->start) { + cobj->start = cobj->pending_start = 0; + g_object_notify_by_pspec (G_OBJECT (cobj), + nleobject_properties[NLEOBJECT_PROP_START]); + } + + if (cobj->duration) { + cobj->pending_duration = cobj->duration = 0; + g_object_notify_by_pspec (G_OBJECT (cobj), + nleobject_properties[NLEOBJECT_PROP_DURATION]); + signal_duration_change (comp); + } + + if (cobj->stop) { + cobj->stop = 0; + g_object_notify_by_pspec (G_OBJECT (cobj), + nleobject_properties[NLEOBJECT_PROP_STOP]); + } + + return; + } + + /* If we have a default object, the start position is 0 */ + if (priv->expandables) { + GST_INFO_OBJECT (cobj, + "Setting start to 0 because we have a default object"); + + if (cobj->start != 0) { + cobj->pending_start = cobj->start = 0; + g_object_notify_by_pspec (G_OBJECT (cobj), + nleobject_properties[NLEOBJECT_PROP_START]); + } + + } else { + + /* Else it's the first object's start value */ + obj = (NleObject *) priv->objects_start->data; + + if (obj->start != cobj->start) { + GST_INFO_OBJECT (obj, "setting start from %s to %" GST_TIME_FORMAT, + GST_OBJECT_NAME (obj), GST_TIME_ARGS (obj->start)); + cobj->pending_start = cobj->start = obj->start; + g_object_notify_by_pspec (G_OBJECT (cobj), + nleobject_properties[NLEOBJECT_PROP_START]); + } + + } + + obj = (NleObject *) priv->objects_stop->data; + + if (obj->stop != cobj->stop) { + GST_INFO_OBJECT (obj, "setting stop from %s to %" GST_TIME_FORMAT, + GST_OBJECT_NAME (obj), GST_TIME_ARGS (obj->stop)); + + if (priv->expandables) { + GList *tmp; + + GST_INFO_OBJECT (comp, "RE-setting all expandables duration and commit"); + for (tmp = priv->expandables; tmp; tmp = tmp->next) { + g_object_set (tmp->data, "duration", obj->stop, NULL); + nle_object_commit (NLE_OBJECT (tmp->data), FALSE); + } + } + + if (reverse || priv->segment->stop == prev_stop + || obj->stop < priv->segment->stop) + priv->segment->stop = obj->stop; + cobj->stop = obj->stop; + g_object_notify_by_pspec (G_OBJECT (cobj), + nleobject_properties[NLEOBJECT_PROP_STOP]); + } + + if ((cobj->stop - cobj->start) != cobj->duration) { + cobj->pending_duration = cobj->duration = cobj->stop - cobj->start; + g_object_notify_by_pspec (G_OBJECT (cobj), + nleobject_properties[NLEOBJECT_PROP_DURATION]); + signal_duration_change (comp); + } + + GST_INFO_OBJECT (comp, + "start:%" GST_TIME_FORMAT + " stop:%" GST_TIME_FORMAT + " duration:%" GST_TIME_FORMAT, + GST_TIME_ARGS (cobj->start), + GST_TIME_ARGS (cobj->stop), GST_TIME_ARGS (cobj->duration)); +} + +static void +_link_to_parent (NleComposition * comp, NleObject * newobj, + NleObject * newparent) +{ + GstPad *sinkpad; + + /* relink to new parent in required order */ + GST_LOG_OBJECT (comp, "Linking %s and %s", + GST_ELEMENT_NAME (GST_ELEMENT (newobj)), + GST_ELEMENT_NAME (GST_ELEMENT (newparent))); + + sinkpad = get_unlinked_sink_ghost_pad ((NleOperation *) newparent); + + if (G_UNLIKELY (sinkpad == NULL)) { + GST_WARNING_OBJECT (comp, + "Couldn't find an unlinked sinkpad from %s", + GST_ELEMENT_NAME (newparent)); + } else { + if (G_UNLIKELY (gst_pad_link_full (NLE_OBJECT_SRC (newobj), sinkpad, + GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) { + GST_WARNING_OBJECT (comp, "Failed to link pads %s:%s - %s:%s", + GST_DEBUG_PAD_NAME (NLE_OBJECT_SRC (newobj)), + GST_DEBUG_PAD_NAME (sinkpad)); + } + gst_object_unref (sinkpad); + } +} + +static void +_relink_children_recursively (NleComposition * comp, + NleObject * newobj, GNode * node, GstEvent * toplevel_seek) +{ + GNode *child; + guint nbchildren = g_node_n_children (node); + NleOperation *oper = (NleOperation *) newobj; + + GST_INFO_OBJECT (newobj, "is a %s operation, analyzing the %d children", + oper->dynamicsinks ? "dynamic" : "regular", nbchildren); + /* Update the operation's number of sinks, that will make it have the proper + * number of sink pads to connect the children to. */ + if (oper->dynamicsinks) + g_object_set (G_OBJECT (newobj), "sinks", nbchildren, NULL); + + for (child = node->children; child; child = child->next) + _relink_single_node (comp, child, toplevel_seek); + + if (G_UNLIKELY (nbchildren < oper->num_sinks)) + GST_ELEMENT_ERROR (comp, STREAM, FAILED, + ("The NleComposition structure is not valid"), + ("%" GST_PTR_FORMAT + " Not enough sinkpads to link all objects to the operation ! " + "%d / %d, current toplevel seek %" GST_PTR_FORMAT, + oper, oper->num_sinks, nbchildren, toplevel_seek)); + + if (G_UNLIKELY (nbchildren == 0)) { + GST_ELEMENT_ERROR (comp, STREAM, FAILED, + ("The NleComposition structure is not valid"), + ("Operation %" GST_PTR_FORMAT + " has no child objects to be connected to " + "current toplevel seek: %" GST_PTR_FORMAT, oper, toplevel_seek)); + } + /* Make sure we have enough sinkpads */ +} + +/* + * recursive depth-first relink stack function on new stack + * + * _ relink nodes with changed parent/order + * _ links new nodes with parents + * _ unblocks available source pads (except for toplevel) + * + * WITH OBJECTS LOCK TAKEN + */ +static void +_relink_single_node (NleComposition * comp, GNode * node, + GstEvent * toplevel_seek) +{ + NleObject *newobj; + NleObject *newparent; + GstPad *srcpad = NULL, *sinkpad = NULL; + + if (G_UNLIKELY (!node)) + return; + + newparent = G_NODE_IS_ROOT (node) ? NULL : (NleObject *) node->parent->data; + newobj = (NleObject *) node->data; + + GST_DEBUG_OBJECT (comp, "newobj:%s", + GST_ELEMENT_NAME ((GstElement *) newobj)); + + srcpad = NLE_OBJECT_SRC (newobj); + + gst_bin_add (GST_BIN (comp->priv->current_bin), GST_ELEMENT (newobj)); + gst_element_sync_state_with_parent (GST_ELEMENT_CAST (newobj)); + + /* link to parent if needed. */ + if (newparent) { + _link_to_parent (comp, newobj, newparent); + + /* If there's an operation, inform it about priority changes */ + sinkpad = gst_pad_get_peer (srcpad); + nle_operation_signal_input_priority_changed ((NleOperation *) + newparent, sinkpad, newobj->priority); + gst_object_unref (sinkpad); + } + + /* Handle children */ + if (NLE_IS_OPERATION (newobj)) + _relink_children_recursively (comp, newobj, node, toplevel_seek); + + GST_LOG_OBJECT (comp, "done with object %s", + GST_ELEMENT_NAME (GST_ELEMENT (newobj))); +} + + + +/* + * compare_relink_stack: + * @comp: The #NleComposition + * @stack: The new stack + * @modify: TRUE if the timeline has changed and needs downstream flushes. + * + * Compares the given stack to the current one and relinks it if needed. + * + * WITH OBJECTS LOCK TAKEN + * + * Returns: The #GList of #NleObject no longer used + */ + +static void +_deactivate_stack (NleComposition * comp, NleUpdateStackReason reason) +{ + GstPad *ptarget; + + GST_INFO_OBJECT (comp, "Deactivating current stack (reason: %s)", + UPDATE_PIPELINE_REASONS[reason]); + _set_current_bin_to_ready (comp, reason); + + ptarget = gst_ghost_pad_get_target (GST_GHOST_PAD (NLE_OBJECT_SRC (comp))); + _empty_bin (GST_BIN_CAST (comp->priv->current_bin)); + + if (comp->priv->ghosteventprobe) { + GST_INFO_OBJECT (comp, "Removing old ghost pad probe"); + + gst_pad_remove_probe (ptarget, comp->priv->ghosteventprobe); + comp->priv->ghosteventprobe = 0; + } + + if (ptarget) + gst_object_unref (ptarget); + + GST_INFO_OBJECT (comp, "Stack desctivated"); + +/* priv->current = NULL; + */ +} + +static void +_relink_new_stack (NleComposition * comp, GNode * stack, + GstEvent * toplevel_seek) +{ + _relink_single_node (comp, stack, toplevel_seek); + + gst_event_unref (toplevel_seek); +} + +/* static void + * unlock_activate_stack (NleComposition * comp, GNode * node, GstState state) + * { + * GNode *child; + * + * GST_LOG_OBJECT (comp, "object:%s", + * GST_ELEMENT_NAME ((GstElement *) (node->data))); + * + * gst_element_set_locked_state ((GstElement *) (node->data), FALSE); + * gst_element_set_state (GST_ELEMENT (node->data), state); + * + * for (child = node->children; child; child = child->next) + * unlock_activate_stack (comp, child, state); + * } + */ + +static gboolean +are_same_stacks (GNode * stack1, GNode * stack2) +{ + gboolean res = FALSE; + + /* TODO : FIXME : we should also compare start/inpoint */ + /* stacks are not equal if one of them is NULL but not the other */ + if ((!stack1 && stack2) || (stack1 && !stack2)) + goto beach; + + if (stack1 && stack2) { + GNode *child1, *child2; + + /* if they don't contain the same source, not equal */ + if (!(stack1->data == stack2->data)) + goto beach; + + /* if they don't have the same number of children, not equal */ + if (!(g_node_n_children (stack1) == g_node_n_children (stack2))) + goto beach; + + child1 = stack1->children; + child2 = stack2->children; + while (child1 && child2) { + if (!(are_same_stacks (child1, child2))) + goto beach; + child1 = g_node_next_sibling (child1); + child2 = g_node_next_sibling (child2); + } + + /* if there's a difference in child number, stacks are not equal */ + if (child1 || child2) + goto beach; + } + + /* if stack1 AND stack2 are NULL, then they're equal (both empty) */ + res = TRUE; + +beach: + GST_LOG ("Stacks are equal : %d", res); + + return res; +} + +static inline gboolean +_activate_new_stack (NleComposition * comp, GstEvent * toplevel_seek) +{ + GstPad *pad; + GstElement *topelement; + + NleCompositionPrivate *priv = comp->priv; + + if (!priv->current) { + if ((!priv->objects_start)) { + nle_composition_reset_target_pad (comp); + priv->current_stack_start = 0; + priv->current_stack_stop = GST_CLOCK_TIME_NONE; + } + + GST_DEBUG_OBJECT (comp, "Nothing else in the composition" + ", update 'worked'"); + gst_event_unref (toplevel_seek); + goto resync_state; + } + + /* The stack is entirely ready, stack initializing seek once ready */ + GST_INFO_OBJECT (comp, "Activating stack with seek: %" GST_PTR_FORMAT, + toplevel_seek); + + if (!toplevel_seek) { + GST_INFO_OBJECT (comp, + "This is a sub composition, not seeking to initialize stack"); + g_atomic_int_set (&priv->send_stream_start, TRUE); + } else { + GST_INFO_OBJECT (comp, "Needs seeking to initialize stack"); + comp->priv->stack_initialization_seek = toplevel_seek; + } + + topelement = GST_ELEMENT (priv->current->data); + /* Get toplevel object source pad */ + pad = NLE_OBJECT_SRC (topelement); + + GST_INFO_OBJECT (comp, + "We have a valid toplevel element pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + + nle_composition_ghost_pad_set_target (comp, pad); + + GST_DEBUG_OBJECT (comp, "New stack activated!"); + +resync_state: + if (toplevel_seek) + g_atomic_int_set (&priv->stack_initialization_seek_sent, FALSE); + gst_element_set_locked_state (priv->current_bin, FALSE); + + GST_DEBUG ("going back to parent state"); + priv->suppress_child_error = TRUE; + if (!gst_element_sync_state_with_parent (priv->current_bin)) { + gst_element_set_locked_state (priv->current_bin, TRUE); + gst_element_set_state (priv->current_bin, GST_STATE_NULL); + priv->suppress_child_error = FALSE; + + GST_ELEMENT_ERROR (comp, CORE, STATE_CHANGE, (NULL), + ("Could not sync %" GST_PTR_FORMAT " state with parent", + priv->current_bin)); + return FALSE; + } + + priv->suppress_child_error = FALSE; + GST_DEBUG ("gone back to parent state"); + + return TRUE; +} + +static void +_set_real_eos_seqnum_from_seek (NleComposition * comp, GstEvent * event) +{ + GList *tmp; + + NleCompositionPrivate *priv = comp->priv; + gboolean reverse = (priv->segment->rate < 0); + gint stack_seqnum = gst_event_get_seqnum (event); + + if (reverse) { + if (!GST_CLOCK_TIME_IS_VALID (priv->current_stack_start)) + goto done; + + if (priv->segment->start != 0 && + priv->current_stack_start <= priv->segment->start + && priv->current_stack_stop > priv->segment->start) { + goto done; + } + } else { + if (!GST_CLOCK_TIME_IS_VALID (priv->current_stack_stop)) + goto done; + + if (GST_CLOCK_TIME_IS_VALID (priv->seek_segment->stop) && + priv->current_stack_start <= priv->segment->stop + && priv->current_stack_stop >= priv->segment->stop) { + goto done; + } + } + + for (tmp = priv->objects_stop; tmp; tmp = g_list_next (tmp)) { + NleObject *object = (NleObject *) tmp->data; + + if (!NLE_IS_SOURCE (object)) + continue; + + if ((!reverse && priv->current_stack_stop < object->stop) || + (reverse && priv->current_stack_start > object->start)) { + priv->next_eos_seqnum = stack_seqnum; + g_atomic_int_set (&priv->real_eos_seqnum, 0); + return; + } + } + +done: + priv->next_eos_seqnum = stack_seqnum; + g_atomic_int_set (&priv->real_eos_seqnum, stack_seqnum); +} + +#ifndef GST_DISABLE_GST_DEBUG +static gboolean +_print_stack (GNode * node, gpointer res) +{ + NleObject *obj = NLE_OBJECT (node->data); + gint i; + + for (i = 0; i < (g_node_depth (node) - 1) * 4; ++i) + g_string_append_c ((GString *) res, ' '); + + g_string_append_printf ((GString *) res, + "%s [s=%" GST_TIME_FORMAT " - d=%" GST_TIME_FORMAT "] prio=%d\n", + GST_OBJECT_NAME (obj), + GST_TIME_ARGS (NLE_OBJECT_START (obj)), + GST_TIME_ARGS (NLE_OBJECT_STOP (obj)), obj->priority); + + return FALSE; +} +#endif + +static void +_dump_stack (NleComposition * comp, NleUpdateStackReason update_reason, + GNode * stack) +{ +#ifndef GST_DISABLE_GST_DEBUG + GString *res; + + if (!stack) + return; + + if (gst_debug_category_get_threshold (nlecomposition_debug) < GST_LEVEL_INFO) + return; + + res = g_string_new (NULL); + g_string_append_printf (res, + " ====> dumping stack [%" GST_TIME_FORMAT " - %" GST_TIME_FORMAT + "] (%s):\n", GST_TIME_ARGS (comp->priv->current_stack_start), + GST_TIME_ARGS (comp->priv->current_stack_stop), + UPDATE_PIPELINE_REASONS[update_reason]); + g_node_traverse (stack, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, _print_stack, res); + + GST_INFO_OBJECT (comp, "%s", res->str); + g_string_free (res, TRUE); +#endif +} + +static gboolean +nle_composition_query_needs_teardown (NleComposition * comp, + NleUpdateStackReason reason) +{ + gboolean res = FALSE; + GstStructure *structure = + gst_structure_new ("NleCompositionQueryNeedsTearDown", "reason", + G_TYPE_STRING, UPDATE_PIPELINE_REASONS[reason], NULL); + GstQuery *query = gst_query_new_custom (GST_QUERY_CUSTOM, structure); + + gst_pad_query (NLE_OBJECT_SRC (comp), query); + gst_structure_get_boolean (structure, "result", &res); + + gst_query_unref (query); + return res; +} + +/* + * update_pipeline: + * @comp: The #NleComposition + * @currenttime: The #GstClockTime to update at, can be GST_CLOCK_TIME_NONE. + * @update_reason: Reason why we are updating the pipeline + * + * Updates the internal pipeline and properties. If @currenttime is + * GST_CLOCK_TIME_NONE, it will not modify the current pipeline + * + * Returns: FALSE if there was an error updating the pipeline. + * + * WITH OBJECTS LOCK TAKEN + */ +static gboolean +update_pipeline (NleComposition * comp, GstClockTime currenttime, gint32 seqnum, + NleUpdateStackReason update_reason) +{ + + GstEvent *toplevel_seek; + + GNode *stack = NULL; + gboolean tear_down = FALSE; + gboolean updatestoponly = FALSE; + GstState state = GST_STATE (comp); + NleCompositionPrivate *priv = comp->priv; + GstClockTime new_stop = GST_CLOCK_TIME_NONE; + GstClockTime new_start = GST_CLOCK_TIME_NONE; + GstClockTime duration = NLE_OBJECT (comp)->duration - 1; + + GstState nextstate = (GST_STATE_NEXT (comp) == GST_STATE_VOID_PENDING) ? + GST_STATE (comp) : GST_STATE_NEXT (comp); + + _assert_proper_thread (comp); + + if (currenttime >= duration) { + currenttime = duration; + priv->segment->start = GST_CLOCK_TIME_NONE; + priv->segment->stop = GST_CLOCK_TIME_NONE; + } + + GST_INFO_OBJECT (comp, + "currenttime:%" GST_TIME_FORMAT + " Reason: %s, Seqnum: %i", GST_TIME_ARGS (currenttime), + UPDATE_PIPELINE_REASONS[update_reason], seqnum); + + if (!GST_CLOCK_TIME_IS_VALID (currenttime)) + return FALSE; + + if (state == GST_STATE_NULL && nextstate == GST_STATE_NULL) { + GST_DEBUG_OBJECT (comp, "STATE_NULL: not updating pipeline"); + return FALSE; + } + + GST_DEBUG_OBJECT (comp, + "now really updating the pipeline, current-state:%s", + gst_element_state_get_name (state)); + + /* Get new stack and compare it to current one */ + stack = get_clean_toplevel_stack (comp, ¤ttime, &new_start, &new_stop); + tear_down = !are_same_stacks (priv->current, stack) + || nle_composition_query_needs_teardown (comp, update_reason); + + /* set new current_stack_start/stop (the current zone over which the new stack + * is valid) */ + if (priv->segment->rate >= 0.0) { + priv->current_stack_start = currenttime; + priv->current_stack_stop = new_stop; + } else { + priv->current_stack_start = new_start; + priv->current_stack_stop = currenttime; + } + +# if 0 + /* FIXME -- We should be ablt to use updatestoponly in that case, + * but it simply does not work! Not using it leads to same + * behaviour, but less optimized */ + + gboolean startchanged, stopchanged; + + if (priv->segment->rate >= 0.0) { + startchanged = priv->current_stack_start != currenttime; + stopchanged = priv->current_stack_stop != new_stop; + } else { + startchanged = priv->current_stack_start != new_start; + stopchanged = priv->current_stack_stop != currenttime; + } + + if (!tear_down) { + if (startchanged || stopchanged) { + /* Update seek events need to be flushing if not in PLAYING, + * else we will encounter deadlocks. */ + updatestoponly = (state == GST_STATE_PLAYING) ? FALSE : TRUE; + } + } +#endif + + toplevel_seek = + get_new_seek_event (comp, TRUE, updatestoponly, update_reason); + gst_event_set_seqnum (toplevel_seek, seqnum); + _set_real_eos_seqnum_from_seek (comp, toplevel_seek); + + _remove_update_actions (comp); + + /* If stacks are different, unlink/relink objects */ + if (tear_down) { + _dump_stack (comp, update_reason, stack); + _deactivate_stack (comp, update_reason); + _relink_new_stack (comp, stack, gst_event_ref (toplevel_seek)); + } + + /* Unlock all elements in new stack */ + GST_INFO_OBJECT (comp, "Setting current stack [%" GST_TIME_FORMAT " - %" + GST_TIME_FORMAT "]", GST_TIME_ARGS (priv->current_stack_start), + GST_TIME_ARGS (priv->current_stack_stop)); + + if (priv->current) + g_node_destroy (priv->current); + + priv->current = stack; + + if (priv->current) { + + GST_INFO_OBJECT (comp, "New stack set and ready to run, probing src pad" + " and stopping children thread until we are actually ready with" + " that new stack"); + + comp->priv->updating_reason = update_reason; + comp->priv->seqnum_to_restart_task = seqnum; + + /* Subcomposition can preroll without sending initializing seeks + * as the toplevel composition will send it anyway. + * + * This avoid seeking round trips (otherwise we get 1 extra seek + * per level of nesting) + */ + + if (tear_down && !nle_composition_needs_topelevel_initializing_seek (comp)) + gst_clear_event (&toplevel_seek); + + if (toplevel_seek) { + if (!_pause_task (comp)) { + gst_event_unref (toplevel_seek); + return FALSE; + } + } else { + GST_INFO_OBJECT (comp, "Not pausing composition when first initializing"); + } + } + + /* Activate stack */ + if (tear_down) + return _activate_new_stack (comp, toplevel_seek); + return _seek_current_stack (comp, toplevel_seek, + _have_to_flush_downstream (update_reason)); +} + +static gboolean +nle_composition_add_object (GstBin * bin, GstElement * element) +{ + NleObject *object; + NleComposition *comp = (NleComposition *) bin; + + if (element == comp->priv->current_bin) { + GST_INFO_OBJECT (comp, "Adding internal bin"); + return GST_BIN_CLASS (parent_class)->add_element (bin, element); + } + + g_return_val_if_fail (NLE_IS_OBJECT (element), FALSE); + + object = NLE_OBJECT (element); + gst_object_ref_sink (object); + + object->in_composition = TRUE; + _add_add_object_action (comp, object); + + return TRUE; +} + +static gboolean +_nle_composition_add_object (NleComposition * comp, NleObject * object) +{ + gboolean ret = TRUE; + NleCompositionPrivate *priv = comp->priv; + + GST_DEBUG_OBJECT (comp, "element %s", GST_OBJECT_NAME (object)); + GST_DEBUG_OBJECT (object, "%" GST_TIME_FORMAT "--%" GST_TIME_FORMAT, + GST_TIME_ARGS (NLE_OBJECT_START (object)), + GST_TIME_ARGS (NLE_OBJECT_STOP (object))); + + if ((NLE_OBJECT_IS_EXPANDABLE (object)) && + g_list_find (priv->expandables, object)) { + GST_WARNING_OBJECT (comp, + "We already have an expandable, remove it before adding new one"); + ret = FALSE; + + goto chiringuito; + } + + nle_object_set_caps (object, NLE_OBJECT (comp)->caps); + nle_object_set_commit_needed (NLE_OBJECT (comp)); + + if (!ret) { + GST_WARNING_OBJECT (comp, "couldn't add object"); + goto chiringuito; + } + + /* lock state of child ! */ + GST_LOG_OBJECT (comp, "Locking state of %s", GST_ELEMENT_NAME (object)); + + if (NLE_OBJECT_IS_EXPANDABLE (object)) { + /* Only react on non-default objects properties */ + g_object_set (object, + "start", (GstClockTime) 0, + "inpoint", (GstClockTime) 0, + "duration", (GstClockTimeDiff) NLE_OBJECT_STOP (comp), NULL); + + GST_INFO_OBJECT (object, "Used as expandable, commiting now"); + nle_object_commit (NLE_OBJECT (object), FALSE); + } + + /* ...and add it to the hash table */ + g_hash_table_add (priv->objects_hash, object); + + /* Set the caps of the composition on the NleObject it handles */ + if (G_UNLIKELY (!gst_caps_is_any (((NleObject *) comp)->caps))) + nle_object_set_caps ((NleObject *) object, ((NleObject *) comp)->caps); + + /* Special case for default source. */ + if (NLE_OBJECT_IS_EXPANDABLE (object)) { + /* It doesn't get added to objects_start and objects_stop. */ + priv->expandables = g_list_prepend (priv->expandables, object); + goto beach; + } + + /* add it sorted to the objects list */ + priv->objects_start = g_list_insert_sorted + (priv->objects_start, object, (GCompareFunc) objects_start_compare); + + if (priv->objects_start) + GST_LOG_OBJECT (comp, + "Head of objects_start is now %s [%" GST_TIME_FORMAT "--%" + GST_TIME_FORMAT "]", + GST_OBJECT_NAME (priv->objects_start->data), + GST_TIME_ARGS (NLE_OBJECT_START (priv->objects_start->data)), + GST_TIME_ARGS (NLE_OBJECT_STOP (priv->objects_start->data))); + + priv->objects_stop = g_list_insert_sorted + (priv->objects_stop, object, (GCompareFunc) objects_stop_compare); + + /* Now the object is ready to be commited and then used */ + +beach: + return ret; + +chiringuito: + { + update_start_stop_duration (comp); + goto beach; + } +} + +static gboolean +nle_composition_remove_object (GstBin * bin, GstElement * element) +{ + NleObject *object; + NleComposition *comp = (NleComposition *) bin; + + if (element == comp->priv->current_bin) { + GST_INFO_OBJECT (comp, "Removing internal bin"); + return GST_BIN_CLASS (parent_class)->remove_element (bin, element); + } + + g_return_val_if_fail (NLE_IS_OBJECT (element), FALSE); + + object = NLE_OBJECT (element); + + _add_remove_object_action (comp, object); + + return TRUE; +} + +static gboolean +_nle_composition_remove_object (NleComposition * comp, NleObject * object) +{ + NleCompositionPrivate *priv = comp->priv; + + GST_DEBUG_OBJECT (comp, "removing object %s", GST_OBJECT_NAME (object)); + + if (!g_hash_table_contains (priv->objects_hash, object)) { + GST_INFO_OBJECT (comp, "object was not in composition"); + return FALSE; + } + + gst_element_set_locked_state (GST_ELEMENT (object), FALSE); + gst_element_set_state (GST_ELEMENT (object), GST_STATE_NULL); + + /* handle default source */ + if (NLE_OBJECT_IS_EXPANDABLE (object)) { + /* Find it in the list */ + priv->expandables = g_list_remove (priv->expandables, object); + } else { + /* remove it from the objects list and resort the lists */ + priv->objects_start = g_list_remove (priv->objects_start, object); + priv->objects_stop = g_list_remove (priv->objects_stop, object); + GST_LOG_OBJECT (object, "Removed from the objects start/stop list"); + } + + if (priv->current && NLE_OBJECT (priv->current->data) == NLE_OBJECT (object)) + nle_composition_reset_target_pad (comp); + + g_hash_table_remove (priv->objects_hash, object); + + GST_LOG_OBJECT (object, "Done removing from the composition, now updating"); + + /* Make it possible to reuse the same object later */ + nle_object_reset (NLE_OBJECT (object)); + gst_object_unref (object); + + return TRUE; +} diff --git a/plugins/nle/nlecomposition.h b/plugins/nle/nlecomposition.h new file mode 100644 index 0000000000..9f5da3d8c3 --- /dev/null +++ b/plugins/nle/nlecomposition.h @@ -0,0 +1,66 @@ +/* GStreamer + * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com> + * 2004-2008 Edward Hervey <bilboed@bilboed.com> + * + * nlecomposition.h: Header for base NleComposition + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __NLE_COMPOSITION_H__ +#define __NLE_COMPOSITION_H__ + +#include <gst/gst.h> +#include "nleobject.h" + +G_BEGIN_DECLS +#define NLE_TYPE_COMPOSITION \ + (nle_composition_get_type()) +#define NLE_COMPOSITION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),NLE_TYPE_COMPOSITION,NleComposition)) +#define NLE_COMPOSITION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),NLE_TYPE_COMPOSITION,NleCompositionClass)) +#define NLE_COMPOSITION_GET_CLASS(obj) \ + (NLE_COMPOSITION_CLASS (G_OBJECT_GET_CLASS (obj))) +#define NLE_IS_COMPOSITION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),NLE_TYPE_COMPOSITION)) +#define NLE_IS_COMPOSITION_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),NLE_TYPE_COMPOSITION)) + +typedef struct _NleCompositionPrivate NleCompositionPrivate; + +struct _NleComposition +{ + NleObject parent; + + GstTask * task; + GRecMutex task_rec_lock; + + /*< private >*/ + NleCompositionPrivate * priv; + +}; + +struct _NleCompositionClass +{ + NleObjectClass parent_class; +}; + +GType nle_composition_get_type (void) G_GNUC_INTERNAL; + +G_END_DECLS +#endif /* __NLE_COMPOSITION_H__ */ diff --git a/plugins/nle/nleghostpad.c b/plugins/nle/nleghostpad.c new file mode 100644 index 0000000000..cb1e5ec24d --- /dev/null +++ b/plugins/nle/nleghostpad.c @@ -0,0 +1,812 @@ +/* Gnonlin + * Copyright (C) <2009> Edward Hervey <bilboed@bilboed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "nle.h" + +GST_DEBUG_CATEGORY_STATIC (nleghostpad); +#define GST_CAT_DEFAULT nleghostpad + +typedef struct _NlePadPrivate NlePadPrivate; + +struct _NlePadPrivate +{ + NleObject *object; + NlePadPrivate *ghostpriv; + GstPadDirection dir; + GstPadEventFunction eventfunc; + GstPadQueryFunction queryfunc; + + GstEvent *pending_seek; +}; + +/** + * nle_object_translate_incoming_seek: + * @object: A #NleObject. + * @event: (transfer full) A #GstEvent to translate + * + * Returns: (transfer full) new translated seek event + */ +GstEvent * +nle_object_translate_incoming_seek (NleObject * object, GstEvent * event) +{ + GstEvent *event2; + GstFormat format; + gdouble rate; + GstSeekFlags flags; + GstSeekType curtype, stoptype; + GstSeekType ncurtype; + gint64 cur; + guint64 ncur; + gint64 stop; + guint64 nstop; + guint32 seqnum = GST_EVENT_SEQNUM (event); + + gst_event_parse_seek (event, &rate, &format, &flags, + &curtype, &cur, &stoptype, &stop); + + GST_DEBUG_OBJECT (object, + "GOT SEEK rate:%f, format:%d, flags:%d, curtype:%d, stoptype:%d, %" + GST_TIME_FORMAT " -- %" GST_TIME_FORMAT, rate, format, flags, curtype, + stoptype, GST_TIME_ARGS (cur), GST_TIME_ARGS (stop)); + + if (G_UNLIKELY (format != GST_FORMAT_TIME)) + goto invalid_format; + + /* convert cur */ + ncurtype = GST_SEEK_TYPE_SET; + if (G_LIKELY ((curtype == GST_SEEK_TYPE_SET) + && (nle_object_to_media_time (object, cur, &ncur)))) { + /* cur is TYPE_SET and value is valid */ + if (ncur > G_MAXINT64) + GST_WARNING_OBJECT (object, "return value too big..."); + GST_LOG_OBJECT (object, "Setting cur to %" GST_TIME_FORMAT, + GST_TIME_ARGS (ncur)); + } else if ((curtype != GST_SEEK_TYPE_NONE)) { + GST_DEBUG_OBJECT (object, "Limiting seek start to inpoint"); + ncur = object->inpoint; + } else { + GST_DEBUG_OBJECT (object, "leaving GST_SEEK_TYPE_NONE"); + ncur = cur; + ncurtype = GST_SEEK_TYPE_NONE; + } + + /* convert stop, we also need to limit it to object->stop */ + if (G_LIKELY ((stoptype == GST_SEEK_TYPE_SET) + && (nle_object_to_media_time (object, stop, &nstop)))) { + if (nstop > G_MAXINT64) + GST_WARNING_OBJECT (object, "return value too big..."); + GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT, + GST_TIME_ARGS (nstop)); + } else { + /* NOTE: for an element that is upstream from a time effect we do not + * want to limit the seek to the object->stop because a time effect + * can reasonably increase the final time. + * In such situations, the seek stop should be set once by the + * source pad of the most downstream object (highest priority in the + * nlecomposition). */ + GST_DEBUG_OBJECT (object, "Limiting end of seek to media_stop"); + nle_object_to_media_time (object, object->stop, &nstop); + if (nstop > G_MAXINT64) + GST_WARNING_OBJECT (object, "return value too big..."); + GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT, + GST_TIME_ARGS (nstop)); + } + + + /* add accurate seekflags */ + if (G_UNLIKELY (!(flags & GST_SEEK_FLAG_ACCURATE))) { + GST_DEBUG_OBJECT (object, "Adding GST_SEEK_FLAG_ACCURATE"); + flags |= GST_SEEK_FLAG_ACCURATE; + } else { + GST_DEBUG_OBJECT (object, + "event already has GST_SEEK_FLAG_ACCURATE : %d", flags); + } + + + + GST_DEBUG_OBJECT (object, + "SENDING SEEK rate:%f, format:TIME, flags:%d, curtype:%d, stoptype:SET, %" + GST_TIME_FORMAT " -- %" GST_TIME_FORMAT, rate, flags, ncurtype, + GST_TIME_ARGS (ncur), GST_TIME_ARGS (nstop)); + + event2 = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, + ncurtype, (gint64) ncur, GST_SEEK_TYPE_SET, (gint64) nstop); + GST_EVENT_SEQNUM (event2) = seqnum; + gst_event_unref (event); + + return event2; + + /* ERRORS */ +invalid_format: + { + GST_WARNING ("GNonLin time shifting only works with GST_FORMAT_TIME"); + return event; + } +} + +static GstEvent * +translate_outgoing_seek (NleObject * object, GstEvent * event) +{ + GstEvent *event2; + GstFormat format; + gdouble rate; + GstSeekFlags flags; + GstSeekType curtype, stoptype; + GstSeekType ncurtype; + gint64 cur; + guint64 ncur; + gint64 stop; + guint64 nstop; + guint32 seqnum = GST_EVENT_SEQNUM (event); + + gst_event_parse_seek (event, &rate, &format, &flags, + &curtype, &cur, &stoptype, &stop); + + GST_DEBUG_OBJECT (object, + "GOT SEEK rate:%f, format:%d, flags:%d, curtype:%d, stoptype:%d, %" + GST_TIME_FORMAT " -- %" GST_TIME_FORMAT, rate, format, flags, curtype, + stoptype, GST_TIME_ARGS (cur), GST_TIME_ARGS (stop)); + + if (G_UNLIKELY (format != GST_FORMAT_TIME)) + goto invalid_format; + + /* convert cur */ + ncurtype = GST_SEEK_TYPE_SET; + if (G_LIKELY ((curtype == GST_SEEK_TYPE_SET) + && (nle_media_to_object_time (object, cur, &ncur)))) { + /* cur is TYPE_SET and value is valid */ + if (ncur > G_MAXINT64) + GST_WARNING_OBJECT (object, "return value too big..."); + GST_LOG_OBJECT (object, "Setting cur to %" GST_TIME_FORMAT, + GST_TIME_ARGS (ncur)); + } else if ((curtype != GST_SEEK_TYPE_NONE)) { + GST_DEBUG_OBJECT (object, "Limiting seek start to start"); + ncur = object->start; + } else { + GST_DEBUG_OBJECT (object, "leaving GST_SEEK_TYPE_NONE"); + ncur = cur; + ncurtype = GST_SEEK_TYPE_NONE; + } + + /* convert stop, we also need to limit it to object->stop */ + if (G_LIKELY ((stoptype == GST_SEEK_TYPE_SET) + && (nle_media_to_object_time (object, stop, &nstop)))) { + if (nstop > G_MAXINT64) + GST_WARNING_OBJECT (object, "return value too big..."); + GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT, + GST_TIME_ARGS (nstop)); + } else { + /* NOTE: when we have time effects, the object stop is not the + * correct stop limit. Therefore, the seek stop time should already + * be set at this point */ + GST_DEBUG_OBJECT (object, "Limiting end of seek to stop"); + nstop = object->stop; + if (nstop > G_MAXINT64) + GST_WARNING_OBJECT (object, "return value too big..."); + GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT, + GST_TIME_ARGS (nstop)); + } + + GST_DEBUG_OBJECT (object, + "SENDING SEEK rate:%f, format:TIME, flags:%d, curtype:%d, stoptype:SET, %" + GST_TIME_FORMAT " -- %" GST_TIME_FORMAT, rate, flags, ncurtype, + GST_TIME_ARGS (ncur), GST_TIME_ARGS (nstop)); + + event2 = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, + ncurtype, (gint64) ncur, GST_SEEK_TYPE_SET, (gint64) nstop); + GST_EVENT_SEQNUM (event2) = seqnum; + + gst_event_unref (event); + + return event2; + + /* ERRORS */ +invalid_format: + { + GST_WARNING ("GNonLin time shifting only works with GST_FORMAT_TIME"); + return event; + } +} + +static GstEvent * +translate_outgoing_segment (NleObject * object, GstEvent * event) +{ + const GstSegment *orig; + GstSegment segment; + GstEvent *event2; + guint32 seqnum = GST_EVENT_SEQNUM (event); + + /* only modify the streamtime */ + gst_event_parse_segment (event, &orig); + + GST_DEBUG_OBJECT (object, "Got SEGMENT %" GST_SEGMENT_FORMAT, orig); + + if (G_UNLIKELY (orig->format != GST_FORMAT_TIME)) { + GST_WARNING_OBJECT (object, + "Can't translate segments with format != GST_FORMAT_TIME"); + return event; + } + + gst_segment_copy_into (orig, &segment); + + nle_media_to_object_time (object, orig->time, &segment.time); + + if (G_UNLIKELY (segment.time > G_MAXINT64)) + GST_WARNING_OBJECT (object, "Return value too big..."); + + GST_DEBUG_OBJECT (object, "Sending SEGMENT %" GST_SEGMENT_FORMAT, &segment); + + event2 = gst_event_new_segment (&segment); + GST_EVENT_SEQNUM (event2) = seqnum; + gst_event_unref (event); + + return event2; +} + +static GstEvent * +translate_incoming_segment (NleObject * object, GstEvent * event) +{ + GstEvent *event2; + const GstSegment *orig; + GstSegment segment; + guint32 seqnum = GST_EVENT_SEQNUM (event); + + /* only modify the streamtime */ + gst_event_parse_segment (event, &orig); + + GST_DEBUG_OBJECT (object, + "Got SEGMENT %" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT " // %" + GST_TIME_FORMAT, GST_TIME_ARGS (orig->start), GST_TIME_ARGS (orig->stop), + GST_TIME_ARGS (orig->time)); + + if (G_UNLIKELY (orig->format != GST_FORMAT_TIME)) { + GST_WARNING_OBJECT (object, + "Can't translate segments with format != GST_FORMAT_TIME"); + return event; + } + + gst_segment_copy_into (orig, &segment); + + if (!nle_object_to_media_time (object, orig->time, &segment.time)) { + GST_DEBUG ("Can't convert media_time, using 0"); + segment.time = 0; + }; + + if (G_UNLIKELY (segment.time > G_MAXINT64)) + GST_WARNING_OBJECT (object, "Return value too big..."); + + GST_DEBUG_OBJECT (object, + "Sending SEGMENT %" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT " // %" + GST_TIME_FORMAT, GST_TIME_ARGS (segment.start), + GST_TIME_ARGS (segment.stop), GST_TIME_ARGS (segment.time)); + + event2 = gst_event_new_segment (&segment); + GST_EVENT_SEQNUM (event2) = seqnum; + gst_event_unref (event); + + return event2; +} + +static gboolean +internalpad_event_function (GstPad * internal, GstObject * parent, + GstEvent * event) +{ + NlePadPrivate *priv = gst_pad_get_element_private (internal); + NleObject *object = priv->object; + gboolean res; + + GST_DEBUG_OBJECT (internal, "event:%s (seqnum::%d)", + GST_EVENT_TYPE_NAME (event), GST_EVENT_SEQNUM (event)); + + if (G_UNLIKELY (!(priv->eventfunc))) { + GST_WARNING_OBJECT (internal, + "priv->eventfunc == NULL !! What is going on ?"); + return FALSE; + } + + switch (priv->dir) { + case GST_PAD_SRC:{ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEGMENT: + event = translate_outgoing_segment (object, event); + break; + case GST_EVENT_EOS: + break; + default: + break; + } + + break; + } + case GST_PAD_SINK:{ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + event = translate_outgoing_seek (object, event); + break; + default: + break; + } + break; + } + default: + break; + } + GST_DEBUG_OBJECT (internal, "Calling priv->eventfunc %p", priv->eventfunc); + res = priv->eventfunc (internal, parent, event); + + return res; +} + +/* + translate_outgoing_position_query + + Should only be called: + _ if the query is a GST_QUERY_POSITION + _ after the query was sent upstream + _ if the upstream query returned TRUE +*/ + +static gboolean +translate_incoming_position_query (NleObject * object, GstQuery * query) +{ + GstFormat format; + gint64 cur, cur2; + + gst_query_parse_position (query, &format, &cur); + if (G_UNLIKELY (format != GST_FORMAT_TIME)) { + GST_WARNING_OBJECT (object, + "position query is in a format different from time, returning without modifying values"); + goto beach; + } + + nle_media_to_object_time (object, (guint64) cur, (guint64 *) & cur2); + + GST_DEBUG_OBJECT (object, + "Adjust position from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT, + GST_TIME_ARGS (cur), GST_TIME_ARGS (cur2)); + gst_query_set_position (query, GST_FORMAT_TIME, cur2); + +beach: + return TRUE; +} + +static gboolean +translate_outgoing_position_query (NleObject * object, GstQuery * query) +{ + GstFormat format; + gint64 cur, cur2; + + gst_query_parse_position (query, &format, &cur); + if (G_UNLIKELY (format != GST_FORMAT_TIME)) { + GST_WARNING_OBJECT (object, + "position query is in a format different from time, returning without modifying values"); + goto beach; + } + + if (G_UNLIKELY (!(nle_object_to_media_time (object, (guint64) cur, + (guint64 *) & cur2)))) { + GST_WARNING_OBJECT (object, + "Couldn't get media time for %" GST_TIME_FORMAT, GST_TIME_ARGS (cur)); + goto beach; + } + + GST_DEBUG_OBJECT (object, + "Adjust position from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT, + GST_TIME_ARGS (cur), GST_TIME_ARGS (cur2)); + gst_query_set_position (query, GST_FORMAT_TIME, cur2); + +beach: + return TRUE; +} + +static gboolean +translate_incoming_duration_query (NleObject * object, GstQuery * query) +{ + GstFormat format; + gint64 cur; + + gst_query_parse_duration (query, &format, &cur); + if (G_UNLIKELY (format != GST_FORMAT_TIME)) { + GST_WARNING_OBJECT (object, + "We can only handle duration queries in GST_FORMAT_TIME"); + return FALSE; + } + + /* NOTE: returns the duration of the object, but this is not the same + * as the source duration when time effects are used. Nor is it the + * duration of the current nlecomposition stack */ + gst_query_set_duration (query, GST_FORMAT_TIME, object->duration); + + return TRUE; +} + +static gboolean +internalpad_query_function (GstPad * internal, GstObject * parent, + GstQuery * query) +{ + NlePadPrivate *priv = gst_pad_get_element_private (internal); + NleObject *object = priv->object; + gboolean ret; + + GST_DEBUG_OBJECT (internal, "querytype:%s", + gst_query_type_get_name (GST_QUERY_TYPE (query))); + + if (!(priv->queryfunc)) { + GST_WARNING_OBJECT (internal, + "priv->queryfunc == NULL !! What is going on ?"); + return FALSE; + } + + if ((ret = priv->queryfunc (internal, parent, query))) { + + switch (priv->dir) { + case GST_PAD_SRC: + break; + case GST_PAD_SINK: + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + ret = translate_outgoing_position_query (object, query); + break; + default: + break; + } + break; + default: + break; + } + } + return ret; +} + +static gboolean +ghostpad_event_function (GstPad * ghostpad, GstObject * parent, + GstEvent * event) +{ + NlePadPrivate *priv; + NleObject *object; + gboolean ret = FALSE; + + priv = gst_pad_get_element_private (ghostpad); + object = priv->object; + + GST_DEBUG_OBJECT (ghostpad, "event:%s", GST_EVENT_TYPE_NAME (event)); + + if (G_UNLIKELY (priv->eventfunc == NULL)) + goto no_function; + + switch (priv->dir) { + case GST_PAD_SRC: + { + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + { + GstPad *target; + + event = nle_object_translate_incoming_seek (object, event); + if (!(target = gst_ghost_pad_get_target (GST_GHOST_PAD (ghostpad)))) { + g_assert ("Seeked a pad with no target SHOULD NOT HAPPEN"); + ret = FALSE; + gst_event_unref (event); + event = NULL; + } else { + gst_object_unref (target); + } + } + break; + default: + break; + } + } + break; + case GST_PAD_SINK:{ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEGMENT: + event = translate_incoming_segment (object, event); + break; + default: + break; + } + } + break; + default: + break; + } + + if (event) { + GST_DEBUG_OBJECT (ghostpad, "Calling priv->eventfunc"); + ret = priv->eventfunc (ghostpad, parent, event); + GST_DEBUG_OBJECT (ghostpad, "Returned from calling priv->eventfunc : %d", + ret); + } + + return ret; + + /* ERRORS */ +no_function: + { + GST_WARNING_OBJECT (ghostpad, + "priv->eventfunc == NULL !! What's going on ?"); + return FALSE; + } +} + +static gboolean +ghostpad_query_function (GstPad * ghostpad, GstObject * parent, + GstQuery * query) +{ + NlePadPrivate *priv = gst_pad_get_element_private (ghostpad); + NleObject *object = NLE_OBJECT (parent); + gboolean pret = TRUE; + + GST_DEBUG_OBJECT (ghostpad, "querytype:%s", GST_QUERY_TYPE_NAME (query)); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_DURATION: + /* skip duration upstream query, we'll fill it in ourselves */ + break; + default: + pret = priv->queryfunc (ghostpad, parent, query); + } + + if (pret) { + /* translate result */ + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + pret = translate_incoming_position_query (object, query); + break; + case GST_QUERY_DURATION: + pret = translate_incoming_duration_query (object, query); + break; + default: + break; + } + } + + return pret; +} + +/* internal pad going away */ +static void +internal_pad_finalizing (NlePadPrivate * priv, GObject * pad G_GNUC_UNUSED) +{ + g_slice_free (NlePadPrivate, priv); +} + +static inline GstPad * +get_proxy_pad (GstPad * ghostpad) +{ + GValue item = { 0, }; + GstIterator *it; + GstPad *ret = NULL; + + it = gst_pad_iterate_internal_links (ghostpad); + g_assert (it); + gst_iterator_next (it, &item); + ret = g_value_dup_object (&item); + g_value_unset (&item); + g_assert (ret); + gst_iterator_free (it); + + return ret; +} + +static void +control_internal_pad (GstPad * ghostpad, NleObject * object) +{ + NlePadPrivate *priv; + NlePadPrivate *privghost; + GstPad *internal; + + if (!ghostpad) { + GST_DEBUG_OBJECT (object, "We don't have a valid ghostpad !"); + return; + } + privghost = gst_pad_get_element_private (ghostpad); + + GST_LOG_OBJECT (ghostpad, "overriding ghostpad's internal pad function"); + + internal = get_proxy_pad (ghostpad); + + if (G_UNLIKELY (!(priv = gst_pad_get_element_private (internal)))) { + GST_DEBUG_OBJECT (internal, + "Creating a NlePadPrivate to put in element_private"); + priv = g_slice_new0 (NlePadPrivate); + + /* Remember existing pad functions */ + priv->eventfunc = GST_PAD_EVENTFUNC (internal); + priv->queryfunc = GST_PAD_QUERYFUNC (internal); + gst_pad_set_element_private (internal, priv); + + g_object_weak_ref ((GObject *) internal, + (GWeakNotify) internal_pad_finalizing, priv); + + /* add query/event function overrides on internal pad */ + gst_pad_set_event_function (internal, + GST_DEBUG_FUNCPTR (internalpad_event_function)); + gst_pad_set_query_function (internal, + GST_DEBUG_FUNCPTR (internalpad_query_function)); + } + + priv->object = object; + priv->ghostpriv = privghost; + priv->dir = GST_PAD_DIRECTION (ghostpad); + gst_object_unref (internal); + + GST_DEBUG_OBJECT (ghostpad, "Done with pad %s:%s", + GST_DEBUG_PAD_NAME (ghostpad)); +} + + +/** + * nle_object_ghost_pad: + * @object: #NleObject to add the ghostpad to + * @name: Name for the new pad + * @target: Target #GstPad to ghost + * + * Adds a #GstGhostPad overridding the correct pad [query|event]_function so + * that time shifting is done correctly + * The #GstGhostPad is added to the #NleObject + * + * /!\ This function doesn't check if the existing [src|sink] pad was removed + * first, so you might end up with more pads than wanted + * + * Returns: The #GstPad if everything went correctly, else NULL. + */ +GstPad * +nle_object_ghost_pad (NleObject * object, const gchar * name, GstPad * target) +{ + GstPadDirection dir = GST_PAD_DIRECTION (target); + GstPad *ghost; + + GST_DEBUG_OBJECT (object, "name:%s, target:%p", name, target); + + g_return_val_if_fail (target, FALSE); + g_return_val_if_fail ((dir != GST_PAD_UNKNOWN), FALSE); + + ghost = nle_object_ghost_pad_no_target (object, name, dir, NULL); + if (!ghost) { + GST_WARNING_OBJECT (object, "Couldn't create ghostpad"); + return NULL; + } + + if (!(nle_object_ghost_pad_set_target (object, ghost, target))) { + GST_WARNING_OBJECT (object, + "Couldn't set the target pad... removing ghostpad"); + gst_object_unref (ghost); + return NULL; + } + + GST_DEBUG_OBJECT (object, "activating ghostpad"); + /* activate pad */ + gst_pad_set_active (ghost, TRUE); + /* add it to element */ + if (!(gst_element_add_pad (GST_ELEMENT (object), ghost))) { + GST_WARNING ("couldn't add newly created ghostpad"); + return NULL; + } + + return ghost; +} + +/* + * nle_object_ghost_pad_no_target: + * /!\ Doesn't add the pad to the NleObject.... + */ +GstPad * +nle_object_ghost_pad_no_target (NleObject * object, const gchar * name, + GstPadDirection dir, GstPadTemplate * template) +{ + GstPad *ghost; + NlePadPrivate *priv; + + /* create a no_target ghostpad */ + if (template) + ghost = gst_ghost_pad_new_no_target_from_template (name, template); + else + ghost = gst_ghost_pad_new_no_target (name, dir); + if (!ghost) + return NULL; + + + /* remember the existing ghostpad event/query/link/unlink functions */ + priv = g_slice_new0 (NlePadPrivate); + priv->dir = dir; + priv->object = object; + + /* grab/replace event/query functions */ + GST_DEBUG_OBJECT (ghost, "Setting priv->eventfunc to %p", + GST_PAD_EVENTFUNC (ghost)); + priv->eventfunc = GST_PAD_EVENTFUNC (ghost); + priv->queryfunc = GST_PAD_QUERYFUNC (ghost); + + gst_pad_set_event_function (ghost, + GST_DEBUG_FUNCPTR (ghostpad_event_function)); + gst_pad_set_query_function (ghost, + GST_DEBUG_FUNCPTR (ghostpad_query_function)); + + gst_pad_set_element_private (ghost, priv); + control_internal_pad (ghost, object); + + return ghost; +} + + + +void +nle_object_remove_ghost_pad (NleObject * object, GstPad * ghost) +{ + NlePadPrivate *priv; + + GST_DEBUG_OBJECT (object, "ghostpad %s:%s", GST_DEBUG_PAD_NAME (ghost)); + + priv = gst_pad_get_element_private (ghost); + gst_ghost_pad_set_target (GST_GHOST_PAD (ghost), NULL); + gst_element_remove_pad (GST_ELEMENT (object), ghost); + if (priv) + g_slice_free (NlePadPrivate, priv); +} + +gboolean +nle_object_ghost_pad_set_target (NleObject * object, GstPad * ghost, + GstPad * target) +{ + NlePadPrivate *priv = gst_pad_get_element_private (ghost); + + g_return_val_if_fail (priv, FALSE); + g_return_val_if_fail (GST_IS_PAD (ghost), FALSE); + + if (target) { + GST_DEBUG_OBJECT (object, "setting target %s:%s on %s:%s", + GST_DEBUG_PAD_NAME (target), GST_DEBUG_PAD_NAME (ghost)); + } else { + GST_DEBUG_OBJECT (object, "removing target from ghostpad"); + priv->pending_seek = NULL; + } + + /* set target */ + if (!(gst_ghost_pad_set_target (GST_GHOST_PAD (ghost), target))) { + GST_WARNING_OBJECT (priv->object, "Could not set ghost %s:%s " + "target to: %s:%s", GST_DEBUG_PAD_NAME (ghost), + GST_DEBUG_PAD_NAME (target)); + return FALSE; + } + + if (target && priv->pending_seek) { + gboolean res = gst_pad_send_event (ghost, priv->pending_seek); + + GST_INFO_OBJECT (object, "Sending our pending seek event: %" GST_PTR_FORMAT + " -- Result is %i", priv->pending_seek, res); + + priv->pending_seek = NULL; + } + + return TRUE; +} + +void +nle_init_ghostpad_category (void) +{ + GST_DEBUG_CATEGORY_INIT (nleghostpad, "nleghostpad", + GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin GhostPad"); + +} diff --git a/plugins/nle/nleghostpad.h b/plugins/nle/nleghostpad.h new file mode 100644 index 0000000000..25fd362657 --- /dev/null +++ b/plugins/nle/nleghostpad.h @@ -0,0 +1,48 @@ +/* GStreamer + * Copyright (C) 2009 Edward Hervey <bilboed@bilboed.com> + * + * nleghostpad.h: Header for helper ghostpad + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __NLE_GHOSTPAD_H__ +#define __NLE_GHOSTPAD_H__ + +#include <gst/gst.h> + +#include "nletypes.h" + +G_BEGIN_DECLS + +GstPad *nle_object_ghost_pad (NleObject * object, + const gchar * name, GstPad * target) G_GNUC_INTERNAL; + +GstPad *nle_object_ghost_pad_no_target (NleObject * object, + const gchar * name, GstPadDirection dir, GstPadTemplate *templ) G_GNUC_INTERNAL; + +gboolean nle_object_ghost_pad_set_target (NleObject * object, + GstPad * ghost, GstPad * target) G_GNUC_INTERNAL; + +void nle_object_remove_ghost_pad (NleObject * object, GstPad * ghost) G_GNUC_INTERNAL; +GstEvent * nle_object_translate_incoming_seek (NleObject * object, GstEvent * event) G_GNUC_INTERNAL; + +void nle_init_ghostpad_category (void) G_GNUC_INTERNAL; + +G_END_DECLS + +#endif /* __NLE_GHOSTPAD_H__ */ diff --git a/plugins/nle/nleobject.c b/plugins/nle/nleobject.c new file mode 100644 index 0000000000..e746dd1a9b --- /dev/null +++ b/plugins/nle/nleobject.c @@ -0,0 +1,859 @@ +/* Gnonlin + * Copyright (C) <2001> Wim Taymans <wim.taymans@gmail.com> + * <2004-2008> Edward Hervey <bilboed@bilboed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> +#include "nle.h" + +/** + * SECTION:nleobject + * @short_description: Base class for GNonLin elements + * + * NleObject encapsulates default behaviour and implements standard + * properties provided by all the GNonLin elements. + */ + + +GST_DEBUG_CATEGORY_STATIC (nleobject_debug); +#define GST_CAT_DEFAULT nleobject_debug + +static GObjectClass *parent_class = NULL; + +/**************************************************** + * Helper macros * + ****************************************************/ +#define CHECK_AND_SET(PROPERTY, property, prop_str, print_format) \ +{ \ +if (object->pending_##property != object->property) { \ + object->property = object->pending_##property; \ + GST_DEBUG_OBJECT(object, "Setting " prop_str " to %" \ + print_format, object->property); \ +} else \ + GST_DEBUG_OBJECT(object, "Nothing to do for " prop_str); \ +} + +#define SET_PENDING_VALUE(property, property_str, type, print_format) \ +nleobject->pending_##property = g_value_get_##type (value); \ +if (nleobject->property != nleobject->pending_##property) { \ + GST_DEBUG_OBJECT(object, "Setting pending " property_str " to %" \ + print_format, nleobject->pending_##property); \ + nle_object_set_commit_needed (nleobject); \ +} else \ + GST_DEBUG_OBJECT(object, "Pending " property_str " did not change"); + +enum +{ + PROP_0, + PROP_START, + PROP_DURATION, + PROP_STOP, + PROP_INPOINT, + PROP_PRIORITY, + PROP_ACTIVE, + PROP_CAPS, + PROP_EXPANDABLE, + PROP_MEDIA_DURATION_FACTOR, + PROP_LAST +}; + +enum +{ + COMMIT_SIGNAL, + LAST_SIGNAL +}; + +static guint _signals[LAST_SIGNAL] = { 0 }; + +static GParamSpec *properties[PROP_LAST]; + +static void nle_object_dispose (GObject * object); + +static void nle_object_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void nle_object_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void nle_object_constructed (GObject * object); + +static GstStateChangeReturn nle_object_change_state (GstElement * element, + GstStateChange transition); + +static gboolean nle_object_prepare_func (NleObject * object); +static gboolean nle_object_cleanup_func (NleObject * object); +static gboolean nle_object_commit_func (NleObject * object, gboolean recurse); + +static GstStateChangeReturn nle_object_prepare (NleObject * object); + +static void +nle_object_class_init (NleObjectClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + NleObjectClass *nleobject_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + nleobject_class = (NleObjectClass *) klass; + GST_DEBUG_CATEGORY_INIT (nleobject_debug, "nleobject", + GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin object"); + parent_class = g_type_class_ref (GST_TYPE_BIN); + + gobject_class->set_property = GST_DEBUG_FUNCPTR (nle_object_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR (nle_object_get_property); + gobject_class->constructed = GST_DEBUG_FUNCPTR (nle_object_constructed); + gobject_class->dispose = GST_DEBUG_FUNCPTR (nle_object_dispose); + + gstelement_class->change_state = GST_DEBUG_FUNCPTR (nle_object_change_state); + + nleobject_class->prepare = GST_DEBUG_FUNCPTR (nle_object_prepare_func); + nleobject_class->cleanup = GST_DEBUG_FUNCPTR (nle_object_cleanup_func); + nleobject_class->commit_signal_handler = + GST_DEBUG_FUNCPTR (nle_object_commit); + nleobject_class->commit = GST_DEBUG_FUNCPTR (nle_object_commit_func); + + /** + * NleObject:start + * + * The start position relative to the parent in nanoseconds. + */ + properties[PROP_START] = g_param_spec_uint64 ("start", "Start", + "The start position relative to the parent (in nanoseconds)", + 0, G_MAXUINT64, 0, G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_START, + properties[PROP_START]); + + /** + * NleObject:duration + * + * The outgoing duration in nanoseconds. + */ + properties[PROP_DURATION] = g_param_spec_int64 ("duration", "Duration", + "Outgoing duration (in nanoseconds)", 0, G_MAXINT64, 0, + G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_DURATION, + properties[PROP_DURATION]); + + /** + * NleObject:stop + * + * The stop position relative to the parent in nanoseconds. + * + * This value is computed based on the values of start and duration. + */ + properties[PROP_STOP] = g_param_spec_uint64 ("stop", "Stop", + "The stop position relative to the parent (in nanoseconds)", + 0, G_MAXUINT64, 0, G_PARAM_READABLE); + g_object_class_install_property (gobject_class, PROP_STOP, + properties[PROP_STOP]); + + /** + * NleObject:inpoint + * + * The media start position in nanoseconds. + * + * Also called 'in-point' in video-editing, this corresponds to + * what position in the 'contained' object we should start outputting from. + */ + properties[PROP_INPOINT] = + g_param_spec_uint64 ("inpoint", "Media start", + "The media start position (in nanoseconds)", 0, G_MAXUINT64, + GST_CLOCK_TIME_NONE, G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_INPOINT, + properties[PROP_INPOINT]); + + /** + * NleObject:priority + * + * The priority of the object in the container. + * + * The highest priority is 0, meaning this object will be selected over + * any other between start and stop. + * + * The lowest priority is G_MAXUINT32. + * + * Objects whose priority is (-1) will be considered as 'default' objects + * in NleComposition and their start/stop values will be modified as to + * fit the whole duration of the composition. + */ + properties[PROP_PRIORITY] = g_param_spec_uint ("priority", "Priority", + "The priority of the object (0 = highest priority)", 0, G_MAXUINT, 0, + G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_PRIORITY, + properties[PROP_PRIORITY]); + + /** + * NleObject:active + * + * Indicates whether this object should be used by its container. + * + * Set to #TRUE to temporarily disable this object in a #NleComposition. + */ + properties[PROP_ACTIVE] = g_param_spec_boolean ("active", "Active", + "Use this object in the NleComposition", TRUE, G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_ACTIVE, + properties[PROP_ACTIVE]); + + /** + * NleObject:caps + * + * Caps used to filter/choose the output stream. + * + * If the controlled object produces several stream, you can set this + * property to choose a specific stream. + * + * If nothing is specified then a source pad will be chosen at random. + */ + properties[PROP_CAPS] = g_param_spec_boxed ("caps", "Caps", + "Caps used to filter/choose the output stream", + GST_TYPE_CAPS, G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CAPS, + properties[PROP_CAPS]); + + /** + * NleObject:expandable + * + * Indicates whether this object should expand to the full duration of its + * container #NleComposition. + */ + properties[PROP_EXPANDABLE] = + g_param_spec_boolean ("expandable", "Expandable", + "Expand to the full duration of the container composition", FALSE, + G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_EXPANDABLE, + properties[PROP_EXPANDABLE]); + + /** + * NleObject:media-duration-factor + * + * Indicates the relative rate caused by this object, in other words, the + * relation between the rate of media entering and leaving this object. I.e. + * if object pulls data at twice the speed it sends it (e.g. `pitch + * tempo=2.0`), this value is set to 2.0. + * + * Deprecated: 1.18: This property is ignored since the wrapped + * #GstElement-s themselves should internally perform any additional time + * translations. + */ + properties[PROP_MEDIA_DURATION_FACTOR] = + g_param_spec_double ("media-duration-factor", "Media duration factor", + "The relative rate caused by this object", 0.01, G_MAXDOUBLE, + 1.0, G_PARAM_READWRITE | G_PARAM_DEPRECATED); + g_object_class_install_property (gobject_class, PROP_MEDIA_DURATION_FACTOR, + properties[PROP_MEDIA_DURATION_FACTOR]); + + /** + * NleObject::commit + * @object: a #NleObject + * @recurse: Whether to commit recursiverly into (NleComposition) children of + * @object. This is used in case we have composition inside + * a nlesource composition, telling it to commit the included + * composition state. + * + * Action signal to commit all the pending changes of the composition and + * its children timing properties + * + * Returns: %TRUE if changes have been commited, %FALSE if nothing had to + * be commited + */ + _signals[COMMIT_SIGNAL] = g_signal_new ("commit", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (NleObjectClass, commit_signal_handler), NULL, NULL, NULL, + G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN); + + gst_type_mark_as_plugin_api (NLE_TYPE_OBJECT, 0); +} + +static void +nle_object_init (NleObject * object, NleObjectClass * klass) +{ + object->start = object->pending_start = 0; + object->duration = object->pending_duration = 0; + object->stop = 0; + + object->inpoint = object->pending_inpoint = GST_CLOCK_TIME_NONE; + object->priority = object->pending_priority = 0; + object->active = object->pending_active = TRUE; + + object->caps = gst_caps_new_any (); + + object->segment_rate = 1.0; + object->segment_start = -1; + object->segment_stop = -1; + + object->srcpad = nle_object_ghost_pad_no_target (object, + "src", GST_PAD_SRC, + gst_element_class_get_pad_template ((GstElementClass *) klass, "src")); + + gst_element_add_pad (GST_ELEMENT (object), object->srcpad); +} + +static void +nle_object_dispose (GObject * object) +{ + NleObject *nle = (NleObject *) object; + + if (nle->caps) { + gst_caps_unref (nle->caps); + nle->caps = NULL; + } + + if (nle->srcpad) { + nle_object_remove_ghost_pad (nle, nle->srcpad); + nle->srcpad = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +/** + * nle_object_to_media_time: + * @object: a #NleObject + * @objecttime: The #GstClockTime we want to convert + * @mediatime: A pointer on a #GstClockTime to fill + * + * Converts a #GstClockTime timestamp received from another nleobject pad + * in the same nlecomposition, or from the nlecomposition itself, to an + * internal source time. + * + * If the object is furthest downstream in the nlecomposition (highest + * priority in the current stack), this will convert the timestamp from + * the composition coordinate time to the internal source coordinate time + * of the object. + * + * If the object is upstream from another nleobject, then this can convert + * the timestamp received from the downstream sink pad to the internal + * source coordinates of the object, to be passed to its internal + * elements. + * + * If the object is downstream from another nleobject, then this can + * convert the timestamp received from the upstream source pad to the + * internal sink coordinates of the object, to be passed to its internal + * elements. + * + * In these latter two cases, the timestamp should have been converted + * by the peer pad using nle_media_to_object_time(). + * + * Note, if an object introduces a time effect, it must have a 0 in-point + * and the same #nleobject:start and #nleobject:duration as all the other + * objects that are further upstream. + * + * Returns: TRUE if @objecttime was below the @object start time, + * FALSE otherwise. + */ +gboolean +nle_object_to_media_time (NleObject * object, GstClockTime otime, + GstClockTime * mtime) +{ + gboolean ret = TRUE; + + g_return_val_if_fail (mtime, FALSE); + + GST_DEBUG_OBJECT (object, "ObjectTime : %" GST_TIME_FORMAT, + GST_TIME_ARGS (otime)); + + GST_DEBUG_OBJECT (object, + "Start/Stop:[%" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT "] " + "Media start: %" GST_TIME_FORMAT, GST_TIME_ARGS (object->start), + GST_TIME_ARGS (object->stop), GST_TIME_ARGS (object->inpoint)); + + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (otime))) { + GST_DEBUG_OBJECT (object, "converting none object time to none"); + *mtime = GST_CLOCK_TIME_NONE; + return TRUE; + } + + /* We do not allow @otime to be below the start of the object. + * If it was below, then the object would have a negative external + * source/sink time. + * + * Note that ges only supports time effects that map the time 0 to + * 0. Such time effects would also not produce an external timestamp + * below start, nor can they receive such a timestamp. */ + if (G_UNLIKELY ((otime < object->start))) { + GST_DEBUG_OBJECT (object, "ObjectTime is before start"); + otime = object->start; + ret = FALSE; + } + /* NOTE: if an nlecomposition contains time effect operations, then + * @otime can reasonably exceed the stop time of the object. So we + * do not limit it here. */ + + /* first we convert the timestamp to the object's external source/sink + * coordinates: + * + For an object that is furthest downstream, we translate from the + * composition coordinates to the external source coordinates by + * subtracting the object start. + * + For an object that is upstream from d_object, we need to + * translate from its external sink coordinates to our external + * source coordinates. This is done by adding + * (d_object->start - object->start) + * However, the sink pad of d_object should have already added the + * d_object->start to the timestamp (see nle_media_to_object_time) + * so we also only need to subtract the object start. + * + For an object that is downstream from u_object, we need to + * translate from its external source coordinates to our external + * sink coordinates. This is similarly done by adding + * (u_object->start - object->start) + * However, the source pad of u_object should have already added the + * u_object->start to the timestamp (see nle_media_to_object_time) + * so we also only need to subtract the object start. + */ + *mtime = otime - object->start; + + /* we then convert the timestamp from the object's external source/sink + * coordinates to its internal source/sink coordinates, to be used by + * internal elements that the object wraps. This is done by adding + * the object in-point. */ + if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (object->inpoint))) + *mtime += object->inpoint; + + GST_DEBUG_OBJECT (object, "Returning MediaTime : %" GST_TIME_FORMAT, + GST_TIME_ARGS (*mtime)); + + return ret; +} + +/** + * nle_media_to_object_time: + * @object: The #NleObject + * @mediatime: The #GstClockTime we want to convert + * @objecttime: A pointer on a #GstClockTime to fill + * + * Converts a #GstClockTime timestamp from an internal time to an + * nleobject pad time. + * + * If the object is furthest downstream in an nlecomposition (highest + * priority in the current stack), this will convert the timestamp from + * the internal source coordinate time of the object to the composition + * coordinate time. + * + * If the object is upstream from another nleobject, then this can convert + * the timestamp from the internal source coordinates of the object to be + * sent to the downstream sink pad. + * + * If the object is downstream from another nleobject, then this can + * convert the timestamp from the internal sink coordinates of the object + * to be sent to the upstream source pad. + * + * Note, if an object introduces a time effect, it must have a 0 in-point + * and the same #nleobject:start and #nleobject:duration as all the other + * objects that are further upstream. + * + * Returns: TRUE if @objecttime was below the @object in-point time, + * FALSE otherwise. + */ + +gboolean +nle_media_to_object_time (NleObject * object, GstClockTime mtime, + GstClockTime * otime) +{ + g_return_val_if_fail (otime, FALSE); + + GST_DEBUG_OBJECT (object, "MediaTime : %" GST_TIME_FORMAT, + GST_TIME_ARGS (mtime)); + + GST_DEBUG_OBJECT (object, + "Start/Stop:[%" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT "] " + "inpoint %" GST_TIME_FORMAT, GST_TIME_ARGS (object->start), + GST_TIME_ARGS (object->stop), GST_TIME_ARGS (object->inpoint)); + + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (mtime))) { + GST_DEBUG_OBJECT (object, "converting none media time to none"); + *otime = GST_CLOCK_TIME_NONE; + return TRUE; + } + + /* the internal time should never go below the in-point! */ + if (G_UNLIKELY (GST_CLOCK_TIME_IS_VALID (object->inpoint) + && (mtime < object->inpoint))) { + GST_DEBUG_OBJECT (object, "media time is before inpoint, forcing to start"); + *otime = object->start; + return FALSE; + } + + /* first we convert the timestamp to the object's external source/sink + * coordinates by removing the in-point */ + if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (object->inpoint))) + *otime = mtime - object->inpoint; + else + *otime = mtime; + + /* then we convert the timestamp by adding start. + * If the object is furthest downstream, this will translate it from + * the external source coordinates to the composition coordinates. + * Otherwise, this will perform part of the conversion from the object's + * source/sink coordinates to the downstream/upstream sink/source + * coordinates (the conversion is completed in + * nle_object_to_media_time). */ + *otime += object->start; + + GST_DEBUG_OBJECT (object, "Returning ObjectTime : %" GST_TIME_FORMAT, + GST_TIME_ARGS (*otime)); + return TRUE; +} + +static gboolean +nle_object_prepare_func (NleObject * object) +{ + GST_DEBUG_OBJECT (object, "default prepare function, returning TRUE"); + + return TRUE; +} + +static GstStateChangeReturn +nle_object_prepare (NleObject * object) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + GST_DEBUG_OBJECT (object, "preparing"); + + if (!(NLE_OBJECT_GET_CLASS (object)->prepare (object))) + ret = GST_STATE_CHANGE_FAILURE; + + GST_DEBUG_OBJECT (object, "finished preparing, returning %d", ret); + + return ret; +} + +static gboolean +nle_object_cleanup_func (NleObject * object) +{ + GST_DEBUG_OBJECT (object, "default cleanup function, returning TRUE"); + + return TRUE; +} + +GstStateChangeReturn +nle_object_cleanup (NleObject * object) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + GST_DEBUG_OBJECT (object, "cleaning-up"); + + if (!(NLE_OBJECT_GET_CLASS (object)->cleanup (object))) + ret = GST_STATE_CHANGE_FAILURE; + + GST_DEBUG_OBJECT (object, "finished preparing, returning %d", ret); + + return ret; +} + +void +nle_object_set_caps (NleObject * object, const GstCaps * caps) +{ + if (object->caps) + gst_caps_unref (object->caps); + + object->caps = gst_caps_copy (caps); +} + +static inline void +_update_stop (NleObject * nleobject) +{ + /* check if start/duration has changed */ + + if ((nleobject->pending_start + nleobject->pending_duration) != + nleobject->stop) { + nleobject->stop = nleobject->pending_start + nleobject->pending_duration; + + GST_LOG_OBJECT (nleobject, + "Updating stop value : %" GST_TIME_FORMAT " [start:%" GST_TIME_FORMAT + ", duration:%" GST_TIME_FORMAT "]", GST_TIME_ARGS (nleobject->stop), + GST_TIME_ARGS (nleobject->pending_start), + GST_TIME_ARGS (nleobject->pending_duration)); + g_object_notify_by_pspec (G_OBJECT (nleobject), properties[PROP_STOP]); + } +} + +static void +update_values (NleObject * object) +{ + CHECK_AND_SET (START, start, "start", G_GUINT64_FORMAT); + CHECK_AND_SET (INPOINT, inpoint, "inpoint", G_GUINT64_FORMAT); + CHECK_AND_SET (DURATION, duration, "duration", G_GINT64_FORMAT); + CHECK_AND_SET (PRIORITY, priority, "priority", G_GUINT32_FORMAT); + CHECK_AND_SET (ACTIVE, active, "active", G_GUINT32_FORMAT); + + _update_stop (object); +} + +static gboolean +nle_object_commit_func (NleObject * object, gboolean recurse) +{ + GST_DEBUG_OBJECT (object, "Commiting object changed"); + + if (object->commit_needed == FALSE) { + GST_INFO_OBJECT (object, "No changes to commit"); + + return FALSE; + } + + update_values (object); + + GST_DEBUG_OBJECT (object, "Done commiting"); + + return TRUE; +} + +static void +nle_object_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + NleObject *nleobject = (NleObject *) object; + + g_return_if_fail (NLE_IS_OBJECT (object)); + + GST_OBJECT_LOCK (object); + switch (prop_id) { + case PROP_START: + SET_PENDING_VALUE (start, "start", uint64, G_GUINT64_FORMAT); + break; + case PROP_DURATION: + SET_PENDING_VALUE (duration, "duration", int64, G_GINT64_FORMAT); + break; + case PROP_INPOINT: + SET_PENDING_VALUE (inpoint, "inpoint", uint64, G_GUINT64_FORMAT); + break; + case PROP_PRIORITY: + SET_PENDING_VALUE (priority, "priority", uint, G_GUINT32_FORMAT); + break; + case PROP_ACTIVE: + SET_PENDING_VALUE (active, "active", boolean, G_GUINT32_FORMAT); + break; + case PROP_CAPS: + nle_object_set_caps (nleobject, gst_value_get_caps (value)); + break; + case PROP_EXPANDABLE: + if (g_value_get_boolean (value)) + GST_OBJECT_FLAG_SET (nleobject, NLE_OBJECT_EXPANDABLE); + else + GST_OBJECT_FLAG_UNSET (nleobject, NLE_OBJECT_EXPANDABLE); + break; + case PROP_MEDIA_DURATION_FACTOR: + { + gdouble val = g_value_get_double (value); + if (val != 1.0) + g_warning ("Ignoring media-duration-factor value of %g since the " + "property is deprecated", val); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (object); +} + +static void +nle_object_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + NleObject *nleobject = (NleObject *) object; + + switch (prop_id) { + case PROP_START: + g_value_set_uint64 (value, nleobject->pending_start); + break; + case PROP_DURATION: + g_value_set_int64 (value, nleobject->pending_duration); + break; + case PROP_STOP: + g_value_set_uint64 (value, nleobject->stop); + break; + case PROP_INPOINT: + g_value_set_uint64 (value, nleobject->pending_inpoint); + break; + case PROP_PRIORITY: + g_value_set_uint (value, nleobject->pending_priority); + break; + case PROP_ACTIVE: + g_value_set_boolean (value, nleobject->pending_active); + break; + case PROP_CAPS: + gst_value_set_caps (value, nleobject->caps); + break; + case PROP_EXPANDABLE: + g_value_set_boolean (value, NLE_OBJECT_IS_EXPANDABLE (object)); + break; + case PROP_MEDIA_DURATION_FACTOR: + g_value_set_double (value, 1.0); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nle_object_constructed (GObject * object) +{ + NleObject *nleobject = (NleObject *) object; + + _update_stop (nleobject); +} + +static GstStateChangeReturn +nle_object_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + { + GstObject *parent = gst_object_get_parent (GST_OBJECT (element)); + + /* Going to READY and if we are not in a composition, we need to make + * sure that the object positioning state is properly commited */ + if (parent) { + if (g_strcmp0 (GST_ELEMENT_NAME (GST_ELEMENT (parent)), "current-bin") + && !NLE_OBJECT_IS_COMPOSITION (NLE_OBJECT (element))) { + GST_INFO ("Adding nleobject to something that is not a composition," + " commiting ourself"); + nle_object_commit (NLE_OBJECT (element), FALSE); + } + + gst_object_unref (parent); + } + } + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + if (nle_object_prepare (NLE_OBJECT (element)) == GST_STATE_CHANGE_FAILURE) { + ret = GST_STATE_CHANGE_FAILURE; + goto beach; + } + break; + default: + break; + } + + GST_DEBUG_OBJECT (element, "Calling parent change_state"); + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + GST_DEBUG_OBJECT (element, "Return from parent change_state was %d", ret); + + if (ret == GST_STATE_CHANGE_FAILURE) + goto beach; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + /* cleanup nleobject */ + if (nle_object_cleanup (NLE_OBJECT (element)) == GST_STATE_CHANGE_FAILURE) + ret = GST_STATE_CHANGE_FAILURE; + break; + default: + break; + } + +beach: + return ret; +} + +void +nle_object_set_commit_needed (NleObject * object) +{ + if (G_UNLIKELY (object->commiting)) { + GST_WARNING_OBJECT (object, + "Trying to set 'commit-needed' while commiting"); + + return; + } + + GST_DEBUG_OBJECT (object, "Setting 'commit_needed'"); + object->commit_needed = TRUE; +} + +gboolean +nle_object_commit (NleObject * object, gboolean recurse) +{ + gboolean ret; + + GST_DEBUG_OBJECT (object, "Commiting object state"); + + object->commiting = TRUE; + ret = NLE_OBJECT_GET_CLASS (object)->commit (object, recurse); + object->commiting = FALSE; + + return ret; + +} + +static void +_send_seek_event (const GValue * item, gpointer seek_event) +{ + GstElement *child = g_value_get_object (item); + + gst_element_send_event (child, gst_event_ref (seek_event)); +} + +void +nle_object_seek_all_children (NleObject * object, GstEvent * seek_event) +{ + GstIterator *it = gst_bin_iterate_recurse (GST_BIN (object)); + + while (gst_iterator_foreach (it, _send_seek_event, + seek_event) == GST_ITERATOR_RESYNC) + gst_iterator_resync (it); + + gst_iterator_free (it); + gst_event_unref (seek_event); +} + +void +nle_object_reset (NleObject * object) +{ + GST_INFO_OBJECT (object, "Resetting child timing values to default"); + + object->start = 0; + object->duration = 0; + object->stop = 0; + object->inpoint = GST_CLOCK_TIME_NONE; + object->priority = 0; + object->active = TRUE; + object->in_composition = FALSE; +} + +GType +nle_object_get_type (void) +{ + static gsize type = 0; + + if (g_once_init_enter (&type)) { + GType _type; + static const GTypeInfo info = { + sizeof (NleObjectClass), + NULL, + NULL, + (GClassInitFunc) nle_object_class_init, + NULL, + NULL, + sizeof (NleObject), + 0, + (GInstanceInitFunc) nle_object_init, + }; + + _type = g_type_register_static (GST_TYPE_BIN, + "NleObject", &info, G_TYPE_FLAG_ABSTRACT); + g_once_init_leave (&type, _type); + } + return type; +} diff --git a/plugins/nle/nleobject.h b/plugins/nle/nleobject.h new file mode 100644 index 0000000000..1e0272e4ae --- /dev/null +++ b/plugins/nle/nleobject.h @@ -0,0 +1,173 @@ +/* GStreamer + * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com> + * 2004-2008 Edward Hervey <bilboed@bilboed.com> + * + * nleobject.h: Header for base NleObject + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __NLE_OBJECT_H__ +#define __NLE_OBJECT_H__ + +#include <gst/gst.h> + +#include "nletypes.h" + +G_BEGIN_DECLS +#define NLE_TYPE_OBJECT \ + (nle_object_get_type()) +#define NLE_OBJECT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),NLE_TYPE_OBJECT,NleObject)) +#define NLE_OBJECT_CAST(obj) ((NleObject*) (obj)) +#define NLE_OBJECT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),NLE_TYPE_OBJECT,NleObjectClass)) +#define NLE_OBJECT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), NLE_TYPE_OBJECT, NleObjectClass)) +#define NLE_IS_OBJECT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),NLE_TYPE_OBJECT)) +#define NLE_IS_OBJECT_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),NLE_TYPE_OBJECT)) + +#define NLE_OBJECT_SRC(obj) (((NleObject *) obj)->srcpad) + +/** + * NleObjectFlags: + * @NLE_OBJECT_IS_SOURCE: + * @NLE_OBJECT_IS_OPERATION: + * @NLE_OBJECT_IS_EXPANDABLE: The #NleObject start/stop will extend accross the full composition. + * @NLE_OBJECT_LAST_FLAG: +*/ + +typedef enum +{ + NLE_OBJECT_SOURCE = (GST_BIN_FLAG_LAST << 0), + NLE_OBJECT_OPERATION = (GST_BIN_FLAG_LAST << 1), + NLE_OBJECT_EXPANDABLE = (GST_BIN_FLAG_LAST << 2), + NLE_OBJECT_COMPOSITION = (GST_BIN_FLAG_LAST << 3), + /* padding */ + NLE_OBJECT_LAST_FLAG = (GST_BIN_FLAG_LAST << 5) +} NleObjectFlags; + + +#define NLE_OBJECT_IS_SOURCE(obj) \ + (GST_OBJECT_FLAG_IS_SET(obj, NLE_OBJECT_SOURCE)) +#define NLE_OBJECT_IS_OPERATION(obj) \ + (GST_OBJECT_FLAG_IS_SET(obj, NLE_OBJECT_OPERATION)) +#define NLE_OBJECT_IS_EXPANDABLE(obj) \ + (GST_OBJECT_FLAG_IS_SET(obj, NLE_OBJECT_EXPANDABLE)) +#define NLE_OBJECT_IS_COMPOSITION(obj) \ + (GST_OBJECT_FLAG_IS_SET(obj, NLE_OBJECT_COMPOSITION)) + +/* For internal usage only */ +#define NLE_OBJECT_START(obj) (NLE_OBJECT_CAST (obj)->start) +#define NLE_OBJECT_STOP(obj) (NLE_OBJECT_CAST (obj)->stop) +#define NLE_OBJECT_DURATION(obj) (NLE_OBJECT_CAST (obj)->duration) +#define NLE_OBJECT_INPOINT(obj) (NLE_OBJECT_CAST (obj)->inpoint) +#define NLE_OBJECT_PRIORITY(obj) (NLE_OBJECT_CAST (obj)->priority) +#define NLE_OBJECT_ACTIVE(obj) (NLE_OBJECT_DURATION(obj) > 0 && NLE_OBJECT_CAST (obj)->active) + +#define NLE_OBJECT_IS_COMMITING(obj) (NLE_OBJECT_CAST (obj)->commiting) + +struct _NleObject +{ + GstBin parent; + + GstPad *srcpad; + + /* Time positionning */ + GstClockTime start; + GstClockTime inpoint; + GstClockTimeDiff duration; + + /* Pending time positionning + * Should be == GST_CLOCK_TIME_NONE when nothing to do + */ + GstClockTime pending_start; + GstClockTime pending_inpoint; + GstClockTimeDiff pending_duration; + guint32 pending_priority; + gboolean pending_active; + + gboolean commit_needed; + gboolean commiting; /* Set to TRUE during the commiting time only */ + + gboolean expandable; + + /* read-only */ + GstClockTime stop; + + /* priority in parent */ + guint32 priority; + + /* active in parent */ + gboolean active; + + /* Filtering caps */ + GstCaps *caps; + + /* current segment seek <RO> */ + gdouble segment_rate; + GstSeekFlags segment_flags; + gint64 segment_start; + gint64 segment_stop; + + gboolean in_composition; +}; + +struct _NleObjectClass +{ + GstBinClass parent_class; + + /* Signal method handler */ + gboolean (*commit_signal_handler) (NleObject * object, gboolean recurse); + + /* virtual methods for subclasses */ + gboolean (*prepare) (NleObject * object); + gboolean (*cleanup) (NleObject * object); + gboolean (*commit) (NleObject * object, gboolean recurse); +}; + +GType nle_object_get_type (void)G_GNUC_INTERNAL; + +gboolean +nle_object_to_media_time (NleObject * object, GstClockTime otime, + GstClockTime * mtime) G_GNUC_INTERNAL; + +gboolean +nle_media_to_object_time (NleObject * object, GstClockTime mtime, + GstClockTime * otime) G_GNUC_INTERNAL; + +void +nle_object_set_caps (NleObject * object, const GstCaps * caps) G_GNUC_INTERNAL; + +void +nle_object_set_commit_needed (NleObject *object) G_GNUC_INTERNAL; + +gboolean +nle_object_commit (NleObject *object, gboolean recurse) G_GNUC_INTERNAL; + +void +nle_object_reset (NleObject *object) G_GNUC_INTERNAL; + +GstStateChangeReturn +nle_object_cleanup (NleObject * object) G_GNUC_INTERNAL; + +void nle_object_seek_all_children (NleObject *object, GstEvent *seek_event) G_GNUC_INTERNAL; + +G_END_DECLS +#endif /* __NLE_OBJECT_H__ */ diff --git a/plugins/nle/nleoperation.c b/plugins/nle/nleoperation.c new file mode 100644 index 0000000000..b39fdd7e17 --- /dev/null +++ b/plugins/nle/nleoperation.c @@ -0,0 +1,868 @@ +/* GStreamer + * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com> + * 2004-2008 Edward Hervey <bilboed@bilboed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "nle.h" + +/** + * SECTION:element-nleoperation + * + * A NleOperation performs a transformation or mixing operation on the + * data from one or more #NleSources, which is used to implement filters or + * effects. + * + * ## Time Effects + * + * An #nleoperation that wraps a #GstElement that transforms seek and + * segment times is considered a time effect. Nle only tries to support + * time effect's whose overall seek transformation: + * + * + Maps the time `0` to `0`. So initial time-shifting effects are + * excluded (the #NleObject:inpoint can sometimes be used instead). + * + Is monotonically increasing. So reversing effects, and effects that + * jump backwards in the stream are excluded. + * + Can handle a reasonable #GstClockTime, relative to the project. So + * this would exclude a time effect with an extremely large speed-up + * that would cause the converted #GstClockTime seeks to overflow. + * + Is 'continuously reversible'. This essentially means that for every + * seek position found on the sink pad of the element, we can, to 'good + * enough' accuracy, calculate the corresponding seek position that was + * received on the source pad. Moreover, this calculation should + * correspond to how the element transforms its #GstSegment + * @time field. This is needed so that a seek can result in an accurate + * segment. + * + * Note that a constant-rate-change effect that is not extremely fast or + * slow would satisfy these conditions. + * + * For such a time effect, they should be configured in nle such that: + * + * + Their #NleObject:inpoint is `0`. Otherwise this will introduce + * seeking problems in its #nlecomposition. + * + They must share the same #NleObject:start and + * #NleObject:duration as all nleobjects of lower priority in its + * #nlecomposition. Otherwise this will introduce jumps in playback. + * + * Note that, at the moment, nle only converts the #GstSegment + * @time field which means the other fields, such as @start and @stop, can + * end up with non-meaningful values when time effects are involved. + * Moreover, it does not convert #GstBuffer times either, which can result + * in them having non-meaningful values. In particular, this means that + * #GstControlBinding-s will not work well with #nlecomposition-s when + * they include time effects. + */ + +static GstStaticPadTemplate nle_operation_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate nle_operation_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC (nleoperation); +#define GST_CAT_DEFAULT nleoperation + +#define _do_init \ + GST_DEBUG_CATEGORY_INIT (nleoperation, "nleoperation", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin Operation element"); +#define nle_operation_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (NleOperation, nle_operation, NLE_TYPE_OBJECT, + _do_init); + +enum +{ + ARG_0, + ARG_SINKS, +}; + +enum +{ + INPUT_PRIORITY_CHANGED, + LAST_SIGNAL +}; + +static guint nle_operation_signals[LAST_SIGNAL] = { 0 }; + +static void nle_operation_dispose (GObject * object); + +static void nle_operation_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void nle_operation_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static gboolean nle_operation_prepare (NleObject * object); +static gboolean nle_operation_cleanup (NleObject * object); + +static gboolean nle_operation_add_element (GstBin * bin, GstElement * element); +static gboolean nle_operation_remove_element (GstBin * bin, + GstElement * element); + +static GstPad *nle_operation_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name, const GstCaps * caps); +static void nle_operation_release_pad (GstElement * element, GstPad * pad); + +static void synchronize_sinks (NleOperation * operation); +static gboolean remove_sink_pad (NleOperation * operation, GstPad * sinkpad); + + +static gboolean +nle_operation_send_event (GstElement * element, GstEvent * event) +{ + gboolean res = TRUE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + nle_object_seek_all_children (NLE_OBJECT (element), event); + break; + default: + res = GST_ELEMENT_CLASS (parent_class)->send_event (element, event); + break; + } + + return res; +} + +static void +nle_operation_class_init (NleOperationClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstBinClass *gstbin_class = (GstBinClass *) klass; + + GstElementClass *gstelement_class = (GstElementClass *) klass; + NleObjectClass *nleobject_class = (NleObjectClass *) klass; + + gst_element_class_set_static_metadata (gstelement_class, "GNonLin Operation", + "Filter/Editor", + "Encapsulates filters/effects for use with NLE Objects", + "Wim Taymans <wim.taymans@gmail.com>, Edward Hervey <bilboed@bilboed.com>"); + + gobject_class->dispose = GST_DEBUG_FUNCPTR (nle_operation_dispose); + + gobject_class->set_property = GST_DEBUG_FUNCPTR (nle_operation_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR (nle_operation_get_property); + + /** + * NleOperation:sinks: + * + * Specifies the number of sink pads the operation should provide. + * If the sinks property is -1 (the default) pads are only created as + * demanded via `get_request_pad()` calls on the element. + */ + g_object_class_install_property (gobject_class, ARG_SINKS, + g_param_spec_int ("sinks", "Sinks", + "Number of input sinks (-1 for automatic handling)", -1, G_MAXINT, -1, + G_PARAM_READWRITE)); + + /** + * NleOperation:input-priority-changed: + * @pad: The operation's input pad whose priority changed. + * @priority: The new priority + * + * Signals that the @priority of the stream being fed to the given @pad + * might have changed. + */ + nle_operation_signals[INPUT_PRIORITY_CHANGED] = + g_signal_new ("input-priority-changed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NleOperationClass, + input_priority_changed), NULL, NULL, NULL, + G_TYPE_NONE, 2, GST_TYPE_PAD, G_TYPE_UINT); + + gstelement_class->request_new_pad = + GST_DEBUG_FUNCPTR (nle_operation_request_new_pad); + gstelement_class->release_pad = GST_DEBUG_FUNCPTR (nle_operation_release_pad); + gstelement_class->send_event = GST_DEBUG_FUNCPTR (nle_operation_send_event); + + gstbin_class->add_element = GST_DEBUG_FUNCPTR (nle_operation_add_element); + gstbin_class->remove_element = + GST_DEBUG_FUNCPTR (nle_operation_remove_element); + + nleobject_class->prepare = GST_DEBUG_FUNCPTR (nle_operation_prepare); + nleobject_class->cleanup = GST_DEBUG_FUNCPTR (nle_operation_cleanup); + + gst_element_class_add_static_pad_template (gstelement_class, + &nle_operation_src_template); + gst_element_class_add_static_pad_template (gstelement_class, + &nle_operation_sink_template); +} + +static void +nle_operation_dispose (GObject * object) +{ + NleOperation *oper = (NleOperation *) object; + + GST_DEBUG_OBJECT (object, "Disposing of source pad"); + + nle_object_ghost_pad_set_target (NLE_OBJECT (object), + NLE_OBJECT (object)->srcpad, NULL); + + GST_DEBUG_OBJECT (object, "Disposing of sink pad(s)"); + while (oper->sinks) { + GstPad *ghost = (GstPad *) oper->sinks->data; + remove_sink_pad (oper, ghost); + } + + GST_DEBUG_OBJECT (object, "Done, calling parent class ::dispose()"); + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +nle_operation_reset (NleOperation * operation) +{ + operation->num_sinks = 1; + operation->realsinks = 0; +} + +static void +nle_operation_init (NleOperation * operation) +{ + GST_OBJECT_FLAG_SET (operation, NLE_OBJECT_OPERATION); + nle_operation_reset (operation); + operation->element = NULL; +} + +static gboolean +element_is_valid_filter (GstElement * element, gboolean * isdynamic) +{ + gboolean havesink = FALSE; + gboolean havesrc = FALSE; + gboolean done = FALSE; + GstIterator *pads; + GValue item = { 0, }; + + if (isdynamic) + *isdynamic = FALSE; + + pads = gst_element_iterate_pads (element); + + while (!done) { + switch (gst_iterator_next (pads, &item)) { + case GST_ITERATOR_OK: + { + GstPad *pad = g_value_get_object (&item); + + if (gst_pad_get_direction (pad) == GST_PAD_SRC) + havesrc = TRUE; + else if (gst_pad_get_direction (pad) == GST_PAD_SINK) + havesink = TRUE; + + g_value_reset (&item); + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync (pads); + havesrc = FALSE; + havesink = FALSE; + break; + default: + /* ERROR and DONE */ + done = TRUE; + break; + } + } + + g_value_unset (&item); + gst_iterator_free (pads); + + /* just look at the element's class, not the factory, since there might + * not be a factory (in case of python elements) or the factory is the + * wrong one (in case of a GstBin sub-class) and doesn't have complete + * information. */ + { + GList *tmp = + gst_element_class_get_pad_template_list (GST_ELEMENT_GET_CLASS + (element)); + + while (tmp) { + GstPadTemplate *template = (GstPadTemplate *) tmp->data; + + if (template->direction == GST_PAD_SRC) + havesrc = TRUE; + else if (template->direction == GST_PAD_SINK) { + if (!havesink && (template->presence == GST_PAD_REQUEST) && isdynamic) + *isdynamic = TRUE; + havesink = TRUE; + } + tmp = tmp->next; + } + } + return (havesink && havesrc); +} + +/* + * get_src_pad: + * element: a #GstElement + * + * Returns: The src pad for the given element. A reference was added to the + * returned pad, remove it when you don't need that pad anymore. + * Returns NULL if there's no source pad. + */ + +static GstPad * +get_src_pad (GstElement * element) +{ + GstIterator *it; + GstIteratorResult itres; + GValue item = { 0, }; + GstPad *srcpad = NULL; + + it = gst_element_iterate_src_pads (element); + itres = gst_iterator_next (it, &item); + if (itres != GST_ITERATOR_OK) { + GST_DEBUG ("%s doesn't have a src pad !", GST_ELEMENT_NAME (element)); + } else { + srcpad = g_value_get_object (&item); + gst_object_ref (srcpad); + } + g_value_reset (&item); + gst_iterator_free (it); + + return srcpad; +} + +/* get_nb_static_sinks: + * + * Returns : The number of static sink pads of the controlled element. + */ +static guint +get_nb_static_sinks (NleOperation * oper) +{ + GstIterator *sinkpads; + gboolean done = FALSE; + guint nbsinks = 0; + GValue item = { 0, }; + + sinkpads = gst_element_iterate_sink_pads (oper->element); + + while (!done) { + switch (gst_iterator_next (sinkpads, &item)) { + case GST_ITERATOR_OK:{ + nbsinks++; + g_value_unset (&item); + } + break; + case GST_ITERATOR_RESYNC: + nbsinks = 0; + gst_iterator_resync (sinkpads); + break; + default: + /* ERROR and DONE */ + done = TRUE; + break; + } + } + + g_value_reset (&item); + gst_iterator_free (sinkpads); + + GST_DEBUG ("We found %d static sinks", nbsinks); + + return nbsinks; +} + +static gboolean +nle_operation_add_element (GstBin * bin, GstElement * element) +{ + NleOperation *operation = (NleOperation *) bin; + gboolean res = FALSE; + gboolean isdynamic; + + GST_DEBUG_OBJECT (bin, "element:%s", GST_ELEMENT_NAME (element)); + + if (operation->element) { + GST_WARNING_OBJECT (operation, + "We already control an element : %s , remove it first", + GST_OBJECT_NAME (operation->element)); + } else { + if (!element_is_valid_filter (element, &isdynamic)) { + GST_WARNING_OBJECT (operation, + "Element %s is not a valid filter element", + GST_ELEMENT_NAME (element)); + } else { + if ((res = GST_BIN_CLASS (parent_class)->add_element (bin, element))) { + GstPad *srcpad; + + srcpad = get_src_pad (element); + if (!srcpad) + return FALSE; + + operation->element = element; + operation->dynamicsinks = isdynamic; + + nle_object_ghost_pad_set_target (NLE_OBJECT (operation), + NLE_OBJECT (operation)->srcpad, srcpad); + + /* Remove the reference get_src_pad gave us */ + gst_object_unref (srcpad); + + /* Figure out number of static sink pads */ + operation->num_sinks = get_nb_static_sinks (operation); + + /* Finally sync the ghostpads with the real pads */ + synchronize_sinks (operation); + } + } + } + + return res; +} + +static gboolean +nle_operation_remove_element (GstBin * bin, GstElement * element) +{ + NleOperation *operation = (NleOperation *) bin; + gboolean res = FALSE; + + if (operation->element) { + if ((res = GST_BIN_CLASS (parent_class)->remove_element (bin, element))) + operation->element = NULL; + } else { + GST_WARNING_OBJECT (bin, + "Element %s is not the one controlled by this operation", + GST_ELEMENT_NAME (element)); + } + return res; +} + +static void +nle_operation_set_sinks (NleOperation * operation, guint sinks) +{ + /* FIXME : Check if sinkpad of element is on-demand .... */ + + operation->num_sinks = sinks; + synchronize_sinks (operation); +} + +static void +nle_operation_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + NleOperation *operation = (NleOperation *) object; + + switch (prop_id) { + case ARG_SINKS: + nle_operation_set_sinks (operation, g_value_get_int (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nle_operation_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + NleOperation *operation = (NleOperation *) object; + + switch (prop_id) { + case ARG_SINKS: + g_value_set_int (value, operation->num_sinks); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +/* + * Returns the first unused sink pad of the controlled element. + * Only use with static element. Unref after usage. + * Returns NULL if there's no more unused sink pads. + */ +static GstPad * +get_unused_static_sink_pad (NleOperation * operation) +{ + GstIterator *pads; + gboolean done = FALSE; + GValue item = { 0, }; + GstPad *ret = NULL; + + if (!operation->element) + return NULL; + + pads = gst_element_iterate_pads (operation->element); + + while (!done) { + switch (gst_iterator_next (pads, &item)) { + case GST_ITERATOR_OK: + { + GstPad *pad = g_value_get_object (&item); + + if (gst_pad_get_direction (pad) == GST_PAD_SINK) { + GList *tmp; + gboolean istaken = FALSE; + + /* 1. figure out if one of our sink ghostpads has this pad as target */ + for (tmp = operation->sinks; tmp; tmp = tmp->next) { + GstGhostPad *gpad = (GstGhostPad *) tmp->data; + GstPad *target = gst_ghost_pad_get_target (gpad); + + GST_LOG ("found ghostpad with target %s:%s", + GST_DEBUG_PAD_NAME (target)); + + if (target) { + if (target == pad) + istaken = TRUE; + gst_object_unref (target); + } + } + + /* 2. if not taken, return that pad */ + if (!istaken) { + gst_object_ref (pad); + ret = pad; + done = TRUE; + } + } + g_value_reset (&item); + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync (pads); + break; + default: + /* ERROR and DONE */ + done = TRUE; + break; + } + } + + g_value_unset (&item); + gst_iterator_free (pads); + + if (ret) + GST_DEBUG_OBJECT (operation, "found free sink pad %s:%s", + GST_DEBUG_PAD_NAME (ret)); + else + GST_DEBUG_OBJECT (operation, "Couldn't find an unused sink pad"); + + return ret; +} + +GstPad * +get_unlinked_sink_ghost_pad (NleOperation * operation) +{ + GstIterator *pads; + gboolean done = FALSE; + GValue item = { 0, }; + GstPad *ret = NULL; + + if (!operation->element) + return NULL; + + pads = gst_element_iterate_sink_pads ((GstElement *) operation); + + while (!done) { + switch (gst_iterator_next (pads, &item)) { + case GST_ITERATOR_OK: + { + GstPad *pad = g_value_get_object (&item); + GstPad *peer = gst_pad_get_peer (pad); + + if (peer == NULL) { + ret = pad; + gst_object_ref (ret); + done = TRUE; + } else { + gst_object_unref (peer); + } + g_value_reset (&item); + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync (pads); + break; + default: + /* ERROR and DONE */ + done = TRUE; + break; + } + } + + g_value_unset (&item); + gst_iterator_free (pads); + + if (ret) + GST_DEBUG_OBJECT (operation, "found unlinked ghost sink pad %s:%s", + GST_DEBUG_PAD_NAME (ret)); + else + GST_DEBUG_OBJECT (operation, "Couldn't find an unlinked ghost sink pad"); + + return ret; + +} + +static GstPad * +get_request_sink_pad (NleOperation * operation) +{ + GstPad *pad = NULL; + GList *templates; + + if (!operation->element) + return NULL; + + templates = gst_element_class_get_pad_template_list + (GST_ELEMENT_GET_CLASS (operation->element)); + + for (; templates; templates = templates->next) { + GstPadTemplate *templ = (GstPadTemplate *) templates->data; + + GST_LOG_OBJECT (operation->element, "Trying template %s", + GST_PAD_TEMPLATE_NAME_TEMPLATE (templ)); + + if ((GST_PAD_TEMPLATE_DIRECTION (templ) == GST_PAD_SINK) && + (GST_PAD_TEMPLATE_PRESENCE (templ) == GST_PAD_REQUEST)) { + pad = + gst_element_request_pad_simple (operation->element, + GST_PAD_TEMPLATE_NAME_TEMPLATE (templ)); + if (pad) + break; + } + } + + return pad; +} + +static GstPad * +add_sink_pad (NleOperation * operation) +{ + GstPad *gpad = NULL; + GstPad *ret = NULL; + + if (!operation->element) + return NULL; + + /* FIXME : implement */ + GST_LOG_OBJECT (operation, "element:%s , dynamicsinks:%d", + GST_ELEMENT_NAME (operation->element), operation->dynamicsinks); + + + if (!operation->dynamicsinks) { + /* static sink pads */ + ret = get_unused_static_sink_pad (operation); + if (ret) { + gpad = nle_object_ghost_pad ((NleObject *) operation, GST_PAD_NAME (ret), + ret); + gst_object_unref (ret); + } + } + + if (!gpad) { + /* request sink pads */ + ret = get_request_sink_pad (operation); + if (ret) { + gpad = nle_object_ghost_pad ((NleObject *) operation, GST_PAD_NAME (ret), + ret); + gst_object_unref (ret); + } + } + + if (gpad) { + operation->sinks = g_list_append (operation->sinks, gpad); + operation->realsinks++; + GST_DEBUG ("Created new pad %s:%s ghosting %s:%s", + GST_DEBUG_PAD_NAME (gpad), GST_DEBUG_PAD_NAME (ret)); + } else { + GST_WARNING ("Couldn't find a usable sink pad!"); + } + + return gpad; +} + +static gboolean +remove_sink_pad (NleOperation * operation, GstPad * sinkpad) +{ + gboolean ret = TRUE; + gboolean need_unref = FALSE; + + GST_DEBUG ("sinkpad %s:%s", GST_DEBUG_PAD_NAME (sinkpad)); + + /* + We can't remove any random pad. + We should remove an unused pad ... which is hard to figure out in a + thread-safe way. + */ + + if ((sinkpad == NULL) && operation->dynamicsinks) { + /* Find an unlinked sinkpad */ + if ((sinkpad = get_unlinked_sink_ghost_pad (operation)) == NULL) { + ret = FALSE; + goto beach; + } + need_unref = TRUE; + } + + if (sinkpad) { + GstPad *target = gst_ghost_pad_get_target ((GstGhostPad *) sinkpad); + + if (target) { + /* release the target pad */ + nle_object_ghost_pad_set_target ((NleObject *) operation, sinkpad, NULL); + if (operation->dynamicsinks) + gst_element_release_request_pad (operation->element, target); + gst_object_unref (target); + } + operation->sinks = g_list_remove (operation->sinks, sinkpad); + nle_object_remove_ghost_pad ((NleObject *) operation, sinkpad); + if (need_unref) + gst_object_unref (sinkpad); + operation->realsinks--; + } + +beach: + return ret; +} + +static void +synchronize_sinks (NleOperation * operation) +{ + + GST_DEBUG_OBJECT (operation, "num_sinks:%d , realsinks:%d, dynamicsinks:%d", + operation->num_sinks, operation->realsinks, operation->dynamicsinks); + + if (operation->num_sinks == operation->realsinks) + return; + + if (operation->num_sinks > operation->realsinks) { + while (operation->num_sinks > operation->realsinks) /* Add pad */ + if (!(add_sink_pad (operation))) { + break; + } + } else { + /* Remove pad */ + /* FIXME, which one do we remove ? :) */ + while (operation->num_sinks < operation->realsinks) + if (!remove_sink_pad (operation, NULL)) + break; + } +} + +static gboolean +nle_operation_prepare (NleObject * object) +{ + /* Prepare the pads */ + synchronize_sinks ((NleOperation *) object); + + return TRUE; +} + +static gboolean +nle_operation_cleanup (NleObject * object) +{ + NleOperation *oper = (NleOperation *) object; + + if (oper->dynamicsinks) { + GST_DEBUG ("Resetting dynamic sinks"); + nle_operation_set_sinks (oper, 0); + } + + return TRUE; +} + +void +nle_operation_hard_cleanup (NleOperation * operation) +{ + gboolean done = FALSE; + + GValue item = { 0, }; + GstIterator *pads; + + GST_INFO_OBJECT (operation, "Hard reset of the operation"); + + pads = gst_element_iterate_sink_pads (GST_ELEMENT (operation)); + while (!done) { + switch (gst_iterator_next (pads, &item)) { + case GST_ITERATOR_OK: + { + GstPad *sinkpad = g_value_get_object (&item); + GstPad *srcpad = gst_pad_get_peer (sinkpad); + + if (srcpad) { + GST_ERROR ("Unlinking %" GST_PTR_FORMAT " and %" + GST_PTR_FORMAT, srcpad, sinkpad); + gst_pad_unlink (srcpad, sinkpad); + gst_object_unref (srcpad); + } + + g_value_reset (&item); + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync (pads); + break; + default: + /* ERROR and DONE */ + done = TRUE; + break; + } + } + nle_object_cleanup (NLE_OBJECT (operation)); + + gst_iterator_free (pads); +} + + +static GstPad * +nle_operation_request_new_pad (GstElement * element, GstPadTemplate * templ, + const gchar * name, const GstCaps * caps) +{ + NleOperation *operation = (NleOperation *) element; + GstPad *ret; + + GST_DEBUG ("template:%s name:%s", templ->name_template, name); + + if (operation->num_sinks == operation->realsinks) { + GST_WARNING_OBJECT (element, + "We already have the maximum number of pads : %d", + operation->num_sinks); + return NULL; + } + + ret = add_sink_pad ((NleOperation *) element); + + return ret; +} + +static void +nle_operation_release_pad (GstElement * element, GstPad * pad) +{ + GST_DEBUG ("pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + + remove_sink_pad ((NleOperation *) element, pad); +} + +void +nle_operation_signal_input_priority_changed (NleOperation * operation, + GstPad * pad, guint32 priority) +{ + GST_DEBUG_OBJECT (operation, "pad:%s:%s, priority:%d", + GST_DEBUG_PAD_NAME (pad), priority); + g_signal_emit (operation, nle_operation_signals[INPUT_PRIORITY_CHANGED], + 0, pad, priority); +} diff --git a/plugins/nle/nleoperation.h b/plugins/nle/nleoperation.h new file mode 100644 index 0000000000..c2db09563b --- /dev/null +++ b/plugins/nle/nleoperation.h @@ -0,0 +1,88 @@ +/* GStreamer + * Copyright (C) 2001 Wim Taymans <wim.taymans@chello.be> + * 2004 Edward Hervey <bilboed@bilboed.com> + * + * nleoperation.h: Header for base NleOperation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __NLE_OPERATION_H__ +#define __NLE_OPERATION_H__ + +#include <gst/gst.h> +#include "nleobject.h" + +G_BEGIN_DECLS +#define NLE_TYPE_OPERATION \ + (nle_operation_get_type()) +#define NLE_OPERATION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),NLE_TYPE_OPERATION,NleOperation)) +#define NLE_OPERATION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),NLE_TYPE_OPERATION,NleOperationClass)) +#define NLE_IS_OPERATION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),NLE_TYPE_OPERATION)) +#define NLE_IS_OPERATION_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),NLE_TYPE_OPERATION)) + struct _NleOperation +{ + NleObject parent; + + /* <private> */ + + /* num_sinks: + * Number of sink inputs of the controlled element. + * -1 if the sink pads are dynamic */ + gint num_sinks; + + /* TRUE if element has request pads */ + gboolean dynamicsinks; + + /* realsinks: + * Number of sink pads currently used on the contolled element. */ + gint realsinks; + + /* FIXME : We might need to use a lock to access this list */ + GList * sinks; /* The sink ghostpads */ + + GstElement *element; /* controlled element */ +}; + +struct _NleOperationClass +{ + NleObjectClass parent_class; + + void (*input_priority_changed) (NleOperation * operation, GstPad *pad, guint32 priority); +}; + +GstPad * get_unlinked_sink_ghost_pad (NleOperation * operation) G_GNUC_INTERNAL; + +void +nle_operation_signal_input_priority_changed(NleOperation * operation, GstPad *pad, + guint32 priority) G_GNUC_INTERNAL; + +void nle_operation_update_base_time (NleOperation *operation, + GstClockTime timestamp) G_GNUC_INTERNAL; + +void nle_operation_hard_cleanup (NleOperation *operation) G_GNUC_INTERNAL; + + +/* normal GOperation stuff */ +GType nle_operation_get_type (void) G_GNUC_INTERNAL; + +G_END_DECLS +#endif /* __NLE_OPERATION_H__ */ diff --git a/plugins/nle/nlesource.c b/plugins/nle/nlesource.c new file mode 100644 index 0000000000..db5316ce4f --- /dev/null +++ b/plugins/nle/nlesource.c @@ -0,0 +1,584 @@ +/* Gnonlin + * Copyright (C) <2001> Wim Taymans <wim.taymans@gmail.com> + * <2004-2008> Edward Hervey <bilboed@bilboed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "nle.h" + +/** + * SECTION:element-nlesource + * + * The NleSource encapsulates a pipeline which produces data for processing + * in a #NleComposition. + */ + +static GstStaticPadTemplate nle_source_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC (nlesource); +#define GST_CAT_DEFAULT nlesource + +#define _do_init \ + GST_DEBUG_CATEGORY_INIT (nlesource, "nlesource", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin Source Element"); +#define nle_source_parent_class parent_class +struct _NleSourcePrivate +{ + gboolean dispose_has_run; + + gboolean dynamicpads; /* TRUE if the controlled element has dynamic pads */ + + gulong padremovedid; /* signal handler for element pad-removed signal */ + gulong padaddedid; /* signal handler for element pad-added signal */ + + gboolean pendingblock; /* We have a pending pad_block */ + gboolean areblocked; /* We already got blocked */ + GstPad *ghostedpad; /* Pad (to be) ghosted */ + GstPad *staticpad; /* The only pad. We keep an extra ref */ + + GMutex seek_lock; + GstEvent *seek_event; + guint32 flush_seqnum; + gulong probeid; +}; + +G_DEFINE_TYPE_WITH_CODE (NleSource, nle_source, NLE_TYPE_OBJECT, + G_ADD_PRIVATE (NleSource) + _do_init); + + +static gboolean nle_source_prepare (NleObject * object); +static gboolean nle_source_send_event (GstElement * element, GstEvent * event); +static gboolean nle_source_add_element (GstBin * bin, GstElement * element); +static gboolean nle_source_remove_element (GstBin * bin, GstElement * element); +static void nle_source_dispose (GObject * object); + +static gboolean +nle_source_control_element_func (NleSource * source, GstElement * element); +static GstStateChangeReturn nle_source_change_state (GstElement * element, + GstStateChange transition); + +static void +nle_source_class_init (NleSourceClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBinClass *gstbin_class; + NleObjectClass *nleobject_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbin_class = (GstBinClass *) klass; + nleobject_class = (NleObjectClass *) klass; + + gst_element_class_set_static_metadata (gstelement_class, "GNonLin Source", + "Filter/Editor", + "Manages source elements", + "Wim Taymans <wim.taymans@gmail.com>, Edward Hervey <bilboed@bilboed.com>"); + + gstelement_class->send_event = GST_DEBUG_FUNCPTR (nle_source_send_event); + gstelement_class->change_state = GST_DEBUG_FUNCPTR (nle_source_change_state); + + parent_class = g_type_class_ref (NLE_TYPE_OBJECT); + + klass->control_element = GST_DEBUG_FUNCPTR (nle_source_control_element_func); + + nleobject_class->prepare = GST_DEBUG_FUNCPTR (nle_source_prepare); + + gstbin_class->add_element = GST_DEBUG_FUNCPTR (nle_source_add_element); + gstbin_class->remove_element = GST_DEBUG_FUNCPTR (nle_source_remove_element); + + gobject_class->dispose = GST_DEBUG_FUNCPTR (nle_source_dispose); + + gst_element_class_add_static_pad_template (gstelement_class, + &nle_source_src_template); + +} + +static GstPadProbeReturn +srcpad_probe_cb (GstPad * pad, GstPadProbeInfo * info, NleSource * source) +{ + GstEvent *event = info->data; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + GST_OBJECT_LOCK (source); + source->priv->flush_seqnum = GST_EVENT_SEQNUM (event); + GST_DEBUG_OBJECT (pad, "Seek seqnum: %d", source->priv->flush_seqnum); + GST_OBJECT_UNLOCK (source); + break; + default: + break; + } + + return GST_PAD_PROBE_OK; +} + +static void +nle_source_init (NleSource * source) +{ + GST_OBJECT_FLAG_SET (source, NLE_OBJECT_SOURCE); + source->element = NULL; + source->priv = nle_source_get_instance_private (source); + g_mutex_init (&source->priv->seek_lock); + + gst_pad_add_probe (NLE_OBJECT_SRC (source), + GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, (GstPadProbeCallback) srcpad_probe_cb, + source, NULL); + + GST_DEBUG_OBJECT (source, "Setting GstBin async-handling to TRUE"); + g_object_set (G_OBJECT (source), "async-handling", TRUE, NULL); +} + +static void +nle_source_dispose (GObject * object) +{ + NleObject *nleobject = (NleObject *) object; + NleSource *source = (NleSource *) object; + NleSourcePrivate *priv = source->priv; + + GST_DEBUG_OBJECT (object, "dispose"); + + if (priv->dispose_has_run) + return; + + GST_OBJECT_LOCK (object); + if (priv->probeid) { + GST_DEBUG_OBJECT (source, "Removing blocking probe! %lu", priv->probeid); + priv->areblocked = FALSE; + gst_pad_remove_probe (priv->ghostedpad, priv->probeid); + priv->probeid = 0; + } + GST_OBJECT_UNLOCK (object); + + + if (source->element) { + gst_object_unref (source->element); + source->element = NULL; + } + + priv->dispose_has_run = TRUE; + if (priv->ghostedpad) + nle_object_ghost_pad_set_target (nleobject, nleobject->srcpad, NULL); + + if (priv->staticpad) { + gst_object_unref (priv->staticpad); + priv->staticpad = NULL; + } + + g_mutex_lock (&priv->seek_lock); + if (priv->seek_event) { + gst_event_unref (priv->seek_event); + priv->seek_event = NULL; + } + g_mutex_unlock (&priv->seek_lock); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +element_pad_added_cb (GstElement * element G_GNUC_UNUSED, GstPad * pad, + NleSource * source) +{ + GstCaps *srccaps; + NleSourcePrivate *priv = source->priv; + NleObject *nleobject = (NleObject *) source; + + GST_DEBUG_OBJECT (source, "pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + + if (priv->ghostedpad) { + GST_DEBUG_OBJECT (source, + "We already have a target, not doing anything with %s:%s", + GST_DEBUG_PAD_NAME (pad)); + + return; + } + + /* FIXME: pass filter caps to query_caps directly */ + srccaps = gst_pad_query_caps (pad, NULL); + if (nleobject->caps && !gst_caps_can_intersect (srccaps, nleobject->caps)) { + gst_caps_unref (srccaps); + GST_DEBUG_OBJECT (source, "Pad doesn't have valid caps, ignoring"); + return; + } + gst_caps_unref (srccaps); + + priv->ghostedpad = pad; + GST_DEBUG_OBJECT (nleobject, "SET target %" GST_PTR_FORMAT, pad); + nle_object_ghost_pad_set_target (nleobject, nleobject->srcpad, pad); + + GST_DEBUG_OBJECT (source, "Using pad pad %s:%s as a target now!", + GST_DEBUG_PAD_NAME (pad)); +} + +static void +element_pad_removed_cb (GstElement * element G_GNUC_UNUSED, GstPad * pad, + NleSource * source) +{ + NleSourcePrivate *priv = source->priv; + NleObject *nleobject = (NleObject *) source; + + GST_DEBUG_OBJECT (source, "pad %s:%s (controlled pad %s:%s)", + GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (priv->ghostedpad)); + + if (pad == priv->ghostedpad) { + GST_DEBUG_OBJECT (source, + "The removed pad is the controlled pad, clearing up"); + + GST_DEBUG_OBJECT (source, "Clearing up ghostpad"); + + if (nleobject->srcpad) + nle_object_ghost_pad_set_target (NLE_OBJECT (source), nleobject->srcpad, + NULL); + priv->ghostedpad = NULL; + } else { + GST_DEBUG_OBJECT (source, "The removed pad is NOT our controlled pad"); + } +} + +static gint +compare_src_pad (GValue * item, GstCaps * caps) +{ + gint ret = 1; + GstPad *pad = g_value_get_object (item); + GstCaps *padcaps; + + GST_DEBUG_OBJECT (pad, "Trying pad for caps %" GST_PTR_FORMAT, caps); + + /* FIXME: can pass the filter caps right away.. */ + padcaps = gst_pad_query_caps (pad, NULL); + + if (gst_caps_can_intersect (padcaps, caps)) + ret = 0; + + gst_caps_unref (padcaps); + + return ret; +} + +/* + get_valid_src_pad + + Returns True if there's a src pad compatible with the NleObject caps in the + given element. Fills in pad if so. The returned pad has an incremented refcount +*/ + +static gboolean +get_valid_src_pad (NleSource * source, GstElement * element, GstPad ** pad) +{ + gboolean res = FALSE; + GstIterator *srcpads; + GValue item = { 0, }; + + g_return_val_if_fail (pad, FALSE); + + srcpads = gst_element_iterate_src_pads (element); + if (gst_iterator_find_custom (srcpads, (GCompareFunc) compare_src_pad, &item, + NLE_OBJECT (source)->caps)) { + *pad = g_value_get_object (&item); + gst_object_ref (*pad); + g_value_reset (&item); + res = TRUE; + } + gst_iterator_free (srcpads); + + return res; +} + +/* + * has_dynamic_pads + * Returns TRUE if the element has only dynamic pads. + */ + +static gboolean +has_dynamic_srcpads (GstElement * element) +{ + gboolean ret = TRUE; + GList *templates; + GstPadTemplate *template; + + templates = + gst_element_class_get_pad_template_list (GST_ELEMENT_GET_CLASS (element)); + + while (templates) { + template = (GstPadTemplate *) templates->data; + + if ((GST_PAD_TEMPLATE_DIRECTION (template) == GST_PAD_SRC) + && (GST_PAD_TEMPLATE_PRESENCE (template) == GST_PAD_ALWAYS)) { + ret = FALSE; + break; + } + + templates = templates->next; + } + + return ret; +} + +static gboolean +nle_source_control_element_func (NleSource * source, GstElement * element) +{ + NleSourcePrivate *priv = source->priv; + GstPad *pad = NULL; + + g_return_val_if_fail (source->element == NULL, FALSE); + + GST_DEBUG_OBJECT (source, "element: %" GST_PTR_FORMAT ", source->element:%" + GST_PTR_FORMAT, element, source->element); + + source->element = element; + gst_object_ref (element); + + if (get_valid_src_pad (source, source->element, &pad)) { + priv->staticpad = pad; + nle_object_ghost_pad_set_target (NLE_OBJECT (source), + NLE_OBJECT_SRC (source), pad); + priv->dynamicpads = FALSE; + } else { + priv->dynamicpads = has_dynamic_srcpads (element); + GST_DEBUG_OBJECT (source, "No valid source pad yet, dynamicpads:%d", + priv->dynamicpads); + if (priv->dynamicpads) { + /* connect to pad-added/removed signals */ + priv->padremovedid = g_signal_connect + (G_OBJECT (element), "pad-removed", + G_CALLBACK (element_pad_removed_cb), source); + priv->padaddedid = + g_signal_connect (G_OBJECT (element), "pad-added", + G_CALLBACK (element_pad_added_cb), source); + } + } + + return TRUE; +} + +static gboolean +nle_source_add_element (GstBin * bin, GstElement * element) +{ + NleSource *source = (NleSource *) bin; + gboolean pret; + + GST_DEBUG_OBJECT (source, "Adding element %s", GST_ELEMENT_NAME (element)); + + if (source->element) { + GST_WARNING_OBJECT (bin, "NleSource can only handle one element at a time"); + return FALSE; + } + + /* call parent add_element */ + pret = GST_BIN_CLASS (parent_class)->add_element (bin, element); + + if (pret) { + nle_source_control_element_func (source, element); + } + return pret; +} + +static gboolean +nle_source_remove_element (GstBin * bin, GstElement * element) +{ + NleSource *source = (NleSource *) bin; + NleObject *nleobject = (NleObject *) element; + NleSourcePrivate *priv = source->priv; + gboolean pret; + + GST_DEBUG_OBJECT (source, "Removing element %s", GST_ELEMENT_NAME (element)); + + /* try to remove it */ + pret = GST_BIN_CLASS (parent_class)->remove_element (bin, element); + + if ((!source->element) || (source->element != element)) { + return TRUE; + } + + if (pret) { + nle_object_ghost_pad_set_target (NLE_OBJECT (source), nleobject->srcpad, + NULL); + + /* remove signal handlers */ + if (priv->padremovedid) { + g_signal_handler_disconnect (source->element, priv->padremovedid); + priv->padremovedid = 0; + } + if (priv->padaddedid) { + g_signal_handler_disconnect (source->element, priv->padaddedid); + priv->padaddedid = 0; + } + + priv->dynamicpads = FALSE; + gst_object_unref (element); + source->element = NULL; + } + return pret; +} + +static gboolean +nle_source_send_event (GstElement * element, GstEvent * event) +{ + gboolean res = TRUE; + NleSource *source = (NleSource *) element; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + g_mutex_lock (&source->priv->seek_lock); + gst_event_replace (&source->priv->seek_event, event); + g_mutex_unlock (&source->priv->seek_lock); + break; + default: + res = GST_ELEMENT_CLASS (parent_class)->send_event (element, event); + break; + } + + return res; +} + +static void +ghost_seek_pad (GstElement * source, gpointer user_data) +{ + NleSourcePrivate *priv = NLE_SOURCE (source)->priv; + + g_assert (!NLE_OBJECT (source)->in_composition); + g_mutex_lock (&priv->seek_lock); + if (priv->seek_event) { + GstEvent *seek_event = priv->seek_event; + priv->seek_event = NULL; + + GST_INFO_OBJECT (source, "Sending seek: %" GST_PTR_FORMAT, seek_event); + GST_OBJECT_LOCK (source); + priv->flush_seqnum = GST_EVENT_SEQNUM (seek_event); + GST_OBJECT_UNLOCK (source); + if (!(gst_pad_send_event (priv->ghostedpad, seek_event))) + GST_ELEMENT_ERROR (source, RESOURCE, SEEK, + (NULL), ("Sending initial seek to upstream element failed")); + } + g_mutex_unlock (&priv->seek_lock); +} + +static GstPadProbeReturn +pad_brobe_cb (GstPad * pad, GstPadProbeInfo * info, NleSource * source) +{ + NleSourcePrivate *priv = source->priv; + GstPadProbeReturn res = GST_PAD_PROBE_OK; + + GST_OBJECT_LOCK (source); + if (!priv->areblocked && priv->seek_event) { + GST_INFO_OBJECT (pad, "Blocked now, launching seek"); + priv->areblocked = TRUE; + gst_element_call_async (GST_ELEMENT (source), ghost_seek_pad, NULL, NULL); + GST_OBJECT_UNLOCK (source); + + return GST_PAD_PROBE_OK; + } + + if (priv->probeid && GST_EVENT_SEQNUM (info->data) == priv->flush_seqnum) { + priv->flush_seqnum = GST_SEQNUM_INVALID; + priv->areblocked = FALSE; + priv->probeid = 0; + res = GST_PAD_PROBE_REMOVE; + } else { + res = GST_PAD_PROBE_DROP; + GST_DEBUG_OBJECT (source, "Dropping %" GST_PTR_FORMAT " - %d ? %d", + info->data, GST_EVENT_SEQNUM (info->data), priv->flush_seqnum); + } + GST_OBJECT_UNLOCK (source); + + return res; +} + +static gboolean +nle_source_prepare (NleObject * object) +{ + GstPad *pad; + NleSource *source = NLE_SOURCE (object); + NleSourcePrivate *priv = source->priv; + GstElement *parent = + (GstElement *) gst_element_get_parent ((GstElement *) object); + + if (!source->element) { + GST_WARNING_OBJECT (source, + "NleSource doesn't have an element to control !"); + if (parent) + gst_object_unref (parent); + return FALSE; + } + + if (!priv->staticpad && !(get_valid_src_pad (source, source->element, &pad))) { + GST_DEBUG_OBJECT (source, "Couldn't find a valid source pad"); + gst_object_unref (parent); + return FALSE; + } + + if (priv->staticpad) + pad = gst_object_ref (priv->staticpad); + priv->ghostedpad = pad; + + if (object->in_composition == FALSE) { + GstClockTime start = + GST_CLOCK_TIME_IS_VALID (object->inpoint) ? object->inpoint : 0; + GstClockTime stop = GST_CLOCK_TIME_NONE; + + if (GST_CLOCK_TIME_IS_VALID (object->inpoint) + && GST_CLOCK_TIME_IS_VALID (object->duration) && object->duration) + stop = object->inpoint + object->duration; + + g_mutex_lock (&source->priv->seek_lock); + source->priv->seek_event = gst_event_new_seek (1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_SET, stop); + g_mutex_unlock (&source->priv->seek_lock); + GST_OBJECT_LOCK (source); + priv->probeid = gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH | + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, (GstPadProbeCallback) pad_brobe_cb, + source, NULL); + GST_OBJECT_UNLOCK (source); + } + + GST_LOG_OBJECT (source, "srcpad:%p, dynamicpads:%d", + object->srcpad, priv->dynamicpads); + + gst_object_unref (pad); + gst_object_unref (parent); + + return TRUE; +} + +static GstStateChangeReturn +nle_source_change_state (GstElement * element, GstStateChange transition) +{ + NleSourcePrivate *priv = NLE_SOURCE (element)->priv; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + g_mutex_lock (&priv->seek_lock); + gst_clear_event (&priv->seek_event); + g_mutex_unlock (&priv->seek_lock); + break; + default: + break; + } + + return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); +} diff --git a/plugins/nle/nlesource.h b/plugins/nle/nlesource.h new file mode 100644 index 0000000000..6fcb1af4a2 --- /dev/null +++ b/plugins/nle/nlesource.h @@ -0,0 +1,66 @@ +/* GStreamer + * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com> + * 2004-2008 Edward Hervey <bilboed@bilboed.com> + * + * nlesource.h: Header for base NleSource + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __NLE_SOURCE_H__ +#define __NLE_SOURCE_H__ + +#include <gst/gst.h> +#include "nleobject.h" + +G_BEGIN_DECLS +#define NLE_TYPE_SOURCE \ + (nle_source_get_type()) +#define NLE_SOURCE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),NLE_TYPE_SOURCE,NleSource)) +#define NLE_SOURCE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),NLE_TYPE_SOURCE,NleSourceClass)) +#define NLE_SOURCE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), NLE_TYPE_SOURCE, NleSourceClass)) +#define NLE_IS_SOURCE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),NLE_TYPE_SOURCE)) +#define NLE_IS_SOURCE_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),NLE_TYPE_SOURCE)) +typedef struct _NleSourcePrivate NleSourcePrivate; + +struct _NleSource +{ + NleObject parent; + + /* controlled source element, acces with gst_bin_[add|remove]_element */ + GstElement *element; + + NleSourcePrivate *priv; +}; + +struct _NleSourceClass +{ + NleObjectClass parent_class; + + /* control_element() takes care of controlling the given element */ + gboolean (*control_element) (NleSource * source, GstElement * element); +}; + +GType nle_source_get_type (void) G_GNUC_INTERNAL; + +G_END_DECLS +#endif /* __NLE_SOURCE_H__ */ diff --git a/plugins/nle/nletypes.h b/plugins/nle/nletypes.h new file mode 100644 index 0000000000..f84cb23b29 --- /dev/null +++ b/plugins/nle/nletypes.h @@ -0,0 +1,42 @@ +/* GStreamer + * Copyright (C) 2004 Edward Hervey <bilboed@bilboed.com> + * + * nletypes.h: Header for class definition + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __NLE_TYPES_H__ +#define __NLE_TYPES_H__ + +#include <glib.h> + +typedef struct _NleObject NleObject; +typedef struct _NleObjectClass NleObjectClass; + +typedef struct _NleComposition NleComposition; +typedef struct _NleCompositionClass NleCompositionClass; + +typedef struct _NleOperation NleOperation; +typedef struct _NleOperationClass NleOperationClass; + +typedef struct _NleSource NleSource; +typedef struct _NleSourceClass NleSourceClass; + +typedef struct _NleURISource NleURISource; +typedef struct _NleURISourceClass NleURISourceClass; + +#endif diff --git a/plugins/nle/nleurisource.c b/plugins/nle/nleurisource.c new file mode 100644 index 0000000000..eed90a5e61 --- /dev/null +++ b/plugins/nle/nleurisource.c @@ -0,0 +1,181 @@ +/* Gnonlin + * Copyright (C) <2005-2008> Edward Hervey <bilboed@bilboed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "nle.h" +#include "nleurisource.h" + +/** + * SECTION:element-nleurisource + * + * NleURISource is a #NleSource which reads and decodes the contents + * of a given file. The data in the file is decoded using any available + * GStreamer plugins. + */ + +static GstStaticPadTemplate nle_urisource_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC (nleurisource); +#define GST_CAT_DEFAULT nleurisource + +#define _do_init \ + GST_DEBUG_CATEGORY_INIT (nleurisource, "nleurisource", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin URI Source Element"); +#define nle_urisource_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (NleURISource, nle_urisource, NLE_TYPE_SOURCE, + _do_init); + +enum +{ + ARG_0, + ARG_URI, +}; + +static gboolean nle_urisource_prepare (NleObject * object); + +static void +nle_urisource_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); + +static void +nle_urisource_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static void +nle_urisource_class_init (NleURISourceClass * klass) +{ + GObjectClass *gobject_class; + NleObjectClass *nleobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + nleobject_class = (NleObjectClass *) klass; + parent_class = g_type_class_ref (NLE_TYPE_SOURCE); + + gst_element_class_set_static_metadata (gstelement_class, "GNonLin URI Source", + "Filter/Editor", + "High-level URI Source element", "Edward Hervey <bilboed@bilboed.com>"); + + gobject_class->set_property = GST_DEBUG_FUNCPTR (nle_urisource_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR (nle_urisource_get_property); + + g_object_class_install_property (gobject_class, ARG_URI, + g_param_spec_string ("uri", "Uri", + "Uri of the file to use", NULL, G_PARAM_READWRITE)); + + gst_element_class_add_static_pad_template (gstelement_class, + &nle_urisource_src_template); + + nleobject_class->prepare = nle_urisource_prepare; +} + +static void +pad_added_cb (GstElement * element, GstPad * srcpad, GstPad * ghostpad) +{ + gst_ghost_pad_set_target (GST_GHOST_PAD (ghostpad), srcpad); +} + +static void +nle_urisource_init (NleURISource * urisource) +{ + GstElement *bin, *decodebin = NULL; + GstPad *ghostpad; + + GST_OBJECT_FLAG_SET (urisource, NLE_OBJECT_SOURCE); + + /* We create a bin with source and decodebin within */ + urisource->decodebin = decodebin = + gst_element_factory_make ("uridecodebin", "internal-uridecodebin"); + g_object_set (decodebin, "expose-all-streams", FALSE, NULL); + + bin = gst_bin_new ("internal-bin"); + gst_bin_add (GST_BIN (bin), decodebin); + + ghostpad = gst_ghost_pad_new_no_target ("src", GST_PAD_SRC); + gst_element_add_pad (bin, ghostpad); + + gst_bin_add (GST_BIN (urisource), bin); + + g_signal_connect (decodebin, "pad-added", G_CALLBACK (pad_added_cb), + ghostpad); +} + +static inline void +nle_urisource_set_uri (NleURISource * fs, const gchar * uri) +{ + g_object_set (fs->decodebin, "uri", uri, NULL); +} + +static void +nle_urisource_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + NleURISource *fs = (NleURISource *) object; + + switch (prop_id) { + case ARG_URI: + nle_urisource_set_uri (fs, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nle_urisource_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + NleURISource *fs = (NleURISource *) object; + + switch (prop_id) { + case ARG_URI: + g_object_get_property ((GObject *) fs->decodebin, "uri", value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +static gboolean +nle_urisource_prepare (NleObject * object) +{ + NleURISource *fs = (NleURISource *) object; + + GST_DEBUG ("prepare"); + + /* Set the caps on uridecodebin */ + if (!gst_caps_is_any (object->caps)) { + GST_DEBUG_OBJECT (object, "Setting uridecodebin caps to %" GST_PTR_FORMAT, + object->caps); + g_object_set (fs->decodebin, "caps", object->caps, NULL); + } + + return NLE_OBJECT_CLASS (parent_class)->prepare (object); +} diff --git a/plugins/nle/nleurisource.h b/plugins/nle/nleurisource.h new file mode 100644 index 0000000000..14bbe4f3d7 --- /dev/null +++ b/plugins/nle/nleurisource.h @@ -0,0 +1,58 @@ +/* GStreamer + * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com> + * 2004-2008 Edward Hervey <bilboed@bilboed.com> + * + * nleurisource.h: Header for NleURISource + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __NLE_URI_SOURCE_H__ +#define __NLE_URI_SOURCE_H__ + +#include <gst/gst.h> +#include "nlesource.h" + +G_BEGIN_DECLS +#define NLE_TYPE_URI_SOURCE \ + (nle_urisource_get_type()) +#define NLE_URI_SOURCE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),NLE_TYPE_URI_SOURCE,NleURIsource)) +#define NLE_URI_SOURCE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),NLE_TYPE_URI_SOURCE,NleURIsourceClass)) +#define NLE_IS_URI_SOURCE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),NLE_TYPE_URI_SOURCE)) +#define NLE_IS_URI_SOURCE_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),NLE_TYPE_URI_SOURCE)) + +struct _NleURISource +{ + NleSource parent; + + gchar *uri; + GstElement *decodebin; +}; + +struct _NleURISourceClass +{ + NleSourceClass parent_class; +}; + +GType nle_urisource_get_type (void) G_GNUC_INTERNAL; + +G_END_DECLS +#endif /* __NLE_URI_SOURCE_H__ */ diff --git a/scripts/extract-release-date-from-doap-file.py b/scripts/extract-release-date-from-doap-file.py new file mode 100755 index 0000000000..f09b60e9d0 --- /dev/null +++ b/scripts/extract-release-date-from-doap-file.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# +# extract-release-date-from-doap-file.py VERSION DOAP-FILE +# +# Extract release date for the given release version from a DOAP file +# +# Copyright (C) 2020 Tim-Philipp Müller <tim centricular com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library 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 +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +import sys +import xml.etree.ElementTree as ET + +if len(sys.argv) != 3: + sys.exit('Usage: {} VERSION DOAP-FILE'.format(sys.argv[0])) + +release_version = sys.argv[1] +doap_fn = sys.argv[2] + +tree = ET.parse(doap_fn) +root = tree.getroot() + +namespaces = {'doap': 'http://usefulinc.com/ns/doap#'} + +for v in root.findall('doap:release/doap:Version', namespaces=namespaces): + if v.findtext('doap:revision', namespaces=namespaces) == release_version: + release_date = v.findtext('doap:created', namespaces=namespaces) + if release_date: + print(release_date) + sys.exit(0) + +sys.exit('Could not find a release with version {} in {}'.format(release_version, doap_fn)) diff --git a/tests/benchmarks/meson.build b/tests/benchmarks/meson.build new file mode 100644 index 0000000000..68d05d4272 --- /dev/null +++ b/tests/benchmarks/meson.build @@ -0,0 +1,10 @@ +ges_benchmarks = ['timeline'] + +foreach b : ges_benchmarks + fname = '@0@.c'.format(b) + executable('benchmark-' + b, fname, + c_args : ges_c_args, + include_directories : [configinc], + dependencies : ges_dep + ) +endforeach diff --git a/tests/benchmarks/timeline.c b/tests/benchmarks/timeline.c new file mode 100644 index 0000000000..1daa8ee924 --- /dev/null +++ b/tests/benchmarks/timeline.c @@ -0,0 +1,97 @@ +/* Gstreamer Editing Services + * + * Copyright (C) <2012> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <ges/ges.h> + + +#define NUM_OBJECTS 1000 + +gint +main (gint argc, gchar * argv[]) +{ + guint i; + GESAsset *asset; + GESTimeline *timeline; + GESLayer *layer; + GESContainer *container; + GstClockTime start, start_ripple, end, end_ripple, max_rippling_time = 0, + min_rippling_time = GST_CLOCK_TIME_NONE; + + gst_init (&argc, &argv); + ges_init (); + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + layer = ges_layer_new (); + timeline = ges_timeline_new_audio_video (); + ges_timeline_add_layer (timeline, layer); + + start = gst_util_get_timestamp (); + container = GES_CONTAINER (ges_layer_add_asset (layer, asset, 0, + 0, 1000, GES_TRACK_TYPE_UNKNOWN)); + + for (i = 1; i < NUM_OBJECTS; i++) + ges_layer_add_asset (layer, asset, i * 1000, 0, + 1000, GES_TRACK_TYPE_UNKNOWN); + end = gst_util_get_timestamp (); + gst_print ("%" GST_TIME_FORMAT " - adding %d clip to the timeline\n", + GST_TIME_ARGS (end - start), i); + + start_ripple = gst_util_get_timestamp (); + for (i = 1; i < 501; i++) { + start = gst_util_get_timestamp (); + ges_container_edit (container, NULL, 0, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, i * 1000); + end = gst_util_get_timestamp (); + max_rippling_time = MAX (max_rippling_time, end - start); + min_rippling_time = MIN (min_rippling_time, end - start); + } + end_ripple = gst_util_get_timestamp (); + gst_print ("%" GST_TIME_FORMAT " - rippling %d times, max: %" + GST_TIME_FORMAT " min: %" GST_TIME_FORMAT "\n", + GST_TIME_ARGS (end_ripple - start_ripple), i - 1, + GST_TIME_ARGS (max_rippling_time), GST_TIME_ARGS (min_rippling_time)); + + + min_rippling_time = GST_CLOCK_TIME_NONE; + max_rippling_time = 0; + ges_layer_set_auto_transition (layer, TRUE); + start_ripple = gst_util_get_timestamp (); + for (i = 1; i < 501; i++) { + start = gst_util_get_timestamp (); + ges_container_edit (container, NULL, 0, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, i * 1000); + end = gst_util_get_timestamp (); + max_rippling_time = MAX (max_rippling_time, end - start); + min_rippling_time = MIN (min_rippling_time, end - start); + } + end_ripple = gst_util_get_timestamp (); + gst_print ("%" GST_TIME_FORMAT " - rippling %d times, max: %" + GST_TIME_FORMAT " min: %" GST_TIME_FORMAT " (with auto-transition on)\n", + GST_TIME_ARGS (end_ripple - start_ripple), i - 1, + GST_TIME_ARGS (max_rippling_time), GST_TIME_ARGS (min_rippling_time)); + + start = gst_util_get_timestamp (); + gst_object_unref (timeline); + end = gst_util_get_timestamp (); + gst_print ("%" GST_TIME_FORMAT " - freeing the timeline\n", + GST_TIME_ARGS (end - start)); + + return 0; +} diff --git a/tests/check/assets/audio_only.ogg b/tests/check/assets/audio_only.ogg new file mode 100644 index 0000000000..6baf00732e Binary files /dev/null and b/tests/check/assets/audio_only.ogg differ diff --git a/tests/check/assets/audio_video.ogg b/tests/check/assets/audio_video.ogg new file mode 100644 index 0000000000..8d7b53879a Binary files /dev/null and b/tests/check/assets/audio_video.ogg differ diff --git a/tests/check/assets/image.png b/tests/check/assets/image.png new file mode 100644 index 0000000000..9dfd5b2143 Binary files /dev/null and b/tests/check/assets/image.png differ diff --git a/tests/check/assets/png.png b/tests/check/assets/png.png new file mode 100644 index 0000000000..7b82cd39f3 Binary files /dev/null and b/tests/check/assets/png.png differ diff --git a/tests/check/assets/test-auto-transition.xges b/tests/check/assets/test-auto-transition.xges new file mode 100644 index 0000000000..5f7734e558 --- /dev/null +++ b/tests/check/assets/test-auto-transition.xges @@ -0,0 +1,28 @@ +<ges version="0.1"> + <project metadatas='metadatas, name=(string)"Example\ project";'> + <encoding-profiles> + <encoding-profile name='first_profile' description='(null)' type='container' format='application/ogg'> + <stream-profile parent='first_profile' id='0' type='video' presence='0' format='video/x-h264' pass='0' variableframerate='0' /> + <stream-profile parent='first_profile' id='1' type='audio' presence='0' format='audio/x-aac' /> + </encoding-profile> + </encoding-profiles> + <resources> + <asset id="file:///test/not/exisiting" + extractable-type-name="GESUriClip"/> + </resources> + <timeline properties='properties, auto-transition=(boolean)false'> + <track track-type="2" caps="audio/x-raw" track-id="0"/> + <track track-type="4" caps="video/x-raw" track-id="1"/> + <layer priority="0" properties='properties, auto-transition=(boolean)false;' metadatas='metadatas, a=(guint)3'> + <clip id="0" layer-priority='0' asset-id="file:///test/not/exisiting" type-name="GESUriClip" track-types="6" start="0" duration="1000000000"> + <effect asset-id='agingtv' clip-id='0' type-name='GESEffect' track-type='4' track-id='1' metadatas='metadatas;' children-properties='properties, scratch-lines=(uint)12;'/> + </clip> + </layer> + <layer priority="1" properties='properties, auto-transition=(boolean)false;'> + <clip id="1" asset-id="file:///test/not/exisiting" layer-priority="1" + type-name="GESUriClip" track-types="2" start="1000000000" duration="1000000000"> + </clip> + </layer> + </timeline> + </project> +</ges> diff --git a/tests/check/assets/test-project.xges b/tests/check/assets/test-project.xges new file mode 100644 index 0000000000..acc71ed467 --- /dev/null +++ b/tests/check/assets/test-project.xges @@ -0,0 +1,28 @@ +<ges version="0.1"> + <project metadatas='metadatas, name=(string)"Example\ project";'> + <encoding-profiles> + <encoding-profile name='first_profile' description='(null)' type='container' format='application/ogg'> + <stream-profile parent='first_profile' enabled='0' id='0' type='video' presence='0' format='video/x-h264' pass='0' variableframerate='0' /> + <stream-profile parent='first_profile' id='1' type='audio' presence='0' format='audio/x-aac' /> + </encoding-profile> + </encoding-profiles> + <resources> + <asset id="file:///test/not/exisiting" + extractable-type-name="GESUriClip"/> + </resources> + <timeline> + <track track-type="2" caps="audio/x-raw" track-id="0"/> + <track track-type="4" caps="video/x-raw" track-id="1"/> + <layer priority="0" properties='properties, auto-transition=(boolean)true;' metadatas='metadatas, a=(guint)3'> + <clip id="0" layer-priority='0' asset-id="file:///test/not/exisiting" type-name="GESUriClip" track-types="6" start="0" duration="1000000000"> + <effect asset-id='agingtv' clip-id='0' type-name='GESEffect' track-type='4' track-id='1' metadatas='metadatas;' children-properties='properties, scratch-lines=(uint)12;'/> + </clip> + </layer> + <layer priority="1" properties='properties, auto-transition=(boolean)true;'> + <clip id="1" asset-id="file:///test/not/exisiting" layer-priority="1" + type-name="GESUriClip" track-types="2" start="1000000000" duration="1000000000"> + </clip> + </layer> + </timeline> + </project> +</ges> diff --git a/tests/check/assets/test-properties.xges b/tests/check/assets/test-properties.xges new file mode 100644 index 0000000000..ff74b63ca5 --- /dev/null +++ b/tests/check/assets/test-properties.xges @@ -0,0 +1,23 @@ +<ges version="0.1"> + <project metadatas='metadatas, name=(string)"Example\ project";'> + <encoding-profiles> + <encoding-profile name='first_profile' description='(null)' type='container' format='application/ogg'> + <stream-profile parent='first_profile' id='0' type='video' presence='0' format='video/x-h264' pass='0' variableframerate='0' /> + <stream-profile parent='first_profile' id='1' type='audio' presence='0' format='audio/x-aac' /> + </encoding-profile> + </encoding-profiles> + <resources> + <asset id="file:///test/not/exisiting" + extractable-type-name="GESUriClip"/> + </resources> + <timeline> + <track track-type="2" caps="audio/x-raw" track-id="0"/> + <track track-type="4" caps="video/x-raw" track-id="1"/> + <layer priority="0" properties='properties, auto-transition=(boolean)true;' metadatas='metadatas, a=(guint)3'> + <clip id="0" layer-priority='0' asset-id="file:///test/not/exisiting" type-name="GESUriClip" track-types="6" start="0" duration="1000000000"> + <effect asset-id='agingtv' clip-id='0' type-name='GESEffect' track-type='4' track-id='1' metadatas='metadatas;' children-properties='properties, scratch-lines=(uint)12;'/> + </clip> + </layer> + </timeline> + </project> +</ges> diff --git a/tests/check/ges/asset.c b/tests/check/ges/asset.c new file mode 100644 index 0000000000..1e1188ad9f --- /dev/null +++ b/tests/check/ges/asset.c @@ -0,0 +1,755 @@ +/* GStreamer Editing Services + * + * Copyright (C) 2012 Volodymyr Rudyi <vladimir.rudoy@gmail.com> + * Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "test-utils.h" +/* #include "../../../ges/ges-internal.h" */ +#include <ges/ges.h> +#include <gst/check/gstcheck.h> + +static GMainLoop *mainloop; + +static void +source_asset_created (GObject * source, GAsyncResult * res, + gpointer expected_ok) +{ + GError *error = NULL; + + GESAsset *a = ges_asset_request_finish (res, &error); + + if (GPOINTER_TO_INT (expected_ok)) { + fail_unless (a != NULL); + fail_unless (error == NULL); + g_object_unref (a); + } else { + fail_unless (a == NULL); + assert_equals_int (error->domain, GST_RESOURCE_ERROR); + } + + g_clear_error (&error); + g_main_loop_quit (mainloop); +} + +GST_START_TEST (test_basic) +{ + ges_init (); + + mainloop = g_main_loop_new (NULL, FALSE); + ges_asset_request_async (GES_TYPE_URI_CLIP, + "file:///this/is/not/for/real", NULL, source_asset_created, + GINT_TO_POINTER (FALSE)); + + g_main_loop_run (mainloop); + g_main_loop_unref (mainloop); + ges_deinit (); +} + +GST_END_TEST; + +typedef struct _CustomContextData +{ + GMutex lock; + GCond cond; + gboolean finish; + gboolean expected_ok; + gchar *uri; +} CustomContextData; + +static gpointer +custom_context_thread_func (CustomContextData * data) +{ + GMainContext *context; + + context = g_main_context_new (); + mainloop = g_main_loop_new (context, FALSE); + + g_main_context_push_thread_default (context); + + /* To use custom context, we need to call ges_init() in the thread */ + fail_unless (ges_init ()); + + ges_asset_request_async (GES_TYPE_URI_CLIP, + data->uri, NULL, source_asset_created, + GINT_TO_POINTER (data->expected_ok)); + g_main_loop_run (mainloop); + + g_main_context_pop_thread_default (context); + ges_deinit (); + g_main_context_unref (context); + g_main_loop_unref (mainloop); + + data->finish = TRUE; + g_cond_signal (&data->cond); + + return NULL; +} + +GST_START_TEST (test_custom_context) +{ + GThread *thread; + CustomContextData data; + + mainloop = NULL; + + g_mutex_init (&data.lock); + g_cond_init (&data.cond); + /* ensure default context here, but we will not use the default context */ + g_main_context_default (); + + /* first run with invalid uri */ + data.finish = FALSE; + data.expected_ok = FALSE; + data.uri = g_strdup ("file:///this/is/not/for/real"); + + thread = g_thread_new ("test-custom-context-thread", + (GThreadFunc) custom_context_thread_func, &data); + + g_mutex_lock (&data.lock); + while (data.finish) + g_cond_wait (&data.cond, &data.lock); + g_mutex_unlock (&data.lock); + + g_thread_join (thread); + g_free (data.uri); + + /* second run with valid uri */ + data.finish = FALSE; + data.expected_ok = TRUE; + data.uri = ges_test_file_uri ("audio_video.ogg"); + + thread = g_thread_new ("test-custom-context-thread", + (GThreadFunc) custom_context_thread_func, &data); + + g_mutex_lock (&data.lock); + while (data.finish) + g_cond_wait (&data.cond, &data.lock); + g_mutex_unlock (&data.lock); + + g_thread_join (thread); + g_free (data.uri); + + g_mutex_clear (&data.lock); + g_cond_clear (&data.cond); +} + +GST_END_TEST; + +GST_START_TEST (test_transition_change_asset) +{ + gchar *id; + GESAsset *a; + GESExtractable *extractable; + + ges_init (); + + a = ges_asset_request (GES_TYPE_TRANSITION_CLIP, "box-wipe-lc", NULL); + + fail_unless (GES_IS_ASSET (a)); + fail_unless_equals_string (ges_asset_get_id (a), "box-wipe-lc"); + + extractable = ges_asset_extract (a, NULL); + fail_unless (ges_extractable_get_asset (extractable) == a); + + id = ges_extractable_get_id (extractable); + fail_unless_equals_string (id, "box-wipe-lc"); + g_free (id); + + g_object_set (extractable, "vtype", 2, NULL); + + id = ges_extractable_get_id (extractable); + fail_unless_equals_string (id, "bar-wipe-tb"); + g_free (id); + + fail_if (ges_extractable_get_asset (extractable) == a); + gst_object_unref (a); + + a = ges_extractable_get_asset (extractable); + fail_unless_equals_string (ges_asset_get_id (a), "bar-wipe-tb"); + + /* Now try to set the a and see if the vtype is properly updated */ + a = ges_asset_request (GES_TYPE_TRANSITION_CLIP, "box-wipe-lc", NULL); + ges_extractable_set_asset (extractable, a); + gst_object_unref (a); + + fail_unless_equals_int (GES_TRANSITION_CLIP (extractable)->vtype, 26); + + gst_object_unref (extractable); + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_uri_clip_change_asset) +{ + GESAsset *asset, *asset1; + GESExtractable *extractable; + GESLayer *layer; + gchar *uri; + gchar *uri1; + GESTimeline *timeline; + + ges_init (); + + layer = ges_layer_new (); + uri = ges_test_file_uri ("audio_video.ogg"); + uri1 = ges_test_file_uri ("audio_only.ogg"); + timeline = ges_timeline_new_audio_video (); + + ges_timeline_add_layer (timeline, layer); + + asset = GES_ASSET (ges_uri_clip_asset_request_sync (uri, NULL)); + + fail_unless (GES_IS_ASSET (asset)); + fail_unless_equals_string (ges_asset_get_id (asset), uri); + + extractable = GES_EXTRACTABLE (ges_layer_add_asset (layer, + asset, 0, 0, GST_CLOCK_TIME_NONE, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (ges_extractable_get_asset (extractable) == asset); + gst_object_unref (asset); + + /* Now try to set the a and see if the vtype is properly updated */ + asset1 = GES_ASSET (ges_uri_clip_asset_request_sync (uri1, NULL)); + fail_unless_equals_int (g_list_length (GES_CONTAINER_CHILDREN (extractable)), + 2); + fail_unless (ges_extractable_set_asset (extractable, asset1)); + fail_unless_equals_int (g_list_length (GES_CONTAINER_CHILDREN (extractable)), + 1); + + gst_object_unref (extractable); + + g_free (uri); + g_free (uri1); + + gst_object_unref (asset1); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_list_asset) +{ + GList *assets; + GEnumClass *enum_class; + + ges_init (); + + enum_class = g_type_class_peek (GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE); + + fail_unless (ges_init ()); + fail_if (ges_list_assets (GES_TYPE_OVERLAY_CLIP)); + + assets = ges_list_assets (GES_TYPE_TRANSITION_CLIP); + /* note: we do not have a a for value=0 "Transition not set" */ + assert_equals_int (g_list_length (assets), enum_class->n_values - 1); + g_list_free (assets); + + ges_deinit (); +} + +GST_END_TEST; + +/* + * NOTE: this test is commented out because it requires the internal + * ges_asset_finish_proxy method. This method replaces the behaviour of + * ges_asset_set_proxy (NULL, proxy), which is no longer supported. + * + * ges_asset_cache_lookup and ges_asset_try_proxy are similarly internal, + * but they are marked with GES_API in ges-internal.h + * The newer ges_asset_finish_proxy is not marked as GES_API, because it + * would add it to the symbols list, and could therefore not be easily + * removed. + * + * Once we have a nice way to call internal methods for tests, we should + * uncomment this. + * +GST_START_TEST (test_proxy_asset) +{ + GESAsset *identity, *nothing, *nothing_at_all; + + fail_unless (ges_init ()); + + identity = ges_asset_request (GES_TYPE_EFFECT, "video identity", NULL); + fail_unless (identity != NULL); + + nothing = ges_asset_request (GES_TYPE_EFFECT, "nothing", NULL); + fail_if (nothing); + + nothing = ges_asset_cache_lookup (GES_TYPE_EFFECT, "nothing"); + fail_unless (nothing != NULL); + + fail_unless (ges_asset_try_proxy (nothing, "video identity")); + fail_unless (ges_asset_finish_proxy (identity)); + + nothing_at_all = ges_asset_request (GES_TYPE_EFFECT, "nothing_at_all", NULL); + fail_if (nothing_at_all); + + nothing_at_all = ges_asset_cache_lookup (GES_TYPE_EFFECT, "nothing_at_all"); + fail_unless (nothing_at_all != NULL); + + // Now we proxy nothing_at_all to nothing which is itself proxied to identity + fail_unless (ges_asset_try_proxy (nothing_at_all, "nothing")); + fail_unless (ges_asset_finish_proxy (nothing)); + fail_unless_equals_int (g_list_length (ges_asset_list_proxies + (nothing_at_all)), 1); + + fail_unless_equals_pointer (ges_asset_get_proxy_target (nothing), + nothing_at_all); + + // If we request nothing_at_all we should get the good proxied identity + nothing_at_all = ges_asset_request (GES_TYPE_EFFECT, "nothing_at_all", NULL); + fail_unless (nothing_at_all == identity); + + gst_object_unref (identity); + gst_object_unref (nothing_at_all); + + ges_deinit (); +} + +GST_END_TEST; +*/ + +static void +_count_cb (GObject * obj, GParamSpec * pspec, gpointer key) +{ + guint count = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (obj), key)); + g_object_set_data (G_OBJECT (obj), key, GUINT_TO_POINTER (count + 1)); +} + +#define _CONNECT_PROXY_SIGNALS(asset) \ + g_signal_connect (asset, "notify::proxy", G_CALLBACK (_count_cb), \ + (gchar *)"test-data-proxy-count"); \ + g_signal_connect (asset, "notify::proxy-target", G_CALLBACK (_count_cb), \ + (gchar *)"test-data-target-count"); + +/* test that @asset has the properties proxy = @proxy and + * proxy-target = @proxy_target + * Also check that the callback for "notify::proxy" (set up in + * _CONNECT_PROXY_SIGNALS) has been called @p_count times, and the + * callback for "notify::target-proxy" has been called @t_count times. + */ +#define _assert_proxy_state(asset, proxy, proxy_target, p_count, t_count) \ +{ \ + const gchar *id = ges_asset_get_id (asset); \ + guint found_p_count = GPOINTER_TO_UINT (g_object_get_data ( \ + G_OBJECT (asset), "test-data-proxy-count")); \ + guint found_t_count = GPOINTER_TO_UINT (g_object_get_data ( \ + G_OBJECT (asset), "test-data-target-count")); \ + GESAsset *found_proxy = ges_asset_get_proxy (asset); \ + GESAsset *found_target = ges_asset_get_proxy_target (asset); \ + fail_unless (found_proxy == proxy, "Asset '%s' has the proxy '%s' " \ + "rather than the expected '%s'", id, \ + found_proxy ? ges_asset_get_id (found_proxy) : NULL, \ + proxy ? ges_asset_get_id (proxy) : NULL); \ + fail_unless (found_target == proxy_target, "Asset '%s' has the proxy " \ + "target '%s' rather than the expected '%s'", id, \ + found_target ? ges_asset_get_id (found_target) : NULL, \ + proxy_target ? ges_asset_get_id (proxy_target) : NULL); \ + fail_unless (p_count == found_p_count, "notify::proxy for asset '%s' " \ + "was called %u times, rather than the expected %u times", \ + id, found_p_count, p_count); \ + fail_unless (t_count == found_t_count, "notify::target-proxy for " \ + "asset '%s' was called %u times, rather than the expected %u times", \ + id, found_t_count, t_count); \ +} + +#define _assert_proxy_list(asset, cmp_list) \ +{ \ + const gchar * id = ges_asset_get_id (asset); \ + int i; \ + GList *tmp; \ + for (i = 0, tmp = ges_asset_list_proxies (asset); cmp_list[i] && tmp; \ + i++, tmp = tmp->next) { \ + GESAsset *proxy = tmp->data; \ + fail_unless (proxy == cmp_list[i], "The asset '%s' has '%s' as its " \ + "%ith proxy, rather than the expected '%s'", id, \ + ges_asset_get_id (proxy), i, ges_asset_get_id (cmp_list[i])); \ + } \ + fail_unless (tmp == NULL, "Found more proxies for '%s' than expected", \ + id); \ + fail_unless (cmp_list[i] == NULL, "Found less proxies (%i) for '%s' " \ + "than expected", i, id); \ +} + +#define _assert_effect_asset_request(req_id, expect) \ +{ \ + GESAsset *requested = ges_asset_request (GES_TYPE_EFFECT, req_id, NULL); \ + fail_unless (requested == expect, "Requested asset for id '%s' is " \ + "'%s' rather than the expected '%s'", req_id, \ + requested ? ges_asset_get_id (requested) : NULL, \ + ges_asset_get_id (expect)); \ + gst_object_unref (requested); \ +} + +GST_START_TEST (test_proxy_setters) +{ + GESAsset *proxies[] = { NULL, NULL, NULL, NULL }; + GESAsset *asset, *alt_asset; + GESAsset *proxy0, *proxy1, *proxy2; + gchar asset_id[] = "video agingtv ! videobalance"; + gchar alt_asset_id[] = "video gamma"; + gchar proxy0_id[] = "video videobalance contrast=0.0"; + gchar proxy1_id[] = "video videobalance contrast=1.0"; + gchar proxy2_id[] = "video videobalance contrast=2.0"; + + ges_init (); + + asset = ges_asset_request (GES_TYPE_EFFECT, asset_id, NULL); + alt_asset = ges_asset_request (GES_TYPE_EFFECT, alt_asset_id, NULL); + + proxy0 = ges_asset_request (GES_TYPE_EFFECT, proxy0_id, NULL); + proxy1 = ges_asset_request (GES_TYPE_EFFECT, proxy1_id, NULL); + proxy2 = ges_asset_request (GES_TYPE_EFFECT, proxy2_id, NULL); + + /* make sure our assets are unique */ + fail_unless (asset); + fail_unless (alt_asset); + fail_unless (proxy0); + fail_unless (proxy1); + fail_unless (proxy2); + fail_unless (asset != alt_asset); + fail_unless (asset != proxy0); + fail_unless (asset != proxy1); + fail_unless (asset != proxy2); + fail_unless (alt_asset != proxy0); + fail_unless (alt_asset != proxy1); + fail_unless (alt_asset != proxy2); + fail_unless (proxy0 != proxy1); + fail_unless (proxy0 != proxy1); + fail_unless (proxy0 != proxy2); + fail_unless (proxy1 != proxy2); + + _CONNECT_PROXY_SIGNALS (asset); + _CONNECT_PROXY_SIGNALS (alt_asset); + _CONNECT_PROXY_SIGNALS (proxy0); + _CONNECT_PROXY_SIGNALS (proxy1); + _CONNECT_PROXY_SIGNALS (proxy2); + + /* no proxies to start with */ + _assert_proxy_state (asset, NULL, NULL, 0, 0); + _assert_proxy_state (alt_asset, NULL, NULL, 0, 0); + _assert_proxy_state (proxy0, NULL, NULL, 0, 0); + _assert_proxy_state (proxy1, NULL, NULL, 0, 0); + _assert_proxy_state (proxy2, NULL, NULL, 0, 0); + _assert_proxy_list (asset, proxies); + _assert_proxy_list (alt_asset, proxies); + _assert_proxy_list (proxy0, proxies); + _assert_proxy_list (proxy1, proxies); + _assert_proxy_list (proxy2, proxies); + + /* id for an asset with no proxy returns itself */ + _assert_effect_asset_request (asset_id, asset); + _assert_effect_asset_request (alt_asset_id, alt_asset); + _assert_effect_asset_request (proxy0_id, proxy0); + _assert_effect_asset_request (proxy1_id, proxy1); + _assert_effect_asset_request (proxy2_id, proxy2); + + /* set a proxy */ + fail_unless (ges_asset_set_proxy (asset, proxy0)); + _assert_proxy_state (asset, proxy0, NULL, 1, 0); + _assert_proxy_state (proxy0, NULL, asset, 0, 1); + _assert_proxy_state (proxy1, NULL, NULL, 0, 0); + _assert_proxy_state (proxy2, NULL, NULL, 0, 0); + + proxies[0] = proxy0; + _assert_proxy_list (asset, proxies); + + /* requesting the same asset should return the proxy instead */ + _assert_effect_asset_request (asset_id, proxy0); + _assert_effect_asset_request (proxy0_id, proxy0); + _assert_effect_asset_request (proxy1_id, proxy1); + _assert_effect_asset_request (proxy2_id, proxy2); + + /* can't proxy a different asset */ + /* Raises ERROR */ + fail_unless (ges_asset_set_proxy (alt_asset, proxy0) == FALSE); + _assert_proxy_state (alt_asset, NULL, NULL, 0, 0); + _assert_proxy_state (asset, proxy0, NULL, 1, 0); + _assert_proxy_state (proxy0, NULL, asset, 0, 1); + _assert_proxy_state (proxy1, NULL, NULL, 0, 0); + _assert_proxy_state (proxy2, NULL, NULL, 0, 0); + + _assert_proxy_list (asset, proxies); + _assert_effect_asset_request (asset_id, proxy0); + _assert_effect_asset_request (proxy0_id, proxy0); + + /* set the same proxy again is safe */ + fail_unless (ges_asset_set_proxy (asset, proxy0)); + /* notify::proxy callback count increases, even though we set the same + * proxy. This is the default behaviour for setters. */ + _assert_proxy_state (asset, proxy0, NULL, 2, 0); + /* but the notify::target-proxy has not increased for the proxy */ + _assert_proxy_state (proxy0, NULL, asset, 0, 1); + _assert_proxy_state (proxy1, NULL, NULL, 0, 0); + _assert_proxy_state (proxy2, NULL, NULL, 0, 0); + + _assert_proxy_list (asset, proxies); + _assert_effect_asset_request (asset_id, proxy0); + _assert_effect_asset_request (proxy0_id, proxy0); + + /* replace the proxy with a new one */ + fail_unless (ges_asset_set_proxy (asset, proxy1)); + _assert_proxy_state (asset, proxy1, NULL, 3, 0); + /* first proxy still keeps its target */ + _assert_proxy_state (proxy0, NULL, asset, 0, 1); + _assert_proxy_state (proxy1, NULL, asset, 0, 1); + _assert_proxy_state (proxy2, NULL, NULL, 0, 0); + + proxies[0] = proxy1; + proxies[1] = proxy0; + _assert_proxy_list (asset, proxies); + + _assert_effect_asset_request (asset_id, proxy1); + _assert_effect_asset_request (proxy0_id, proxy0); + _assert_effect_asset_request (proxy1_id, proxy1); + _assert_effect_asset_request (proxy2_id, proxy2); + + /* replace again */ + fail_unless (ges_asset_set_proxy (asset, proxy2)); + _assert_proxy_state (asset, proxy2, NULL, 4, 0); + _assert_proxy_state (proxy0, NULL, asset, 0, 1); + _assert_proxy_state (proxy1, NULL, asset, 0, 1); + _assert_proxy_state (proxy2, NULL, asset, 0, 1); + + proxies[0] = proxy2; + proxies[1] = proxy1; + proxies[2] = proxy0; + _assert_proxy_list (asset, proxies); + + _assert_effect_asset_request (asset_id, proxy2); + _assert_effect_asset_request (proxy0_id, proxy0); + _assert_effect_asset_request (proxy1_id, proxy1); + _assert_effect_asset_request (proxy2_id, proxy2); + + /* move proxy0 back to being the default */ + fail_unless (ges_asset_set_proxy (asset, proxy0)); + _assert_proxy_state (asset, proxy0, NULL, 5, 0); + _assert_proxy_state (proxy0, NULL, asset, 0, 1); + _assert_proxy_state (proxy1, NULL, asset, 0, 1); + _assert_proxy_state (proxy2, NULL, asset, 0, 1); + + proxies[0] = proxy0; + proxies[1] = proxy2; + proxies[2] = proxy1; + _assert_proxy_list (asset, proxies); + + _assert_effect_asset_request (asset_id, proxy0); + _assert_effect_asset_request (proxy0_id, proxy0); + _assert_effect_asset_request (proxy1_id, proxy1); + _assert_effect_asset_request (proxy2_id, proxy2); + + /* remove proxy2 */ + fail_unless (ges_asset_unproxy (asset, proxy2)); + /* notify::proxy not released since we have not switched defaults */ + _assert_proxy_state (asset, proxy0, NULL, 5, 0); + _assert_proxy_state (proxy0, NULL, asset, 0, 1); + _assert_proxy_state (proxy1, NULL, asset, 0, 1); + _assert_proxy_state (proxy2, NULL, NULL, 0, 2); + + proxies[0] = proxy0; + proxies[1] = proxy1; + proxies[2] = NULL; + _assert_proxy_list (asset, proxies); + + _assert_effect_asset_request (asset_id, proxy0); + _assert_effect_asset_request (proxy0_id, proxy0); + _assert_effect_asset_request (proxy1_id, proxy1); + _assert_effect_asset_request (proxy2_id, proxy2); + + /* make proxy2 a proxy for proxy0 */ + fail_unless (ges_asset_set_proxy (proxy0, proxy2)); + _assert_proxy_state (asset, proxy0, NULL, 5, 0); + _assert_proxy_state (proxy0, proxy2, asset, 1, 1); + _assert_proxy_state (proxy1, NULL, asset, 0, 1); + _assert_proxy_state (proxy2, NULL, proxy0, 0, 3); + + proxies[0] = proxy0; + proxies[1] = proxy1; + proxies[2] = NULL; + _assert_proxy_list (asset, proxies); + + proxies[0] = proxy2; + proxies[1] = NULL; + _assert_proxy_list (proxy0, proxies); + + /* original id will now follows two proxy links to get proxy2 */ + _assert_effect_asset_request (asset_id, proxy2); + _assert_effect_asset_request (proxy0_id, proxy2); + _assert_effect_asset_request (proxy1_id, proxy1); + _assert_effect_asset_request (proxy2_id, proxy2); + + /* remove proxy0 from asset, should now default to proxy1 */ + fail_unless (ges_asset_unproxy (asset, proxy0)); + /* notify::proxy released since we have switched defaults */ + _assert_proxy_state (asset, proxy1, NULL, 6, 0); + _assert_proxy_state (proxy0, proxy2, NULL, 1, 2); + _assert_proxy_state (proxy1, NULL, asset, 0, 1); + _assert_proxy_state (proxy2, NULL, proxy0, 0, 3); + + proxies[0] = proxy1; + proxies[1] = NULL; + _assert_proxy_list (asset, proxies); + + proxies[0] = proxy2; + proxies[1] = NULL; + _assert_proxy_list (proxy0, proxies); + + _assert_effect_asset_request (asset_id, proxy1); + _assert_effect_asset_request (proxy0_id, proxy2); + _assert_effect_asset_request (proxy1_id, proxy1); + _assert_effect_asset_request (proxy2_id, proxy2); + + /* remove proxy2 from proxy0 */ + fail_unless (ges_asset_unproxy (proxy0, proxy2)); + _assert_proxy_state (asset, proxy1, NULL, 6, 0); + _assert_proxy_state (proxy0, NULL, NULL, 2, 2); + _assert_proxy_state (proxy1, NULL, asset, 0, 1); + _assert_proxy_state (proxy2, NULL, NULL, 0, 4); + + proxies[0] = proxy1; + proxies[1] = NULL; + _assert_proxy_list (asset, proxies); + + proxies[0] = NULL; + _assert_proxy_list (proxy0, proxies); + + _assert_effect_asset_request (asset_id, proxy1); + _assert_effect_asset_request (proxy0_id, proxy0); + _assert_effect_asset_request (proxy1_id, proxy1); + _assert_effect_asset_request (proxy2_id, proxy2); + + /* make both proxy0 and proxy2 proxies of proxy1 */ + fail_unless (ges_asset_set_proxy (proxy1, proxy0)); + _assert_proxy_state (asset, proxy1, NULL, 6, 0); + _assert_proxy_state (proxy0, NULL, proxy1, 2, 3); + _assert_proxy_state (proxy1, proxy0, asset, 1, 1); + _assert_proxy_state (proxy2, NULL, NULL, 0, 4); + fail_unless (ges_asset_set_proxy (proxy1, proxy2)); + _assert_proxy_state (asset, proxy1, NULL, 6, 0); + _assert_proxy_state (proxy0, NULL, proxy1, 2, 3); + _assert_proxy_state (proxy1, proxy2, asset, 2, 1); + _assert_proxy_state (proxy2, NULL, proxy1, 0, 5); + + proxies[0] = proxy1; + proxies[1] = NULL; + _assert_proxy_list (asset, proxies); + + proxies[0] = proxy2; + proxies[1] = proxy0; + proxies[2] = NULL; + _assert_proxy_list (proxy1, proxies); + + _assert_effect_asset_request (asset_id, proxy2); + _assert_effect_asset_request (proxy0_id, proxy0); + _assert_effect_asset_request (proxy1_id, proxy2); + _assert_effect_asset_request (proxy2_id, proxy2); + + /* should not be able to set up any circular proxies */ + /* Raises ERROR */ + fail_unless (ges_asset_set_proxy (proxy1, asset) == FALSE); + _assert_proxy_state (asset, proxy1, NULL, 6, 0); + _assert_proxy_state (proxy0, NULL, proxy1, 2, 3); + _assert_proxy_state (proxy1, proxy2, asset, 2, 1); + _assert_proxy_state (proxy2, NULL, proxy1, 0, 5); + /* Raises ERROR */ + fail_unless (ges_asset_set_proxy (proxy0, asset) == FALSE); + _assert_proxy_state (asset, proxy1, NULL, 6, 0); + _assert_proxy_state (proxy0, NULL, proxy1, 2, 3); + _assert_proxy_state (proxy1, proxy2, asset, 2, 1); + _assert_proxy_state (proxy2, NULL, proxy1, 0, 5); + /* Raises ERROR */ + fail_unless (ges_asset_set_proxy (proxy2, asset) == FALSE); + _assert_proxy_state (asset, proxy1, NULL, 6, 0); + _assert_proxy_state (proxy0, NULL, proxy1, 2, 3); + _assert_proxy_state (proxy1, proxy2, asset, 2, 1); + _assert_proxy_state (proxy2, NULL, proxy1, 0, 5); + + /* remove last proxy from asset, should set its proxy to NULL */ + fail_unless (ges_asset_unproxy (asset, proxy1)); + _assert_proxy_state (asset, NULL, NULL, 7, 0); + _assert_proxy_state (proxy0, NULL, proxy1, 2, 3); + _assert_proxy_state (proxy1, proxy2, NULL, 2, 2); + _assert_proxy_state (proxy2, NULL, proxy1, 0, 5); + + proxies[0] = NULL; + _assert_proxy_list (asset, proxies); + + proxies[0] = proxy2; + proxies[1] = proxy0; + proxies[2] = NULL; + _assert_proxy_list (proxy1, proxies); + + /* get asset back */ + _assert_effect_asset_request (asset_id, asset); + _assert_effect_asset_request (proxy0_id, proxy0); + _assert_effect_asset_request (proxy1_id, proxy2); + _assert_effect_asset_request (proxy2_id, proxy2); + + /* set the proxy property to NULL for proxy1, should remove all of + * its proxies */ + fail_unless (ges_asset_set_proxy (proxy1, NULL)); + _assert_proxy_state (asset, NULL, NULL, 7, 0); + /* only one notify for proxy1, but two separate ones for ex-proxies */ + _assert_proxy_state (proxy0, NULL, NULL, 2, 4); + _assert_proxy_state (proxy1, NULL, NULL, 3, 2); + _assert_proxy_state (proxy2, NULL, NULL, 0, 6); + + proxies[0] = NULL; + _assert_proxy_list (asset, proxies); + _assert_proxy_list (proxy0, proxies); + _assert_proxy_list (proxy1, proxies); + _assert_proxy_list (proxy2, proxies); + + _assert_effect_asset_request (asset_id, asset); + _assert_effect_asset_request (proxy0_id, proxy0); + _assert_effect_asset_request (proxy1_id, proxy1); + _assert_effect_asset_request (proxy2_id, proxy2); + + gst_object_unref (asset); + gst_object_unref (alt_asset); + gst_object_unref (proxy0); + gst_object_unref (proxy1); + gst_object_unref (proxy2); + + ges_deinit (); +} + +GST_END_TEST; + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges"); + TCase *tc_chain = tcase_create ("a"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_basic); + tcase_add_test (tc_chain, test_custom_context); + tcase_add_test (tc_chain, test_transition_change_asset); + tcase_add_test (tc_chain, test_uri_clip_change_asset); + tcase_add_test (tc_chain, test_list_asset); + tcase_add_test (tc_chain, test_proxy_setters); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/backgroundsource.c b/tests/check/ges/backgroundsource.c new file mode 100644 index 0000000000..09bdabfe8d --- /dev/null +++ b/tests/check/ges/backgroundsource.c @@ -0,0 +1,425 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" +#include <ges/ges.h> +#include <gst/check/gstcheck.h> + +#define DEFAULT_VOLUME 1.0 + +GST_START_TEST (test_test_source_basic) +{ + GESTestClip *source; + + ges_init (); + + source = ges_test_clip_new (); + fail_unless (source != NULL); + + gst_object_unref (source); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_test_source_properties) +{ + GESClip *clip; + GESTrack *track; + GESTimeline *timeline; + GESLayer *layer; + GESTrackElement *trackelement; + + ges_init (); + + track = ges_track_new (GES_TRACK_TYPE_AUDIO, gst_caps_ref (GST_CAPS_ANY)); + fail_unless (track != NULL); + + layer = ges_layer_new (); + fail_unless (layer != NULL); + + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + fail_unless (ges_timeline_add_layer (timeline, layer)); + fail_unless (ges_timeline_add_track (timeline, track)); + + clip = (GESClip *) ges_test_clip_new (); + fail_unless (clip != NULL); + + /* Set some properties */ + GST_DEBUG ("Setting start duration and inpoint to %" GST_PTR_FORMAT, clip); + g_object_set (clip, "start", (guint64) 42, "duration", (guint64) 51, + "in-point", (guint64) 12, NULL); + assert_equals_uint64 (_START (clip), 42); + assert_equals_uint64 (_DURATION (clip), 51); + assert_equals_uint64 (_INPOINT (clip), 12); + + ges_layer_add_clip (layer, GES_CLIP (clip)); + assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1); + trackelement = GES_CONTAINER_CHILDREN (clip)->data; + fail_unless (trackelement != NULL); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) == + GES_TIMELINE_ELEMENT (clip)); + fail_unless (ges_track_element_get_track (trackelement) == track); + + /* Check that trackelement has the same properties */ + assert_equals_uint64 (_START (trackelement), 42); + assert_equals_uint64 (_DURATION (trackelement), 51); + assert_equals_uint64 (_INPOINT (trackelement), 12); + + fail_unless (ges_timeline_commit (timeline)); + /* And let's also check that it propagated correctly to GNonLin */ + nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 51, 12, + 51, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE); + + /* Change more properties, see if they propagate */ + g_object_set (clip, "start", (guint64) 420, "duration", (guint64) 510, + "in-point", (guint64) 120, NULL); + assert_equals_uint64 (_START (clip), 420); + assert_equals_uint64 (_DURATION (clip), 510); + assert_equals_uint64 (_INPOINT (clip), 120); + assert_equals_uint64 (_START (trackelement), 420); + assert_equals_uint64 (_DURATION (trackelement), 510); + assert_equals_uint64 (_INPOINT (trackelement), 120); + + fail_unless (ges_timeline_commit (timeline)); + /* And let's also check that it propagated correctly to GNonLin */ + nle_object_check (ges_track_element_get_nleobject (trackelement), 420, 510, + 120, 510, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE); + + /* Test mute support */ + g_object_set (clip, "mute", TRUE, NULL); + fail_unless (ges_timeline_commit (timeline)); + nle_object_check (ges_track_element_get_nleobject (trackelement), 420, 510, + 120, 510, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, FALSE); + g_object_set (clip, "mute", FALSE, NULL); + fail_unless (ges_timeline_commit (timeline)); + nle_object_check (ges_track_element_get_nleobject (trackelement), 420, 510, + 120, 510, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE); + + ges_container_remove (GES_CONTAINER (clip), + GES_TIMELINE_ELEMENT (trackelement)); + gst_object_unref (clip); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_test_source_in_layer) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *a, *v; + GESTrackElement *track_element; + GESTestClip *source; + GESVideoTestPattern ptrn; + gdouble freq, volume; + + ges_init (); + + timeline = ges_timeline_new (); + layer = ges_layer_new (); + a = GES_TRACK (ges_audio_track_new ()); + v = GES_TRACK (ges_video_track_new ()); + + ges_timeline_add_track (timeline, a); + ges_timeline_add_track (timeline, v); + ges_timeline_add_layer (timeline, layer); + + source = ges_test_clip_new_for_nick ((gchar *) "red"); + g_object_get (source, "vpattern", &ptrn, NULL); + assert_equals_int (ptrn, GES_VIDEO_TEST_PATTERN_RED); + + g_object_set (source, "duration", (guint64) GST_SECOND, NULL); + + ges_layer_add_clip (layer, (GESClip *) source); + + /* specifically test the vpattern property */ + g_object_set (source, "vpattern", (gint) GES_VIDEO_TEST_PATTERN_WHITE, NULL); + g_object_get (source, "vpattern", &ptrn, NULL); + assert_equals_int (ptrn, GES_VIDEO_TEST_PATTERN_WHITE); + + track_element = + ges_clip_find_track_element (GES_CLIP (source), v, + GES_TYPE_VIDEO_TEST_SOURCE); + + g_assert (GES_IS_VIDEO_TEST_SOURCE (track_element)); + + ptrn = (ges_video_test_source_get_pattern ((GESVideoTestSource *) + track_element)); + assert_equals_int (ptrn, GES_VIDEO_TEST_PATTERN_WHITE); + gst_object_unref (track_element); + + /* test audio properties as well */ + + track_element = ges_clip_find_track_element (GES_CLIP (source), + a, GES_TYPE_AUDIO_TEST_SOURCE); + g_assert (GES_IS_AUDIO_TEST_SOURCE (track_element)); + assert_equals_float (ges_test_clip_get_frequency (source), 440); + assert_equals_float (ges_test_clip_get_volume (source), DEFAULT_VOLUME); + + g_object_get (source, "freq", &freq, "volume", &volume, NULL); + assert_equals_float (freq, 440); + assert_equals_float (volume, DEFAULT_VOLUME); + + + freq = ges_audio_test_source_get_freq (GES_AUDIO_TEST_SOURCE (track_element)); + volume = + ges_audio_test_source_get_volume (GES_AUDIO_TEST_SOURCE (track_element)); + g_assert (freq == 440); + g_assert (volume == DEFAULT_VOLUME); + + g_object_set (source, "freq", (gdouble) 2000, "volume", (gdouble) 0.5, NULL); + g_object_get (source, "freq", &freq, "volume", &volume, NULL); + assert_equals_float (freq, 2000); + assert_equals_float (volume, 0.5); + + freq = ges_audio_test_source_get_freq (GES_AUDIO_TEST_SOURCE (track_element)); + volume = + ges_audio_test_source_get_volume (GES_AUDIO_TEST_SOURCE (track_element)); + g_assert (freq == 2000); + g_assert (volume == 0.5); + + gst_object_unref (track_element); + + ges_layer_remove_clip (layer, (GESClip *) source); + + GST_DEBUG ("removing the layer"); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +#if 0 +static gint +find_composition_func (const GValue * velement) +{ + GstElement *element = g_value_get_object (velement); + GstElementFactory *fac = gst_element_get_factory (element); + const gchar *name = gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (fac)); + + if (g_strcmp0 (name, "nlecomposition") == 0) + return 0; + + return 1; +} + +static GstElement * +find_composition (GESTrack * track) +{ + GstIterator *it = gst_bin_iterate_recurse (GST_BIN (track)); + GValue val = { 0, }; + GstElement *ret = NULL; + + if (gst_iterator_find_custom (it, (GCompareFunc) find_composition_func, &val, + NULL)) + ret = g_value_get_object (&val); + + g_value_unset (&val); + gst_iterator_free (it); + + return ret; +} + +#define gap_object_check(nleobj, start, duration, priority) \ +{ \ + guint64 pstart, pdur, pprio; \ + g_object_get (nleobj, "start", &pstart, "duration", &pdur, \ + "priority", &pprio, NULL); \ + assert_equals_uint64 (pstart, start); \ + assert_equals_uint64 (pdur, duration); \ + assert_equals_int (pprio, priority); \ +} + +GST_START_TEST (test_gap_filling_basic) +{ + GList *tmp; + GESTrack *track; + GESTimeline *timeline; + GstElement *composition; + GESLayer *layer; + GESClip *clip, *clip1, *clip2; + + GstElement *nlesrc, *nlesrc1, *gap = NULL; + GESTrackElement *trackelement, *trackelement1, *trackelement2; + + track = GES_TRACK (ges_audio_track_new ()); + fail_unless (track != NULL); + + composition = find_composition (track); + fail_unless (composition != NULL); + + layer = ges_layer_new (); + fail_unless (layer != NULL); + + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + fail_unless (ges_timeline_add_layer (timeline, layer)); + fail_unless (ges_timeline_add_track (timeline, track)); + + clip = GES_CLIP (ges_test_clip_new ()); + fail_unless (clip != NULL); + + /* Set some properties */ + g_object_set (clip, "start", (guint64) 0, "duration", (guint64) 5, NULL); + assert_equals_uint64 (_START (clip), 0); + assert_equals_uint64 (_DURATION (clip), 5); + + ges_layer_add_clip (layer, GES_CLIP (clip)); + assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1); + trackelement = GES_CONTAINER_CHILDREN (clip)->data; + fail_unless (trackelement != NULL); + fail_unless (ges_track_element_get_track (trackelement) == track); + + nlesrc = ges_track_element_get_nleobject (trackelement); + fail_unless (nlesrc != NULL); + + /* Check that trackelement has the same properties */ + assert_equals_uint64 (_START (trackelement), 0); + assert_equals_uint64 (_DURATION (trackelement), 5); + + /* Check no gap were wrongly added + * 2: 1 for the trackelement and 1 for the mixer */ + assert_equals_int (g_list_length (GST_BIN_CHILDREN (composition)), 2); + + clip1 = GES_CLIP (ges_test_clip_new ()); + fail_unless (clip1 != NULL); + + g_object_set (clip1, "start", (guint64) 15, "duration", (guint64) 5, NULL); + assert_equals_uint64 (_START (clip1), 15); + assert_equals_uint64 (_DURATION (clip1), 5); + + ges_layer_add_clip (layer, GES_CLIP (clip1)); + ges_timeline_commit (timeline); + assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip1)), 1); + trackelement1 = GES_CONTAINER_CHILDREN (clip1)->data; + fail_unless (trackelement1 != NULL); + fail_unless (ges_track_element_get_track (trackelement1) == track); + nlesrc1 = ges_track_element_get_nleobject (trackelement1); + fail_unless (nlesrc1 != NULL); + + /* Check that trackelement1 has the same properties */ + assert_equals_uint64 (_START (trackelement1), 15); + assert_equals_uint64 (_DURATION (trackelement1), 5); + + /* Check the gap as properly been added */ + assert_equals_int (g_list_length (GST_BIN_CHILDREN (composition)), 4); + + for (tmp = GST_BIN_CHILDREN (composition); tmp; tmp = tmp->next) { + guint prio; + GstElement *tmp_nleobj = GST_ELEMENT (tmp->data); + + g_object_get (tmp_nleobj, "priority", &prio, NULL); + if (tmp_nleobj != nlesrc && tmp_nleobj != nlesrc1 && prio == 1) { + gap = tmp_nleobj; + } + } + fail_unless (gap != NULL); + gap_object_check (gap, 5, 10, 1); + + clip2 = GES_CLIP (ges_test_clip_new ()); + fail_unless (clip2 != NULL); + g_object_set (clip2, "start", (guint64) 35, "duration", (guint64) 5, NULL); + ges_layer_add_clip (layer, GES_CLIP (clip2)); + fail_unless (ges_timeline_commit (timeline)); + assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip2)), 1); + trackelement2 = GES_CONTAINER_CHILDREN (clip2)->data; + fail_unless (trackelement2 != NULL); + fail_unless (ges_track_element_get_track (trackelement2) == track); + assert_equals_uint64 (_START (trackelement2), 35); + assert_equals_uint64 (_DURATION (trackelement2), 5); + assert_equals_int (g_list_length (GST_BIN_CHILDREN (composition)), 6); + + gst_object_unref (timeline); +} + +GST_END_TEST; + +GST_START_TEST (test_gap_filling_empty_track) +{ + GESAsset *asset; + GESTrack *track; + GESTimeline *timeline; + GstElement *gap; + GstElement *composition; + GESLayer *layer; + GESClip *clip; + + track = GES_TRACK (ges_audio_track_new ()); + + layer = ges_layer_new (); + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + fail_unless (ges_timeline_add_layer (timeline, layer)); + fail_unless (ges_timeline_add_track (timeline, track)); + fail_unless (ges_timeline_add_track (timeline, + GES_TRACK (ges_video_track_new ()))); + + /* Set some properties */ + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + clip = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_VIDEO); + ges_timeline_commit (timeline); + assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1); + + /* We should not have created any TrackElement in the audio track */ + fail_unless (ges_track_get_elements (track) == NULL); + + /* Check that a gap was properly added */ + composition = find_composition (track); + /* We also have an adder in that composition */ + assert_equals_int (g_list_length (GST_BIN_CHILDREN (composition)), 2); + + gap = GST_BIN_CHILDREN (composition)->data; + fail_unless (gap != NULL); + gap_object_check (gap, 0, 10, 1); + fail_unless (ges_timeline_commit (timeline)); + + gst_object_unref (asset); + gst_object_unref (timeline); +} + +GST_END_TEST; +#endif + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-backgroundsource"); + TCase *tc_chain = tcase_create ("backgroundsource"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_test_source_basic); + tcase_add_test (tc_chain, test_test_source_properties); + tcase_add_test (tc_chain, test_test_source_in_layer); + +#if 0 + tcase_add_test (tc_chain, test_gap_filling_basic); + tcase_add_test (tc_chain, test_gap_filling_empty_track); +#endif + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/basic.c b/tests/check/ges/basic.c new file mode 100644 index 0000000000..d34593e141 --- /dev/null +++ b/tests/check/ges/basic.c @@ -0,0 +1,1204 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <bilboed@bilboed.com> + * 2012 Collabora Ltd. + * Author: Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" + +#include <ges/ges.h> +#include <gst/check/gstcheck.h> + + +GST_START_TEST (test_ges_scenario) +{ + GESTimeline *timeline; + GESLayer *layer, *tmp_layer; + GESTrack *track; + GESTestClip *source; + GESTrackElement *trackelement; + GList *trackelements, *layers, *tracks; + + ges_init (); + /* This is the simplest scenario ever */ + + /* Timeline and 1 Layer */ + GST_DEBUG ("Create a timeline"); + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + + GST_DEBUG ("Create a layer"); + layer = ges_layer_new (); + fail_unless (layer != NULL); + + GST_DEBUG ("Add the layer to the timeline"); + fail_unless (ges_timeline_add_layer (timeline, layer)); + /* The timeline steals our reference to the layer */ + ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); + fail_unless (layer->timeline == timeline); + + layers = ges_timeline_get_layers (timeline); + fail_unless (g_list_find (layers, layer) != NULL); + g_list_free_full (layers, gst_object_unref); + + /* Give the Timeline a Track */ + GST_DEBUG ("Create a Track"); + track = GES_TRACK (ges_video_track_new ()); + fail_unless (track != NULL); + + GST_DEBUG ("Add the track to the timeline"); + fail_unless (ges_timeline_add_track (timeline, track)); + + /* The timeline steals the reference to the track */ + ASSERT_OBJECT_REFCOUNT (track, "track", 1); + fail_unless (ges_track_get_timeline (track) == timeline); + fail_unless ((gpointer) GST_ELEMENT_PARENT (track) == (gpointer) timeline); + + /* Create a source and add it to the Layer */ + GST_DEBUG ("Creating a source"); + source = ges_test_clip_new (); + fail_unless (source != NULL); + + /* The source will be floating before added to the layer... */ + fail_unless (g_object_is_floating (source)); + GST_DEBUG ("Adding the source to the timeline layer"); + fail_unless (ges_layer_add_clip (layer, GES_CLIP (source))); + fail_if (g_object_is_floating (source)); + tmp_layer = ges_clip_get_layer (GES_CLIP (source)); + fail_unless (tmp_layer == layer); + /* The timeline stole our reference */ + ASSERT_OBJECT_REFCOUNT (source, "source + 1 timeline", 2); + gst_object_unref (tmp_layer); + ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); + + /* Make sure the associated TrackElement is in the Track */ + trackelements = GES_CONTAINER_CHILDREN (source); + fail_unless (trackelements != NULL); + trackelement = GES_TRACK_ELEMENT (trackelements->data); + /* There are 3 references: + * 1 by the clip + * 1 by the track + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3); + /* There are 3 references: + * 1 by the clip + * 3 by the timeline + * 1 by the track */ + ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3); + fail_unless (ges_track_element_get_track (trackelement) == track); + + GST_DEBUG ("Remove the Clip from the layer"); + + /* Now remove the clip */ + gst_object_ref (source); + ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); + fail_unless (ges_layer_remove_clip (layer, GES_CLIP (source))); + /* track elements emptied from the track, but stay in clip */ + fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) == + GES_TIMELINE_ELEMENT (source)); + fail_unless (ges_track_element_get_track (trackelement) == NULL); + ASSERT_OBJECT_REFCOUNT (source, "source", 1); + ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); + tmp_layer = ges_clip_get_layer (GES_CLIP (source)); + fail_unless (tmp_layer == NULL); + gst_object_unref (source); + + GST_DEBUG ("Removing track from the timeline"); + /* Remove the track from the timeline */ + gst_object_ref (track); + fail_unless (ges_timeline_remove_track (timeline, track)); + assert_num_in_track (track, 0); + + tracks = ges_timeline_get_tracks (timeline); + fail_unless (tracks == NULL); + ASSERT_OBJECT_REFCOUNT (track, "track", 1); + gst_object_unref (track); + + GST_DEBUG ("Removing layer from the timeline"); + /* Remove the layer from the timeline */ + gst_object_ref (layer); + fail_unless (ges_timeline_remove_layer (timeline, layer)); + fail_unless (layer->timeline == NULL); + + layers = ges_timeline_get_layers (timeline); + fail_unless (layers == NULL); + ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); + gst_object_unref (layer); + + /* Finally clean up our object */ + ASSERT_OBJECT_REFCOUNT (timeline, "timeline", 1); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +/* very similar to the above, except we add the clip to the layer + * and then add it to the timeline. + */ + +#define _CREATE_SOURCE(layer, clip, start, duration) \ +{ \ + GESAsset *asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); \ + GST_DEBUG ("Creating a source"); \ + fail_unless (clip = ges_layer_add_asset (layer, asset, start, 0, \ + duration, GES_TRACK_TYPE_UNKNOWN)); \ + assert_layer(clip, layer); \ + ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); \ + gst_object_unref (asset); \ +} + +GST_START_TEST (test_ges_timeline_add_layer) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *track; + GESClip *s1, *s2, *s3; + GList *trackelements, *layers; + GESTrackElement *trackelement; + + ges_init (); + + /* Timeline and 1 Layer */ + GST_DEBUG ("Create a timeline"); + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + + GST_DEBUG ("Create a layer"); + layer = ges_layer_new (); + fail_unless (layer != NULL); + /* Give the Timeline a Track */ + GST_DEBUG ("Create a Track"); + track = GES_TRACK (ges_video_track_new ()); + fail_unless (track != NULL); + + GST_DEBUG ("Add the track to the timeline"); + fail_unless (ges_timeline_add_track (timeline, track)); + /* The timeline steals the reference to the track */ + ASSERT_OBJECT_REFCOUNT (track, "track", 1); + fail_unless (ges_track_get_timeline (track) == timeline); + fail_unless ((gpointer) GST_ELEMENT_PARENT (track) == (gpointer) timeline); + + GST_DEBUG ("Add the layer to the timeline"); + fail_unless (ges_timeline_add_layer (timeline, layer)); + /* The timeline steals our reference to the layer */ + ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); + fail_unless (layer->timeline == timeline); + layers = ges_timeline_get_layers (timeline); + fail_unless (g_list_find (layers, layer) != NULL); + g_list_free_full (layers, gst_object_unref); + + _CREATE_SOURCE (layer, s1, 0, 10); + ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); + _CREATE_SOURCE (layer, s2, 20, 10); + ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); + _CREATE_SOURCE (layer, s3, 40, 10); + ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); + + /* Make sure the associated TrackElements are in the Track */ + trackelements = GES_CONTAINER_CHILDREN (s1); + fail_unless (trackelements != NULL); + trackelement = GES_TRACK_ELEMENT (trackelements->data); + /* There are 3 references: + * 1 by the clip + * 1 by the trackelement + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3); + /* There are 3 references: + * 1 by the clip + * 1 by the timeline + * 1 by the trackelement */ + ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3); + + trackelements = GES_CONTAINER_CHILDREN (s2); + trackelement = GES_TRACK_ELEMENT (trackelements->data); + fail_unless (trackelements != NULL); + + /* There are 3 references: + * 1 by the clip + * 1 by the timeline + * 1 by the trackelement */ + ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (trackelement), "trackelement", 3); + + trackelements = GES_CONTAINER_CHILDREN (s3); + trackelement = GES_TRACK_ELEMENT (trackelements->data); + fail_unless (trackelements != NULL); + + /* There are 3 references: + * 1 by the clip + * 1 by the timeline + * 2 by the trackelement */ + ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3); + + /* theoretically this is all we need to do to ensure cleanup */ + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +/* this time we add the layer before we add the track. */ + +#define _assert_child_in_track(clip, child_type, track) \ +{ \ + GESTrackElement *el; \ + GList *tmp = ges_clip_find_track_elements (clip, NULL, \ + GES_TRACK_TYPE_UNKNOWN, child_type); \ + fail_unless (g_list_length (tmp), 1); \ + el = tmp->data; \ + g_list_free_full (tmp, gst_object_unref); \ + fail_unless (ges_track_element_get_track (el) == track); \ + if (track) \ + ASSERT_OBJECT_REFCOUNT (el, "1 clip + 1 track + 1 timeline", 3); \ + else \ + ASSERT_OBJECT_REFCOUNT (el, "1 clip", 1); \ +} + +#define _assert_no_child_in_track(clip, track) \ + fail_if (ges_clip_find_track_elements (clip, track, \ + GES_TRACK_TYPE_UNKNOWN, G_TYPE_NONE)); + +#define _assert_add_track(timeline, track) \ +{ \ + GList *tmp; \ + GST_DEBUG ("Adding " #track " to the timeline"); \ + fail_unless (ges_timeline_add_track (timeline, track)); \ + ASSERT_OBJECT_REFCOUNT (track, #track, 1); \ + fail_unless (ges_track_get_timeline (track) == timeline); \ + fail_unless ((gpointer) GST_ELEMENT_PARENT (track) \ + == (gpointer) timeline); \ + tmp = ges_timeline_get_tracks (timeline); \ + fail_unless (g_list_find (tmp, track), #track " not found in tracks"); \ + g_list_free_full (tmp, gst_object_unref); \ +} + +#define _remove_sources(clip, expect_num) \ +{ \ + GList *tmp, *els; \ + els = ges_clip_find_track_elements (clip, NULL, GES_TRACK_TYPE_UNKNOWN, \ + GES_TYPE_SOURCE); \ + assert_equals_int (g_list_length (els), expect_num); \ + for (tmp = els; tmp; tmp = tmp->next) \ + fail_unless (ges_container_remove (GES_CONTAINER (clip), tmp->data)); \ + g_list_free_full (els, gst_object_unref); \ +} + +#define _remove_from_track(clip, track, expect_num) \ +{ \ + GList *tmp, *els; \ + els = ges_clip_find_track_elements (clip, track, GES_TRACK_TYPE_UNKNOWN, \ + G_TYPE_NONE); \ + assert_equals_int (g_list_length (els), expect_num); \ + for (tmp = els; tmp; tmp = tmp->next) \ + fail_unless (ges_track_remove_element (track, tmp->data)); \ + g_list_free_full (els, gst_object_unref); \ +} + +GST_START_TEST (test_ges_timeline_add_layer_first) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *track, *track1, *track2, *track3; + GESClip *s1, *s2, *s3; + GList *layers; + + ges_init (); + + /* Timeline and 1 Layer */ + GST_DEBUG ("Create a timeline"); + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + + GST_DEBUG ("Create a layer"); + layer = ges_layer_new (); + fail_unless (layer != NULL); + /* Give the Timeline a Track */ + GST_DEBUG ("Create a Track"); + track = GES_TRACK (ges_video_track_new ()); + fail_unless (track != NULL); + + _CREATE_SOURCE (layer, s1, 0, 10); + _CREATE_SOURCE (layer, s2, 20, 10); + _CREATE_SOURCE (layer, s3, 40, 10); + + fail_unless (ges_container_add (GES_CONTAINER (s1), + GES_TIMELINE_ELEMENT (ges_effect_new ("agingtv")))); + assert_num_children (s1, 1); + assert_num_children (s2, 0); + assert_num_children (s3, 0); + + GST_DEBUG ("Add the layer to the timeline"); + fail_unless (ges_timeline_add_layer (timeline, layer)); + /* The timeline steals our reference to the layer */ + ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); + fail_unless (layer->timeline == timeline); + layers = ges_timeline_get_layers (timeline); + fail_unless (g_list_find (layers, layer) != NULL); + g_list_free_full (layers, gst_object_unref); + + /* core children not created yet since no tracks */ + assert_num_children (s1, 1); + assert_num_children (s2, 0); + assert_num_children (s3, 0); + + _assert_add_track (timeline, track); + /* 3 sources, 1 effect */ + assert_num_in_track (track, 4); + + /* Make sure the associated TrackElements are in the Track */ + assert_num_children (s1, 2); + _assert_child_in_track (s1, GES_TYPE_EFFECT, track); + _assert_child_in_track (s1, GES_TYPE_VIDEO_TEST_SOURCE, track); + + assert_num_children (s2, 1); + _assert_child_in_track (s2, GES_TYPE_VIDEO_TEST_SOURCE, track); + + assert_num_children (s3, 1); + _assert_child_in_track (s3, GES_TYPE_VIDEO_TEST_SOURCE, track); + + /* adding an audio track should create new audio sources */ + + track1 = GES_TRACK (ges_audio_track_new ()); + _assert_add_track (timeline, track1); + /* other track stays the same */ + assert_num_in_track (track, 4); + /* 3 sources */ + assert_num_in_track (track1, 3); + + /* one new core child */ + assert_num_children (s1, 3); + _assert_child_in_track (s1, GES_TYPE_EFFECT, track); + _assert_child_in_track (s1, GES_TYPE_VIDEO_TEST_SOURCE, track); + _assert_child_in_track (s1, GES_TYPE_AUDIO_TEST_SOURCE, track1); + + assert_num_children (s2, 2); + _assert_child_in_track (s2, GES_TYPE_VIDEO_TEST_SOURCE, track); + _assert_child_in_track (s2, GES_TYPE_AUDIO_TEST_SOURCE, track1); + + assert_num_children (s3, 2); + _assert_child_in_track (s3, GES_TYPE_VIDEO_TEST_SOURCE, track); + _assert_child_in_track (s3, GES_TYPE_AUDIO_TEST_SOURCE, track1); + + /* adding another track should not prompt the change anything + * unrelated to the new track */ + + /* remove the core children from s1 */ + _remove_sources (s1, 2); + + /* only have effect left, and not in any track */ + assert_num_children (s1, 1); + /* effect is emptied from its track, since the corresponding core child + * was removed */ + _assert_child_in_track (s1, GES_TYPE_EFFECT, NULL); + + assert_num_in_track (track, 2); + assert_num_in_track (track1, 2); + + track2 = GES_TRACK (ges_video_track_new ()); + _assert_add_track (timeline, track2); + /* other tracks stay the same */ + assert_num_in_track (track, 2); + assert_num_in_track (track1, 2); + /* 1 sources + 1 effect */ + assert_num_in_track (track2, 2); + + /* s1 only has a child created for the new track, not the other two */ + assert_num_children (s1, 2); + _assert_child_in_track (s1, GES_TYPE_EFFECT, track2); + _assert_child_in_track (s1, GES_TYPE_VIDEO_TEST_SOURCE, track2); + _assert_no_child_in_track (s1, track); + _assert_no_child_in_track (s1, track1); + + /* other clips stay the same since their children were already created + * with set tracks */ + assert_num_children (s2, 2); + _assert_child_in_track (s2, GES_TYPE_VIDEO_TEST_SOURCE, track); + _assert_child_in_track (s2, GES_TYPE_AUDIO_TEST_SOURCE, track1); + _assert_no_child_in_track (s2, track2); + + assert_num_children (s3, 2); + _assert_child_in_track (s3, GES_TYPE_VIDEO_TEST_SOURCE, track); + _assert_child_in_track (s3, GES_TYPE_AUDIO_TEST_SOURCE, track1); + _assert_no_child_in_track (s3, track2); + + /* same with an audio track */ + + /* remove the core child from s1 */ + _remove_sources (s1, 1); + + assert_num_children (s1, 1); + _assert_child_in_track (s1, GES_TYPE_EFFECT, NULL); + + assert_num_in_track (track, 2); + assert_num_in_track (track1, 2); + assert_num_in_track (track2, 0); + + /* unset the core tracks for s2 */ + _remove_from_track (s2, track, 1); + _remove_from_track (s2, track1, 1); + /* but keep children in clip */ + assert_num_children (s2, 2); + + assert_num_in_track (track, 1); + assert_num_in_track (track1, 1); + assert_num_in_track (track2, 0); + + track3 = GES_TRACK (ges_audio_track_new ()); + _assert_add_track (timeline, track3); + /* other tracks stay the same */ + assert_num_in_track (track, 1); + assert_num_in_track (track1, 1); + assert_num_in_track (track2, 0); + /* 2 sources */ + assert_num_in_track (track3, 2); + + /* s1 creates core for the new track, but effect does not have a track + * set since the new track is not a video track */ + assert_num_children (s1, 2); + _assert_child_in_track (s1, GES_TYPE_AUDIO_TEST_SOURCE, track3); + _assert_child_in_track (s1, GES_TYPE_EFFECT, NULL); + _assert_no_child_in_track (s1, track); + _assert_no_child_in_track (s1, track1); + _assert_no_child_in_track (s1, track2); + + /* s2 audio core is in the new track, but video remains trackless */ + assert_num_children (s2, 2); + _assert_child_in_track (s2, GES_TYPE_AUDIO_TEST_SOURCE, track3); + _assert_child_in_track (s2, GES_TYPE_VIDEO_TEST_SOURCE, NULL); + _assert_no_child_in_track (s1, track); + _assert_no_child_in_track (s1, track1); + _assert_no_child_in_track (s1, track2); + + /* s3 remains the same since core already had tracks */ + assert_num_children (s3, 2); + _assert_child_in_track (s3, GES_TYPE_VIDEO_TEST_SOURCE, track); + _assert_child_in_track (s3, GES_TYPE_AUDIO_TEST_SOURCE, track1); + _assert_no_child_in_track (s3, track2); + _assert_no_child_in_track (s3, track3); + + /* theoretically this is all we need to do to ensure cleanup */ + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_ges_timeline_remove_track) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *track; + GESClip *s1, *s2, *s3; + GESTrackElement *t1, *t2, *t3; + GList *trackelements, *tmp, *layers; + + ges_init (); + + /* Timeline and 1 Layer */ + GST_DEBUG ("Create a timeline"); + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + + GST_DEBUG ("Create a layer"); + layer = ges_layer_new (); + fail_unless (layer != NULL); + /* Give the Timeline a Track */ + GST_DEBUG ("Create a Track"); + track = GES_TRACK (ges_video_track_new ()); + fail_unless (track != NULL); + + _CREATE_SOURCE (layer, s1, 0, 10); + ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); + _CREATE_SOURCE (layer, s2, 20, 10); + ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); + _CREATE_SOURCE (layer, s3, 40, 10); + ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); + + GST_DEBUG ("Add the layer to the timeline"); + fail_unless (ges_timeline_add_layer (timeline, layer)); + /* The timeline steals our reference to the layer */ + ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); + fail_unless (layer->timeline == timeline); + + layers = ges_timeline_get_layers (timeline); + fail_unless (g_list_find (layers, layer) != NULL); + g_list_free_full (layers, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); + + GST_DEBUG ("Add the track to the timeline"); + fail_unless (ges_timeline_add_track (timeline, track)); + ASSERT_OBJECT_REFCOUNT (track, "track", 1); + fail_unless (ges_track_get_timeline (track) == timeline); + fail_unless ((gpointer) GST_ELEMENT_PARENT (track) == (gpointer) timeline); + + /* Make sure the associated TrackElements are in the Track */ + trackelements = GES_CONTAINER_CHILDREN (s1); + fail_unless (trackelements != NULL); + t1 = GES_TRACK_ELEMENT ((trackelements)->data); + for (tmp = trackelements; tmp; tmp = tmp->next) { + /* There are 3 references held: + * 1 by the clip + * 1 by the track + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3); + } + /* There are 3 references held: + * 1 by the container + * 1 by the track + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 3); + + trackelements = GES_CONTAINER_CHILDREN (s2); + fail_unless (trackelements != NULL); + t2 = GES_TRACK_ELEMENT (trackelements->data); + for (tmp = trackelements; tmp; tmp = tmp->next) { + /* There are 3 references held: + * 1 by the clip + * 1 by the track + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3); + } + /* There are 3 references held: + * 1 by the container + * 1 by the track + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (t2, "t2", 3); + + trackelements = GES_CONTAINER_CHILDREN (s3); + fail_unless (trackelements != NULL); + t3 = GES_TRACK_ELEMENT (trackelements->data); + for (tmp = trackelements; tmp; tmp = tmp->next) { + /* There are 3 references held: + * 1 by the clip + * 1 by the track + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3); + } + /* There are 3 references held: + * 1 by the container + * 1 by the track + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (t3, "t3", 3); + + fail_unless (ges_track_element_get_track (t1) == track); + fail_unless (ges_track_element_get_track (t2) == track); + fail_unless (ges_track_element_get_track (t3) == track); + + /* remove the track and check that the track elements have been released */ + gst_object_ref (track); + fail_unless (ges_timeline_remove_track (timeline, track)); + assert_num_in_track (track, 0); + gst_object_unref (track); + fail_unless (ges_track_element_get_track (t1) == NULL); + fail_unless (ges_track_element_get_track (t2) == NULL); + fail_unless (ges_track_element_get_track (t3) == NULL); + + ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 1); + ASSERT_OBJECT_REFCOUNT (t2, "trackelement", 1); + ASSERT_OBJECT_REFCOUNT (t3, "trackelement", 1); + ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); + ASSERT_OBJECT_REFCOUNT (timeline, "1 for the us", 1); + tmp = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (tmp), 3); + g_list_free_full (tmp, (GDestroyNotify) gst_object_unref); + + gst_check_objects_destroyed_on_unref (G_OBJECT (timeline), + G_OBJECT (layer), t1, t2, t3, NULL); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_ges_timeline_remove_layer) +{ + GESTimeline *timeline; + GESLayer *layer0, *layer1, *layer2, *layer3; + GESTrack *track; + GESClip *s1, *s2, *s3, *s4, *s5; + GList *tmp, *clips, *clip, *layers; + + ges_init (); + + timeline = ges_timeline_new (); + + layer0 = ges_timeline_append_layer (timeline); + layer1 = ges_timeline_append_layer (timeline); + layer2 = ges_timeline_append_layer (timeline); + + assert_equals_int (ges_layer_get_priority (layer0), 0); + assert_equals_int (ges_layer_get_priority (layer1), 1); + assert_equals_int (ges_layer_get_priority (layer2), 2); + + track = GES_TRACK (ges_video_track_new ()); + fail_unless (ges_timeline_add_track (timeline, track)); + + _CREATE_SOURCE (layer0, s1, 0, 10); + _CREATE_SOURCE (layer1, s2, 0, 10); + _CREATE_SOURCE (layer1, s3, 10, 20); + _CREATE_SOURCE (layer2, s4, 0, 10); + _CREATE_SOURCE (layer2, s5, 10, 20); + + assert_num_in_track (track, 5); + + gst_object_ref (layer1); + fail_unless (ges_timeline_remove_layer (timeline, layer1)); + /* check removed, and rest of the layers stay */ + layers = ges_timeline_get_layers (timeline); + fail_if (g_list_find (layers, layer1)); + fail_unless (g_list_find (layers, layer0)); + fail_unless (g_list_find (layers, layer2)); + g_list_free_full (layers, gst_object_unref); + /* keeps its layer priority */ + assert_equals_int (ges_layer_get_priority (layer1), 1); + + /* Rest also keep their layer priority */ + /* NOTE: it may be better to resync the layer priorities to plug the + * gap, but this way we leave the gap open to add the layer back in */ + assert_equals_int (ges_layer_get_priority (layer0), 0); + assert_equals_int (ges_layer_get_priority (layer2), 2); + /* clip children removed from track */ + assert_num_in_track (track, 3); + + fail_unless (ges_layer_get_timeline (layer1) == NULL); + clips = ges_layer_get_clips (layer1); + for (clip = clips; clip; clip = clip->next) { + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (clip->data) == NULL); + for (tmp = GES_CONTAINER_CHILDREN (clip->data); tmp; tmp = tmp->next) { + GESTrackElement *el = GES_TRACK_ELEMENT (tmp->data); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (el) == NULL); + fail_unless (ges_track_element_get_track (el) == NULL); + } + } + g_list_free_full (clips, gst_object_unref); + + /* layer2 children have same layer priority */ + clips = ges_layer_get_clips (layer2); + for (clip = clips; clip; clip = clip->next) { + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (clip->data) == timeline); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip->data), 2); + for (tmp = GES_CONTAINER_CHILDREN (clip->data); tmp; tmp = tmp->next) { + GESTrackElement *el = GES_TRACK_ELEMENT (tmp->data); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (el) == timeline); + fail_unless (ges_track_element_get_track (el) == track); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (el), 2); + } + } + g_list_free_full (clips, gst_object_unref); + + /* layer0 stays the same */ + clips = ges_layer_get_clips (layer0); + for (clip = clips; clip; clip = clip->next) { + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (clip->data) == timeline); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip->data), 0); + for (tmp = GES_CONTAINER_CHILDREN (clip->data); tmp; tmp = tmp->next) { + GESTrackElement *el = GES_TRACK_ELEMENT (tmp->data); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (el) == timeline); + fail_unless (ges_track_element_get_track (el) == track); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (el), 0); + } + } + g_list_free_full (clips, gst_object_unref); + + /* can add a new layer with the correct priority */ + layer3 = ges_timeline_append_layer (timeline); + + assert_equals_int (ges_layer_get_priority (layer0), 0); + assert_equals_int (ges_layer_get_priority (layer2), 2); + assert_equals_int (ges_layer_get_priority (layer3), 3); + + gst_object_unref (layer1); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +typedef struct +{ + GESClip *clips[4]; + guint num_calls[4]; + GESTrackElement *effects[3]; + GESTrack *tr1, *tr2; + guint num_unrecognised; +} SelectTracksData; + +static GPtrArray * +select_tracks_cb (GESTimeline * timeline, GESClip * clip, + GESTrackElement * track_element, SelectTracksData * data) +{ + GPtrArray *ret = g_ptr_array_new (); + gboolean track1 = FALSE; + gboolean track2 = FALSE; + guint i; + gboolean recognise_clip = FALSE; + + for (i = 0; i < 4; i++) { + if (clip == data->clips[i]) { + data->num_calls[i]++; + recognise_clip = TRUE; + } + } + + if (!recognise_clip) { + GST_DEBUG_OBJECT (timeline, "unrecognised clip %" GES_FORMAT " for " + "track element %" GES_FORMAT, GES_ARGS (clip), + GES_ARGS (track_element)); + data->num_unrecognised++; + return ret; + } + + if (GES_IS_BASE_EFFECT (track_element)) { + if (track_element == data->effects[0]) { + track1 = TRUE; + } else if (track_element == data->effects[1]) { + track1 = TRUE; + track2 = TRUE; + } else if (track_element == data->effects[2]) { + track2 = TRUE; + } else { + GST_DEBUG_OBJECT (timeline, "unrecognised effect %" GES_FORMAT, + GES_ARGS (track_element)); + data->num_unrecognised++; + } + } else if (GES_IS_SOURCE (track_element)) { + if (clip == data->clips[0] || clip == data->clips[1]) + track1 = TRUE; + if (clip == data->clips[1] || clip == data->clips[2]) + track2 = TRUE; + /* clips[3] has no tracks selected */ + } else { + GST_DEBUG_OBJECT (timeline, "unrecognised track element %" GES_FORMAT, + GES_ARGS (track_element)); + data->num_unrecognised++; + } + + if (track1) + g_ptr_array_add (ret, gst_object_ref (data->tr1)); + if (track2) + g_ptr_array_add (ret, gst_object_ref (data->tr2)); + + return ret; +} + +GST_START_TEST (test_ges_timeline_multiple_tracks) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *track1, *track2; + GESClip *s1, *s2, *s3, *s4, *transition; + GESTrackElement *e1, *e2, *e3, *el, *el2, *e_copy; + gboolean found_e1 = FALSE, found_e2 = FALSE, found_e3 = FALSE; + GList *trackelements, *tmp, *layers; + GstControlSource *ctrl_source; + SelectTracksData st_data; + + ges_init (); + + /* Timeline and 1 Layer */ + GST_DEBUG ("Create a timeline"); + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + ges_timeline_set_auto_transition (timeline, TRUE); + + GST_DEBUG ("Create a layer"); + layer = ges_layer_new (); + fail_unless (layer != NULL); + /* Give the Timeline a Track */ + GST_DEBUG ("Create Track 1"); + track1 = GES_TRACK (ges_video_track_new ()); + fail_unless (track1 != NULL); + GST_DEBUG ("Create Track 2"); + track2 = GES_TRACK (ges_video_track_new ()); + fail_unless (track2 != NULL); + + GST_DEBUG ("Add the track 1 to the timeline"); + fail_unless (ges_timeline_add_track (timeline, track1)); + ASSERT_OBJECT_REFCOUNT (track1, "track", 1); + fail_unless (ges_track_get_timeline (track1) == timeline); + fail_unless ((gpointer) GST_ELEMENT_PARENT (track1) == (gpointer) timeline); + + GST_DEBUG ("Add the track 2 to the timeline"); + fail_unless (ges_timeline_add_track (timeline, track2)); + ASSERT_OBJECT_REFCOUNT (track2, "track", 1); + fail_unless (ges_track_get_timeline (track2) == timeline); + fail_unless ((gpointer) GST_ELEMENT_PARENT (track2) == (gpointer) timeline); + + /* adding to the layer before it is part of the timeline does not + * trigger track selection */ + /* s1 and s3 can overlap since they are destined for different tracks */ + /* s2 will overlap both */ + /* s4 destined for no track */ + _CREATE_SOURCE (layer, s1, 0, 12); + _CREATE_SOURCE (layer, s2, 5, 10); + _CREATE_SOURCE (layer, s3, 0, 10); + _CREATE_SOURCE (layer, s4, 0, 20); + + e1 = GES_TRACK_ELEMENT (ges_effect_new ("videobalance")); + fail_unless (ges_container_add (GES_CONTAINER (s2), + GES_TIMELINE_ELEMENT (e1))); + e2 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv ! vertigotv")); + fail_unless (ges_container_add (GES_CONTAINER (s2), + GES_TIMELINE_ELEMENT (e2))); + e3 = GES_TRACK_ELEMENT (ges_effect_new ("alpha")); + fail_unless (ges_container_add (GES_CONTAINER (s2), + GES_TIMELINE_ELEMENT (e3))); + assert_equals_int (0, + ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e1))); + assert_equals_int (1, + ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e2))); + assert_equals_int (2, + ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e3))); + + assert_num_children (s1, 0); + assert_num_children (s2, 3); + assert_num_children (s3, 0); + + ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (s2), + "scratch-lines", 2, "speed", 50.0, NULL); + + ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ()); + g_object_set (G_OBJECT (ctrl_source), "mode", + GST_INTERPOLATION_MODE_NONE, NULL); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 0, 1.0)); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 4, 7.0)); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 8, 3.0)); + fail_unless (ges_track_element_set_control_source (e2, ctrl_source, + "scratch-lines", "direct-absolute")); + gst_object_unref (ctrl_source); + + st_data.tr1 = track1; + st_data.tr2 = track2; + st_data.clips[0] = s1; + st_data.clips[1] = s2; + st_data.clips[2] = s3; + st_data.clips[3] = s4; + st_data.num_calls[0] = 0; + st_data.num_calls[1] = 0; + st_data.num_calls[2] = 0; + st_data.num_calls[3] = 0; + st_data.effects[0] = e1; + st_data.effects[1] = e2; + st_data.effects[2] = e3; + st_data.num_unrecognised = 0; + + g_signal_connect (timeline, "select-tracks-for-object", + G_CALLBACK (select_tracks_cb), &st_data); + + /* adding layer to the timeline will trigger track selection, this */ + GST_DEBUG ("Add the layer to the timeline"); + fail_unless (ges_timeline_add_layer (timeline, layer)); + /* The timeline steals our reference to the layer */ + ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); + fail_unless (layer->timeline == timeline); + + layers = ges_timeline_get_layers (timeline); + fail_unless (g_list_find (layers, layer) != NULL); + g_list_free_full (layers, gst_object_unref); + + fail_unless (ges_layer_get_auto_transition (layer)); + + assert_equals_int (st_data.num_unrecognised, 0); + + /* Make sure the associated TrackElements are in the Track */ + assert_num_children (s1, 1); + el = GES_CONTAINER_CHILDREN (s1)->data; + fail_unless (GES_IS_SOURCE (el)); + fail_unless (ges_track_element_get_track (el) == track1); + ASSERT_OBJECT_REFCOUNT (el, "1 timeline + 1 track + 1 clip", 3); + /* called once for source */ + assert_equals_int (st_data.num_calls[0], 1); + + /* 2 sources + 4 effects */ + assert_num_children (s2, 6); + trackelements = GES_CONTAINER_CHILDREN (s2); + /* sources at the end */ + el = g_list_nth_data (trackelements, 5); + fail_unless (GES_IS_SOURCE (el)); + el2 = g_list_nth_data (trackelements, 4); + fail_unless (GES_IS_SOURCE (el2)); + + /* font-desc is originally "", but on setting switches to Normal, so we + * set it explicitly */ + ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (el), + "font-desc", "Normal", NULL); + assert_equal_children_properties (el, el2); + assert_equal_bindings (el, el2); + + assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (el), + GES_TIMELINE_ELEMENT_PRIORITY (el2)); + + /* check one in each track */ + fail_unless (ges_track_element_get_track (el) + != ges_track_element_get_track (el2)); + fail_unless (ges_track_element_get_track (el) == track1 + || ges_track_element_get_track (el2) == track1); + fail_unless (ges_track_element_get_track (el) == track2 + || ges_track_element_get_track (el2) == track2); + + /* effects */ + e_copy = NULL; + for (tmp = trackelements; tmp; tmp = tmp->next) { + el = tmp->data; + ASSERT_OBJECT_REFCOUNT (el, "1 timeline + 1 track + 1 clip", 3); + if (GES_IS_BASE_EFFECT (el)) { + if (el == e1) { + fail_if (found_e1); + found_e1 = TRUE; + } else if (el == e2) { + fail_if (found_e2); + found_e2 = TRUE; + } else if (el == e3) { + fail_if (found_e3); + found_e3 = TRUE; + } else { + fail_if (e_copy); + e_copy = el; + } + } + } + fail_unless (found_e1); + fail_unless (found_e2); + fail_unless (found_e3); + fail_unless (e_copy); + + fail_unless (ges_track_element_get_track (e1) == track1); + fail_unless (ges_track_element_get_track (e3) == track2); + + assert_equal_children_properties (e2, e_copy); + assert_equal_bindings (e2, e_copy); + + /* check one in each track */ + fail_unless (ges_track_element_get_track (e2) + != ges_track_element_get_track (e_copy)); + fail_unless (ges_track_element_get_track (e2) == track1 + || ges_track_element_get_track (e_copy) == track1); + fail_unless (ges_track_element_get_track (e2) == track2 + || ges_track_element_get_track (e_copy) == track2); + + /* e2 copy placed next to e2 in top effect list */ + assert_equals_int (0, + ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e1))); + assert_equals_int (1, + ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e2))); + assert_equals_int (2, + ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e_copy))); + assert_equals_int (3, + ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e3))); + + /* called 4 times: 1 for source, and 1 for each effect (3) */ + assert_equals_int (st_data.num_calls[1], 4); + + assert_num_children (s3, 1); + el = GES_CONTAINER_CHILDREN (s3)->data; + fail_unless (GES_IS_SOURCE (el)); + fail_unless (ges_track_element_get_track (el) == track2); + ASSERT_OBJECT_REFCOUNT (el, "1 timeline + 1 track + 1 clip", 3); + /* called once for source */ + assert_equals_int (st_data.num_calls[2], 1); + + /* one child but no track */ + assert_num_children (s4, 1); + el = GES_CONTAINER_CHILDREN (s4)->data; + fail_unless (GES_IS_SOURCE (el)); + fail_unless (ges_track_element_get_track (el) == NULL); + ASSERT_OBJECT_REFCOUNT (el, "1 clip", 1); + /* called once for source (where no track was selected) */ + assert_equals_int (st_data.num_calls[0], 1); + + /* 2 sources + 1 transition + 2 effects */ + assert_num_in_track (track1, 5); + assert_num_in_track (track2, 5); + + el = NULL; + trackelements = ges_track_get_elements (track1); + for (tmp = trackelements; tmp; tmp = tmp->next) { + if (GES_IS_VIDEO_TRANSITION (tmp->data)) { + fail_if (el); + el = tmp->data; + } + } + g_list_free_full (trackelements, gst_object_unref); + fail_unless (GES_IS_CLIP (GES_TIMELINE_ELEMENT_PARENT (el))); + transition = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (el)); + assert_layer (transition, layer); + + CHECK_OBJECT_PROPS (transition, 5, 0, 7); + CHECK_OBJECT_PROPS (el, 5, 0, 7); + fail_unless (ges_track_element_get_track (el) == track1); + /* make sure we can change the transition type */ + fail_unless (ges_video_transition_set_transition_type (GES_VIDEO_TRANSITION + (el), GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_H)); + + el = NULL; + trackelements = ges_track_get_elements (track2); + for (tmp = trackelements; tmp; tmp = tmp->next) { + if (GES_IS_VIDEO_TRANSITION (tmp->data)) { + fail_if (el); + el = tmp->data; + } + } + g_list_free_full (trackelements, gst_object_unref); + fail_unless (GES_IS_CLIP (GES_TIMELINE_ELEMENT_PARENT (el))); + transition = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (el)); + assert_layer (transition, layer); + + CHECK_OBJECT_PROPS (transition, 5, 0, 5); + CHECK_OBJECT_PROPS (el, 5, 0, 5); + fail_unless (ges_track_element_get_track (el) == track2); + /* make sure we can change the transition type */ + fail_unless (ges_video_transition_set_transition_type (GES_VIDEO_TRANSITION + (el), GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_H)); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_ges_pipeline_change_state) +{ + GstState state; + GESAsset *asset; + GESLayer *layer; + GESTimeline *timeline; + GESPipeline *pipeline; + + ges_init (); + + layer = ges_layer_new (); + timeline = ges_timeline_new_audio_video (); + fail_unless (ges_timeline_add_layer (timeline, layer)); + + pipeline = ges_test_create_pipeline (timeline); + + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + gst_object_unref (asset); + + ges_timeline_commit (timeline); + ASSERT_SET_STATE (GST_ELEMENT (pipeline), GST_STATE_PLAYING, + GST_STATE_CHANGE_ASYNC); + fail_unless (gst_element_get_state (GST_ELEMENT (pipeline), &state, NULL, + GST_CLOCK_TIME_NONE) == GST_STATE_CHANGE_SUCCESS); + fail_unless (state == GST_STATE_PLAYING); + ASSERT_SET_STATE (GST_ELEMENT (pipeline), GST_STATE_NULL, + GST_STATE_CHANGE_SUCCESS); + + gst_object_unref (pipeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_ges_timeline_element_name) +{ + GESClip *clip, *clip1, *clip2, *clip3, *clip4, *clip5; + GESAsset *asset; + GESTimeline *timeline; + GESLayer *layer; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + fail_unless (ges_timeline_add_layer (timeline, layer)); + + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + clip = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + fail_unless_equals_string (GES_TIMELINE_ELEMENT_NAME (clip), "testclip0"); + + + clip1 = GES_CLIP (ges_test_clip_new ()); + fail_unless_equals_string (GES_TIMELINE_ELEMENT_NAME (clip1), "testclip1"); + + ges_timeline_element_set_name (GES_TIMELINE_ELEMENT (clip1), "testclip1"); + fail_unless_equals_string (GES_TIMELINE_ELEMENT_NAME (clip1), "testclip1"); + + /* Check that trying to set to a name that is already used leads to + * a change in the name */ + ges_timeline_element_set_name (GES_TIMELINE_ELEMENT (clip), "testclip1"); + fail_unless_equals_string (GES_TIMELINE_ELEMENT_NAME (clip), "testclip2"); + + ges_timeline_element_set_name (GES_TIMELINE_ELEMENT (clip1), "testclip4"); + fail_unless_equals_string (GES_TIMELINE_ELEMENT_NAME (clip1), "testclip4"); + + clip2 = GES_CLIP (ges_test_clip_new ()); + fail_unless_equals_string (GES_TIMELINE_ELEMENT_NAME (clip2), "testclip5"); + ges_timeline_element_set_name (GES_TIMELINE_ELEMENT (clip2), NULL); + fail_unless_equals_string (GES_TIMELINE_ELEMENT_NAME (clip2), "testclip6"); + + clip3 = GES_CLIP (ges_test_clip_new ()); + fail_unless_equals_string (GES_TIMELINE_ELEMENT_NAME (clip3), "testclip7"); + ges_timeline_element_set_name (GES_TIMELINE_ELEMENT (clip3), "testclip5"); + fail_unless_equals_string (GES_TIMELINE_ELEMENT_NAME (clip3), "testclip8"); + + clip4 = GES_CLIP (ges_test_clip_new ()); + fail_unless_equals_string (GES_TIMELINE_ELEMENT_NAME (clip4), "testclip9"); + + + clip5 = GES_CLIP (ges_test_clip_new ()); + ges_timeline_element_set_name (GES_TIMELINE_ELEMENT (clip5), + "Something I want!"); + fail_unless_equals_string (GES_TIMELINE_ELEMENT_NAME (clip5), + "Something I want!"); + + gst_object_unref (asset); + + gst_object_unref (clip1); + gst_object_unref (clip2); + gst_object_unref (clip3); + gst_object_unref (clip4); + gst_object_unref (clip5); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-basic"); + TCase *tc_chain = tcase_create ("basic"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_ges_scenario); + tcase_add_test (tc_chain, test_ges_timeline_add_layer); + tcase_add_test (tc_chain, test_ges_timeline_add_layer_first); + tcase_add_test (tc_chain, test_ges_timeline_remove_track); + tcase_add_test (tc_chain, test_ges_timeline_remove_layer); + tcase_add_test (tc_chain, test_ges_timeline_multiple_tracks); + tcase_add_test (tc_chain, test_ges_pipeline_change_state); + tcase_add_test (tc_chain, test_ges_timeline_element_name); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/clip.c b/tests/check/ges/clip.c new file mode 100644 index 0000000000..f2a9d23dc2 --- /dev/null +++ b/tests/check/ges/clip.c @@ -0,0 +1,5558 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <bilboed@bilboed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" +#include "../../../ges/ges-structured-interface.h" +#include <ges/ges.h> +#include <gst/check/gstcheck.h> + +#define _assert_add(clip, child) \ + fail_unless (ges_container_add (GES_CONTAINER (clip), \ + GES_TIMELINE_ELEMENT (child))) + +#define _assert_remove(clip, child) \ + fail_unless (ges_container_remove (GES_CONTAINER (clip), \ + GES_TIMELINE_ELEMENT (child))) + +GST_START_TEST (test_object_properties) +{ + GESClip *clip; + GESTrack *track; + GESTimeline *timeline; + GESLayer *layer; + GESTrackElement *trackelement; + + ges_init (); + + track = GES_TRACK (ges_video_track_new ()); + fail_unless (track != NULL); + + layer = ges_layer_new (); + fail_unless (layer != NULL); + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + fail_unless (ges_timeline_add_layer (timeline, layer)); + fail_unless (ges_timeline_add_track (timeline, track)); + + clip = (GESClip *) ges_test_clip_new (); + fail_unless (clip != NULL); + + /* Set some properties */ + g_object_set (clip, "start", (guint64) 42, "duration", (guint64) 51, + "in-point", (guint64) 12, NULL); + assert_equals_uint64 (_START (clip), 42); + assert_equals_uint64 (_DURATION (clip), 51); + assert_equals_uint64 (_INPOINT (clip), 12); + + ges_layer_add_clip (layer, GES_CLIP (clip)); + ges_timeline_commit (timeline); + assert_num_children (clip, 1); + trackelement = GES_CONTAINER_CHILDREN (clip)->data; + fail_unless (trackelement != NULL); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) == + GES_TIMELINE_ELEMENT (clip)); + fail_unless (ges_track_element_get_track (trackelement) == track); + + /* Check that trackelement has the same properties */ + assert_equals_uint64 (_START (trackelement), 42); + assert_equals_uint64 (_DURATION (trackelement), 51); + assert_equals_uint64 (_INPOINT (trackelement), 12); + + /* And let's also check that it propagated correctly to GNonLin */ + nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 51, 12, + 51, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE); + + /* Change more properties, see if they propagate */ + g_object_set (clip, "start", (guint64) 420, "duration", (guint64) 510, + "in-point", (guint64) 120, NULL); + assert_equals_uint64 (_START (clip), 420); + assert_equals_uint64 (_DURATION (clip), 510); + assert_equals_uint64 (_INPOINT (clip), 120); + assert_equals_uint64 (_START (trackelement), 420); + assert_equals_uint64 (_DURATION (trackelement), 510); + assert_equals_uint64 (_INPOINT (trackelement), 120); + + /* And let's also check that it propagated correctly to GNonLin */ + ges_timeline_commit (timeline); + nle_object_check (ges_track_element_get_nleobject (trackelement), 420, 510, + 120, 510, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE); + + + /* This time, we move the trackelement to see if the changes move + * along to the parent and the gnonlin clip */ + g_object_set (trackelement, "start", (guint64) 400, NULL); + ges_timeline_commit (timeline); + assert_equals_uint64 (_START (clip), 400); + assert_equals_uint64 (_START (trackelement), 400); + nle_object_check (ges_track_element_get_nleobject (trackelement), 400, 510, + 120, 510, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE); + + _assert_remove (clip, trackelement); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_split_direct_bindings) +{ + GList *values; + GstControlSource *source; + GESTimeline *timeline; + GESClip *clip, *splitclip; + GstControlBinding *binding = NULL, *splitbinding; + GstTimedValueControlSource *splitsource; + GESLayer *layer; + GESAsset *asset; + GValue *tmpvalue; + + GESTrackElement *element; + + ges_init (); + + fail_unless ((timeline = ges_timeline_new ())); + fail_unless ((layer = ges_layer_new ())); + fail_unless (ges_timeline_add_track (timeline, + GES_TRACK (ges_video_track_new ()))); + fail_unless (ges_timeline_add_layer (timeline, layer)); + + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + clip = ges_layer_add_asset (layer, asset, 0, 10 * GST_SECOND, 10 * GST_SECOND, + GES_TRACK_TYPE_UNKNOWN); + g_object_unref (asset); + + CHECK_OBJECT_PROPS (clip, 0 * GST_SECOND, 10 * GST_SECOND, 10 * GST_SECOND); + assert_num_children (clip, 1); + check_layer (clip, 0); + + source = gst_interpolation_control_source_new (); + g_object_set (source, "mode", GST_INTERPOLATION_MODE_LINEAR, NULL); + element = GES_CONTAINER_CHILDREN (clip)->data; + fail_unless (ges_track_element_set_control_source (element, + source, "alpha", "direct")); + + gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE (source), + 10 * GST_SECOND, 0.0); + gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE (source), + 20 * GST_SECOND, 1.0); + + binding = ges_track_element_get_control_binding (element, "alpha"); + tmpvalue = gst_control_binding_get_value (binding, 10 * GST_SECOND); + assert_equals_int (g_value_get_double (tmpvalue), 0.0); + g_value_unset (tmpvalue); + g_free (tmpvalue); + + tmpvalue = gst_control_binding_get_value (binding, 20 * GST_SECOND); + assert_equals_int (g_value_get_double (tmpvalue), 1.0); + g_value_unset (tmpvalue); + g_free (tmpvalue); + + splitclip = ges_clip_split (clip, 5 * GST_SECOND); + CHECK_OBJECT_PROPS (splitclip, 5 * GST_SECOND, 15 * GST_SECOND, + 5 * GST_SECOND); + check_layer (splitclip, 0); + + splitbinding = + ges_track_element_get_control_binding (GES_CONTAINER_CHILDREN + (splitclip)->data, "alpha"); + g_object_get (splitbinding, "control_source", &splitsource, NULL); + + values = + gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE + (splitsource)); + assert_equals_int (g_list_length (values), 2); + assert_equals_uint64 (((GstTimedValue *) values->data)->timestamp, + 15 * GST_SECOND); + assert_equals_float (((GstTimedValue *) values->data)->value, 0.5); + + assert_equals_uint64 (((GstTimedValue *) values->next->data)->timestamp, + 20 * GST_SECOND); + assert_equals_float (((GstTimedValue *) values->next->data)->value, 1.0); + g_list_free (values); + + values = + gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE + (source)); + assert_equals_int (g_list_length (values), 2); + assert_equals_uint64 (((GstTimedValue *) values->data)->timestamp, + 10 * GST_SECOND); + assert_equals_float (((GstTimedValue *) values->data)->value, 0.0); + + assert_equals_uint64 (((GstTimedValue *) values->next->data)->timestamp, + 15 * GST_SECOND); + assert_equals_float (((GstTimedValue *) values->next->data)->value, 0.50); + g_list_free (values); + + CHECK_OBJECT_PROPS (clip, 0 * GST_SECOND, 10 * GST_SECOND, 5 * GST_SECOND); + check_layer (clip, 0); + + gst_object_unref (timeline); + gst_object_unref (source); + gst_object_unref (splitsource); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_split_direct_absolute_bindings) +{ + GList *values; + GstControlSource *source; + GESTimeline *timeline; + GESClip *clip, *splitclip; + GstControlBinding *binding = NULL, *splitbinding; + GstTimedValueControlSource *splitsource; + GESLayer *layer; + GESAsset *asset; + GValue *tmpvalue; + + GESTrackElement *element; + + ges_init (); + + fail_unless ((timeline = ges_timeline_new ())); + fail_unless ((layer = ges_layer_new ())); + fail_unless (ges_timeline_add_track (timeline, + GES_TRACK (ges_video_track_new ()))); + fail_unless (ges_timeline_add_layer (timeline, layer)); + + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + clip = ges_layer_add_asset (layer, asset, 0, 10 * GST_SECOND, 10 * GST_SECOND, + GES_TRACK_TYPE_UNKNOWN); + g_object_unref (asset); + + CHECK_OBJECT_PROPS (clip, 0 * GST_SECOND, 10 * GST_SECOND, 10 * GST_SECOND); + assert_num_children (clip, 1); + check_layer (clip, 0); + + source = gst_interpolation_control_source_new (); + g_object_set (source, "mode", GST_INTERPOLATION_MODE_LINEAR, NULL); + element = GES_CONTAINER_CHILDREN (clip)->data; + fail_unless (ges_track_element_set_control_source (element, + source, "posx", "direct-absolute")); + + gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE (source), + 10 * GST_SECOND, 0); + gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE (source), + 20 * GST_SECOND, 500); + + binding = ges_track_element_get_control_binding (element, "posx"); + tmpvalue = gst_control_binding_get_value (binding, 10 * GST_SECOND); + assert_equals_int (g_value_get_int (tmpvalue), 0); + g_value_unset (tmpvalue); + g_free (tmpvalue); + + tmpvalue = gst_control_binding_get_value (binding, 20 * GST_SECOND); + assert_equals_int (g_value_get_int (tmpvalue), 500); + g_value_unset (tmpvalue); + g_free (tmpvalue); + + splitclip = ges_clip_split (clip, 5 * GST_SECOND); + CHECK_OBJECT_PROPS (splitclip, 5 * GST_SECOND, 15 * GST_SECOND, + 5 * GST_SECOND); + check_layer (splitclip, 0); + + splitbinding = + ges_track_element_get_control_binding (GES_CONTAINER_CHILDREN + (splitclip)->data, "posx"); + g_object_get (splitbinding, "control_source", &splitsource, NULL); + + values = + gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE + (splitsource)); + assert_equals_int (g_list_length (values), 2); + assert_equals_uint64 (((GstTimedValue *) values->data)->timestamp, + 15 * GST_SECOND); + assert_equals_float (((GstTimedValue *) values->data)->value, 250.0); + + assert_equals_uint64 (((GstTimedValue *) values->next->data)->timestamp, + 20 * GST_SECOND); + assert_equals_float (((GstTimedValue *) values->next->data)->value, 500.0); + g_list_free (values); + + values = + gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE + (source)); + assert_equals_int (g_list_length (values), 2); + assert_equals_uint64 (((GstTimedValue *) values->data)->timestamp, + 10 * GST_SECOND); + assert_equals_float (((GstTimedValue *) values->data)->value, 0.0); + + assert_equals_uint64 (((GstTimedValue *) values->next->data)->timestamp, + 15 * GST_SECOND); + assert_equals_float (((GstTimedValue *) values->next->data)->value, 250.0); + g_list_free (values); + + CHECK_OBJECT_PROPS (clip, 0 * GST_SECOND, 10 * GST_SECOND, 5 * GST_SECOND); + check_layer (clip, 0); + + gst_object_unref (timeline); + gst_object_unref (source); + gst_object_unref (splitsource); + + ges_deinit (); +} + +GST_END_TEST; + +static GESTimelineElement * +_find_auto_transition (GESTrack * track, GESClip * from_clip, GESClip * to_clip) +{ + GstClockTime start, end; + GList *tmp, *track_els; + GESTimelineElement *ret = NULL; + GESLayer *layer0, *layer1; + + layer0 = ges_clip_get_layer (from_clip); + layer1 = ges_clip_get_layer (to_clip); + + fail_unless (layer0 == layer1, "%" GES_FORMAT " and %" GES_FORMAT " do not " + "share the same layer", GES_ARGS (from_clip), GES_ARGS (to_clip)); + gst_object_unref (layer1); + + start = GES_TIMELINE_ELEMENT_START (to_clip); + end = GES_TIMELINE_ELEMENT_START (from_clip) + + GES_TIMELINE_ELEMENT_DURATION (from_clip); + + fail_if (end <= start, "%" GES_FORMAT " starts after %" GES_FORMAT " ends", + GES_ARGS (to_clip), GES_ARGS (from_clip)); + + track_els = ges_track_get_elements (track); + + for (tmp = track_els; tmp; tmp = tmp->next) { + GESTimelineElement *el = tmp->data; + if (GES_IS_TRANSITION (el) && el->start == start + && (el->start + el->duration) == end) { + fail_if (ret, "Found two transitions %" GES_FORMAT " and %" GES_FORMAT + " between %" GES_FORMAT " and %" GES_FORMAT " in track %" + GST_PTR_FORMAT, GES_ARGS (el), GES_ARGS (ret), GES_ARGS (from_clip), + GES_ARGS (to_clip), track); + ret = el; + } + } + fail_unless (ret, "Found no transitions between %" GES_FORMAT " and %" + GES_FORMAT " in track %" GST_PTR_FORMAT, GES_ARGS (from_clip), + GES_ARGS (to_clip), track); + + g_list_free_full (track_els, gst_object_unref); + + fail_unless (GES_IS_CLIP (ret->parent), "Transition %" GES_FORMAT + " between %" GES_FORMAT " and %" GES_FORMAT " in track %" + GST_PTR_FORMAT " has no parent clip", GES_ARGS (ret), + GES_ARGS (from_clip), GES_ARGS (to_clip), track); + + layer1 = ges_clip_get_layer (GES_CLIP (ret->parent)); + + fail_unless (layer0 == layer1, "Transition %" GES_FORMAT " between %" + GES_FORMAT " and %" GES_FORMAT " in track %" GST_PTR_FORMAT + " belongs to layer %" GST_PTR_FORMAT " rather than %" GST_PTR_FORMAT, + GES_ARGS (ret), GES_ARGS (from_clip), GES_ARGS (to_clip), track, + layer1, layer0); + + gst_object_unref (layer0); + gst_object_unref (layer1); + + return ret; +} + +GST_START_TEST (test_split_with_auto_transitions) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *tracks[3]; + GESTimelineElement *found; + GESTimelineElement *prev_trans[3]; + GESTimelineElement *post_trans[3]; + GESAsset *asset; + GESClip *clip, *split, *prev, *post; + guint i; + + ges_init (); + + timeline = ges_timeline_new (); + + ges_timeline_set_auto_transition (timeline, TRUE); + + tracks[0] = GES_TRACK (ges_audio_track_new ()); + tracks[1] = GES_TRACK (ges_audio_track_new ()); + tracks[2] = GES_TRACK (ges_video_track_new ()); + + for (i = 0; i < 3; i++) + fail_unless (ges_timeline_add_track (timeline, tracks[i])); + + layer = ges_timeline_append_layer (timeline); + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + prev = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + clip = ges_layer_add_asset (layer, asset, 5, 0, 20, GES_TRACK_TYPE_UNKNOWN); + post = ges_layer_add_asset (layer, asset, 20, 0, 10, GES_TRACK_TYPE_UNKNOWN); + + fail_unless (prev); + fail_unless (clip); + fail_unless (post); + + for (i = 0; i < 3; i++) { + prev_trans[i] = _find_auto_transition (tracks[i], prev, clip); + post_trans[i] = _find_auto_transition (tracks[i], clip, post); + /* 3 sources, 2 auto-transitions */ + assert_num_in_track (tracks[i], 5); + + } + + /* cannot split within a transition */ + fail_if (ges_clip_split (clip, 5)); + fail_if (ges_clip_split (clip, 20)); + + /* we should keep the same auto-transitions during a split */ + split = ges_clip_split (clip, 15); + fail_unless (split); + + for (i = 0; i < 3; i++) { + found = _find_auto_transition (tracks[i], prev, clip); + fail_unless (found == prev_trans[i], "Transition between %" GES_FORMAT + " and %" GES_FORMAT " changed", GES_ARGS (prev), GES_ARGS (clip)); + + found = _find_auto_transition (tracks[i], split, post); + fail_unless (found == post_trans[i], "Transition between %" GES_FORMAT + " and %" GES_FORMAT " changed", GES_ARGS (clip), GES_ARGS (post)); + } + + gst_object_unref (timeline); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + +static GPtrArray * +_select_none (GESTimeline * timeline, GESClip * clip, + GESTrackElement * track_element, guint * called_p) +{ + (*called_p)++; + return NULL; +} + +static GPtrArray * +_select_track (GESTimeline * timeline, GESClip * clip, + GESTrackElement * track_element, GESTrack ** track_p) +{ + GPtrArray *tracks = g_ptr_array_new (); + fail_unless (track_p); + fail_unless (*track_p); + g_ptr_array_insert (tracks, -1, gst_object_ref (*track_p)); + *track_p = NULL; + return tracks; +} + +GST_START_TEST (test_split_object) +{ + GESTimeline *timeline; + GESTrack *track1, *track2, *effect_track; + GESLayer *layer; + GESClip *clip, *splitclip; + GList *splittrackelements; + GESTrackElement *trackelement1, *trackelement2, *effect1, *effect2, + *splittrackelement; + guint32 priority1, priority2, effect_priority1, effect_priority2; + guint selection_called = 0; + const gchar *meta; + + ges_init (); + + layer = ges_layer_new (); + fail_unless (layer != NULL); + timeline = ges_timeline_new_audio_video (); + fail_unless (timeline != NULL); + fail_unless (ges_timeline_add_layer (timeline, layer)); + ASSERT_OBJECT_REFCOUNT (timeline, "timeline", 1); + + clip = GES_CLIP (ges_test_clip_new ()); + fail_unless (clip != NULL); + ASSERT_OBJECT_REFCOUNT (timeline, "timeline", 1); + + /* Set some properties */ + g_object_set (clip, "start", (guint64) 42, "duration", (guint64) 50, + "in-point", (guint64) 12, NULL); + ASSERT_OBJECT_REFCOUNT (timeline, "timeline", 1); + CHECK_OBJECT_PROPS (clip, 42, 12, 50); + + ges_layer_add_clip (layer, GES_CLIP (clip)); + ges_timeline_commit (timeline); + assert_num_children (clip, 2); + trackelement1 = GES_CONTAINER_CHILDREN (clip)->data; + fail_unless (trackelement1 != NULL); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement1) == + GES_TIMELINE_ELEMENT (clip)); + ges_meta_container_set_string (GES_META_CONTAINER (trackelement1), "test_key", + "test_value"); + + trackelement2 = GES_CONTAINER_CHILDREN (clip)->next->data; + fail_unless (trackelement2 != NULL); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement2) == + GES_TIMELINE_ELEMENT (clip)); + + effect1 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv")); + _assert_add (clip, effect1); + + effect2 = GES_TRACK_ELEMENT (ges_effect_new ("vertigotv")); + _assert_add (clip, effect2); + + /* Check that trackelement has the same properties */ + CHECK_OBJECT_PROPS (trackelement1, 42, 12, 50); + CHECK_OBJECT_PROPS (trackelement2, 42, 12, 50); + CHECK_OBJECT_PROPS (effect1, 42, 0, 50); + CHECK_OBJECT_PROPS (effect2, 42, 0, 50); + + /* And let's also check that it propagated correctly to GNonLin */ + nle_object_check (ges_track_element_get_nleobject (trackelement1), 42, 50, 12, + 50, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 2, TRUE); + nle_object_check (ges_track_element_get_nleobject (trackelement2), 42, 50, 12, + 50, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 2, TRUE); + + track1 = ges_track_element_get_track (trackelement1); + fail_unless (track1); + track2 = ges_track_element_get_track (trackelement2); + fail_unless (track2); + fail_unless (track1 != track2); + effect_track = ges_track_element_get_track (effect1); + fail_unless (effect_track); + fail_unless (ges_track_element_get_track (effect2) == effect_track); + + priority1 = GES_TIMELINE_ELEMENT_PRIORITY (trackelement1); + priority2 = GES_TIMELINE_ELEMENT_PRIORITY (trackelement2); + effect_priority1 = GES_TIMELINE_ELEMENT_PRIORITY (effect1); + effect_priority2 = GES_TIMELINE_ELEMENT_PRIORITY (effect2); + + fail_unless (priority1 == priority2); + fail_unless (priority1 > effect_priority2); + fail_unless (effect_priority2 > effect_priority1); + + ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (clip), + "font-desc", "Normal", "posx", 30, "posy", 50, "alpha", 0.1, + "freq", 449.0, "scratch-lines", 2, "zoom-speed", 1.05, NULL); + + /* splitting should avoid track selection */ + g_signal_connect (timeline, "select-tracks-for-object", + G_CALLBACK (_select_none), &selection_called); + + splitclip = ges_clip_split (clip, 67); + fail_unless (GES_IS_CLIP (splitclip)); + fail_unless (splitclip != clip); + + fail_if (selection_called); + + CHECK_OBJECT_PROPS (clip, 42, 12, 25); + CHECK_OBJECT_PROPS (trackelement1, 42, 12, 25); + CHECK_OBJECT_PROPS (trackelement1, 42, 12, 25); + CHECK_OBJECT_PROPS (effect1, 42, 0, 25); + CHECK_OBJECT_PROPS (effect2, 42, 0, 25); + + CHECK_OBJECT_PROPS (splitclip, 67, 37, 25); + + assert_equal_children_properties (splitclip, clip); + + splittrackelements = GES_CONTAINER_CHILDREN (splitclip); + fail_unless_equals_int (g_list_length (splittrackelements), 4); + + /* first is the effects */ + splittrackelement = GES_TRACK_ELEMENT (splittrackelements->data); + fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement)); + CHECK_OBJECT_PROPS (splittrackelement, 67, 0, 25); + + assert_equal_children_properties (splittrackelement, effect1); + fail_unless (ges_track_element_get_track (splittrackelement) == effect_track); + fail_unless (ges_track_element_get_track (effect1) == effect_track); + /* +3 priority from layer */ + assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement), + effect_priority1 + 3); + fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (effect1) == effect_priority1); + + fail_unless (splittrackelement != trackelement1); + fail_unless (splittrackelement != trackelement2); + fail_unless (splittrackelement != effect1); + fail_unless (splittrackelement != effect2); + + splittrackelement = GES_TRACK_ELEMENT (splittrackelements->next->data); + fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement)); + CHECK_OBJECT_PROPS (splittrackelement, 67, 0, 25); + + assert_equal_children_properties (splittrackelement, effect2); + fail_unless (ges_track_element_get_track (splittrackelement) == effect_track); + fail_unless (ges_track_element_get_track (effect2) == effect_track); + assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement), + effect_priority2 + 3); + fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (effect2) == effect_priority2); + + fail_unless (splittrackelement != trackelement1); + fail_unless (splittrackelement != trackelement2); + fail_unless (splittrackelement != effect1); + fail_unless (splittrackelement != effect2); + + splittrackelement = GES_TRACK_ELEMENT (splittrackelements->next->next->data); + fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement)); + CHECK_OBJECT_PROPS (splittrackelement, 67, 37, 25); + + /* core elements have swapped order in the clip, this is ok since they + * share the same priority */ + assert_equal_children_properties (splittrackelement, trackelement2); + fail_unless (ges_track_element_get_track (splittrackelement) == track2); + fail_unless (ges_track_element_get_track (trackelement2) == track2); + assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement), + priority2 + 3); + fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (trackelement2) == priority2); + + fail_unless (splittrackelement != trackelement1); + fail_unless (splittrackelement != trackelement2); + fail_unless (splittrackelement != effect1); + fail_unless (splittrackelement != effect2); + + splittrackelement = + GES_TRACK_ELEMENT (splittrackelements->next->next->next->data); + fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement)); + CHECK_OBJECT_PROPS (splittrackelement, 67, 37, 25); + + assert_equal_children_properties (splittrackelement, trackelement1); + fail_unless (ges_track_element_get_track (splittrackelement) == track1); + fail_unless (ges_track_element_get_track (trackelement1) == track1); + assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement), + priority1 + 3); + fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (trackelement1) == priority2); + meta = ges_meta_container_get_string (GES_META_CONTAINER (splittrackelement), + "test_key"); + fail_unless_equals_string (meta, "test_value"); + + fail_unless (splittrackelement != trackelement1); + fail_unless (splittrackelement != trackelement2); + fail_unless (splittrackelement != effect1); + fail_unless (splittrackelement != effect2); + + /* We own the only ref */ + ASSERT_OBJECT_REFCOUNT (splitclip, "1 ref for us + 1 for the timeline", 2); + /* 1 ref for the Clip, 1 ref for the Track and 2 ref for the timeline + * (1 for the "all_element" hashtable, another for the sequence of TrackElement*/ + ASSERT_OBJECT_REFCOUNT (splittrackelement, + "1 ref for the Clip, 1 ref for the Track and 1 ref for the timeline", 3); + + check_destroyed (G_OBJECT (timeline), G_OBJECT (splitclip), clip, + splittrackelement, NULL); + + ges_deinit (); +} + +GST_END_TEST; + +typedef struct +{ + gboolean duration_cb_called; + gboolean clip_added_cb_called; + gboolean track_selected_cb_called; + GESClip *clip; +} SplitOrderData; + +static void +_track_selected_cb (GESTimelineElement * el, GParamSpec * spec, + SplitOrderData * data) +{ + GESClip *clip = GES_CLIP (el->parent); + + fail_unless (data->clip == clip, "Parent is %" GES_FORMAT " rather than %" + GES_FORMAT, GES_ARGS (clip), GES_ARGS (data->clip)); + + fail_unless (data->duration_cb_called, "notify::duration not emitted " + "for neighbour of %" GES_FORMAT, GES_ARGS (data->clip)); + fail_unless (data->clip_added_cb_called, "child-added not emitted for %" + GES_FORMAT, GES_ARGS (data->clip)); + + data->track_selected_cb_called = TRUE; +} + +static void +_child_added_cb (GESClip * clip, GESTimelineElement * child, + SplitOrderData * data) +{ + fail_unless (data->clip == clip, "Received %" GES_FORMAT " rather than %" + GES_FORMAT, GES_ARGS (clip), GES_ARGS (data->clip)); + + g_signal_connect (child, "notify::track", G_CALLBACK (_track_selected_cb), + data); +} + +static void +_clip_added_cb (GESLayer * layer, GESClip * clip, SplitOrderData * data) +{ + GList *tmp; + + data->clip = clip; + + fail_unless (data->duration_cb_called, "notify::duration not emitted " + "for neighbour of %" GES_FORMAT, GES_ARGS (data->clip)); + /* only called once */ + fail_if (data->clip_added_cb_called, "clip-added already emitted for %" + GES_FORMAT, GES_ARGS (data->clip)); + fail_if (data->track_selected_cb_called, "track selection already " + "occurred for %" GES_FORMAT, GES_ARGS (data->clip)); + + data->clip_added_cb_called = TRUE; + + g_signal_connect (clip, "child-added", G_CALLBACK (_child_added_cb), data); + + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) + g_signal_connect (tmp->data, "notify::track", + G_CALLBACK (_track_selected_cb), data); +} + +static void +_disconnect_cbs (GESLayer * layer, GESClip * clip, SplitOrderData * data) +{ + GList *tmp; + g_signal_handlers_disconnect_by_func (clip, _child_added_cb, data); + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) + g_signal_handlers_disconnect_by_func (tmp->data, _track_selected_cb, data); +} + +static void +_duration_cb (GObject * object, GParamSpec * pspec, SplitOrderData * data) +{ + /* only called once */ + fail_if (data->duration_cb_called, "notify::duration of neighbour %" + GES_FORMAT " already emitted ", GES_ARGS (object)); + fail_if (data->clip_added_cb_called, "clip-added already emitted"); + fail_if (data->track_selected_cb_called, "track selection already " + "occurred"); + + data->duration_cb_called = TRUE; +} + +GST_START_TEST (test_split_ordering) +{ + GESTimeline *timeline; + GESLayer *layer; + GESClip *clip, *splitclip; + SplitOrderData data; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + + layer = ges_timeline_append_layer (timeline); + + clip = GES_CLIP (ges_test_clip_new ()); + assert_set_duration (clip, 10); + + /* test order when adding clip to a layer */ + /* don't care about duration yet */ + data.duration_cb_called = TRUE; + data.clip_added_cb_called = FALSE; + data.track_selected_cb_called = FALSE; + data.clip = NULL; + + g_signal_connect (layer, "clip-added", G_CALLBACK (_clip_added_cb), &data); + + fail_unless (ges_layer_add_clip (layer, clip)); + + fail_unless (data.duration_cb_called); + fail_unless (data.clip_added_cb_called); + fail_unless (data.track_selected_cb_called); + fail_unless (data.clip == clip); + + /* now check for the same ordering when splitting, which the original + * clip shrinking before the new one is added to the layer */ + data.duration_cb_called = FALSE; + data.clip_added_cb_called = FALSE; + data.track_selected_cb_called = FALSE; + data.clip = NULL; + + g_signal_connect (clip, "notify::duration", G_CALLBACK (_duration_cb), &data); + + splitclip = ges_clip_split (clip, 5); + + fail_unless (splitclip); + fail_unless (data.duration_cb_called); + fail_unless (data.clip_added_cb_called); + fail_unless (data.track_selected_cb_called); + fail_unless (data.clip == splitclip); + + /* disconnect since track of children will change when timeline is + * freed */ + _disconnect_cbs (layer, clip, &data); + _disconnect_cbs (layer, splitclip, &data); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +#define _assert_higher_priority(el, higher) \ +{ \ + if (higher) { \ + guint32 el_prio = GES_TIMELINE_ELEMENT_PRIORITY (el); \ + guint32 higher_prio = GES_TIMELINE_ELEMENT_PRIORITY (higher); \ + fail_unless (el_prio > higher_prio, "%s does not have a higher " \ + "priority than %s (%u vs %u)", GES_TIMELINE_ELEMENT_NAME (el), \ + GES_TIMELINE_ELEMENT_NAME (higher), el_prio, higher_prio); \ + } \ +} + +#define _assert_regroup_fails(clip_list) \ +{ \ + GESContainer *regrouped = ges_container_group (clip_list); \ + fail_unless (GES_IS_GROUP (regrouped)); \ + assert_equals_int (g_list_length (regrouped->children), \ + g_list_length (clip_list)); \ + g_list_free_full (ges_container_ungroup (regrouped, FALSE), \ + gst_object_unref); \ +} + +GST_START_TEST (test_clip_group_ungroup) +{ + GESAsset *asset; + GESTimeline *timeline; + GESClip *clip, *video_clip, *audio_clip; + GESTrackElement *el; + GList *containers, *tmp; + GESLayer *layer; + GESContainer *regrouped_clip; + GESTrack *audio_track, *video_track; + guint selection_called = 0; + struct + { + GESTrackElement *element; + GESTrackElement *higher_priority; + } audio_els[2]; + struct + { + GESTrackElement *element; + GESTrackElement *higher_priority; + } video_els[3]; + guint i, j; + const gchar *name; + GESTrackType type; + + ges_init (); + + timeline = ges_timeline_new (); + layer = ges_layer_new (); + audio_track = GES_TRACK (ges_audio_track_new ()); + video_track = GES_TRACK (ges_video_track_new ()); + + fail_unless (ges_timeline_add_track (timeline, audio_track)); + fail_unless (ges_timeline_add_track (timeline, video_track)); + fail_unless (ges_timeline_add_layer (timeline, layer)); + + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + assert_is_type (asset, GES_TYPE_ASSET); + + clip = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + ASSERT_OBJECT_REFCOUNT (clip, "1 layer + 1 timeline.all_els", 2); + assert_num_children (clip, 2); + CHECK_OBJECT_PROPS (clip, 0, 0, 10); + + el = GES_TRACK_ELEMENT (ges_effect_new ("audioecho")); + ges_track_element_set_track_type (el, GES_TRACK_TYPE_AUDIO); + _assert_add (clip, el); + + el = GES_TRACK_ELEMENT (ges_effect_new ("agingtv")); + ges_track_element_set_track_type (el, GES_TRACK_TYPE_VIDEO); + _assert_add (clip, el); + + el = GES_TRACK_ELEMENT (ges_effect_new ("videobalance")); + ges_track_element_set_track_type (el, GES_TRACK_TYPE_VIDEO); + _assert_add (clip, el); + + assert_num_children (clip, 5); + CHECK_OBJECT_PROPS (clip, 0, 0, 10); + + i = j = 0; + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + el = tmp->data; + type = ges_track_element_get_track_type (el); + if (type == GES_TRACK_TYPE_AUDIO) { + fail_unless (i < G_N_ELEMENTS (audio_els)); + audio_els[i].element = el; + fail_unless (ges_track_element_get_track (el) == audio_track, + "%s not in audio track", GES_TIMELINE_ELEMENT_NAME (el)); + if (i == 0) + audio_els[i].higher_priority = NULL; + else + audio_els[i].higher_priority = audio_els[i - 1].element; + _assert_higher_priority (el, audio_els[i].higher_priority); + i++; + } + if (type == GES_TRACK_TYPE_VIDEO) { + fail_unless (j < G_N_ELEMENTS (video_els)); + video_els[j].element = el; + fail_unless (ges_track_element_get_track (el) == video_track, + "%s not in video track", GES_TIMELINE_ELEMENT_NAME (el)); + if (j == 0) + video_els[j].higher_priority = NULL; + else + video_els[j].higher_priority = video_els[j - 1].element; + _assert_higher_priority (el, video_els[j].higher_priority); + j++; + } + } + fail_unless (i == G_N_ELEMENTS (audio_els)); + fail_unless (j == G_N_ELEMENTS (video_els)); + assert_num_in_track (audio_track, 2); + assert_num_in_track (video_track, 3); + + /* group and ungroup should avoid track selection */ + g_signal_connect (timeline, "select-tracks-for-object", + G_CALLBACK (_select_none), &selection_called); + + containers = ges_container_ungroup (GES_CONTAINER (clip), FALSE); + + fail_if (selection_called); + + video_clip = NULL; + audio_clip = NULL; + + assert_equals_int (g_list_length (containers), 2); + + type = ges_clip_get_supported_formats (containers->data); + if (type == GES_TRACK_TYPE_VIDEO) + video_clip = containers->data; + if (type == GES_TRACK_TYPE_AUDIO) + audio_clip = containers->data; + + type = ges_clip_get_supported_formats (containers->next->data); + if (type == GES_TRACK_TYPE_VIDEO) + video_clip = containers->next->data; + if (type == GES_TRACK_TYPE_AUDIO) + audio_clip = containers->next->data; + + fail_unless (video_clip); + fail_unless (audio_clip); + fail_unless (video_clip == clip || audio_clip == clip); + + assert_layer (video_clip, layer); + assert_num_children (video_clip, 3); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (video_clip) == timeline); + CHECK_OBJECT_PROPS (video_clip, 0, 0, 10); + ASSERT_OBJECT_REFCOUNT (video_clip, "1 for the layer + 1 for the timeline + " + "1 in containers list", 3); + + assert_layer (audio_clip, layer); + assert_num_children (audio_clip, 2); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (audio_clip) == timeline); + CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10); + ASSERT_OBJECT_REFCOUNT (audio_clip, "1 for the layer + 1 for the timeline + " + "1 in containers list", 3); + + for (i = 0; i < G_N_ELEMENTS (audio_els); i++) { + el = audio_els[i].element; + name = GES_TIMELINE_ELEMENT_NAME (el); + fail_unless (ges_track_element_get_track (el) == audio_track, + "%s not in audio track", name); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) == + GES_TIMELINE_ELEMENT (audio_clip), "%s not in the audio clip", name); + ASSERT_OBJECT_REFCOUNT (el, + "1 for the track + 1 for the container " "+ 1 for the timeline", 3); + _assert_higher_priority (el, audio_els[i].higher_priority); + } + for (i = 0; i < G_N_ELEMENTS (video_els); i++) { + el = video_els[i].element; + name = GES_TIMELINE_ELEMENT_NAME (el); + fail_unless (ges_track_element_get_track (el) == video_track, + "%s not in video track", name); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) == + GES_TIMELINE_ELEMENT (video_clip), "%s not in the video clip", name); + ASSERT_OBJECT_REFCOUNT (el, + "1 for the track + 1 for the container " "+ 1 for the timeline", 3); + _assert_higher_priority (el, video_els[i].higher_priority); + } + assert_num_in_track (audio_track, 2); + assert_num_in_track (video_track, 3); + + assert_set_start (video_clip, 10); + CHECK_OBJECT_PROPS (video_clip, 10, 0, 10); + CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10); + + _assert_regroup_fails (containers); + + assert_set_start (video_clip, 0); + assert_set_inpoint (video_clip, 10); + CHECK_OBJECT_PROPS (video_clip, 0, 10, 10); + CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10); + + _assert_regroup_fails (containers); + + assert_set_inpoint (video_clip, 0); + assert_set_duration (video_clip, 15); + CHECK_OBJECT_PROPS (video_clip, 0, 0, 15); + CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10); + + _assert_regroup_fails (containers); + + assert_set_duration (video_clip, 10); + CHECK_OBJECT_PROPS (video_clip, 0, 0, 10); + CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10); + + regrouped_clip = ges_container_group (containers); + + fail_if (selection_called); + + assert_is_type (regrouped_clip, GES_TYPE_CLIP); + assert_num_children (regrouped_clip, 5); + assert_equals_int (ges_clip_get_supported_formats (GES_CLIP (regrouped_clip)), + GES_TRACK_TYPE_VIDEO | GES_TRACK_TYPE_AUDIO); + g_list_free_full (containers, gst_object_unref); + + assert_layer (regrouped_clip, layer); + + for (i = 0; i < G_N_ELEMENTS (audio_els); i++) { + el = audio_els[i].element; + name = GES_TIMELINE_ELEMENT_NAME (el); + fail_unless (ges_track_element_get_track (el) == audio_track, + "%s not in audio track", name); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) == + GES_TIMELINE_ELEMENT (regrouped_clip), "%s not in the regrouped clip", + name); + ASSERT_OBJECT_REFCOUNT (el, + "1 for the track + 1 for the container " "+ 1 for the timeline", 3); + _assert_higher_priority (el, audio_els[i].higher_priority); + } + for (i = 0; i < G_N_ELEMENTS (video_els); i++) { + el = video_els[i].element; + name = GES_TIMELINE_ELEMENT_NAME (el); + fail_unless (ges_track_element_get_track (el) == video_track, + "%s not in video track", name); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) == + GES_TIMELINE_ELEMENT (regrouped_clip), "%s not in the regrouped clip", + name); + ASSERT_OBJECT_REFCOUNT (el, + "1 for the track + 1 for the container " "+ 1 for the timeline", 3); + _assert_higher_priority (el, video_els[i].higher_priority); + } + assert_num_in_track (audio_track, 2); + assert_num_in_track (video_track, 3); + + gst_object_unref (timeline); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_clip_can_group) +{ + GESTimeline *timeline; + GESLayer *layer1, *layer2; + GESTrack *track1, *track2, *track3, *select_track; + GESAsset *asset1, *asset2, *asset3; + GESContainer *container; + GESClip *clip1, *clip2, *clip3, *grouped; + GList *clips = NULL; + + ges_init (); + + timeline = ges_timeline_new (); + + track1 = GES_TRACK (ges_audio_track_new ()); + track2 = GES_TRACK (ges_video_track_new ()); + track3 = GES_TRACK (ges_video_track_new ()); + + fail_unless (ges_timeline_add_track (timeline, track1)); + fail_unless (ges_timeline_add_track (timeline, track2)); + + layer1 = ges_timeline_append_layer (timeline); + layer2 = ges_timeline_append_layer (timeline); + + asset1 = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + asset2 = ges_asset_request (GES_TYPE_TEST_CLIP, "width=700", NULL); + asset3 = + ges_asset_request (GES_TYPE_EFFECT_CLIP, "audioecho || agingtv", NULL); + + /* fail if different layer */ + clip1 = ges_layer_add_asset (layer1, asset1, 0, 0, 10, GES_TRACK_TYPE_VIDEO); + fail_unless (clip1); + assert_num_children (clip1, 1); + assert_num_in_track (track1, 0); + assert_num_in_track (track2, 1); + + clip2 = ges_layer_add_asset (layer2, asset1, 0, 0, 10, GES_TRACK_TYPE_AUDIO); + fail_unless (clip2); + assert_num_children (clip2, 1); + assert_num_in_track (track1, 1); + assert_num_in_track (track2, 1); + + clips = g_list_append (clips, clip1); + clips = g_list_append (clips, clip2); + + _assert_regroup_fails (clips); + + g_list_free (clips); + clips = NULL; + + gst_object_ref (clip1); + gst_object_ref (clip2); + fail_unless (ges_layer_remove_clip (layer1, clip1)); + fail_unless (ges_layer_remove_clip (layer2, clip2)); + assert_num_children (clip1, 1); + assert_num_children (clip2, 1); + gst_object_unref (clip1); + gst_object_unref (clip2); + assert_num_in_track (track1, 0); + assert_num_in_track (track2, 0); + + /* fail if different asset */ + clip1 = ges_layer_add_asset (layer1, asset1, 0, 0, 10, GES_TRACK_TYPE_VIDEO); + fail_unless (clip1); + assert_num_children (clip1, 1); + + clip2 = ges_layer_add_asset (layer1, asset2, 0, 0, 10, GES_TRACK_TYPE_AUDIO); + fail_unless (clip2); + assert_num_children (clip2, 1); + assert_num_in_track (track1, 1); + assert_num_in_track (track2, 1); + + clips = g_list_append (clips, clip1); + clips = g_list_append (clips, clip2); + + _assert_regroup_fails (clips); + + g_list_free (clips); + clips = NULL; + + fail_unless (ges_layer_remove_clip (layer1, clip1)); + fail_unless (ges_layer_remove_clip (layer1, clip2)); + + /* fail if sharing track */ + clip1 = ges_layer_add_asset (layer1, asset3, 0, 0, 10, GES_TRACK_TYPE_VIDEO); + fail_unless (clip1); + assert_num_children (clip1, 1); + + clip2 = ges_layer_add_asset (layer1, asset3, 0, 0, 10, GES_TRACK_TYPE_VIDEO); + fail_unless (clip2); + assert_num_children (clip2, 1); + assert_num_in_track (track1, 0); + assert_num_in_track (track2, 2); + + clips = g_list_append (clips, clip1); + clips = g_list_append (clips, clip2); + + _assert_regroup_fails (clips); + + g_list_free (clips); + clips = NULL; + + fail_unless (ges_layer_remove_clip (layer1, clip1)); + fail_unless (ges_layer_remove_clip (layer1, clip2)); + + clip1 = ges_layer_add_asset (layer1, asset1, 0, 0, 10, GES_TRACK_TYPE_VIDEO); + fail_unless (clip1); + assert_num_children (clip1, 1); + assert_num_in_track (track1, 0); + assert_num_in_track (track2, 1); + + clip2 = ges_layer_add_asset (layer1, asset2, 0, 0, 10, GES_TRACK_TYPE_AUDIO); + fail_unless (clip2); + assert_num_children (clip2, 1); + assert_num_in_track (track1, 1); + assert_num_in_track (track2, 1); + + clips = g_list_append (clips, clip1); + clips = g_list_append (clips, clip2); + + _assert_regroup_fails (clips); + + g_list_free (clips); + clips = NULL; + + fail_unless (ges_layer_remove_clip (layer1, clip1)); + fail_unless (ges_layer_remove_clip (layer1, clip2)); + + /* can group if same asset but different tracks */ + clip1 = ges_layer_add_asset (layer1, asset2, 0, 0, 10, GES_TRACK_TYPE_VIDEO); + fail_unless (clip1); + _assert_add (clip1, ges_effect_new ("agingtv")); + assert_num_children (clip1, 2); + + clip2 = ges_layer_add_asset (layer1, asset2, 0, 0, 10, GES_TRACK_TYPE_AUDIO); + fail_unless (clip2); + assert_num_children (clip2, 1); + + fail_unless (ges_timeline_add_track (timeline, track3)); + assert_num_children (clip1, 2); + assert_num_children (clip2, 1); + assert_num_in_track (track1, 1); + assert_num_in_track (track2, 2); + assert_num_in_track (track3, 0); + + select_track = track3; + g_signal_connect (timeline, "select-tracks-for-object", + G_CALLBACK (_select_track), &select_track); + + clip3 = ges_layer_add_asset (layer1, asset2, 0, 0, 10, GES_TRACK_TYPE_VIDEO); + fail_unless (select_track == NULL); + assert_num_children (clip1, 2); + assert_num_children (clip2, 1); + assert_num_children (clip3, 1); + assert_num_in_track (track1, 1); + assert_num_in_track (track2, 2); + assert_num_in_track (track3, 1); + + clips = g_list_append (clips, clip1); + clips = g_list_append (clips, clip2); + clips = g_list_append (clips, clip3); + + container = ges_container_group (clips); + + fail_unless (GES_IS_CLIP (container)); + grouped = GES_CLIP (container); + assert_num_children (grouped, 4); + assert_num_in_track (track1, 1); + assert_num_in_track (track2, 2); + assert_num_in_track (track3, 1); + + fail_unless (ges_clip_get_supported_formats (grouped), + GES_TRACK_TYPE_VIDEO | GES_TRACK_TYPE_AUDIO); + fail_unless (ges_extractable_get_asset (GES_EXTRACTABLE (grouped)) + == asset2); + CHECK_OBJECT_PROPS (grouped, 0, 0, 10); + + g_list_free (clips); + + clips = ges_layer_get_clips (layer1); + fail_unless (g_list_length (clips), 1); + fail_unless (GES_CLIP (clips->data) == grouped); + g_list_free_full (clips, gst_object_unref); + + gst_object_unref (asset1); + gst_object_unref (asset2); + gst_object_unref (asset3); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_adding_children_to_track) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *track1, *track2; + GESClip *clip, *clip2; + GESAsset *asset; + GESTrackElement *source, *effect, *effect2, *added, *added2, *added3; + GstControlSource *ctrl_source; + guint selection_called = 0; + GError *error = NULL; + + ges_init (); + + timeline = ges_timeline_new (); + ges_timeline_set_auto_transition (timeline, TRUE); + track1 = GES_TRACK (ges_video_track_new ()); + track2 = GES_TRACK (ges_video_track_new ()); + + /* only add two for now */ + fail_unless (ges_timeline_add_track (timeline, track1)); + + layer = ges_timeline_append_layer (timeline); + + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + clip = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + fail_unless (clip); + assert_num_children (clip, 1); + assert_num_in_track (track1, 1); + assert_num_in_track (track2, 0); + source = GES_CONTAINER_CHILDREN (clip)->data; + fail_unless (ges_track_element_get_track (source) == track1); + + effect = GES_TRACK_ELEMENT (ges_effect_new ("agingtv")); + _assert_add (clip, effect); + effect2 = GES_TRACK_ELEMENT (ges_effect_new ("vertigotv")); + _assert_add (clip, effect2); + assert_num_children (clip, 3); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + fail_unless (ges_track_element_get_track (effect) == track1); + fail_unless (ges_track_element_get_track (effect2) == track1); + + ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (clip), + "font-desc", "Normal", "posx", 30, "posy", 50, "alpha", 0.1, + "freq", 449.0, "scratch-lines", 2, NULL); + + ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ()); + g_object_set (G_OBJECT (ctrl_source), "mode", + GST_INTERPOLATION_MODE_CUBIC, NULL); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 0, 20.0)); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 5, 45.0)); + fail_unless (ges_track_element_set_control_source (source, ctrl_source, + "posx", "direct-absolute")); + gst_object_unref (ctrl_source); + + ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ()); + g_object_set (G_OBJECT (ctrl_source), "mode", + GST_INTERPOLATION_MODE_LINEAR, NULL); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 2, 0.1)); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 5, 0.7)); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 8, 0.3)); + fail_unless (ges_track_element_set_control_source (source, ctrl_source, + "alpha", "direct")); + gst_object_unref (ctrl_source); + + ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ()); + g_object_set (G_OBJECT (ctrl_source), "mode", + GST_INTERPOLATION_MODE_NONE, NULL); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 0, 1.0)); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 4, 7.0)); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 8, 3.0)); + fail_unless (ges_track_element_set_control_source (effect, ctrl_source, + "scratch-lines", "direct-absolute")); + gst_object_unref (ctrl_source); + + /* can't add to a track that does not belong to the timeline */ + fail_if (ges_clip_add_child_to_track (clip, source, track2, &error)); + assert_num_children (clip, 3); + fail_unless (ges_track_element_get_track (source) == track1); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + /* programming/usage error gives no error code/message */ + fail_if (error); + + /* can't add the clip to a track that already contains our source */ + fail_if (ges_clip_add_child_to_track (clip, source, track1, &error)); + assert_num_children (clip, 3); + fail_unless (ges_track_element_get_track (source) == track1); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + fail_if (error); + + /* can't remove a core element from its track whilst a non-core sits + * above it */ + fail_if (ges_track_remove_element (track1, source)); + assert_num_children (clip, 3); + fail_unless (ges_track_element_get_track (source) == track1); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + + /* can not add to the same track as it is currently in */ + fail_if (ges_clip_add_child_to_track (clip, effect, track1, &error)); + fail_unless (ges_track_element_get_track (effect) == track1); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + fail_if (error); + + /* adding another video track, select-tracks-for-object will do nothing + * since no each track element is already part of a track */ + fail_unless (ges_timeline_add_track (timeline, track2)); + assert_num_children (clip, 3); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + + /* can not add effect to a track that does not contain a core child */ + fail_if (ges_clip_add_child_to_track (clip, effect, track2, &error)); + assert_num_children (clip, 3); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + fail_if (error); + + /* can add core */ + + added = ges_clip_add_child_to_track (clip, source, track2, &error); + fail_unless (added); + assert_num_children (clip, 4); + fail_unless (added != source); + fail_unless (ges_track_element_get_track (source) == track1); + fail_unless (ges_track_element_get_track (added) == track2); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 1); + fail_if (error); + + assert_equal_children_properties (added, source); + assert_equal_bindings (added, source); + + /* can now add non-core */ + assert_equals_int (0, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect))); + assert_equals_int (1, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2))); + + added2 = ges_clip_add_child_to_track (clip, effect, track2, &error); + fail_unless (added2); + fail_if (error); + assert_num_children (clip, 5); + fail_unless (added2 != effect); + fail_unless (ges_track_element_get_track (effect) == track1); + fail_unless (ges_track_element_get_track (added2) == track2); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 2); + + assert_equal_children_properties (added2, effect); + assert_equal_bindings (added2, effect); + + assert_equals_int (0, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect))); + assert_equals_int (1, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (added2))); + assert_equals_int (2, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2))); + + added3 = ges_clip_add_child_to_track (clip, effect2, track2, &error); + fail_unless (added3); + fail_if (error); + assert_num_children (clip, 6); + fail_unless (added3 != effect2); + fail_unless (ges_track_element_get_track (effect2) == track1); + fail_unless (ges_track_element_get_track (added3) == track2); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 3); + + assert_equal_children_properties (added3, effect2); + assert_equal_bindings (added3, effect2); + + /* priorities within new track match that in previous track! */ + assert_equals_int (0, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect))); + assert_equals_int (1, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (added2))); + assert_equals_int (2, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2))); + assert_equals_int (3, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (added3))); + + /* removing core from the container, empties the non-core from their + * tracks */ + gst_object_ref (added); + _assert_remove (clip, added); + assert_num_children (clip, 5); + fail_unless (ges_track_element_get_track (source) == track1); + fail_if (ges_track_element_get_track (added)); + fail_if (ges_track_element_get_track (added2)); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (added) == NULL); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (added2) == + GES_TIMELINE_ELEMENT (clip)); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + gst_object_unref (added); + + _assert_remove (clip, added2); + _assert_remove (clip, added3); + assert_num_children (clip, 3); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + + /* remove from layer empties all children from the tracks */ + gst_object_ref (clip); + + fail_unless (ges_layer_remove_clip (layer, clip)); + assert_num_children (clip, 3); + fail_if (ges_track_element_get_track (source)); + fail_if (ges_track_element_get_track (effect)); + assert_num_in_track (track1, 0); + assert_num_in_track (track2, 0); + + /* add different sources to the layer */ + fail_unless (ges_layer_add_asset (layer, asset, 0, 0, 10, + GES_TRACK_TYPE_UNKNOWN)); + fail_unless (ges_layer_add_asset (layer, asset, 20, 0, 10, + GES_TRACK_TYPE_UNKNOWN)); + fail_unless (clip2 = ges_layer_add_asset (layer, asset, 25, 0, 10, + GES_TRACK_TYPE_UNKNOWN)); + assert_num_children (clip2, 2); + /* 3 sources + 1 transition */ + assert_num_in_track (track1, 4); + assert_num_in_track (track2, 4); + + /* removing the track from the timeline empties it of track elements */ + gst_object_ref (track2); + fail_unless (ges_timeline_remove_track (timeline, track2)); + /* but children remain in the clips */ + assert_num_children (clip2, 2); + assert_num_in_track (track1, 4); + assert_num_in_track (track2, 0); + gst_object_unref (track2); + + /* add clip back in, but don't select any tracks */ + g_signal_connect (timeline, "select-tracks-for-object", + G_CALLBACK (_select_none), &selection_called); + + /* can add the clip to the layer, despite a source existing between + * 0 and 10 because the clip will not fill any track */ + /* NOTE: normally this would be useless because it would not trigger + * the creation of any core children. But clip currently still has + * its core children */ + fail_unless (ges_layer_add_clip (layer, clip)); + gst_object_unref (clip); + + /* one call for each child */ + assert_equals_int (selection_called, 3); + + fail_if (ges_track_element_get_track (source)); + fail_if (ges_track_element_get_track (effect)); + assert_num_children (clip, 3); + assert_num_in_track (track1, 4); + + /* can not add the source to the track because it would overlap another + * source */ + fail_if (ges_clip_add_child_to_track (clip, source, track1, &error)); + assert_num_children (clip, 3); + assert_num_in_track (track1, 4); + assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + + /* can not add source at time 23 because it would result in three + * overlapping sources in the track */ + assert_set_start (clip, 23); + fail_if (ges_clip_add_child_to_track (clip, source, track1, &error)); + assert_num_children (clip, 3); + assert_num_in_track (track1, 4); + assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + + /* can add at 5, with overlap */ + assert_set_start (clip, 5); + added = ges_clip_add_child_to_track (clip, source, track1, &error); + /* added is the source since it was not already in a track */ + fail_unless (added == source); + fail_if (error); + assert_num_children (clip, 3); + /* 4 sources + 2 transitions */ + assert_num_in_track (track1, 6); + + /* also add effect */ + added = ges_clip_add_child_to_track (clip, effect, track1, &error); + /* added is the source since it was not already in a track */ + fail_unless (added == effect); + fail_if (error); + assert_num_children (clip, 3); + assert_num_in_track (track1, 7); + + added = ges_clip_add_child_to_track (clip, effect2, track1, &error); + /* added is the source since it was not already in a track */ + fail_unless (added == effect2); + fail_if (error); + assert_num_children (clip, 3); + assert_num_in_track (track1, 8); + + assert_equals_int (0, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect))); + assert_equals_int (1, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2))); + + gst_object_unref (timeline); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + +static void +child_removed_cb (GESClip * clip, GESTimelineElement * effect, + gboolean * called) +{ + ASSERT_OBJECT_REFCOUNT (effect, "1 test ref + 1 keeping alive ref + " + "emission ref", 3); + *called = TRUE; +} + +GST_START_TEST (test_clip_refcount_remove_child) +{ + GESClip *clip; + GESTrack *track; + gboolean called; + GESTrackElement *effect, *source; + GESTimeline *timeline; + GESLayer *layer; + + ges_init (); + + timeline = ges_timeline_new (); + track = GES_TRACK (ges_audio_track_new ()); + fail_unless (ges_timeline_add_track (timeline, track)); + + layer = ges_timeline_append_layer (timeline); + clip = GES_CLIP (ges_test_clip_new ()); + fail_unless (ges_layer_add_clip (layer, clip)); + + assert_num_children (clip, 1); + assert_num_in_track (track, 1); + + source = GES_CONTAINER_CHILDREN (clip)->data; + ASSERT_OBJECT_REFCOUNT (source, "1 for the container + 1 for the track" + " + 1 timeline", 3); + + effect = GES_TRACK_ELEMENT (ges_effect_new ("identity")); + fail_unless (ges_track_add_element (track, effect)); + assert_num_in_track (track, 2); + ASSERT_OBJECT_REFCOUNT (effect, "1 for the track + 1 timeline", 2); + + _assert_add (clip, effect); + assert_num_children (clip, 2); + ASSERT_OBJECT_REFCOUNT (effect, "1 for the container + 1 for the track" + " + 1 timeline", 3); + + fail_unless (ges_track_remove_element (track, effect)); + ASSERT_OBJECT_REFCOUNT (effect, "1 for the container", 1); + + g_signal_connect (clip, "child-removed", G_CALLBACK (child_removed_cb), + &called); + gst_object_ref (effect); + _assert_remove (clip, effect); + fail_unless (called == TRUE); + ASSERT_OBJECT_REFCOUNT (effect, "1 test ref", 1); + gst_object_unref (effect); + + check_destroyed (G_OBJECT (timeline), G_OBJECT (track), + G_OBJECT (layer), G_OBJECT (clip), G_OBJECT (source), NULL); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_clip_find_track_element) +{ + GESClip *clip; + GList *foundelements; + GESTimeline *timeline; + GESLayer *layer; + GESTrack *track, *track1, *track2; + guint selection_called = 0; + + GESTrackElement *effect, *effect1, *effect2, *foundelem, *video_source; + + ges_init (); + + track = GES_TRACK (ges_audio_track_new ()); + track1 = GES_TRACK (ges_audio_track_new ()); + track2 = GES_TRACK (ges_video_track_new ()); + + timeline = ges_timeline_new (); + fail_unless (ges_timeline_add_track (timeline, track)); + fail_unless (ges_timeline_add_track (timeline, track1)); + fail_unless (ges_timeline_add_track (timeline, track2)); + + layer = ges_timeline_append_layer (timeline); + clip = GES_CLIP (ges_test_clip_new ()); + + /* should have a source in every track */ + fail_unless (ges_layer_add_clip (layer, clip)); + assert_num_children (clip, 3); + assert_num_in_track (track, 1); + assert_num_in_track (track1, 1); + assert_num_in_track (track2, 1); + + g_signal_connect (timeline, "select-tracks-for-object", + G_CALLBACK (_select_none), &selection_called); + + effect = GES_TRACK_ELEMENT (ges_effect_new ("audio identity")); + fail_unless (ges_track_add_element (track, effect)); + _assert_add (clip, effect); + + effect1 = GES_TRACK_ELEMENT (ges_effect_new ("audio identity")); + fail_unless (ges_track_add_element (track1, effect1)); + _assert_add (clip, effect1); + + effect2 = GES_TRACK_ELEMENT (ges_effect_new ("identity")); + fail_unless (ges_track_add_element (track2, effect2)); + _assert_add (clip, effect2); + + fail_if (selection_called); + assert_num_children (clip, 6); + assert_num_in_track (track, 2); + assert_num_in_track (track1, 2); + assert_num_in_track (track2, 2); + + foundelem = ges_clip_find_track_element (clip, track, GES_TYPE_EFFECT); + fail_unless (foundelem == effect); + gst_object_unref (foundelem); + + foundelem = ges_clip_find_track_element (clip, track1, GES_TYPE_EFFECT); + fail_unless (foundelem == effect1); + gst_object_unref (foundelem); + + foundelem = ges_clip_find_track_element (clip, track2, GES_TYPE_EFFECT); + fail_unless (foundelem == effect2); + gst_object_unref (foundelem); + + foundelem = ges_clip_find_track_element (clip, NULL, GES_TYPE_TRANSITION); + fail_unless (foundelem == NULL); + + foundelem = ges_clip_find_track_element (clip, track, GES_TYPE_TRANSITION); + fail_unless (foundelem == NULL); + + foundelem = ges_clip_find_track_element (clip, track1, GES_TYPE_TRANSITION); + fail_unless (foundelem == NULL); + + foundelem = ges_clip_find_track_element (clip, track2, GES_TYPE_TRANSITION); + fail_unless (foundelem == NULL); + + foundelem = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE); + fail_unless (GES_IS_AUDIO_TEST_SOURCE (foundelem)); + gst_object_unref (foundelem); + + foundelem = ges_clip_find_track_element (clip, track1, GES_TYPE_SOURCE); + fail_unless (GES_IS_AUDIO_TEST_SOURCE (foundelem)); + gst_object_unref (foundelem); + + foundelem = ges_clip_find_track_element (clip, track2, GES_TYPE_SOURCE); + fail_unless (GES_IS_VIDEO_TEST_SOURCE (foundelem)); + gst_object_unref (foundelem); + + video_source = ges_clip_find_track_element (clip, NULL, + GES_TYPE_VIDEO_TEST_SOURCE); + fail_unless (foundelem == video_source); + gst_object_unref (video_source); + + + foundelements = ges_clip_find_track_elements (clip, NULL, + GES_TRACK_TYPE_AUDIO, G_TYPE_NONE); + fail_unless_equals_int (g_list_length (foundelements), 4); + g_list_free_full (foundelements, gst_object_unref); + + foundelements = ges_clip_find_track_elements (clip, NULL, + GES_TRACK_TYPE_VIDEO, G_TYPE_NONE); + fail_unless_equals_int (g_list_length (foundelements), 2); + g_list_free_full (foundelements, gst_object_unref); + + foundelements = ges_clip_find_track_elements (clip, NULL, + GES_TRACK_TYPE_UNKNOWN, GES_TYPE_SOURCE); + fail_unless_equals_int (g_list_length (foundelements), 3); + fail_unless (g_list_find (foundelements, video_source)); + g_list_free_full (foundelements, gst_object_unref); + + foundelements = ges_clip_find_track_elements (clip, NULL, + GES_TRACK_TYPE_UNKNOWN, GES_TYPE_EFFECT); + fail_unless_equals_int (g_list_length (foundelements), 3); + fail_unless (g_list_find (foundelements, effect)); + fail_unless (g_list_find (foundelements, effect1)); + fail_unless (g_list_find (foundelements, effect2)); + g_list_free_full (foundelements, gst_object_unref); + + foundelements = ges_clip_find_track_elements (clip, NULL, + GES_TRACK_TYPE_VIDEO, GES_TYPE_SOURCE); + fail_unless_equals_int (g_list_length (foundelements), 1); + fail_unless (foundelements->data == video_source); + g_list_free_full (foundelements, gst_object_unref); + + foundelements = ges_clip_find_track_elements (clip, track2, + GES_TRACK_TYPE_UNKNOWN, GES_TYPE_SOURCE); + fail_unless_equals_int (g_list_length (foundelements), 1); + fail_unless (foundelements->data == video_source); + g_list_free_full (foundelements, gst_object_unref); + + foundelements = ges_clip_find_track_elements (clip, track2, + GES_TRACK_TYPE_UNKNOWN, G_TYPE_NONE); + fail_unless_equals_int (g_list_length (foundelements), 2); + fail_unless (g_list_find (foundelements, effect2)); + fail_unless (g_list_find (foundelements, video_source)); + g_list_free_full (foundelements, gst_object_unref); + + foundelements = ges_clip_find_track_elements (clip, track1, + GES_TRACK_TYPE_UNKNOWN, GES_TYPE_EFFECT); + fail_unless_equals_int (g_list_length (foundelements), 1); + fail_unless (foundelements->data == effect1); + g_list_free_full (foundelements, gst_object_unref); + + /* NOTE: search in *either* track or track type + * TODO 2.0: this should be an AND condition, rather than OR */ + foundelements = ges_clip_find_track_elements (clip, track, + GES_TRACK_TYPE_VIDEO, G_TYPE_NONE); + fail_unless_equals_int (g_list_length (foundelements), 4); + fail_unless (g_list_find (foundelements, effect)); + fail_unless (g_list_find (foundelements, effect2)); + fail_unless (g_list_find (foundelements, video_source)); + g_list_free_full (foundelements, gst_object_unref); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_effects_priorities) +{ + GESClip *clip; + GESTimeline *timeline; + GESTrack *audio_track, *video_track; + GESLayer *layer, *layer1; + + GESTrackElement *effect, *effect1, *effect2; + + ges_init (); + + clip = GES_CLIP (ges_test_clip_new ()); + audio_track = GES_TRACK (ges_audio_track_new ()); + video_track = GES_TRACK (ges_video_track_new ()); + + timeline = ges_timeline_new (); + fail_unless (ges_timeline_add_track (timeline, audio_track)); + fail_unless (ges_timeline_add_track (timeline, video_track)); + + layer = ges_timeline_append_layer (timeline); + layer1 = ges_timeline_append_layer (timeline); + + ges_layer_add_clip (layer, clip); + + effect = GES_TRACK_ELEMENT (ges_effect_new ("agingtv")); + _assert_add (clip, effect); + + effect1 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv")); + _assert_add (clip, effect1); + + effect2 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv")); + _assert_add (clip, effect2); + + fail_unless_equals_int (MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0, + _PRIORITY (effect)); + fail_unless_equals_int (MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 1, + _PRIORITY (effect1)); + fail_unless_equals_int (MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 2, + _PRIORITY (effect2)); + + fail_unless (ges_clip_set_top_effect_index (clip, GES_BASE_EFFECT (effect), + 2)); + fail_unless_equals_int (MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0, + _PRIORITY (effect1)); + fail_unless_equals_int (MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 1, + _PRIORITY (effect2)); + fail_unless_equals_int (MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 2, + _PRIORITY (effect)); + + fail_unless (ges_clip_set_top_effect_index (clip, GES_BASE_EFFECT (effect), + 0)); + fail_unless_equals_int (MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0, + _PRIORITY (effect)); + fail_unless_equals_int (MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 1, + _PRIORITY (effect1)); + fail_unless_equals_int (MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 2, + _PRIORITY (effect2)); + + fail_unless (ges_clip_move_to_layer (clip, layer1)); + fail_unless_equals_int (LAYER_HEIGHT + MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0, + _PRIORITY (effect)); + fail_unless_equals_int (LAYER_HEIGHT + MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 1, + _PRIORITY (effect1)); + fail_unless_equals_int (LAYER_HEIGHT + MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 2, + _PRIORITY (effect2)); + + fail_unless (ges_clip_set_top_effect_index (clip, GES_BASE_EFFECT (effect), + 2)); + fail_unless_equals_int (LAYER_HEIGHT + MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0, + _PRIORITY (effect1)); + fail_unless_equals_int (LAYER_HEIGHT + MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 1, + _PRIORITY (effect2)); + fail_unless_equals_int (LAYER_HEIGHT + MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 2, + _PRIORITY (effect)); + + fail_unless (ges_clip_set_top_effect_index (clip, GES_BASE_EFFECT (effect), + 0)); + fail_unless_equals_int (LAYER_HEIGHT + MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0, + _PRIORITY (effect)); + fail_unless_equals_int (LAYER_HEIGHT + MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 1, + _PRIORITY (effect1)); + fail_unless_equals_int (LAYER_HEIGHT + MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 2, + _PRIORITY (effect2)); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +static void +_count_cb (GObject * obj, GParamSpec * pspec, gint * count) +{ + *count = *count + 1; +} + +#define _assert_children_time_setter(clip, child, prop, setter, val1, val2) \ +{ \ + gint clip_count = 0; \ + gint child_count = 0; \ + gchar *notify_name = g_strconcat ("notify::", prop, NULL); \ + gchar *clip_name = GES_TIMELINE_ELEMENT_NAME (clip); \ + gchar *child_name = NULL; \ + g_signal_connect (clip, notify_name, G_CALLBACK (_count_cb), \ + &clip_count); \ + if (child) { \ + child_name = GES_TIMELINE_ELEMENT_NAME (child); \ + g_signal_connect (child, notify_name, G_CALLBACK (_count_cb), \ + &child_count); \ + } \ + \ + fail_unless (setter (GES_TIMELINE_ELEMENT (clip), val1), \ + "Failed to set the %s property for clip %s", prop, clip_name); \ + assert_clip_children_time_val (clip, prop, val1); \ + \ + fail_unless (clip_count == 1, "The callback for the %s property was " \ + "called %i times for clip %s, rather than once", \ + prop, clip_count, clip_name); \ + if (child) { \ + fail_unless (child_count == 1, "The callback for the %s property " \ + "was called %i times for the child %s of clip %s, rather than " \ + "once", prop, child_count, child_name, clip_name); \ + } \ + \ + clip_count = 0; \ + if (child) { \ + child_count = 0; \ + fail_unless (setter (GES_TIMELINE_ELEMENT (child), val2), \ + "Failed to set the %s property for the child %s of clip %s", \ + prop, child_name, clip_name); \ + fail_unless (child_count == 1, "The callback for the %s property " \ + "was called %i more times for the child %s of clip %s, rather " \ + "than once more", prop, child_count, child_name, clip_name); \ + } else { \ + fail_unless (setter (GES_TIMELINE_ELEMENT (clip), val2), \ + "Failed to set the %s property for clip %s", prop, clip_name); \ + } \ + assert_clip_children_time_val (clip, prop, val2); \ + \ + fail_unless (clip_count == 1, "The callback for the %s property " \ + "was called %i more times for clip %s, rather than once more", \ + prop, clip_count, clip_name); \ + assert_equals_int (g_signal_handlers_disconnect_by_func (clip, \ + G_CALLBACK (_count_cb), &clip_count), 1); \ + if (child) { \ + assert_equals_int (g_signal_handlers_disconnect_by_func (child, \ + G_CALLBACK (_count_cb), &child_count), 1); \ + } \ + \ + g_free (notify_name); \ +} + +static void +_test_children_time_setting_on_clip (GESClip * clip, GESTrackElement * child) +{ + _assert_children_time_setter (clip, child, "in-point", + ges_timeline_element_set_inpoint, 11, 101); + _assert_children_time_setter (clip, child, "in-point", + ges_timeline_element_set_inpoint, 51, 1); + _assert_children_time_setter (clip, child, "start", + ges_timeline_element_set_start, 12, 102); + _assert_children_time_setter (clip, child, "start", + ges_timeline_element_set_start, 52, 2); + _assert_children_time_setter (clip, child, "duration", + ges_timeline_element_set_duration, 13, 103); + _assert_children_time_setter (clip, child, "duration", + ges_timeline_element_set_duration, 53, 3); +} + +GST_START_TEST (test_children_time_setters) +{ + GESTimeline *timeline; + GESLayer *layer; + GESClip *clips[] = { NULL, NULL }; + gint i; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + fail_unless (timeline); + + layer = ges_timeline_append_layer (timeline); + + clips[0] = + GES_CLIP (ges_transition_clip_new + (GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE)); + clips[1] = GES_CLIP (ges_test_clip_new ()); + + for (i = 0; i < G_N_ELEMENTS (clips); i++) { + GESClip *clip = clips[i]; + GESContainer *group = GES_CONTAINER (ges_group_new ()); + GList *children; + GESTrackElement *child; + /* no children */ + _test_children_time_setting_on_clip (clip, NULL); + /* child in timeline */ + fail_unless (ges_layer_add_clip (layer, clip)); + children = GES_CONTAINER_CHILDREN (clip); + fail_unless (children); + child = GES_TRACK_ELEMENT (children->data); + /* make sure the child can have its in-point set */ + ges_track_element_set_has_internal_source (child, TRUE); + _test_children_time_setting_on_clip (clip, child); + /* clip in a group */ + _assert_add (group, clip); + _test_children_time_setting_on_clip (clip, child); + /* group is removed from the timeline and destroyed when empty */ + _assert_remove (group, clip); + /* child not in timeline */ + gst_object_ref (clip); + fail_unless (ges_layer_remove_clip (layer, clip)); + children = GES_CONTAINER_CHILDREN (clip); + fail_unless (children); + child = GES_TRACK_ELEMENT (children->data); + ges_track_element_set_has_internal_source (child, TRUE); + _test_children_time_setting_on_clip (clip, child); + gst_object_unref (clip); + } + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_not_enough_internal_content_for_core) +{ + GESTimeline *timeline; + GESLayer *layer; + GESAsset *asset; + GError *error = NULL; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_timeline_append_layer (timeline); + + asset = ges_asset_request (GES_TYPE_TEST_CLIP, "max-duration=30", NULL); + fail_unless (asset); + + fail_if (ges_layer_add_asset_full (layer, asset, 0, 31, 10, + GES_TRACK_TYPE_UNKNOWN, &error)); + assert_GESError (error, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT); + + gst_object_unref (timeline); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_can_add_effect) +{ + struct CanAddEffectData + { + GESClip *clip; + gboolean can_add_effect; + } clips[6]; + guint i; + gchar *uri; + + ges_init (); + + uri = ges_test_get_audio_video_uri (); + + clips[0] = (struct CanAddEffectData) { + GES_CLIP (ges_test_clip_new ()), TRUE}; + clips[1] = (struct CanAddEffectData) { + GES_CLIP (ges_uri_clip_new (uri)), TRUE}; + clips[2] = (struct CanAddEffectData) { + GES_CLIP (ges_title_clip_new ()), TRUE}; + clips[3] = (struct CanAddEffectData) { + GES_CLIP (ges_effect_clip_new ("agingtv", "audioecho")), TRUE}; + clips[4] = (struct CanAddEffectData) { + GES_CLIP (ges_transition_clip_new + (GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE)), FALSE}; + clips[5] = (struct CanAddEffectData) { + GES_CLIP (ges_text_overlay_clip_new ()), FALSE}; + + g_free (uri); + + for (i = 0; i < G_N_ELEMENTS (clips); i++) { + GESClip *clip = clips[i].clip; + GESTimelineElement *effect = + GES_TIMELINE_ELEMENT (ges_effect_new ("agingtv")); + gst_object_ref_sink (effect); + gst_object_ref_sink (clip); + fail_unless (clip); + if (clips[i].can_add_effect) + fail_unless (ges_container_add (GES_CONTAINER (clip), effect), + "Could not add an effect to clip %s", + GES_TIMELINE_ELEMENT_NAME (clip)); + else + fail_if (ges_container_add (GES_CONTAINER (clip), effect), + "Could add an effect to clip %s, but we expect this to fail", + GES_TIMELINE_ELEMENT_NAME (clip)); + gst_object_unref (effect); + gst_object_unref (clip); + } + + ges_deinit (); +} + +GST_END_TEST; + +#define _assert_active(el, active) \ + fail_unless (ges_track_element_is_active (el) == active) + +#define _assert_set_active(el, active) \ + fail_unless (ges_track_element_set_active (el, active)) + +GST_START_TEST (test_children_active) +{ + GESTimeline *timeline; + GESLayer *layer; + GESClip *clip; + GESTrack *track0, *track1, *select_track; + GESTrackElement *effect0, *effect1, *effect2, *effect3; + GESTrackElement *source0, *source1; + + ges_init (); + + timeline = ges_timeline_new (); + + track0 = GES_TRACK (ges_video_track_new ()); + track1 = GES_TRACK (ges_video_track_new ()); + + fail_unless (ges_timeline_add_track (timeline, track0)); + fail_unless (ges_timeline_add_track (timeline, track1)); + + layer = ges_timeline_append_layer (timeline); + + clip = GES_CLIP (ges_test_clip_new ()); + + fail_unless (ges_layer_add_clip (layer, clip)); + + assert_num_children (clip, 2); + + source0 = + ges_clip_find_track_element (clip, track0, GES_TYPE_VIDEO_TEST_SOURCE); + source1 = + ges_clip_find_track_element (clip, track1, GES_TYPE_VIDEO_TEST_SOURCE); + + fail_unless (source0); + fail_unless (source1); + + gst_object_unref (source0); + gst_object_unref (source1); + + _assert_active (source0, TRUE); + _assert_active (source1, TRUE); + + _assert_set_active (source0, FALSE); + + _assert_active (source0, FALSE); + _assert_active (source1, TRUE); + + select_track = track0; + g_signal_connect (timeline, "select-tracks-for-object", + G_CALLBACK (_select_track), &select_track); + + /* add an active effect should become inactive to match the core */ + effect0 = GES_TRACK_ELEMENT (ges_effect_new ("videobalance")); + _assert_active (effect0, TRUE); + + _assert_add (clip, effect0); + fail_if (select_track); + + _assert_active (source0, FALSE); + _assert_active (effect0, FALSE); + _assert_active (source1, TRUE); + + /* adding inactive to track with inactive core does nothing */ + effect1 = GES_TRACK_ELEMENT (ges_effect_new ("vertigotv")); + _assert_active (effect1, TRUE); + _assert_set_active (effect1, FALSE); + _assert_active (effect1, FALSE); + + select_track = track0; + _assert_add (clip, effect1); + fail_if (select_track); + + _assert_active (source0, FALSE); + _assert_active (effect0, FALSE); + _assert_active (effect1, FALSE); + _assert_active (source1, TRUE); + + /* adding active to track with active core does nothing */ + effect2 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv")); + _assert_active (effect2, TRUE); + + select_track = track1; + _assert_add (clip, effect2); + fail_if (select_track); + + _assert_active (source0, FALSE); + _assert_active (effect0, FALSE); + _assert_active (effect1, FALSE); + _assert_active (source1, TRUE); + _assert_active (effect2, TRUE); + + /* adding inactive to track with active core does nothing */ + effect3 = GES_TRACK_ELEMENT (ges_effect_new ("alpha")); + _assert_active (effect3, TRUE); + _assert_set_active (effect3, FALSE); + _assert_active (effect3, FALSE); + + select_track = track1; + _assert_add (clip, effect3); + fail_if (select_track); + + _assert_active (source0, FALSE); + _assert_active (effect0, FALSE); + _assert_active (effect1, FALSE); + _assert_active (source1, TRUE); + _assert_active (effect2, TRUE); + _assert_active (effect3, FALSE); + + /* activate a core does not change non-core */ + _assert_set_active (source0, TRUE); + + _assert_active (source0, TRUE); + _assert_active (effect0, FALSE); + _assert_active (effect1, FALSE); + _assert_active (source1, TRUE); + _assert_active (effect2, TRUE); + _assert_active (effect3, FALSE); + + /* but de-activating a core will de-activate the non-core */ + _assert_set_active (source1, FALSE); + + _assert_active (source0, TRUE); + _assert_active (effect0, FALSE); + _assert_active (effect1, FALSE); + _assert_active (source1, FALSE); + _assert_active (effect2, FALSE); + _assert_active (effect3, FALSE); + + /* activate a non-core will activate the core */ + _assert_set_active (effect3, TRUE); + + _assert_active (source0, TRUE); + _assert_active (effect0, FALSE); + _assert_active (effect1, FALSE); + _assert_active (source1, TRUE); + _assert_active (effect2, FALSE); + _assert_active (effect3, TRUE); + + /* if core is already active, nothing else happens */ + _assert_set_active (effect0, TRUE); + + _assert_active (source0, TRUE); + _assert_active (effect0, TRUE); + _assert_active (effect1, FALSE); + _assert_active (source1, TRUE); + _assert_active (effect2, FALSE); + _assert_active (effect3, TRUE); + + _assert_set_active (effect1, TRUE); + + _assert_active (source0, TRUE); + _assert_active (effect0, TRUE); + _assert_active (effect1, TRUE); + _assert_active (source1, TRUE); + _assert_active (effect2, FALSE); + _assert_active (effect3, TRUE); + + _assert_set_active (effect2, TRUE); + + _assert_active (source0, TRUE); + _assert_active (effect0, TRUE); + _assert_active (effect1, TRUE); + _assert_active (source1, TRUE); + _assert_active (effect2, TRUE); + _assert_active (effect3, TRUE); + + /* de-activate a core will de-active all the non-core */ + _assert_set_active (source0, FALSE); + + _assert_active (source0, FALSE); + _assert_active (effect0, FALSE); + _assert_active (effect1, FALSE); + _assert_active (source1, TRUE); + _assert_active (effect2, TRUE); + _assert_active (effect3, TRUE); + + /* de-activate a non-core does nothing else */ + _assert_set_active (effect3, FALSE); + + _assert_active (source0, FALSE); + _assert_active (effect0, FALSE); + _assert_active (effect1, FALSE); + _assert_active (source1, TRUE); + _assert_active (effect2, TRUE); + _assert_active (effect3, FALSE); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_children_inpoint) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTimelineElement *clip, *child0, *child1, *effect; + GList *children; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + fail_unless (timeline); + + layer = ges_timeline_append_layer (timeline); + + clip = GES_TIMELINE_ELEMENT (ges_test_clip_new ()); + + assert_set_start (clip, 5); + assert_set_duration (clip, 20); + assert_set_inpoint (clip, 30); + + CHECK_OBJECT_PROPS (clip, 5, 30, 20); + + fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip))); + + /* clip now has children */ + children = GES_CONTAINER_CHILDREN (clip); + fail_unless (children); + child0 = children->data; + fail_unless (children->next); + child1 = children->next->data; + fail_unless (children->next->next == NULL); + + fail_unless (ges_track_element_has_internal_source (GES_TRACK_ELEMENT + (child0))); + fail_unless (ges_track_element_has_internal_source (GES_TRACK_ELEMENT + (child1))); + + CHECK_OBJECT_PROPS (clip, 5, 30, 20); + CHECK_OBJECT_PROPS (child0, 5, 30, 20); + CHECK_OBJECT_PROPS (child1, 5, 30, 20); + + /* add a non-core element */ + effect = GES_TIMELINE_ELEMENT (ges_effect_new ("agingtv")); + fail_if (ges_track_element_has_internal_source (GES_TRACK_ELEMENT (effect))); + /* allow us to choose our own in-point */ + ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (effect), TRUE); + assert_set_start (effect, 104); + assert_set_duration (effect, 53); + assert_set_inpoint (effect, 67); + + /* adding the effect will change its start and duration, but not its + * in-point */ + _assert_add (clip, effect); + + CHECK_OBJECT_PROPS (clip, 5, 30, 20); + CHECK_OBJECT_PROPS (child0, 5, 30, 20); + CHECK_OBJECT_PROPS (child1, 5, 30, 20); + CHECK_OBJECT_PROPS (effect, 5, 67, 20); + + /* register child0 as having no internal source, which means its + * in-point will be set to 0 */ + ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (child0), FALSE); + + CHECK_OBJECT_PROPS (clip, 5, 30, 20); + CHECK_OBJECT_PROPS (child0, 5, 0, 20); + CHECK_OBJECT_PROPS (child1, 5, 30, 20); + CHECK_OBJECT_PROPS (effect, 5, 67, 20); + + /* should not be able to set the in-point to non-zero */ + assert_fail_set_inpoint (child0, 40); + + CHECK_OBJECT_PROPS (clip, 5, 30, 20); + CHECK_OBJECT_PROPS (child0, 5, 0, 20); + CHECK_OBJECT_PROPS (child1, 5, 30, 20); + CHECK_OBJECT_PROPS (effect, 5, 67, 20); + + /* when we set the in-point on a core-child with an internal source we + * also set the clip and siblings with the same features */ + assert_set_inpoint (child1, 50); + + CHECK_OBJECT_PROPS (clip, 5, 50, 20); + /* child with no internal source not changed */ + CHECK_OBJECT_PROPS (child0, 5, 0, 20); + CHECK_OBJECT_PROPS (child1, 5, 50, 20); + /* non-core no changed */ + CHECK_OBJECT_PROPS (effect, 5, 67, 20); + + /* setting back to having internal source will put in sync with the + * in-point of the clip */ + ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (child0), TRUE); + + CHECK_OBJECT_PROPS (clip, 5, 50, 20); + CHECK_OBJECT_PROPS (child0, 5, 50, 20); + CHECK_OBJECT_PROPS (child1, 5, 50, 20); + CHECK_OBJECT_PROPS (effect, 5, 67, 20); + + assert_set_inpoint (child0, 40); + + CHECK_OBJECT_PROPS (clip, 5, 40, 20); + CHECK_OBJECT_PROPS (child0, 5, 40, 20); + CHECK_OBJECT_PROPS (child1, 5, 40, 20); + CHECK_OBJECT_PROPS (effect, 5, 67, 20); + + /* setting in-point on effect shouldn't change any other siblings */ + assert_set_inpoint (effect, 77); + + CHECK_OBJECT_PROPS (clip, 5, 40, 20); + CHECK_OBJECT_PROPS (child0, 5, 40, 20); + CHECK_OBJECT_PROPS (child1, 5, 40, 20); + CHECK_OBJECT_PROPS (effect, 5, 77, 20); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_children_max_duration) +{ + GESTimeline *timeline; + GESLayer *layer; + gchar *uri; + GESTimelineElement *child0, *child1, *effect; + guint i; + GstClockTime max_duration, new_max; + GList *children; + struct + { + GESTimelineElement *clip; + GstClockTime max_duration; + } clips[] = { + { + NULL, GST_SECOND}, { + NULL, GST_CLOCK_TIME_NONE} + }; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + fail_unless (timeline); + + layer = ges_timeline_append_layer (timeline); + + uri = ges_test_get_audio_video_uri (); + clips[0].clip = GES_TIMELINE_ELEMENT (ges_uri_clip_new (uri)); + fail_unless (clips[0].clip); + g_free (uri); + + clips[1].clip = GES_TIMELINE_ELEMENT (ges_test_clip_new ()); + + for (i = 0; i < G_N_ELEMENTS (clips); i++) { + GESTimelineElement *clip = clips[i].clip; + + max_duration = clips[i].max_duration; + fail_unless_equals_uint64 (_MAX_DURATION (clip), max_duration); + assert_set_start (clip, 5); + assert_set_duration (clip, 20); + assert_set_inpoint (clip, 30); + + /* can set the max duration the clip to anything whilst it has + * no core child */ + assert_set_max_duration (clip, 150); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 150); + + /* add a non-core element */ + effect = GES_TIMELINE_ELEMENT (ges_effect_new ("agingtv")); + fail_if (ges_track_element_has_internal_source (GES_TRACK_ELEMENT + (effect))); + /* allow us to choose our own max-duration */ + ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (effect), + TRUE); + assert_set_start (effect, 104); + assert_set_duration (effect, 53); + assert_set_max_duration (effect, 400); + + /* adding the effect will change its start and duration, but not its + * max-duration (or in-point) */ + _assert_add (clip, effect); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 150); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* only non-core, so can still set the max-duration */ + assert_set_max_duration (clip, 200); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 200); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* removing should not change the max-duration we set on the clip */ + gst_object_ref (effect); + _assert_remove (clip, effect); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 200); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + _assert_add (clip, effect); + gst_object_unref (effect); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 200); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* now add to a layer to create the core children */ + fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip))); + + children = GES_CONTAINER_CHILDREN (clip); + fail_unless (children); + fail_unless (GES_TIMELINE_ELEMENT (children->data) == effect); + fail_unless (children->next); + child0 = children->next->data; + fail_unless (children->next->next); + child1 = children->next->next->data; + fail_unless (children->next->next->next == NULL); + + fail_unless (ges_track_element_has_internal_source (GES_TRACK_ELEMENT + (child0))); + fail_unless (ges_track_element_has_internal_source (GES_TRACK_ELEMENT + (child1))); + + if (GES_IS_URI_CLIP (clip)) + new_max = max_duration; + else + /* need a valid clock time that is not too large */ + new_max = 500; + + /* added children do not change the clip's max-duration, but will + * instead set it to the minimum value of its children */ + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, max_duration); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, max_duration); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, max_duration); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* when setting max_duration of core children, clip will take the + * minimum value */ + assert_set_max_duration (child0, new_max - 1); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 1); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max - 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, max_duration); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + assert_set_max_duration (child1, new_max - 2); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max - 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + assert_set_max_duration (child0, new_max + 1); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* can not set in-point above max_duration, nor max_duration below + * in-point */ + + assert_fail_set_max_duration (child0, 29); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + assert_fail_set_max_duration (child1, 29); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + assert_fail_set_max_duration (clip, 29); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* can't set the inpoint to (new_max), even though it is lower than + * our own max-duration (new_max + 1) because it is too high for our + * sibling child1 */ + assert_fail_set_inpoint (child0, new_max); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + assert_fail_set_inpoint (child1, new_max); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + assert_fail_set_inpoint (clip, new_max); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* setting below new_max is ok */ + assert_set_inpoint (child0, 15); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 15, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 15, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 15, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + assert_set_inpoint (child1, 25); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 25, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 25, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 25, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + assert_set_inpoint (clip, 30); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* non-core has no effect */ + assert_set_max_duration (effect, new_max + 500); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, new_max + 500); + + /* can set the in-point of non-core to be higher than the max_duration + * of the clip */ + assert_set_inpoint (effect, new_max + 2); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, new_max + 2, 20, new_max + 500); + + /* but not higher than our own */ + assert_fail_set_inpoint (effect, new_max + 501); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, new_max + 2, 20, new_max + 500); + + assert_fail_set_max_duration (effect, new_max + 1); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, new_max + 2, 20, new_max + 500); + + assert_set_inpoint (effect, 0); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, new_max + 500); + + assert_set_max_duration (effect, 400); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* setting on the clip will set all the core children to the same + * value */ + assert_set_max_duration (clip, 180); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 180); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 180); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, 180); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* register child0 as having no internal source, which means its + * in-point will be set to 0 and max-duration set to + * GST_CLOCK_TIME_NONE */ + ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (child0), + FALSE); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 180); + CHECK_OBJECT_PROPS_MAX (child0, 5, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, 180); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* should not be able to set the max-duration to a valid time */ + assert_fail_set_max_duration (child0, 40); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 180); + CHECK_OBJECT_PROPS_MAX (child0, 5, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, 180); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* same with child1 */ + /* clock time of the clip should now be GST_CLOCK_TIME_NONE */ + ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (child1), + FALSE); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (child0, 5, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (child1, 5, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* should not be able to set the max of the clip to anything else + * when it has no core children with an internal source */ + assert_fail_set_max_duration (clip, 150); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (child0, 5, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (child1, 5, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* setting back to having an internal source will not immediately + * change the max-duration (unlike in-point) */ + ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (child0), + TRUE); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (child1, 5, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* can now set the max-duration, which will effect the clip */ + assert_set_max_duration (child0, 140); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 140); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140); + CHECK_OBJECT_PROPS_MAX (child1, 5, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (child1), + TRUE); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 140); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + assert_set_max_duration (child1, 130); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 130); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, 130); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* removing a child may change the max_duration of the clip */ + gst_object_ref (child0); + gst_object_ref (child1); + gst_object_ref (effect); + + /* removing non-core does nothing */ + _assert_remove (clip, effect); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 130); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, 130); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* new minimum max-duration for the clip when we remove child1 */ + _assert_remove (clip, child1); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 140); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, 130); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + /* with no core-children, the max-duration of the clip is set to + * GST_CLOCK_TIME_NONE */ + _assert_remove (clip, child0); + + CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140); + CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, 130); + CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400); + + fail_unless (ges_layer_remove_clip (layer, GES_CLIP (clip))); + + gst_object_unref (child0); + gst_object_unref (child1); + gst_object_unref (effect); + } + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +#define _assert_duration_limit(clip, expect) \ + assert_equals_uint64 (ges_clip_get_duration_limit (GES_CLIP (clip)), expect) + +GST_START_TEST (test_duration_limit) +{ + GESTimeline *timeline; + GESLayer *layer; + GESClip *clip; + GESTrackElement *video_source, *audio_source; + GESTrackElement *effect1, *effect2, *effect3; + GESTrack *track1, *track2; + gint limit_notify_count = 0; + gint duration_notify_count = 0; + + ges_init (); + + timeline = ges_timeline_new (); + track1 = GES_TRACK (ges_video_track_new ()); + track2 = GES_TRACK (ges_audio_track_new ()); + + fail_unless (ges_timeline_add_track (timeline, track1)); + fail_unless (ges_timeline_add_track (timeline, track2)); + /* add track3 later */ + + layer = ges_timeline_append_layer (timeline); + + clip = GES_CLIP (ges_test_clip_new ()); + g_signal_connect (clip, "notify::duration-limit", G_CALLBACK (_count_cb), + &limit_notify_count); + g_signal_connect (clip, "notify::duration", G_CALLBACK (_count_cb), + &duration_notify_count); + + /* no limit to begin with */ + _assert_duration_limit (clip, GST_CLOCK_TIME_NONE); + + /* add effects */ + effect1 = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay")); + ges_track_element_set_has_internal_source (effect1, TRUE); + + effect2 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv")); + ges_track_element_set_has_internal_source (effect2, TRUE); + + effect3 = GES_TRACK_ELEMENT (ges_effect_new ("audioecho")); + ges_track_element_set_has_internal_source (effect3, TRUE); + + _assert_add (clip, effect1); + _assert_add (clip, effect2); + _assert_add (clip, effect3); + assert_num_children (clip, 3); + _assert_duration_limit (clip, GST_CLOCK_TIME_NONE); + assert_equals_int (limit_notify_count, 0); + assert_equals_int (duration_notify_count, 0); + + /* no change in duration limit whilst children are not in any track */ + assert_set_max_duration (effect1, 20); + _assert_duration_limit (clip, GST_CLOCK_TIME_NONE); + assert_equals_int (limit_notify_count, 0); + assert_equals_int (duration_notify_count, 0); + + assert_set_inpoint (effect1, 5); + _assert_duration_limit (clip, GST_CLOCK_TIME_NONE); + assert_equals_int (limit_notify_count, 0); + assert_equals_int (duration_notify_count, 0); + + /* set a duration that will be above the duration-limit */ + assert_set_duration (clip, 20); + assert_equals_int (duration_notify_count, 1); + + /* add to layer to create sources */ + fail_unless (ges_layer_add_clip (layer, clip)); + + /* duration-limit changes once because of effect1 */ + _assert_duration_limit (clip, 15); + assert_equals_int (limit_notify_count, 1); + assert_equals_int (duration_notify_count, 2); + /* duration has automatically been set to the duration-limit */ + CHECK_OBJECT_PROPS_MAX (clip, 0, 0, 15, GST_CLOCK_TIME_NONE); + + assert_num_children (clip, 5); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 2); + + video_source = ges_clip_find_track_element (clip, track1, GES_TYPE_SOURCE); + fail_unless (video_source); + gst_object_unref (video_source); + + audio_source = ges_clip_find_track_element (clip, track2, GES_TYPE_SOURCE); + fail_unless (audio_source); + gst_object_unref (audio_source); + + CHECK_OBJECT_PROPS_MAX (video_source, 0, 0, 15, GST_CLOCK_TIME_NONE); + fail_unless (ges_track_element_get_track (video_source) == track1); + fail_unless (ges_track_element_get_track_type (video_source) == + GES_TRACK_TYPE_VIDEO); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 0, 15, GST_CLOCK_TIME_NONE); + fail_unless (ges_track_element_get_track (audio_source) == track2); + fail_unless (ges_track_element_get_track_type (audio_source) == + GES_TRACK_TYPE_AUDIO); + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20); + fail_unless (ges_track_element_get_track (effect1) == track1); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE); + fail_unless (ges_track_element_get_track (effect2) == track1); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE); + fail_unless (ges_track_element_get_track (effect3) == track2); + + /* Make effect1 inactive, which will remove the duration-limit */ + fail_unless (ges_track_element_set_active (effect1, FALSE)); + _assert_duration_limit (clip, GST_CLOCK_TIME_NONE); + assert_equals_int (limit_notify_count, 2); + /* duration is unchanged */ + assert_equals_int (duration_notify_count, 2); + CHECK_OBJECT_PROPS_MAX (clip, 0, 0, 15, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 0, 15, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 0, 15, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE); + + /* changing the in-point does not change the duration limit whilst + * there is no max-duration */ + assert_set_inpoint (clip, 10); + _assert_duration_limit (clip, GST_CLOCK_TIME_NONE); + assert_equals_int (limit_notify_count, 2); + assert_equals_int (duration_notify_count, 2); + CHECK_OBJECT_PROPS_MAX (clip, 0, 10, 15, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 10, 15, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 10, 15, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE); + + assert_set_max_duration (video_source, 40); + _assert_duration_limit (clip, 30); + assert_equals_int (limit_notify_count, 3); + assert_equals_int (duration_notify_count, 2); + CHECK_OBJECT_PROPS_MAX (clip, 0, 10, 15, 40); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 10, 15, 40); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 10, 15, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE); + + /* set in-point using child */ + assert_set_inpoint (audio_source, 15); + _assert_duration_limit (clip, 25); + assert_equals_int (limit_notify_count, 4); + assert_equals_int (duration_notify_count, 2); + CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 40); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 40); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE); + + /* set max-duration of core children */ + assert_set_max_duration (clip, 60); + _assert_duration_limit (clip, 45); + assert_equals_int (limit_notify_count, 5); + assert_equals_int (duration_notify_count, 2); + CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE); + + /* can set duration up to the limit */ + assert_set_duration (clip, 45); + _assert_duration_limit (clip, 45); + assert_equals_int (limit_notify_count, 5); + assert_equals_int (duration_notify_count, 3); + CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 45, 60); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 45, 60); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 45, 60); + /* effect1 has a duration that exceeds max-duration - in-point + * ok since it is currently inactive */ + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 45, 20); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 45, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 45, GST_CLOCK_TIME_NONE); + + /* limit the effects */ + assert_set_max_duration (effect2, 70); + _assert_duration_limit (clip, 45); + assert_equals_int (limit_notify_count, 5); + assert_equals_int (duration_notify_count, 3); + CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 45, 60); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 45, 60); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 45, 60); + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 45, 20); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 45, 70); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 45, GST_CLOCK_TIME_NONE); + + assert_set_inpoint (effect2, 40); + _assert_duration_limit (clip, 30); + assert_equals_int (limit_notify_count, 6); + assert_equals_int (duration_notify_count, 4); + CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 30, 60); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 30, 60); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 30, 60); + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 30, 20); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 30, 70); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 30, GST_CLOCK_TIME_NONE); + + /* no change */ + assert_set_max_duration (effect3, 35); + _assert_duration_limit (clip, 30); + assert_equals_int (limit_notify_count, 6); + assert_equals_int (duration_notify_count, 4); + CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 30, 60); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 30, 60); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 30, 60); + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 30, 20); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 30, 70); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 30, 35); + + /* make effect1 active again */ + fail_unless (ges_track_element_set_active (effect1, TRUE)); + _assert_duration_limit (clip, 15); + assert_equals_int (limit_notify_count, 7); + assert_equals_int (duration_notify_count, 5); + CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35); + + /* removing effect2 from track does not change limit */ + fail_unless (ges_track_remove_element (track1, effect2)); + _assert_duration_limit (clip, 15); + assert_equals_int (limit_notify_count, 7); + assert_equals_int (duration_notify_count, 5); + CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35); + /* no track */ + CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70); + + /* removing effect1 does */ + fail_unless (ges_track_remove_element (track1, effect1)); + _assert_duration_limit (clip, 35); + assert_equals_int (limit_notify_count, 8); + assert_equals_int (duration_notify_count, 5); + CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35); + /* no track */ + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70); + + /* add back */ + fail_unless (ges_track_add_element (track1, effect2)); + _assert_duration_limit (clip, 30); + assert_equals_int (limit_notify_count, 9); + assert_equals_int (duration_notify_count, 5); + CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70); + /* no track */ + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20); + + assert_set_duration (clip, 20); + _assert_duration_limit (clip, 30); + assert_equals_int (limit_notify_count, 9); + assert_equals_int (duration_notify_count, 6); + CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 20, 60); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 20, 60); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 20, 35); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 20, 60); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 20, 70); + /* no track */ + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 20, 20); + + fail_unless (ges_track_add_element (track1, effect1)); + _assert_duration_limit (clip, 15); + assert_equals_int (limit_notify_count, 10); + assert_equals_int (duration_notify_count, 7); + CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70); + + gst_object_ref (clip); + fail_unless (ges_layer_remove_clip (layer, clip)); + + assert_num_in_track (track1, 0); + assert_num_in_track (track2, 0); + assert_num_children (clip, 5); + + _assert_duration_limit (clip, GST_CLOCK_TIME_NONE); + /* may have several changes in duration limit as the children are + * emptied from their tracks */ + fail_unless (limit_notify_count > 10); + assert_equals_int (duration_notify_count, 7); + /* none in any track */ + CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35); + CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60); + CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20); + CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70); + + gst_object_unref (timeline); + gst_object_unref (clip); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_can_set_duration_limit) +{ + GESTimeline *timeline; + GESLayer *layer; + GESClip *clip; + GESTrackElement *source0, *source1; + GESTrackElement *effect0, *effect1, *effect2; + GESTrack *track0, *track1; + gint limit_notify_count = 0; + GError *error = NULL; + + ges_init (); + + timeline = ges_timeline_new (); + track0 = GES_TRACK (ges_video_track_new ()); + track1 = GES_TRACK (ges_audio_track_new ()); + + fail_unless (ges_timeline_add_track (timeline, track0)); + fail_unless (ges_timeline_add_track (timeline, track1)); + /* add track3 later */ + + layer = ges_timeline_append_layer (timeline); + + /* place a dummy clip at the start of the layer */ + clip = GES_CLIP (ges_test_clip_new ()); + assert_set_start (clip, 0); + assert_set_duration (clip, 20); + + fail_unless (ges_layer_add_clip (layer, clip)); + + /* the clip we will be editing overlaps the first clip at its start */ + clip = GES_CLIP (ges_test_clip_new ()); + + g_signal_connect (clip, "notify::duration-limit", G_CALLBACK (_count_cb), + &limit_notify_count); + + assert_set_start (clip, 10); + assert_set_duration (clip, 20); + + fail_unless (ges_layer_add_clip (layer, clip)); + + source0 = + ges_clip_find_track_element (clip, track0, GES_TYPE_VIDEO_TEST_SOURCE); + source1 = + ges_clip_find_track_element (clip, track1, GES_TYPE_AUDIO_TEST_SOURCE); + + fail_unless (source0); + fail_unless (source1); + + gst_object_unref (source0); + gst_object_unref (source1); + + assert_equals_int (limit_notify_count, 0); + _assert_duration_limit (clip, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (clip, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source0, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source1, 10, 0, 20, GST_CLOCK_TIME_NONE); + + assert_set_inpoint (clip, 16); + + assert_equals_int (limit_notify_count, 0); + _assert_duration_limit (clip, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, GST_CLOCK_TIME_NONE); + + assert_set_max_duration (clip, 36); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36); + + /* add effects */ + effect0 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv")); + effect1 = GES_TRACK_ELEMENT (ges_effect_new ("vertigotv")); + effect2 = GES_TRACK_ELEMENT (ges_effect_new ("alpha")); + + ges_track_element_set_has_internal_source (effect0, TRUE); + fail_unless (ges_track_element_has_internal_source (effect1) == FALSE); + ges_track_element_set_has_internal_source (effect2, TRUE); + + assert_set_max_duration (effect0, 10); + /* already set the track */ + fail_unless (ges_track_add_element (track0, effect0)); + /* cannot add because it would cause the duration-limit to go to 10, + * causing a full overlap with the clip at the beginning of the layer */ + + gst_object_ref (effect0); + fail_if (ges_container_add (GES_CONTAINER (clip), + GES_TIMELINE_ELEMENT (effect0))); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36); + + /* removing from the track and adding will work, but track selection + * will fail */ + fail_unless (ges_track_remove_element (track0, effect0)); + + _assert_add (clip, effect0); + fail_if (ges_track_element_get_track (effect0)); + gst_object_unref (effect0); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 10); + + fail_if (ges_clip_add_child_to_track (clip, effect0, track0, &error)); + assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + + /* set max-duration to 11 and we are fine to select a track */ + assert_set_max_duration (effect0, 11); + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 20); + + fail_unless (ges_clip_add_child_to_track (clip, effect0, track0, + &error) == effect0); + fail_if (error); + + assert_equals_int (limit_notify_count, 2); + _assert_duration_limit (clip, 11); + CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 11, 36); + CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 11, 36); + CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 11, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 11); + + /* cannot set duration above the limit */ + assert_fail_set_duration (clip, 12); + assert_fail_set_duration (source0, 12); + assert_fail_set_duration (effect0, 12); + + /* allow the max_duration to increase again */ + assert_set_max_duration (effect0, 25); + + assert_equals_int (limit_notify_count, 3); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 11, 36); + CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 11, 36); + CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 11, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25); + + assert_set_duration (clip, 20); + + assert_equals_int (limit_notify_count, 3); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + + /* add another effect */ + _assert_add (clip, effect1); + fail_unless (ges_track_element_get_track (effect1) == track0); + + assert_equals_int (limit_notify_count, 3); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + + /* make source0 inactive and reduce its max-duration + * note that this causes effect0 and effect1 to also become in-active */ + _assert_set_active (source0, FALSE); + _assert_active (source0, FALSE); + _assert_active (effect0, FALSE); + _assert_active (effect1, FALSE); + + assert_equals_int (limit_notify_count, 3); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + + /* can set a low max duration whilst the source is inactive */ + assert_set_max_duration (source0, 26); + + assert_equals_int (limit_notify_count, 3); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + + /* add the last effect */ + assert_set_inpoint (effect2, 7); + assert_set_max_duration (effect2, 17); + _assert_active (effect2, TRUE); + + /* safe to add because the source is inactive */ + assert_equals_int (limit_notify_count, 3); + _assert_add (clip, effect2); + assert_equals_int (limit_notify_count, 3); + _assert_active (source0, FALSE); + _assert_active (effect0, FALSE); + _assert_active (effect1, FALSE); + _assert_active (effect2, FALSE); + + fail_unless (ges_track_element_get_track (effect2) == track0); + + assert_equals_int (limit_notify_count, 3); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 17); + + /* want to make the source and its effects active again */ + assert_set_inpoint (source0, 6); + assert_set_max_duration (effect2, 33); + + assert_equals_int (limit_notify_count, 4); + _assert_duration_limit (clip, 30); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33); + + _assert_set_active (source0, TRUE); + _assert_set_active (effect0, TRUE); + _assert_set_active (effect1, TRUE); + _assert_set_active (effect2, TRUE); + + assert_equals_int (limit_notify_count, 5); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33); + + /* cannot set in-point of clip to 16, nor of either source */ + assert_fail_set_inpoint (clip, 16); + assert_fail_set_inpoint (source0, 16); + assert_fail_set_inpoint (source1, 16); + + assert_equals_int (limit_notify_count, 5); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33); + + /* can set just below */ + assert_set_inpoint (source1, 15); + + assert_equals_int (limit_notify_count, 6); + _assert_duration_limit (clip, 11); + CHECK_OBJECT_PROPS_MAX (clip, 10, 15, 11, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 15, 11, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 15, 11, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 11, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 11, 33); + + assert_set_inpoint (clip, 6); + assert_set_duration (clip, 20); + + assert_equals_int (limit_notify_count, 7); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33); + + /* cannot set in-point of non-core in a way that would cause limit to + * drop */ + assert_fail_set_inpoint (effect2, 23); + assert_fail_set_inpoint (effect0, 15); + + assert_equals_int (limit_notify_count, 7); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33); + + /* can set just below */ + assert_set_inpoint (effect2, 22); + + assert_equals_int (limit_notify_count, 8); + _assert_duration_limit (clip, 11); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 11, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 11, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 11, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 11, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 22, 11, 33); + + assert_set_inpoint (effect2, 7); + assert_set_duration (clip, 20); + + assert_equals_int (limit_notify_count, 9); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33); + + /* same but with max-duration */ + + /* core */ + assert_fail_set_max_duration (clip, 16); + assert_fail_set_max_duration (source0, 16); + assert_fail_set_max_duration (source1, 16); + + assert_equals_int (limit_notify_count, 9); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 36); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33); + + assert_set_max_duration (source1, 17); + + assert_equals_int (limit_notify_count, 10); + _assert_duration_limit (clip, 11); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 11, 17); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 11, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 11, 17); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 11, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 11, 33); + + assert_set_max_duration (source1, 30); + assert_set_duration (clip, 20); + + assert_equals_int (limit_notify_count, 11); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 30); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33); + + assert_set_max_duration (clip, 17); + + assert_equals_int (limit_notify_count, 12); + _assert_duration_limit (clip, 11); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 11, 17); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 11, 17); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 11, 17); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 11, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 11, 33); + + assert_set_max_duration (clip, 26); + assert_set_duration (clip, 20); + + assert_equals_int (limit_notify_count, 13); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33); + + /* non-core */ + assert_fail_set_max_duration (effect0, 10); + assert_fail_set_max_duration (effect2, 17); + + assert_equals_int (limit_notify_count, 13); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33); + + assert_set_max_duration (effect2, 18); + + assert_equals_int (limit_notify_count, 14); + _assert_duration_limit (clip, 11); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 11, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 11, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 11, 26); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 11, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 11, 18); + + assert_set_max_duration (effect2, 33); + assert_set_duration (clip, 20); + + assert_equals_int (limit_notify_count, 15); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33); + + /* test setting active */ + _assert_active (effect2, TRUE); + _assert_set_active (effect2, FALSE); + assert_set_max_duration (effect2, 17); + + assert_equals_int (limit_notify_count, 15); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 17); + + fail_if (ges_track_element_set_active (effect2, TRUE)); + + assert_equals_int (limit_notify_count, 15); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 17); + + assert_set_inpoint (effect2, 6); + _assert_set_active (effect2, TRUE); + + assert_equals_int (limit_notify_count, 16); + _assert_duration_limit (clip, 11); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 11, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 11, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 11, 26); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 11, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 6, 11, 17); + + /* make source0 in-active */ + _assert_active (source0, TRUE); + _assert_set_active (source0, FALSE); + _assert_active (source0, FALSE); + _assert_active (effect0, FALSE); + _assert_active (effect1, FALSE); + _assert_active (effect2, FALSE); + + assert_set_duration (source0, 20); + + assert_equals_int (limit_notify_count, 17); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 6, 20, 17); + + /* lower duration-limit for source for when it becomes active */ + assert_set_max_duration (source0, 16); + + assert_equals_int (limit_notify_count, 17); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 16); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 16); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 6, 20, 17); + + /* cannot make the source active */ + fail_if (ges_track_element_set_active (source0, TRUE)); + + assert_equals_int (limit_notify_count, 17); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 16); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 16); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 6, 20, 17); + + /* nor can we make the effects active because this would activate the + * source */ + fail_if (ges_track_element_set_active (effect0, TRUE)); + fail_if (ges_track_element_set_active (effect1, TRUE)); + fail_if (ges_track_element_set_active (effect2, TRUE)); + + assert_equals_int (limit_notify_count, 17); + _assert_duration_limit (clip, 20); + CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 16); + CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 16); + CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 6, 20, 17); + + /* allow it to just succeed */ + assert_set_inpoint (source0, 5); + + assert_equals_int (limit_notify_count, 18); + _assert_duration_limit (clip, 21); + CHECK_OBJECT_PROPS_MAX (clip, 10, 5, 20, 16); + CHECK_OBJECT_PROPS_MAX (source0, 10, 5, 20, 16); + CHECK_OBJECT_PROPS_MAX (source1, 10, 5, 20, 26); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 6, 20, 17); + + _assert_set_active (effect1, TRUE); + + assert_equals_int (limit_notify_count, 19); + _assert_duration_limit (clip, 11); + CHECK_OBJECT_PROPS_MAX (clip, 10, 5, 11, 16); + CHECK_OBJECT_PROPS_MAX (source0, 10, 5, 11, 16); + CHECK_OBJECT_PROPS_MAX (source1, 10, 5, 11, 26); + CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25); + CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 11, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (effect2, 10, 6, 11, 17); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +#define _assert_set_rate(element, prop_name, rate, val) \ +{ \ + GError *error = NULL; \ + if (G_VALUE_TYPE (&val) == G_TYPE_DOUBLE) \ + g_value_set_double (&val, rate); \ + else if (G_VALUE_TYPE (&val) == G_TYPE_FLOAT) \ + g_value_set_float (&val, rate); \ + \ + fail_unless (ges_timeline_element_set_child_property_full ( \ + GES_TIMELINE_ELEMENT (element), prop_name, &val, &error)); \ + fail_if (error); \ + g_value_reset (&val); \ +} + +#define _assert_fail_set_rate(element, prop_name, rate, val, code) \ +{ \ + GError * error = NULL; \ + if (G_VALUE_TYPE (&val) == G_TYPE_DOUBLE) \ + g_value_set_double (&val, rate); \ + else if (G_VALUE_TYPE (&val) == G_TYPE_FLOAT) \ + g_value_set_float (&val, rate); \ + \ + fail_if (ges_timeline_element_set_child_property_full ( \ + GES_TIMELINE_ELEMENT (element), prop_name, &val, &error)); \ + assert_GESError (error, code); \ + g_value_reset (&val); \ +} + +#define _assert_rate_equal(element, prop_name, rate, val) \ +{ \ + gdouble found = -1.0; \ + fail_unless (ges_timeline_element_get_child_property ( \ + GES_TIMELINE_ELEMENT (element), prop_name, &val)); \ + \ + if (G_VALUE_TYPE (&val) == G_TYPE_DOUBLE) \ + found = g_value_get_double (&val); \ + else if (G_VALUE_TYPE (&val) == G_TYPE_FLOAT) \ + found = g_value_get_float (&val); \ + \ + fail_unless (found == rate, "found %s: %g != expected: %g", found, \ + prop_name, rate); \ + g_value_reset (&val); \ +} + +GST_START_TEST (test_rate_effects_duration_limit) +{ + GESTimeline *timeline; + GESLayer *layer; + GESClip *clip; + GESTrackElement *source0, *source1; + GESTrackElement *overlay0, *overlay1, *videorate, *pitch; + GESTrack *track0, *track1; + gint limit_notify_count = 0; + GValue fval = G_VALUE_INIT; + GValue dval = G_VALUE_INIT; + + ges_init (); + + g_value_init (&fval, G_TYPE_FLOAT); + g_value_init (&dval, G_TYPE_DOUBLE); + + timeline = ges_timeline_new (); + track0 = GES_TRACK (ges_video_track_new ()); + track1 = GES_TRACK (ges_audio_track_new ()); + + fail_unless (ges_timeline_add_track (timeline, track0)); + fail_unless (ges_timeline_add_track (timeline, track1)); + + layer = ges_timeline_append_layer (timeline); + + /* place a dummy clip at the start of the layer */ + clip = GES_CLIP (ges_test_clip_new ()); + assert_set_start (clip, 0); + assert_set_duration (clip, 26); + + fail_unless (ges_layer_add_clip (layer, clip)); + + /* the clip we will be editing overlaps first clip by 16 at its start */ + clip = GES_CLIP (ges_test_clip_new ()); + + g_signal_connect (clip, "notify::duration-limit", G_CALLBACK (_count_cb), + &limit_notify_count); + + assert_set_start (clip, 10); + assert_set_duration (clip, 64); + + fail_unless (ges_layer_add_clip (layer, clip)); + + source0 = + ges_clip_find_track_element (clip, track0, GES_TYPE_VIDEO_TEST_SOURCE); + source1 = + ges_clip_find_track_element (clip, track1, GES_TYPE_AUDIO_TEST_SOURCE); + + fail_unless (source0); + fail_unless (source1); + + gst_object_unref (source0); + gst_object_unref (source1); + + assert_equals_int (limit_notify_count, 0); + _assert_duration_limit (clip, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (clip, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source0, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source1, 10, 0, 64, GST_CLOCK_TIME_NONE); + + assert_set_inpoint (clip, 13); + + assert_equals_int (limit_notify_count, 0); + _assert_duration_limit (clip, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, GST_CLOCK_TIME_NONE); + + assert_set_max_duration (clip, 77); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + + /* add effects */ + overlay0 = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay")); + ges_track_element_set_has_internal_source (overlay0, TRUE); + + videorate = GES_TRACK_ELEMENT (ges_effect_new ("videorate")); + fail_unless (ges_base_effect_is_time_effect (GES_BASE_EFFECT (videorate))); + + overlay1 = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay")); + ges_track_element_set_has_internal_source (overlay1, TRUE); + + pitch = GES_TRACK_ELEMENT (ges_effect_new ("pitch")); + fail_unless (ges_base_effect_is_time_effect (GES_BASE_EFFECT (pitch))); + + /* add overlay1 at highest priority */ + _assert_add (clip, overlay1); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE); + + _assert_set_rate (videorate, "rate", 4.0, dval); + _assert_rate_equal (videorate, "rate", 4.0, dval); + fail_unless (ges_track_add_element (track0, videorate)); + + /* cannot add videorate as it would cause the duration-limit to drop + * to 16, causing a full overlap */ + /* track keeps alive */ + fail_if (ges_container_add (GES_CONTAINER (clip), + GES_TIMELINE_ELEMENT (videorate))); + + /* setting to 1.0 makes it work again */ + _assert_set_rate (videorate, "rate", 1.0, dval); + _assert_add (clip, videorate); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + + /* add second overlay at lower priority */ + _assert_add (clip, overlay0); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + + /* also add a pitch element in another track */ + _assert_add (clip, pitch); + _assert_set_rate (pitch, "rate", 1.0, fval); + _assert_set_rate (pitch, "tempo", 1.0, fval); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + _assert_rate_equal (pitch, "rate", 1.0, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + fail_unless (ges_track_element_get_track (overlay0) == track0); + fail_unless (ges_track_element_get_track (videorate) == track0); + fail_unless (ges_track_element_get_track (overlay1) == track0); + fail_unless (ges_track_element_get_track (pitch) == track1); + + /* flow in track0: + * source0 -> overlay0 -> videorate -> overlay1 -> timeline output + * + * flow in track1: + * source1 -> pitch -> timeline output + */ + + /* cannot set the rates to 4.0 since this would cause a full overlap */ + _assert_fail_set_rate (videorate, "rate", 4.0, dval, + GES_ERROR_INVALID_OVERLAP_IN_TRACK); + _assert_fail_set_rate (pitch, "rate", 4.0, fval, + GES_ERROR_INVALID_OVERLAP_IN_TRACK); + _assert_fail_set_rate (pitch, "tempo", 4.0, fval, + GES_ERROR_INVALID_OVERLAP_IN_TRACK); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + _assert_rate_equal (pitch, "rate", 1.0, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* limit overlay0 */ + assert_set_max_duration (overlay0, 91); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + _assert_rate_equal (pitch, "rate", 1.0, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + assert_set_inpoint (overlay0, 59); + + assert_equals_int (limit_notify_count, 2); + _assert_duration_limit (clip, 32); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + _assert_rate_equal (pitch, "rate", 1.0, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* can set pitch rate to 2.0, but not videorate rate because videorate + * shares a track with overlay0 */ + + _assert_set_rate (pitch, "rate", 2.0, fval); + assert_equals_int (limit_notify_count, 2); + _assert_fail_set_rate (videorate, "rate", 2.0, dval, + GES_ERROR_INVALID_OVERLAP_IN_TRACK); + assert_equals_int (limit_notify_count, 2); + /* can't set tempo to 2.0 since overall effect would bring duration + * limit too low */ + _assert_fail_set_rate (pitch, "tempo", 2.0, fval, + GES_ERROR_INVALID_OVERLAP_IN_TRACK); + + assert_equals_int (limit_notify_count, 2); + _assert_duration_limit (clip, 32); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + _assert_rate_equal (pitch, "rate", 2.0, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* cannot set in-point of clip because pitch would cause limit to go + * to 16 */ + assert_fail_set_inpoint (clip, 45); + /* same for max-duration of source1 */ + assert_fail_set_max_duration (source1, 45); + + assert_equals_int (limit_notify_count, 2); + _assert_duration_limit (clip, 32); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + _assert_rate_equal (pitch, "rate", 2.0, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* can set rate to 0.5 */ + _assert_set_rate (videorate, "rate", 0.5, dval); + + /* no change yet, since pitch rate is still 2.0 */ + assert_equals_int (limit_notify_count, 2); + _assert_duration_limit (clip, 32); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 2.0, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + _assert_set_rate (pitch, "rate", 0.5, fval); + + assert_equals_int (limit_notify_count, 3); + /* duration-limit is 64 because overlay0 only has 32 nanoseconds of + * content, stretched to 64 by videorate */ + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* setting the max-duration of the sources does not change the limit + * since the limit on overlay0 is fine. + * Note that pitch handles the unlimited duration (GST_CLOCK_TIME_NONE) + * without any problems */ + assert_set_max_duration (clip, GST_CLOCK_TIME_NONE); + + assert_equals_int (limit_notify_count, 3); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + assert_set_max_duration (clip, 77); + + assert_equals_int (limit_notify_count, 3); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* limit overlay1. It should not be changes by the videorate element + * since it acts at a lower priority + * first make it last longer, so no change in duration-limit */ + + assert_set_max_duration (overlay1, 81); + + assert_equals_int (limit_notify_count, 3); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, 81); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* now make it shorter */ + assert_set_inpoint (overlay1, 51); + + assert_equals_int (limit_notify_count, 4); + _assert_duration_limit (clip, 30); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 30, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 30, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 30, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 30, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 30, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 30, 81); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 30, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* remove the overlay0 limit */ + assert_set_max_duration (overlay0, GST_CLOCK_TIME_NONE); + + /* no change because of overlay1 */ + assert_equals_int (limit_notify_count, 4); + _assert_duration_limit (clip, 30); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 30, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 30, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 30, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 30, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 30, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 30, 81); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 30, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + assert_set_max_duration (overlay1, GST_CLOCK_TIME_NONE); + + assert_equals_int (limit_notify_count, 5); + _assert_duration_limit (clip, 128); + /* can set up to the limit */ + assert_set_duration (clip, 128); + + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 128, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 128, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 128, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 128, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 128, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 128, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 128, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* tempo contributes the same factor as rate */ + + _assert_set_rate (pitch, "tempo", 2.0, fval); + + assert_equals_int (limit_notify_count, 6); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 2.0, fval); + + _assert_set_rate (videorate, "rate", 0.1, dval); + assert_equals_int (limit_notify_count, 6); + _assert_set_rate (pitch, "tempo", 0.5, fval); + + assert_equals_int (limit_notify_count, 7); + _assert_duration_limit (clip, 256); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.1, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 0.5, fval); + + _assert_set_rate (pitch, "tempo", 1.0, fval); + assert_equals_int (limit_notify_count, 8); + _assert_set_rate (videorate, "rate", 0.5, dval); + + assert_equals_int (limit_notify_count, 8); + _assert_duration_limit (clip, 128); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* make videorate in-active */ + fail_unless (ges_track_element_set_active (videorate, FALSE)); + + assert_equals_int (limit_notify_count, 9); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + fail_unless (ges_track_element_set_active (videorate, TRUE)); + + assert_equals_int (limit_notify_count, 10); + _assert_duration_limit (clip, 128); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* removing pitch, same effect as making inactive */ + _assert_remove (clip, pitch); + + assert_equals_int (limit_notify_count, 11); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + + /* no max-duration will give unlimited limit */ + assert_set_max_duration (source1, GST_CLOCK_TIME_NONE); + + assert_equals_int (limit_notify_count, 12); + _assert_duration_limit (clip, 128); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + + assert_set_max_duration (source0, GST_CLOCK_TIME_NONE); + + assert_equals_int (limit_notify_count, 13); + _assert_duration_limit (clip, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + + gst_object_unref (timeline); + + g_value_unset (&fval); + g_value_unset (&dval); + + ges_deinit (); +} + +GST_END_TEST; + + +GST_START_TEST (test_children_properties_contain) +{ + GESTimeline *timeline; + GESLayer *layer; + GESClip *clip; + GList *tmp; + GParamSpec **clips_child_props, **childrens_child_props = NULL; + guint num_clips_props, num_childrens_props = 0; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_timeline_append_layer (timeline); + clip = GES_CLIP (ges_test_clip_new ()); + assert_set_duration (clip, 50); + + fail_unless (ges_layer_add_clip (layer, clip)); + + clips_child_props = + ges_timeline_element_list_children_properties (GES_TIMELINE_ELEMENT + (clip), &num_clips_props); + fail_unless (clips_child_props); + fail_unless (num_clips_props); + + fail_unless (GES_CONTAINER_CHILDREN (clip)); + + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) + childrens_child_props = + append_children_properties (childrens_child_props, tmp->data, + &num_childrens_props); + + assert_property_list_match (clips_child_props, num_clips_props, + childrens_child_props, num_childrens_props); + + free_children_properties (clips_child_props, num_clips_props); + free_children_properties (childrens_child_props, num_childrens_props); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +static gboolean +_has_child_property (GESTimelineElement * element, GParamSpec * property) +{ + gboolean has_prop = FALSE; + guint num_props, i; + GParamSpec **props = + ges_timeline_element_list_children_properties (element, &num_props); + for (i = 0; i < num_props; i++) { + if (props[i] == property) + has_prop = TRUE; + g_param_spec_unref (props[i]); + } + g_free (props); + return has_prop; +} + +typedef struct +{ + GstElement *child; + GParamSpec *property; + guint num_calls; +} PropChangedData; + +#define _INIT_PROP_CHANGED_DATA(data) \ + data.child = NULL; \ + data.property = NULL; \ + data.num_calls = 0; + +static void +_prop_changed_cb (GESTimelineElement * element, GstElement * child, + GParamSpec * property, PropChangedData * data) +{ + data->num_calls++; + data->property = property; + data->child = child; +} + +#define _assert_prop_changed_data(element, data, num_cmp, chld_cmp, prop_cmp) \ + fail_unless (num_cmp == data.num_calls, \ + "%s: num calls to callback (%u) not the expected %u", element->name, \ + data.num_calls, num_cmp); \ + fail_unless (prop_cmp == data.property, \ + "%s: property %s is not the expected property %s", element->name, \ + data.property->name, prop_cmp ? ((GParamSpec *)prop_cmp)->name : NULL); \ + fail_unless (chld_cmp == data.child, \ + "%s: child %s is not the expected child %s", element->name, \ + GST_ELEMENT_NAME (data.child), \ + chld_cmp ? GST_ELEMENT_NAME (chld_cmp) : NULL); + +#define _assert_int_val_child_prop(element, val, int_cmp, prop, prop_name) \ + g_value_init (&val, G_TYPE_INT); \ + ges_timeline_element_get_child_property_by_pspec (element, prop, &val); \ + assert_equals_int (g_value_get_int (&val), int_cmp); \ + g_value_unset (&val); \ + g_value_init (&val, G_TYPE_INT); \ + fail_unless (ges_timeline_element_get_child_property ( \ + element, prop_name, &val)); \ + assert_equals_int (g_value_get_int (&val), int_cmp); \ + g_value_unset (&val); \ + +GST_START_TEST (test_children_properties_change) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTimelineElement *clip, *child; + PropChangedData clip_add_data, clip_remove_data, clip_notify_data, + child_add_data, child_remove_data, child_notify_data; + GstElement *sub_child; + GParamSpec *prop1, *prop2, *prop3; + GValue val = G_VALUE_INIT; + gint num_buffs; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_timeline_append_layer (timeline); + clip = GES_TIMELINE_ELEMENT (ges_test_clip_new ()); + assert_set_duration (clip, 50); + + fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip))); + fail_unless (GES_CONTAINER_CHILDREN (clip)); + child = GES_CONTAINER_CHILDREN (clip)->data; + + /* fake sub-child */ + sub_child = gst_element_factory_make ("fakesink", "sub-child"); + fail_unless (sub_child); + gst_object_ref_sink (sub_child); + prop1 = g_object_class_find_property (G_OBJECT_GET_CLASS (sub_child), + "num-buffers"); + fail_unless (prop1); + prop2 = g_object_class_find_property (G_OBJECT_GET_CLASS (sub_child), "dump"); + fail_unless (prop2); + prop3 = g_object_class_find_property (G_OBJECT_GET_CLASS (sub_child), + "silent"); + fail_unless (prop2); + + _INIT_PROP_CHANGED_DATA (clip_add_data); + _INIT_PROP_CHANGED_DATA (clip_remove_data); + _INIT_PROP_CHANGED_DATA (clip_notify_data); + _INIT_PROP_CHANGED_DATA (child_add_data); + _INIT_PROP_CHANGED_DATA (child_remove_data); + _INIT_PROP_CHANGED_DATA (child_notify_data); + g_signal_connect (clip, "child-property-added", + G_CALLBACK (_prop_changed_cb), &clip_add_data); + g_signal_connect (clip, "child-property-removed", + G_CALLBACK (_prop_changed_cb), &clip_remove_data); + g_signal_connect (clip, "deep-notify", + G_CALLBACK (_prop_changed_cb), &clip_notify_data); + g_signal_connect (child, "child-property-added", + G_CALLBACK (_prop_changed_cb), &child_add_data); + g_signal_connect (child, "child-property-removed", + G_CALLBACK (_prop_changed_cb), &child_remove_data); + g_signal_connect (child, "deep-notify", + G_CALLBACK (_prop_changed_cb), &child_notify_data); + + /* adding to child should also add it to the parent clip */ + fail_unless (ges_timeline_element_add_child_property (child, prop1, + G_OBJECT (sub_child))); + + fail_unless (_has_child_property (child, prop1)); + fail_unless (_has_child_property (clip, prop1)); + + _assert_prop_changed_data (clip, clip_add_data, 1, sub_child, prop1); + _assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL); + _assert_prop_changed_data (clip, clip_notify_data, 0, NULL, NULL); + _assert_prop_changed_data (child, child_add_data, 1, sub_child, prop1); + _assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL); + _assert_prop_changed_data (child, child_notify_data, 0, NULL, NULL); + + fail_unless (ges_timeline_element_add_child_property (child, prop2, + G_OBJECT (sub_child))); + + fail_unless (_has_child_property (child, prop2)); + fail_unless (_has_child_property (clip, prop2)); + + _assert_prop_changed_data (clip, clip_add_data, 2, sub_child, prop2); + _assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL); + _assert_prop_changed_data (clip, clip_notify_data, 0, NULL, NULL); + _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2); + _assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL); + _assert_prop_changed_data (child, child_notify_data, 0, NULL, NULL); + + /* adding to parent does not add to the child */ + + fail_unless (ges_timeline_element_add_child_property (clip, prop3, + G_OBJECT (sub_child))); + + fail_if (_has_child_property (child, prop3)); + fail_unless (_has_child_property (clip, prop3)); + + _assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3); + _assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL); + _assert_prop_changed_data (clip, clip_notify_data, 0, NULL, NULL); + _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2); + _assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL); + _assert_prop_changed_data (child, child_notify_data, 0, NULL, NULL); + + /* both should be notified of a change in the value */ + + g_object_set (G_OBJECT (sub_child), "num-buffers", 100, NULL); + + _assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3); + _assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL); + _assert_prop_changed_data (clip, clip_notify_data, 1, sub_child, prop1); + _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2); + _assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL); + _assert_prop_changed_data (child, child_notify_data, 1, sub_child, prop1); + + _assert_int_val_child_prop (clip, val, 100, prop1, + "GstFakeSink::num-buffers"); + _assert_int_val_child_prop (child, val, 100, prop1, + "GstFakeSink::num-buffers"); + + g_value_init (&val, G_TYPE_INT); + g_value_set_int (&val, 79); + ges_timeline_element_set_child_property_by_pspec (clip, prop1, &val); + g_value_unset (&val); + + _assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3); + _assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL); + _assert_prop_changed_data (clip, clip_notify_data, 2, sub_child, prop1); + _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2); + _assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL); + _assert_prop_changed_data (child, child_notify_data, 2, sub_child, prop1); + + _assert_int_val_child_prop (clip, val, 79, prop1, "GstFakeSink::num-buffers"); + _assert_int_val_child_prop (child, val, 79, prop1, + "GstFakeSink::num-buffers"); + g_object_get (G_OBJECT (sub_child), "num-buffers", &num_buffs, NULL); + assert_equals_int (num_buffs, 79); + + g_value_init (&val, G_TYPE_INT); + g_value_set_int (&val, 97); + fail_unless (ges_timeline_element_set_child_property (child, + "GstFakeSink::num-buffers", &val)); + g_value_unset (&val); + + _assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3); + _assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL); + _assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1); + _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2); + _assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL); + _assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1); + + _assert_int_val_child_prop (clip, val, 97, prop1, "GstFakeSink::num-buffers"); + _assert_int_val_child_prop (child, val, 97, prop1, + "GstFakeSink::num-buffers"); + g_object_get (G_OBJECT (sub_child), "num-buffers", &num_buffs, NULL); + assert_equals_int (num_buffs, 97); + + /* remove a property from the child, removes from the parent */ + + fail_unless (ges_timeline_element_remove_child_property (child, prop2)); + + _assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3); + _assert_prop_changed_data (clip, clip_remove_data, 1, sub_child, prop2); + _assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1); + _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2); + _assert_prop_changed_data (child, child_remove_data, 1, sub_child, prop2); + _assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1); + + fail_if (_has_child_property (child, prop2)); + fail_if (_has_child_property (clip, prop2)); + + /* removing from parent doesn't remove from child */ + + fail_unless (ges_timeline_element_remove_child_property (clip, prop1)); + + _assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3); + _assert_prop_changed_data (clip, clip_remove_data, 2, sub_child, prop1); + _assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1); + _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2); + _assert_prop_changed_data (child, child_remove_data, 1, sub_child, prop2); + _assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1); + + fail_unless (_has_child_property (child, prop1)); + fail_if (_has_child_property (clip, prop1)); + + /* but still safe to remove it from the child later */ + + fail_unless (ges_timeline_element_remove_child_property (child, prop1)); + + _assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3); + _assert_prop_changed_data (clip, clip_remove_data, 2, sub_child, prop1); + _assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1); + _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2); + _assert_prop_changed_data (child, child_remove_data, 2, sub_child, prop1); + _assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1); + + fail_if (_has_child_property (child, prop1)); + fail_if (_has_child_property (clip, prop1)); + + gst_object_unref (sub_child); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +static GESTimelineElement * +_el_with_child_prop (GESTimelineElement * clip, GObject * prop_child, + GParamSpec * prop) +{ + GList *tmp; + GESTimelineElement *child = NULL; + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GObject *found_child; + GParamSpec *found_prop; + if (ges_timeline_element_lookup_child (tmp->data, prop->name, + &found_child, &found_prop)) { + if (found_child == prop_child && found_prop == prop) { + g_param_spec_unref (found_prop); + g_object_unref (found_child); + child = tmp->data; + break; + } + g_param_spec_unref (found_prop); + g_object_unref (found_child); + } + } + return child; +} + +static GstTimedValue * +_new_timed_value (GstClockTime time, gdouble val) +{ + GstTimedValue *tmval = g_new0 (GstTimedValue, 1); + tmval->value = val; + tmval->timestamp = time; + return tmval; +} + +#define _assert_binding(element, prop_name, child, timed_vals, mode) \ +{ \ + GstInterpolationMode found_mode; \ + GSList *tmp1; \ + GList *tmp2; \ + guint i; \ + GList *found_timed_vals; \ + GObject *found_object = NULL; \ + GstControlSource *source = NULL; \ + GstControlBinding *binding = ges_track_element_get_control_binding ( \ + GES_TRACK_ELEMENT (element), prop_name); \ + fail_unless (binding, "No control binding found for %s on %s", \ + prop_name, GES_TIMELINE_ELEMENT_NAME (element)); \ + g_object_get (G_OBJECT (binding), "control-source", &source, \ + "object", &found_object, NULL); \ + \ + if (child) \ + fail_unless (found_object == child); \ + g_object_unref (found_object); \ + \ + fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source)); \ + found_timed_vals = gst_timed_value_control_source_get_all ( \ + GST_TIMED_VALUE_CONTROL_SOURCE (source)); \ + \ + for (i = 0, tmp1 = timed_vals, tmp2 = found_timed_vals; tmp1 && tmp2; \ + tmp1 = tmp1->next, tmp2 = tmp2->next, i++) { \ + GstTimedValue *val1 = tmp1->data, *val2 = tmp2->data; \ + gdouble diff = (val1->value > val2->value) ? \ + val1->value - val2->value : val2->value - val1->value; \ + fail_unless (val1->timestamp == val2->timestamp && diff < 0.0001, \ + "The %ith timed value (%lu: %g) does not match the found timed " \ + "value (%lu: %g)", i, val1->timestamp, val1->value, \ + val2->timestamp, val2->value); \ + } \ + fail_unless (tmp1 == NULL, "Found too few timed values"); \ + fail_unless (tmp2 == NULL, "Found too many timed values"); \ + \ + g_list_free (found_timed_vals); \ + g_object_get (G_OBJECT (source), "mode", &found_mode, NULL); \ + fail_unless (found_mode == mode); \ + g_object_unref (source); \ +} + +GST_START_TEST (test_copy_paste_children_properties) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTimelineElement *clip, *copy, *pasted, *track_el, *pasted_el; + GObject *sub_child, *pasted_sub_child; + GParamSpec **orig_props; + guint num_orig_props; + GParamSpec *prop, *found_prop; + GValue val = G_VALUE_INIT; + GstControlSource *source; + GSList *timed_vals; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_timeline_append_layer (timeline); + clip = GES_TIMELINE_ELEMENT (ges_source_clip_new_time_overlay ()); + assert_set_duration (clip, 50); + + fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip))); + + /* get children properties */ + orig_props = + ges_timeline_element_list_children_properties (clip, &num_orig_props); + fail_unless (num_orig_props); + + /* font-desc is originally "", but on setting switches to Normal, so we + * set it explicitly */ + ges_timeline_element_set_child_properties (clip, "font-desc", "Normal", + "posx", 30, "posy", 50, "alpha", 0.1, "freq", 449.0, NULL); + + /* focus on one property */ + fail_unless (ges_timeline_element_lookup_child (clip, "posx", + &sub_child, &prop)); + _assert_int_val_child_prop (clip, val, 30, prop, "posx"); + + /* find the track element where the child property comes from */ + fail_unless (track_el = _el_with_child_prop (clip, sub_child, prop)); + _assert_int_val_child_prop (track_el, val, 30, prop, "posx"); + ges_track_element_set_auto_clamp_control_sources (GES_TRACK_ELEMENT + (track_el), FALSE); + + /* set a control binding */ + timed_vals = g_slist_prepend (NULL, _new_timed_value (200, 5)); + timed_vals = g_slist_prepend (timed_vals, _new_timed_value (40, 50)); + timed_vals = g_slist_prepend (timed_vals, _new_timed_value (20, 10)); + timed_vals = g_slist_prepend (timed_vals, _new_timed_value (0, 20)); + + source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ()); + g_object_set (G_OBJECT (source), "mode", GST_INTERPOLATION_MODE_CUBIC, NULL); + fail_unless (gst_timed_value_control_source_set_from_list + (GST_TIMED_VALUE_CONTROL_SOURCE (source), timed_vals)); + + fail_unless (ges_track_element_set_control_source (GES_TRACK_ELEMENT + (track_el), source, "posx", "direct-absolute")); + + g_object_unref (source); + + /* check the control binding */ + _assert_binding (track_el, "posx", sub_child, timed_vals, + GST_INTERPOLATION_MODE_CUBIC); + + /* copy and paste */ + fail_unless (copy = ges_timeline_element_copy (clip, TRUE)); + fail_unless (pasted = ges_timeline_element_paste (copy, 30)); + + gst_object_unref (copy); + gst_object_unref (pasted); + + /* test that the new clip has the same child properties */ + assert_equal_children_properties (clip, pasted); + + /* get the details for the copied 'prop' property */ + fail_unless (ges_timeline_element_lookup_child (pasted, + "posx", &pasted_sub_child, &found_prop)); + fail_unless (found_prop == prop); + g_param_spec_unref (found_prop); + fail_unless (G_OBJECT_TYPE (pasted_sub_child) == G_OBJECT_TYPE (sub_child)); + + _assert_int_val_child_prop (pasted, val, 30, prop, "posx"); + + /* get the associated child */ + fail_unless (pasted_el = + _el_with_child_prop (pasted, pasted_sub_child, prop)); + _assert_int_val_child_prop (pasted_el, val, 30, prop, "posx"); + + assert_equal_children_properties (track_el, pasted_el); + + /* check the control binding on the pasted element */ + _assert_binding (pasted_el, "posx", pasted_sub_child, timed_vals, + GST_INTERPOLATION_MODE_CUBIC); + + assert_equal_bindings (pasted_el, track_el); + + /* free */ + g_slist_free_full (timed_vals, g_free); + + free_children_properties (orig_props, num_orig_props); + + g_param_spec_unref (prop); + g_object_unref (pasted_sub_child); + g_object_unref (sub_child); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +#define _THREE_TIMED_VALS(timed_vals, tm1, val1, tm2, val2, tm3, val3) \ + if (timed_vals) \ + g_slist_free_full (timed_vals, g_free); \ + timed_vals = g_slist_prepend (NULL, _new_timed_value (tm3, val3)); \ + timed_vals = g_slist_prepend (timed_vals, _new_timed_value (tm2, val2)); \ + timed_vals = g_slist_prepend (timed_vals, _new_timed_value (tm1, val1)); + +#define _TWO_TIMED_VALS(timed_vals, tm1, val1, tm2, val2) \ + if (timed_vals) \ + g_slist_free_full (timed_vals, g_free); \ + timed_vals = g_slist_prepend (NULL, _new_timed_value (tm2, val2)); \ + timed_vals = g_slist_prepend (timed_vals, _new_timed_value (tm1, val1)); + +#define _assert_control_source(obj, prop, vals) \ + _assert_binding (obj, prop, NULL, vals, GST_INTERPOLATION_MODE_LINEAR); + +GST_START_TEST (test_children_property_bindings_with_rate_effects) +{ + GESTimeline *timeline; + GESTrack *track; + GESLayer *layer; + GESClip *clip; + GESTrackElement *video_source, *rate0, *rate1, *overlay; + GstControlSource *ctrl_source; + GSList *video_source_vals = NULL, *overlay_vals = NULL; + GValue value = G_VALUE_INIT; + GstControlBinding *binding; + + ges_init (); + + g_value_init (&value, G_TYPE_DOUBLE); + + timeline = ges_timeline_new (); + track = GES_TRACK (ges_video_track_new ()); + fail_unless (ges_timeline_add_track (timeline, track)); + + layer = ges_timeline_append_layer (timeline); + + clip = GES_CLIP (ges_test_clip_new ()); + assert_set_duration (clip, 4); + assert_set_start (clip, 20); + assert_set_inpoint (clip, 3); + + fail_unless (ges_layer_add_clip (layer, clip)); + + video_source = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE); + fail_unless (video_source); + gst_object_unref (video_source); + + rate0 = GES_TRACK_ELEMENT (ges_effect_new ("videorate rate=0.5")); + rate1 = GES_TRACK_ELEMENT (ges_effect_new ("videorate rate=4.0")); + overlay = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay")); + ges_track_element_set_has_internal_source (overlay, TRUE); + assert_set_inpoint (overlay, 9); + + fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (rate0), -1, + NULL)); + fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (overlay), 0, + NULL)); + fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (rate1), 0, + NULL)); + + fail_unless (ges_track_element_get_auto_clamp_control_sources (video_source)); + fail_unless (ges_track_element_get_auto_clamp_control_sources (overlay)); + + /* source's alpha property */ + _THREE_TIMED_VALS (video_source_vals, 1, 0.7, 7, 1.0, 15, 0.2); + + ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ()); + g_object_set (G_OBJECT (ctrl_source), "mode", + GST_INTERPOLATION_MODE_LINEAR, NULL); + fail_unless (gst_timed_value_control_source_set_from_list + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), video_source_vals)); + + fail_unless (ges_track_element_set_control_source (video_source, ctrl_source, + "alpha", "direct")); + gst_object_unref (ctrl_source); + + /* values have been clamped between its in-point:3 and its + * out-point:11 (4ns in timeline is 8ns in source) */ + _THREE_TIMED_VALS (video_source_vals, 3, 0.8, 7, 1.0, 11, 0.6); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* overlay's xpos property */ + _THREE_TIMED_VALS (overlay_vals, 9, 12, 17, 16, 25, 8); + + ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ()); + g_object_set (G_OBJECT (ctrl_source), "mode", + GST_INTERPOLATION_MODE_LINEAR, NULL); + fail_unless (gst_timed_value_control_source_set_from_list + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), overlay_vals)); + + fail_unless (ges_track_element_set_control_source (overlay, ctrl_source, + "xpos", "direct-absolute")); + gst_object_unref (ctrl_source); + + /* unchanged since values are at the edges already + * in-point:9 out-point:25 (4ns in timeline is 16ns in source) */ + _assert_control_source (overlay, "xpos", overlay_vals); + + /* setting the in-point changes the in-point and out-point */ + /* increase in-point */ + assert_set_inpoint (video_source, 5); + + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 13, 0.4); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* decrease in-point */ + assert_set_inpoint (overlay, 7); + + _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 23, 10); + _assert_control_source (overlay, "xpos", overlay_vals); + + /* when trimming start, out-point should stay the same */ + fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip), + -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, 19, NULL)); + + /* in-point of video_source now 3 */ + _THREE_TIMED_VALS (video_source_vals, 3, 0.8, 7, 1.0, 13, 0.4); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* in-point of video_source now 3 */ + _THREE_TIMED_VALS (overlay_vals, 3, 9, 17, 16, 23, 10); + _assert_control_source (overlay, "xpos", overlay_vals); + + /* trim forwards */ + fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip), + -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, 20, NULL)); + + /* in-point of video_source now 5 again */ + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 13, 0.4); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* in-point of overlay now 7 again */ + _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 23, 10); + _assert_control_source (overlay, "xpos", overlay_vals); + + /* trim end */ + fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip), + -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 25, NULL)); + + /* out-point of video_source now 15 */ + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 15, 0.2); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* out-point of overlay now 27 */ + _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 27, 6); + _assert_control_source (overlay, "xpos", overlay_vals); + + /* trim backwards */ + fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip), + -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 23, NULL)); + + /* out-point of video_source now 11 */ + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* in-point of overlay now 19 */ + _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 19, 14); + _assert_control_source (overlay, "xpos", overlay_vals); + + /* changing the rate changes the out-point */ + _assert_set_rate (rate0, "rate", 1.0, value); + + /* out-point of video_source now 17 */ + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 17, 0.0); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* unchanged for overlay, which is after rate0 */ + _assert_control_source (overlay, "xpos", overlay_vals); + + /* change back */ + _assert_set_rate (rate0, "rate", 0.5, value); + + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* unchanged for overlay, which is after rate0 */ + _assert_control_source (overlay, "xpos", overlay_vals); + + /* make inactive */ + fail_unless (ges_track_element_set_active (rate0, FALSE)); + + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 17, 0.0); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* unchanged for overlay, which is after rate0 */ + _assert_control_source (overlay, "xpos", overlay_vals); + + /* make active again */ + fail_unless (ges_track_element_set_active (rate0, TRUE)); + + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* unchanged for overlay, which is after rate0 */ + _assert_control_source (overlay, "xpos", overlay_vals); + + /* change order */ + fail_unless (ges_clip_set_top_effect_index (clip, GES_BASE_EFFECT (overlay), + 2)); + + /* video source unchanged */ + _assert_control_source (video_source, "alpha", video_source_vals); + + /* new out-point is 13 + * new value is interpolated between the previous value + * (at time 7, value 11) and the *final* value (at time 19, value 14) + * Not the middle value at time 17, value 16! */ + _TWO_TIMED_VALS (overlay_vals, 7, 11, 13, 12.5); + _assert_control_source (overlay, "xpos", overlay_vals); + + /* removing time effect changes out-point */ + gst_object_ref (rate0); + fail_unless (ges_clip_remove_top_effect (clip, GES_BASE_EFFECT (rate0), + NULL)); + + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 17, 0.0); + _assert_control_source (video_source, "alpha", video_source_vals); + + _TWO_TIMED_VALS (overlay_vals, 7, 11, 19, 14); + _assert_control_source (overlay, "xpos", overlay_vals); + + /* adding also changes it */ + fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (rate0), 2, + NULL)); + + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* unchanged for overlay */ + _assert_control_source (overlay, "xpos", overlay_vals); + + /* new value will use the value already set at in-point if possible */ + + assert_set_inpoint (video_source, 7); + + _TWO_TIMED_VALS (video_source_vals, 7, 1.0, 13, 0.4); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* same with out-point for overlay */ + binding = ges_track_element_get_control_binding (overlay, "xpos"); + fail_unless (binding); + g_object_get (binding, "control-source", &ctrl_source, NULL); + + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 11, 5)); + gst_object_unref (ctrl_source); + _THREE_TIMED_VALS (overlay_vals, 7, 11, 11, 5, 19, 14); + + _assert_control_source (overlay, "xpos", overlay_vals); + + fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip), + -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 21, NULL)); + + _TWO_TIMED_VALS (video_source_vals, 7, 1.0, 9, 0.8); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* overlay uses existing value, rather than an interpolation */ + _TWO_TIMED_VALS (overlay_vals, 7, 11, 11, 5); + _assert_control_source (overlay, "xpos", overlay_vals); + + g_slist_free_full (video_source_vals, g_free); + g_slist_free_full (overlay_vals, g_free); + + gst_object_unref (timeline); + g_value_unset (&value); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_unchanged_after_layer_add_failure) +{ + GList *found; + GESTrack *track; + GESTimeline *timeline; + GESLayer *layer; + GESClip *clip0, *clip1; + GESTimelineElement *effect, *source; + + ges_init (); + + timeline = ges_timeline_new (); + layer = ges_timeline_append_layer (timeline); + + /* two video tracks */ + track = GES_TRACK (ges_video_track_new ()); + fail_unless (ges_timeline_add_track (timeline, track)); + + track = GES_TRACK (ges_video_track_new ()); + fail_unless (ges_timeline_add_track (timeline, track)); + + clip0 = GES_CLIP (ges_test_clip_new ()); + clip1 = GES_CLIP (ges_test_clip_new ()); + + gst_object_ref (clip0); + gst_object_ref (clip1); + + assert_set_start (clip0, 0); + assert_set_duration (clip0, 10); + assert_set_start (clip1, 0); + assert_set_duration (clip1, 10); + + effect = GES_TIMELINE_ELEMENT (ges_effect_new ("agingtv")); + _assert_add (clip1, effect); + + assert_num_children (clip0, 0); + assert_num_children (clip1, 1); + + fail_unless (ges_layer_add_clip (layer, clip0)); + + assert_num_children (clip0, 2); + assert_num_children (clip1, 1); + + fail_unless (GES_CONTAINER_CHILDREN (clip1)->data == effect); + + /* addition should fail since sources would fully overlap */ + fail_if (ges_layer_add_clip (layer, clip1)); + + /* children should be the same */ + assert_num_children (clip0, 2); + assert_num_children (clip1, 1); + + fail_unless (GES_CONTAINER_CHILDREN (clip1)->data == effect); + + /* should be able to add again once we have fixed the problem */ + fail_unless (ges_layer_remove_clip (layer, clip0)); + + assert_num_children (clip0, 2); + assert_num_children (clip1, 1); + + fail_unless (ges_layer_add_clip (layer, clip1)); + + assert_num_children (clip0, 2); + /* now has two sources and two effects */ + assert_num_children (clip1, 4); + + found = ges_clip_find_track_elements (clip1, NULL, GES_TRACK_TYPE_VIDEO, + GES_TYPE_VIDEO_SOURCE); + fail_unless_equals_int (g_list_length (found), 2); + g_list_free_full (found, gst_object_unref); + + found = ges_clip_find_track_elements (clip1, NULL, GES_TRACK_TYPE_VIDEO, + GES_TYPE_EFFECT); + fail_unless_equals_int (g_list_length (found), 2); + g_list_free_full (found, gst_object_unref); + + /* similarly cannot add clip0 back, and children should not change */ + /* remove the extra source */ + _assert_remove (clip0, GES_CONTAINER_CHILDREN (clip0)->data); + assert_num_children (clip0, 1); + source = GES_CONTAINER_CHILDREN (clip0)->data; + + fail_if (ges_layer_add_clip (layer, clip0)); + + /* children should be the same */ + assert_num_children (clip0, 1); + assert_num_children (clip1, 4); + + fail_unless (GES_CONTAINER_CHILDREN (clip0)->data == source); + + gst_object_unref (clip0); + gst_object_unref (clip1); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +#define _assert_timeline_to_internal(clip, child, in, expect_out) \ +{\ + GError *error = NULL; \ + GstClockTime found = ges_clip_get_internal_time_from_timeline_time ( \ + clip, child, (in) * GST_SECOND, &error); \ + GstClockTime expect = expect_out * GST_SECOND; \ + fail_unless (found == expect, "Conversion from timeline time %" \ + GST_TIME_FORMAT " to the internal time of %" GES_FORMAT " gave %" \ + GST_TIME_FORMAT " rather than the expected %" GST_TIME_FORMAT \ + " (error: %s)", GST_TIME_ARGS ((in) * GST_SECOND), \ + GES_ARGS (child), GST_TIME_ARGS (found), GST_TIME_ARGS (expect), \ + error ? error->message : "None"); \ + fail_if (error); \ +} + +#define _assert_timeline_to_internal_fails(clip, child, in, error_code) \ +{ \ + GError *error = NULL; \ + GstClockTime found = ges_clip_get_internal_time_from_timeline_time ( \ + clip, child, (in) * GST_SECOND, &error); \ + fail_if (GST_CLOCK_TIME_IS_VALID (found), "Conversion from timeline " \ + "time %" GST_TIME_FORMAT " to the internal time of %" GES_FORMAT \ + " successfully converted to %" GST_TIME_FORMAT " rather than " \ + "GST_CLOCK_TIME_NONE", GST_TIME_ARGS ((in) * GST_SECOND), \ + GES_ARGS (child), GST_TIME_ARGS (found)); \ + assert_GESError (error, error_code); \ +} + +#define _assert_internal_to_timeline(clip, child, in, expect_out) \ +{\ + GError *error = NULL; \ + GstClockTime found = ges_clip_get_timeline_time_from_internal_time ( \ + clip, child, (in) * GST_SECOND, &error); \ + GstClockTime expect = expect_out * GST_SECOND; \ + fail_unless (found == expect, "Conversion from the internal time %" \ + GST_TIME_FORMAT " of %" GES_FORMAT " to the timeline time gave %" \ + GST_TIME_FORMAT " rather than the expected %" GST_TIME_FORMAT, \ + GST_TIME_ARGS ((in) * GST_SECOND), GES_ARGS (child), \ + GST_TIME_ARGS (found), GST_TIME_ARGS (expect)); \ + fail_if (error); \ +} + +#define _assert_internal_to_timeline_fails(clip, child, in, error_code) \ +{\ + GError *error = NULL; \ + GstClockTime found = ges_clip_get_timeline_time_from_internal_time ( \ + clip, child, (in) * GST_SECOND, &error); \ + fail_if (GST_CLOCK_TIME_IS_VALID (found), "Conversion from the " \ + "internal time %" GST_TIME_FORMAT " of %" GES_FORMAT " to the " \ + "timeline time gave %" GST_TIME_FORMAT " rather than " \ + "GST_CLOCK_TIME_NONE", GST_TIME_ARGS ((in) * GST_SECOND), \ + GES_ARGS (child), GST_TIME_ARGS (found)); \ + assert_GESError (error, error_code); \ +} + +#define _assert_frame_to_timeline(clip, frame, expect_out) \ +{\ + GError *error = NULL; \ + GstClockTime found = ges_clip_get_timeline_time_from_source_frame ( \ + clip, frame, &error); \ + GstClockTime expect = expect_out * GST_SECOND; \ + fail_unless (found == expect, "Conversion from the source frame %" \ + G_GINT64_FORMAT " to the timeline time gave %" GST_TIME_FORMAT \ + " rather than the expected %" GST_TIME_FORMAT, frame, \ + GST_TIME_ARGS (found), GST_TIME_ARGS (expect)); \ + fail_if (error); \ +} + +#define _assert_frame_to_timeline_fails(clip, frame, error_code) \ +{\ + GError *error = NULL; \ + GstClockTime found = ges_clip_get_timeline_time_from_source_frame ( \ + clip, frame, &error); \ + fail_if (GST_CLOCK_TIME_IS_VALID (found), "Conversion from the " \ + "source frame %" G_GINT64_FORMAT " to the timeline time gave %" \ + GST_TIME_FORMAT " rather than the expected GST_CLOCK_TIME_NONE", \ + frame, GST_TIME_ARGS (found)); \ + assert_GESError (error, error_code); \ +} + +GST_START_TEST (test_convert_time) +{ + GESTimeline *timeline; + GESTrack *track0, *track1; + GESAsset *asset; + GESLayer *layer; + GESClip *clip; + GESTrackElement *source0, *source1, *rate0, *rate1, *rate2, *overlay; + GValue val = G_VALUE_INIT; + + ges_init (); + + asset = ges_asset_request (GES_TYPE_TEST_CLIP, + "framerate=30/1, max-duration=93.0", NULL); + fail_unless (asset); + + timeline = ges_timeline_new (); + + track0 = GES_TRACK (ges_video_track_new ()); + track1 = GES_TRACK (ges_video_track_new ()); + + fail_unless (ges_timeline_add_track (timeline, track0)); + fail_unless (ges_timeline_add_track (timeline, track1)); + + layer = ges_timeline_append_layer (timeline); + + clip = ges_layer_add_asset (layer, asset, 20 * GST_SECOND, + 13 * GST_SECOND, 10 * GST_SECOND, GES_TRACK_TYPE_VIDEO); + fail_unless (clip); + CHECK_OBJECT_PROPS_MAX (clip, 20 * GST_SECOND, 13 * GST_SECOND, + 10 * GST_SECOND, 93 * GST_SECOND); + + source0 = + ges_clip_find_track_element (clip, track0, GES_TYPE_VIDEO_TEST_SOURCE); + source1 = + ges_clip_find_track_element (clip, track1, GES_TYPE_VIDEO_TEST_SOURCE); + + rate0 = GES_TRACK_ELEMENT (ges_effect_new ("videorate")); + rate1 = GES_TRACK_ELEMENT (ges_effect_new ("videorate")); + rate2 = GES_TRACK_ELEMENT (ges_effect_new ("videorate")); + overlay = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay")); + ges_track_element_set_has_internal_source (overlay, TRUE); + /* enough internal content to last 10 seconds at a rate of 4.0 */ + assert_set_inpoint (overlay, 7 * GST_SECOND); + assert_set_max_duration (overlay, 50 * GST_SECOND); + + fail_unless (ges_track_add_element (track0, rate0)); + fail_unless (ges_track_add_element (track1, rate1)); + fail_unless (ges_track_add_element (track1, rate2)); + fail_unless (ges_track_add_element (track1, overlay)); + + _assert_add (clip, rate0); + _assert_add (clip, rate2); + _assert_add (clip, overlay); + _assert_add (clip, rate1); + + /* in track0: + * + * source0 -> rate0 -> out + * + * in track1: + * + * source1 -> rate1 -> overlay -> rate2 -> out + */ + + g_value_init (&val, G_TYPE_DOUBLE); + + _assert_rate_equal (rate0, "rate", 1.0, val); + _assert_rate_equal (rate1, "rate", 1.0, val); + _assert_rate_equal (rate2, "rate", 1.0, val); + + /* without rates */ + + /* start of the clip */ + _assert_internal_to_timeline (clip, source0, 13, 20); + _assert_internal_to_timeline (clip, source1, 13, 20); + _assert_internal_to_timeline (clip, overlay, 7, 20); + _assert_frame_to_timeline (clip, 390, 20); + _assert_timeline_to_internal (clip, source0, 20, 13); + _assert_timeline_to_internal (clip, source1, 20, 13); + _assert_timeline_to_internal (clip, overlay, 20, 7); + + /* middle of the clip */ + _assert_internal_to_timeline (clip, source0, 18, 25); + _assert_internal_to_timeline (clip, source1, 18, 25); + _assert_internal_to_timeline (clip, overlay, 12, 25); + _assert_frame_to_timeline (clip, 540, 25); + _assert_timeline_to_internal (clip, source0, 25, 18); + _assert_timeline_to_internal (clip, source1, 25, 18); + _assert_timeline_to_internal (clip, overlay, 25, 12); + + /* end of the clip */ + _assert_internal_to_timeline (clip, source0, 23, 30); + _assert_internal_to_timeline (clip, source1, 23, 30); + _assert_internal_to_timeline (clip, overlay, 17, 30); + _assert_frame_to_timeline (clip, 690, 30); + _assert_timeline_to_internal (clip, source0, 30, 23); + _assert_timeline_to_internal (clip, source1, 30, 23); + _assert_timeline_to_internal (clip, overlay, 30, 17); + + /* beyond the end of the clip */ + /* exceeds the max-duration of the elements, but that is ok */ + _assert_internal_to_timeline (clip, source0, 123, 130); + _assert_internal_to_timeline (clip, source1, 123, 130); + _assert_internal_to_timeline (clip, overlay, 117, 130); + _assert_frame_to_timeline (clip, 3690, 130); + _assert_timeline_to_internal (clip, source0, 130, 123); + _assert_timeline_to_internal (clip, source1, 130, 123); + _assert_timeline_to_internal (clip, overlay, 130, 117); + + /* before the start of the clip */ + _assert_internal_to_timeline (clip, source0, 8, 15); + _assert_internal_to_timeline (clip, source1, 8, 15); + _assert_internal_to_timeline (clip, overlay, 2, 15); + _assert_frame_to_timeline (clip, 240, 15); + _assert_timeline_to_internal (clip, source0, 15, 8); + _assert_timeline_to_internal (clip, source1, 15, 8); + _assert_timeline_to_internal (clip, overlay, 15, 2); + + /* too early for overlay */ + _assert_timeline_to_internal (clip, source0, 10, 3); + _assert_timeline_to_internal (clip, source1, 10, 3); + _assert_timeline_to_internal_fails (clip, overlay, 10, + GES_ERROR_NEGATIVE_TIME); + + /* too early for sources */ + _assert_timeline_to_internal_fails (clip, source0, 5, + GES_ERROR_NEGATIVE_TIME); + _assert_timeline_to_internal_fails (clip, source1, 5, + GES_ERROR_NEGATIVE_TIME); + _assert_timeline_to_internal_fails (clip, overlay, 5, + GES_ERROR_NEGATIVE_TIME); + + assert_set_start (clip, 10 * GST_SECOND); + + /* too early in the timeline */ + _assert_internal_to_timeline_fails (clip, source0, 2, + GES_ERROR_NEGATIVE_TIME); + _assert_internal_to_timeline_fails (clip, source1, 2, + GES_ERROR_NEGATIVE_TIME); + _assert_internal_to_timeline (clip, overlay, 2, 5); + _assert_frame_to_timeline_fails (clip, 60, GES_ERROR_INVALID_FRAME_NUMBER); + + assert_set_start (clip, 6 * GST_SECOND); + _assert_internal_to_timeline_fails (clip, source0, 6, + GES_ERROR_NEGATIVE_TIME); + _assert_internal_to_timeline_fails (clip, source1, 6, + GES_ERROR_NEGATIVE_TIME); + _assert_internal_to_timeline_fails (clip, overlay, 0, + GES_ERROR_NEGATIVE_TIME); + _assert_frame_to_timeline_fails (clip, 180, GES_ERROR_INVALID_FRAME_NUMBER); + + assert_set_start (clip, 20 * GST_SECOND); + + /* now with rate effects + * Note, they are currently out of sync */ + _assert_set_rate (rate0, "rate", 0.5, val); + _assert_set_rate (rate1, "rate", 2.0, val); + _assert_set_rate (rate2, "rate", 4.0, val); + + CHECK_OBJECT_PROPS_MAX (clip, 20 * GST_SECOND, 13 * GST_SECOND, + 10 * GST_SECOND, 93 * GST_SECOND); + + /* start of the clip is the same */ + _assert_internal_to_timeline (clip, source0, 13, 20); + _assert_internal_to_timeline (clip, source1, 13, 20); + _assert_internal_to_timeline (clip, overlay, 7, 20); + _assert_timeline_to_internal (clip, source0, 20, 13); + _assert_timeline_to_internal (clip, source1, 20, 13); + _assert_timeline_to_internal (clip, overlay, 20, 7); + + /* middle is different */ + /* 5 seconds in the timeline is 2.5 seconds into the source */ + _assert_internal_to_timeline (clip, source0, 15.5, 25); + /* 5 seconds in the timeline is 40 seconds into the source */ + _assert_internal_to_timeline (clip, source1, 53, 25); + /* 5 seconds in the timeline is 20 seconds into the source */ + _assert_internal_to_timeline (clip, overlay, 27, 25); + /* reverse */ + _assert_timeline_to_internal (clip, source0, 25, 15.5); + _assert_timeline_to_internal (clip, source1, 25, 53); + _assert_timeline_to_internal (clip, overlay, 25, 27); + + /* end is different */ + _assert_internal_to_timeline (clip, source0, 18, 30); + _assert_internal_to_timeline (clip, source1, 93, 30); + _assert_internal_to_timeline (clip, overlay, 47, 30); + _assert_timeline_to_internal (clip, source0, 30, 18); + _assert_timeline_to_internal (clip, source1, 30, 93); + _assert_timeline_to_internal (clip, overlay, 30, 47); + + /* beyond end is different */ + _assert_internal_to_timeline (clip, source0, 68, 130); + _assert_internal_to_timeline (clip, source1, 893, 130); + _assert_internal_to_timeline (clip, overlay, 447, 130); + _assert_timeline_to_internal (clip, source0, 130, 68); + _assert_timeline_to_internal (clip, source1, 130, 893); + _assert_timeline_to_internal (clip, overlay, 130, 447); + + /* before the start */ + _assert_internal_to_timeline (clip, source0, 12.5, 19); + _assert_internal_to_timeline (clip, source1, 5, 19); + _assert_internal_to_timeline (clip, overlay, 3, 19); + _assert_timeline_to_internal (clip, source0, 19, 12.5); + _assert_timeline_to_internal (clip, source1, 19, 5); + _assert_timeline_to_internal (clip, overlay, 19, 3); + + /* too early for source1 and overlay */ + _assert_internal_to_timeline (clip, source0, 12, 18); + _assert_timeline_to_internal (clip, source0, 18, 12); + _assert_timeline_to_internal_fails (clip, source1, 18, + GES_ERROR_NEGATIVE_TIME); + _assert_timeline_to_internal_fails (clip, overlay, 18, + GES_ERROR_NEGATIVE_TIME); + + assert_set_inpoint (overlay, 8 * GST_SECOND); + /* now fine */ + _assert_internal_to_timeline (clip, overlay, 0, 18); + _assert_timeline_to_internal (clip, overlay, 18, 0); + + assert_set_inpoint (overlay, 7 * GST_SECOND); + + /* still not too early for source0 */ + _assert_internal_to_timeline (clip, source0, 5.5, 5); + _assert_timeline_to_internal (clip, source0, 5, 5.5); + _assert_timeline_to_internal_fails (clip, source1, 5, + GES_ERROR_NEGATIVE_TIME); + _assert_timeline_to_internal_fails (clip, overlay, 5, + GES_ERROR_NEGATIVE_TIME); + + _assert_internal_to_timeline (clip, source0, 3, 0); + _assert_timeline_to_internal (clip, source0, 0, 3); + _assert_timeline_to_internal_fails (clip, source1, 5, + GES_ERROR_NEGATIVE_TIME); + _assert_timeline_to_internal_fails (clip, overlay, 5, + GES_ERROR_NEGATIVE_TIME); + + /* too early for the timeline */ + _assert_internal_to_timeline_fails (clip, source0, 2, + GES_ERROR_NEGATIVE_TIME); + + /* re-sync rates between tracks */ + _assert_set_rate (rate2, "rate", 0.25, val); + + CHECK_OBJECT_PROPS_MAX (clip, 20 * GST_SECOND, 13 * GST_SECOND, + 10 * GST_SECOND, 93 * GST_SECOND); + + /* start of the clip */ + _assert_internal_to_timeline (clip, source0, 13, 20); + _assert_internal_to_timeline (clip, source1, 13, 20); + _assert_internal_to_timeline (clip, overlay, 7, 20); + _assert_frame_to_timeline (clip, 390, 20); + _assert_timeline_to_internal (clip, source0, 20, 13); + _assert_timeline_to_internal (clip, source1, 20, 13); + _assert_timeline_to_internal (clip, overlay, 20, 7); + + /* middle of the clip */ + _assert_internal_to_timeline (clip, source0, 15.5, 25); + _assert_internal_to_timeline (clip, source1, 15.5, 25); + _assert_internal_to_timeline (clip, overlay, 8.25, 25); + _assert_frame_to_timeline (clip, 465, 25); + _assert_timeline_to_internal (clip, source0, 25, 15.5); + _assert_timeline_to_internal (clip, source1, 25, 15.5); + _assert_timeline_to_internal (clip, overlay, 25, 8.25); + + /* end of the clip */ + _assert_internal_to_timeline (clip, source0, 18, 30); + _assert_internal_to_timeline (clip, source1, 18, 30); + _assert_internal_to_timeline (clip, overlay, 9.5, 30); + _assert_frame_to_timeline (clip, 540, 30); + _assert_timeline_to_internal (clip, source0, 30, 18); + _assert_timeline_to_internal (clip, source1, 30, 18); + _assert_timeline_to_internal (clip, overlay, 30, 9.5); + + /* beyond the end of the clip */ + /* exceeds the max-duration of the elements, but that is ok */ + _assert_internal_to_timeline (clip, source0, 68, 130); + _assert_internal_to_timeline (clip, source1, 68, 130); + _assert_internal_to_timeline (clip, overlay, 34.5, 130); + _assert_frame_to_timeline (clip, 2040, 130); + _assert_timeline_to_internal (clip, source0, 130, 68); + _assert_timeline_to_internal (clip, source1, 130, 68); + _assert_timeline_to_internal (clip, overlay, 130, 34.5); + + /* before the start of the clip */ + _assert_internal_to_timeline (clip, source0, 10.5, 15); + _assert_internal_to_timeline (clip, source1, 10.5, 15); + _assert_internal_to_timeline (clip, overlay, 5.75, 15); + _assert_frame_to_timeline (clip, 315, 15); + _assert_timeline_to_internal (clip, source0, 15, 10.5); + _assert_timeline_to_internal (clip, source1, 15, 10.5); + _assert_timeline_to_internal (clip, overlay, 15, 5.75); + + /* not too early */ + _assert_internal_to_timeline (clip, source0, 3, 0); + _assert_internal_to_timeline (clip, source1, 3, 0); + _assert_internal_to_timeline (clip, overlay, 2, 0); + _assert_frame_to_timeline (clip, 90, 0); + _assert_timeline_to_internal (clip, source0, 0, 3); + _assert_timeline_to_internal (clip, source1, 0, 3); + _assert_timeline_to_internal (clip, overlay, 0, 2); + + /* too early for timeline */ + _assert_internal_to_timeline_fails (clip, source0, 2, + GES_ERROR_NEGATIVE_TIME); + _assert_internal_to_timeline_fails (clip, source1, 2, + GES_ERROR_NEGATIVE_TIME); + _assert_internal_to_timeline_fails (clip, overlay, 1, + GES_ERROR_NEGATIVE_TIME); + _assert_frame_to_timeline_fails (clip, 89, GES_ERROR_INVALID_FRAME_NUMBER); + + assert_set_start (clip, 30 * GST_SECOND); + /* timeline times have shifted by 10 */ + _assert_timeline_to_internal (clip, source0, 10, 3); + _assert_timeline_to_internal (clip, source1, 10, 3); + _assert_timeline_to_internal (clip, overlay, 10, 2); + + _assert_timeline_to_internal (clip, source0, 4, 0); + _assert_timeline_to_internal (clip, source1, 4, 0); + _assert_timeline_to_internal (clip, overlay, 2, 0); + /* too early for internal */ + _assert_timeline_to_internal_fails (clip, source0, 3, + GES_ERROR_NEGATIVE_TIME); + _assert_timeline_to_internal_fails (clip, source1, 3, + GES_ERROR_NEGATIVE_TIME); + _assert_timeline_to_internal_fails (clip, overlay, 1, + GES_ERROR_NEGATIVE_TIME); + + g_value_unset (&val); + gst_object_unref (asset); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-clip"); + TCase *tc_chain = tcase_create ("clip"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_object_properties); + tcase_add_test (tc_chain, test_split_object); + tcase_add_test (tc_chain, test_split_ordering); + tcase_add_test (tc_chain, test_split_direct_bindings); + tcase_add_test (tc_chain, test_split_direct_absolute_bindings); + tcase_add_test (tc_chain, test_split_with_auto_transitions); + tcase_add_test (tc_chain, test_clip_group_ungroup); + tcase_add_test (tc_chain, test_clip_can_group); + tcase_add_test (tc_chain, test_adding_children_to_track); + tcase_add_test (tc_chain, test_clip_refcount_remove_child); + tcase_add_test (tc_chain, test_clip_find_track_element); + tcase_add_test (tc_chain, test_effects_priorities); + tcase_add_test (tc_chain, test_children_time_setters); + tcase_add_test (tc_chain, test_not_enough_internal_content_for_core); + tcase_add_test (tc_chain, test_can_add_effect); + tcase_add_test (tc_chain, test_children_active); + tcase_add_test (tc_chain, test_children_inpoint); + tcase_add_test (tc_chain, test_children_max_duration); + tcase_add_test (tc_chain, test_duration_limit); + tcase_add_test (tc_chain, test_can_set_duration_limit); + tcase_add_test (tc_chain, test_rate_effects_duration_limit); + tcase_add_test (tc_chain, test_children_properties_contain); + tcase_add_test (tc_chain, test_children_properties_change); + tcase_add_test (tc_chain, test_copy_paste_children_properties); + tcase_add_test (tc_chain, test_children_property_bindings_with_rate_effects); + tcase_add_test (tc_chain, test_unchanged_after_layer_add_failure); + tcase_add_test (tc_chain, test_convert_time); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/effects.c b/tests/check/ges/effects.c new file mode 100644 index 0000000000..b89c0780bf --- /dev/null +++ b/tests/check/ges/effects.c @@ -0,0 +1,913 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Thibault Saunier <tsaunier@gnome.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" +#include <ges/ges.h> +#include <gst/check/gstcheck.h> + +void +deep_prop_changed_cb (GESTrackElement * track_element, GstElement * element, + GParamSpec * spec); + +GST_START_TEST (test_effect_basic) +{ + GESEffect *effect; + + ges_init (); + + effect = ges_effect_new ("agingtv"); + fail_unless (effect != NULL); + gst_object_unref (effect); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_add_effect_to_clip) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *track_audio, *track_video; + GESEffect *effect; + GESTestClip *source; + + ges_init (); + + timeline = ges_timeline_new (); + layer = ges_layer_new (); + track_audio = GES_TRACK (ges_audio_track_new ()); + track_video = GES_TRACK (ges_video_track_new ()); + + ges_timeline_add_track (timeline, track_audio); + ges_timeline_add_track (timeline, track_video); + ges_timeline_add_layer (timeline, layer); + + source = ges_test_clip_new (); + + g_object_set (source, "duration", 10 * GST_SECOND, NULL); + + ges_layer_add_clip (layer, (GESClip *) source); + + + GST_DEBUG ("Create effect"); + effect = ges_effect_new ("agingtv"); + + fail_unless (GES_IS_EFFECT (effect)); + fail_unless (ges_container_add (GES_CONTAINER (source), + GES_TIMELINE_ELEMENT (effect))); + fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect)) != + NULL); + + assert_equals_int (GES_TRACK_ELEMENT (effect)->active, TRUE); + + ges_layer_remove_clip (layer, (GESClip *) source); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_get_effects_from_tl) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *track_video; + GESTrackElement *video_source; + GESEffect *effect, *effect1, *effect2; + GESTestClip *source; + GList *effects, *tmp = NULL; + gint effect_prio = -1; + + ges_init (); + + timeline = ges_timeline_new (); + layer = ges_layer_new (); + track_video = GES_TRACK (ges_video_track_new ()); + + ges_timeline_add_track (timeline, track_video); + ges_timeline_add_layer (timeline, layer); + + source = ges_test_clip_new (); + + g_object_set (source, "duration", 10 * GST_SECOND, NULL); + + GST_DEBUG ("Adding source to layer"); + ges_layer_add_clip (layer, (GESClip *) source); + assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (source)), 1); + video_source = GES_CONTAINER_CHILDREN (source)->data; + fail_unless (GES_IS_VIDEO_TEST_SOURCE (video_source)); + assert_equals_int (_PRIORITY (video_source), + MIN_NLE_PRIO + TRANSITIONS_HEIGHT); + + GST_DEBUG ("Create effect"); + effect = ges_effect_new ("agingtv"); + effect1 = ges_effect_new ("agingtv"); + effect2 = ges_effect_new ("agingtv"); + + fail_unless (GES_IS_EFFECT (effect)); + fail_unless (GES_IS_EFFECT (effect1)); + fail_unless (GES_IS_EFFECT (effect2)); + + GST_DEBUG ("Adding effect (0)"); + fail_unless (ges_container_add (GES_CONTAINER (source), + GES_TIMELINE_ELEMENT (effect))); + fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect)) == + track_video); + assert_equals_int (_PRIORITY (effect), MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0); + assert_equals_int (_PRIORITY (video_source), + MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 1); + + GST_DEBUG ("Adding effect 1"); + fail_unless (ges_container_add (GES_CONTAINER (source), + GES_TIMELINE_ELEMENT (effect1))); + fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect1)) == + track_video); + assert_equals_int (_PRIORITY (effect), MIN_NLE_PRIO + TRANSITIONS_HEIGHT); + assert_equals_int (_PRIORITY (effect1), + MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 1); + assert_equals_int (_PRIORITY (video_source), + MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 2); + + GST_DEBUG ("Adding effect 2"); + fail_unless (ges_container_add (GES_CONTAINER (source), + GES_TIMELINE_ELEMENT (effect2))); + fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect2)) == + track_video); + assert_equals_int (GES_CONTAINER_HEIGHT (source), 4); + + effects = ges_clip_get_top_effects (GES_CLIP (source)); + fail_unless (g_list_length (effects) == 3); + for (tmp = effects; tmp; tmp = tmp->next) { + gint priority = ges_clip_get_top_effect_position (GES_CLIP (source), + GES_BASE_EFFECT (tmp->data)); + fail_unless (priority > effect_prio); + fail_unless (GES_IS_EFFECT (tmp->data)); + effect_prio = priority; + + gst_object_unref (tmp->data); + } + g_list_free (effects); + + ges_layer_remove_clip (layer, (GESClip *) source); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_effect_clip) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *track_audio, *track_video; + GESEffectClip *effect_clip; + GESEffect *effect, *effect1, *core_effect, *core_effect1; + GList *children, *top_effects, *tmp; + gint clip_height; + gint core_effect_prio; + gint effect_index, effect1_index; + + ges_init (); + + timeline = ges_timeline_new (); + layer = ges_layer_new (); + track_audio = GES_TRACK (ges_audio_track_new ()); + track_video = GES_TRACK (ges_video_track_new ()); + + ges_timeline_add_track (timeline, track_audio); + ges_timeline_add_track (timeline, track_video); + ges_timeline_add_layer (timeline, layer); + + GST_DEBUG ("Create effect"); + /* these are the core video and audio effects for the clip */ + effect_clip = ges_effect_clip_new ("videobalance", "audioecho"); + + g_object_set (effect_clip, "duration", 25 * GST_SECOND, NULL); + + ges_layer_add_clip (layer, (GESClip *) effect_clip); + + /* core elements should now be created */ + fail_unless (children = GES_CONTAINER_CHILDREN (effect_clip)); + core_effect = GES_EFFECT (children->data); + fail_unless (children = children->next); + core_effect1 = GES_EFFECT (children->data); + fail_unless (children->next == NULL); + + /* both effects are placed at the same priority since they are core + * children of the clip, destined for different tracks */ + core_effect_prio = _PRIORITY (core_effect); + assert_equals_int (core_effect_prio, _PRIORITY (core_effect1)); + g_object_get (effect_clip, "height", &clip_height, NULL); + assert_equals_int (clip_height, 1); + + /* add additional non-core effects */ + effect = ges_effect_new ("agingtv"); + fail_unless (ges_container_add (GES_CONTAINER (effect_clip), + GES_TIMELINE_ELEMENT (effect))); + fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect)) == + track_video); + + /* placed at a higher priority than the effects */ + core_effect_prio = _PRIORITY (core_effect); + assert_equals_int (core_effect_prio, _PRIORITY (core_effect1)); + fail_unless (_PRIORITY (effect) < core_effect_prio); + g_object_get (effect_clip, "height", &clip_height, NULL); + assert_equals_int (clip_height, 2); + + effect_index = + ges_clip_get_top_effect_index (GES_CLIP (effect_clip), + GES_BASE_EFFECT (effect)); + assert_equals_int (effect_index, 0); + + /* 'effect1' is placed in between the core children and 'effect' */ + effect1 = ges_effect_new ("audiopanorama"); + fail_unless (ges_container_add (GES_CONTAINER (effect_clip), + GES_TIMELINE_ELEMENT (effect1))); + fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect1)) == + track_audio); + + /* 'effect' is still the highest priority effect, and the core + * elements are at the lowest priority */ + core_effect_prio = _PRIORITY (core_effect); + assert_equals_int (core_effect_prio, _PRIORITY (core_effect1)); + fail_unless (_PRIORITY (effect1) < core_effect_prio); + fail_unless (_PRIORITY (effect1) > _PRIORITY (effect)); + g_object_get (effect_clip, "height", &clip_height, NULL); + assert_equals_int (clip_height, 3); + + effect_index = + ges_clip_get_top_effect_index (GES_CLIP (effect_clip), + GES_BASE_EFFECT (effect)); + effect1_index = + ges_clip_get_top_effect_index (GES_CLIP (effect_clip), + GES_BASE_EFFECT (effect1)); + assert_equals_int (effect_index, 0); + assert_equals_int (effect1_index, 1); + + /* all effects are children of the effect_clip, ordered by priority */ + fail_unless (children = GES_CONTAINER_CHILDREN (effect_clip)); + fail_unless (children->data == effect); + fail_unless (children = children->next); + fail_unless (children->data == effect1); + fail_unless (children = children->next); + fail_unless (children->data == core_effect); + fail_unless (children = children->next); + fail_unless (children->data == core_effect1); + fail_unless (children->next == NULL); + + /* but only the additional effects are part of the top effects */ + top_effects = ges_clip_get_top_effects (GES_CLIP (effect_clip)); + fail_unless (tmp = top_effects); + fail_unless (tmp->data == effect); + fail_unless (tmp = tmp->next); + fail_unless (tmp->data == effect1); + fail_unless (tmp->next == NULL); + + g_list_free_full (top_effects, gst_object_unref); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_priorities_clip) +{ + GList *top_effects, *tmp; + GESTimeline *timeline; + GESLayer *layer; + GESClip *effect_clip; + GESTrack *track_audio, *track_video, *track; + GESBaseEffect *effects[6], *audio_effect = NULL, *video_effect = NULL; + gint prev_index, i, num_effects = G_N_ELEMENTS (effects); + guint32 base_prio = MIN_NLE_PRIO + TRANSITIONS_HEIGHT; + + ges_init (); + + timeline = ges_timeline_new (); + layer = ges_layer_new (); + track_audio = GES_TRACK (ges_audio_track_new ()); + track_video = GES_TRACK (ges_video_track_new ()); + + ges_timeline_add_track (timeline, track_audio); + ges_timeline_add_track (timeline, track_video); + ges_timeline_add_layer (timeline, layer); + + GST_DEBUG ("Create effect"); + effect_clip = GES_CLIP (ges_effect_clip_new ("videobalance", "audioecho")); + + g_object_set (effect_clip, "duration", 25 * GST_SECOND, NULL); + + ges_layer_add_clip ((layer), (GESClip *) effect_clip); + for (tmp = GES_CONTAINER_CHILDREN (effect_clip); tmp; tmp = tmp->next) { + if (ges_track_element_get_track_type (GES_TRACK_ELEMENT (tmp->data)) == + GES_TRACK_TYPE_AUDIO) + audio_effect = tmp->data; + else if (ges_track_element_get_track_type (GES_TRACK_ELEMENT (tmp->data)) == + GES_TRACK_TYPE_VIDEO) + video_effect = tmp->data; + else + g_assert (0); + } + fail_unless (GES_IS_EFFECT (audio_effect)); + fail_unless (GES_IS_EFFECT (video_effect)); + fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (audio_effect)) == + track_audio); + fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (video_effect)) == + track_video); + + /* both the core effects have the same priority */ + assert_equals_int (_PRIORITY (audio_effect), base_prio); + assert_equals_int (_PRIORITY (video_effect), base_prio); + assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 1); + + /* can not change their priority using the top effect methods since + * they are not top effects */ + fail_unless (ges_clip_set_top_effect_index (effect_clip, audio_effect, 1) + == FALSE); + fail_unless (ges_clip_set_top_effect_index (effect_clip, video_effect, 0) + == FALSE); + + /* adding non-core effects */ + GST_DEBUG ("Adding effects to the effect clip "); + for (i = 0; i < num_effects; i++) { + if (i % 2) + effects[i] = GES_BASE_EFFECT (ges_effect_new ("agingtv")); + else + effects[i] = GES_BASE_EFFECT (ges_effect_new ("audiopanorama")); + fail_unless (ges_container_add (GES_CONTAINER (effect_clip), + GES_TIMELINE_ELEMENT (effects[i]))); + assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 2 + i); + track = ges_track_element_get_track (GES_TRACK_ELEMENT (effects[i])); + if (i % 2) + fail_unless (track == track_video); + else + fail_unless (track == track_audio); + } + + /* change top effect index */ + for (i = 0; i < num_effects; i++) { + assert_equals_int (ges_clip_get_top_effect_index (effect_clip, effects[i]), + i); + assert_equals_int (_PRIORITY (effects[i]), i + base_prio); + } + + assert_equals_int (_PRIORITY (video_effect), num_effects + base_prio); + assert_equals_int (_PRIORITY (audio_effect), num_effects + base_prio); + assert_equals_int (_PRIORITY (effect_clip), 1); + assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), num_effects + 1); + + /* moving 4th effect to index 1 should only change the priority of + * effects 1, 2, 3, and 4 because these lie between the new index (1) + * and the old index (4). */ + fail_unless (ges_clip_set_top_effect_index (effect_clip, effects[4], 1)); + + assert_equals_int (_PRIORITY (effects[0]), 0 + base_prio); + assert_equals_int (_PRIORITY (effects[1]), 2 + base_prio); + assert_equals_int (_PRIORITY (effects[2]), 3 + base_prio); + assert_equals_int (_PRIORITY (effects[3]), 4 + base_prio); + assert_equals_int (_PRIORITY (effects[4]), 1 + base_prio); + assert_equals_int (_PRIORITY (effects[5]), 5 + base_prio); + + /* everything else stays the same */ + assert_equals_int (_PRIORITY (video_effect), num_effects + base_prio); + assert_equals_int (_PRIORITY (audio_effect), num_effects + base_prio); + assert_equals_int (_PRIORITY (effect_clip), 1); + assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), num_effects + 1); + + /* move back */ + fail_unless (ges_clip_set_top_effect_index (effect_clip, effects[4], 4)); + + for (i = 0; i < num_effects; i++) { + assert_equals_int (ges_clip_get_top_effect_index (effect_clip, effects[i]), + i); + assert_equals_int (_PRIORITY (effects[i]), i + base_prio); + } + + assert_equals_int (_PRIORITY (video_effect), num_effects + base_prio); + assert_equals_int (_PRIORITY (audio_effect), num_effects + base_prio); + assert_equals_int (_PRIORITY (effect_clip), 1); + assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), num_effects + 1); + + /* moving 2nd effect to index 4 should only change the priority of + * effects 2, 3 and 4 because these lie between the new index (4) and + * the old index (2). */ + + fail_unless (ges_clip_set_top_effect_index (effect_clip, effects[2], 4)); + + assert_equals_int (_PRIORITY (effects[0]), 0 + base_prio); + assert_equals_int (_PRIORITY (effects[1]), 1 + base_prio); + assert_equals_int (_PRIORITY (effects[2]), 4 + base_prio); + assert_equals_int (_PRIORITY (effects[3]), 2 + base_prio); + assert_equals_int (_PRIORITY (effects[4]), 3 + base_prio); + assert_equals_int (_PRIORITY (effects[5]), 5 + base_prio); + + /* everything else stays the same */ + assert_equals_int (_PRIORITY (video_effect), num_effects + base_prio); + assert_equals_int (_PRIORITY (audio_effect), num_effects + base_prio); + assert_equals_int (_PRIORITY (effect_clip), 1); + assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), num_effects + 1); + + /* move 4th effect to index 0 should only change the priority of + * effects 0, 1, 3 and 4 because these lie between the new index (0) and + * the old index (3) */ + + fail_unless (ges_clip_set_top_effect_index (effect_clip, effects[4], 0)); + + assert_equals_int (_PRIORITY (effects[0]), 1 + base_prio); + assert_equals_int (_PRIORITY (effects[1]), 2 + base_prio); + assert_equals_int (_PRIORITY (effects[2]), 4 + base_prio); + assert_equals_int (_PRIORITY (effects[3]), 3 + base_prio); + assert_equals_int (_PRIORITY (effects[4]), 0 + base_prio); + assert_equals_int (_PRIORITY (effects[5]), 5 + base_prio); + + /* everything else stays the same */ + assert_equals_int (_PRIORITY (video_effect), num_effects + base_prio); + assert_equals_int (_PRIORITY (audio_effect), num_effects + base_prio); + assert_equals_int (_PRIORITY (effect_clip), 1); + assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), num_effects + 1); + + /* make sure top effects are ordered by index */ + top_effects = ges_clip_get_top_effects (effect_clip); + prev_index = -1; + for (tmp = top_effects; tmp; tmp = tmp->next) { + gint index = ges_clip_get_top_effect_index (effect_clip, + GES_BASE_EFFECT (tmp->data)); + fail_unless (index >= 0); + fail_unless (index > prev_index); + fail_unless (GES_IS_EFFECT (tmp->data)); + prev_index = index; + } + g_list_free_full (top_effects, gst_object_unref); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_effect_set_properties) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *track_video; + GESEffectClip *effect_clip; + GESTimelineElement *effect; + guint scratch_line, n_props, i; + gboolean color_aging; + GParamSpec **pspecs, *spec; + GValue val = { 0 }; + GValue nval = { 0 }; + + ges_init (); + + timeline = ges_timeline_new (); + layer = ges_layer_new (); + track_video = GES_TRACK (ges_video_track_new ()); + + ges_timeline_add_track (timeline, track_video); + ges_timeline_add_layer (timeline, layer); + + GST_DEBUG ("Create effect"); + effect_clip = ges_effect_clip_new ("agingtv", NULL); + + g_object_set (effect_clip, "duration", 25 * GST_SECOND, NULL); + + ges_layer_add_clip (layer, (GESClip *) effect_clip); + + effect = GES_TIMELINE_ELEMENT (ges_effect_new ("agingtv")); + fail_unless (ges_container_add (GES_CONTAINER (effect_clip), + GES_TIMELINE_ELEMENT (effect))); + fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect)) == + track_video); + + ges_timeline_element_set_child_properties (effect, + "GstAgingTV::scratch-lines", 17, "color-aging", FALSE, NULL); + ges_timeline_element_get_child_properties (effect, + "GstAgingTV::scratch-lines", &scratch_line, + "color-aging", &color_aging, NULL); + fail_unless (scratch_line == 17); + fail_unless (color_aging == FALSE); + + pspecs = ges_timeline_element_list_children_properties (effect, &n_props); + fail_unless (n_props == 7); + + spec = pspecs[0]; + i = 1; + while (g_strcmp0 (spec->name, "scratch-lines")) { + spec = pspecs[i++]; + } + + g_value_init (&val, G_TYPE_UINT); + g_value_init (&nval, G_TYPE_UINT); + g_value_set_uint (&val, 10); + + ges_timeline_element_set_child_property_by_pspec (effect, spec, &val); + ges_timeline_element_get_child_property_by_pspec (effect, spec, &nval); + fail_unless (g_value_get_uint (&nval) == 10); + + for (i = 0; i < n_props; i++) { + g_param_spec_unref (pspecs[i]); + } + g_free (pspecs); + + ges_layer_remove_clip (layer, (GESClip *) effect_clip); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +static void +effect_added_cb (GESClip * clip, GESBaseEffect * trop, gboolean * effect_added) +{ + GST_DEBUG ("Effect added"); + fail_unless (GES_IS_CLIP (clip)); + fail_unless (GES_IS_EFFECT (trop)); + *effect_added = TRUE; +} + +void +deep_prop_changed_cb (GESTrackElement * track_element, GstElement * element, + GParamSpec * spec) +{ + GST_DEBUG ("%s property changed", g_param_spec_get_name (spec)); + fail_unless (GES_IS_TRACK_ELEMENT (track_element)); + fail_unless (GST_IS_ELEMENT (element)); +} + +GST_START_TEST (test_clip_signals) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *track_video; + GESEffectClip *effect_clip; + GESTimelineElement *effect; + GValue val = { 0, }; + gboolean effect_added = FALSE; + + ges_init (); + + timeline = ges_timeline_new (); + layer = ges_layer_new (); + track_video = GES_TRACK (ges_video_track_new ()); + + ges_timeline_add_track (timeline, track_video); + ges_timeline_add_layer (timeline, layer); + + GST_DEBUG ("Create effect"); + effect_clip = ges_effect_clip_new ("agingtv", NULL); + g_signal_connect (effect_clip, "child-added", (GCallback) effect_added_cb, + &effect_added); + + g_object_set (effect_clip, "duration", 25 * GST_SECOND, NULL); + + ges_layer_add_clip (layer, (GESClip *) effect_clip); + + effect = GES_TIMELINE_ELEMENT (ges_effect_new ("agingtv")); + fail_unless (ges_container_add (GES_CONTAINER (effect_clip), effect)); + fail_unless (effect_added); + g_signal_handlers_disconnect_by_func (effect_clip, effect_added_cb, + &effect_added); + fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect)) == + track_video); + g_signal_connect (effect, "deep-notify", (GCallback) deep_prop_changed_cb, + effect); + + ges_timeline_element_set_child_properties (effect, + "GstAgingTV::scratch-lines", 17, NULL); + + g_value_init (&val, G_TYPE_UINT); + ges_timeline_element_get_child_property (effect, + "GstAgingTV::scratch-lines", &val); + fail_unless (G_VALUE_HOLDS_UINT (&val)); + g_value_unset (&val); + + ges_layer_remove_clip (layer, (GESClip *) effect_clip); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_split_clip_effect_priorities) +{ + GESLayer *layer; + GESTimeline *timeline; + GESTrack *track_video; + GESClip *clip, *nclip; + GESEffect *effect; + GESTrackElement *source, *nsource, *neffect; + + ges_init (); + + timeline = ges_timeline_new (); + layer = ges_timeline_append_layer (timeline); + track_video = GES_TRACK (ges_video_track_new ()); + + g_object_set (timeline, "auto-transition", TRUE, NULL); + ges_timeline_add_track (timeline, track_video); + ges_timeline_add_layer (timeline, layer); + + GST_DEBUG ("Create effect"); + effect = ges_effect_new ("agingtv"); + clip = GES_CLIP (ges_test_clip_new ()); + g_object_set (clip, "duration", GST_SECOND * 2, NULL); + + fail_unless (ges_container_add (GES_CONTAINER (clip), + GES_TIMELINE_ELEMENT (effect))); + ges_layer_add_clip (layer, clip); + + source = ges_clip_find_track_element (clip, NULL, GES_TYPE_VIDEO_SOURCE); + assert_equals_uint64 (GES_TIMELINE_ELEMENT_PRIORITY (effect), 3); + assert_equals_uint64 (GES_TIMELINE_ELEMENT_PRIORITY (source), 4); + + nclip = ges_clip_split (clip, GST_SECOND); + fail_unless (nclip); + neffect = ges_clip_find_track_element (nclip, NULL, GES_TYPE_EFFECT); + nsource = ges_clip_find_track_element (nclip, NULL, GES_TYPE_VIDEO_SOURCE); + + assert_equals_uint64 (GES_TIMELINE_ELEMENT_PRIORITY (effect), 3); + assert_equals_uint64 (GES_TIMELINE_ELEMENT_PRIORITY (source), 4); + assert_equals_uint64 (GES_TIMELINE_ELEMENT_PRIORITY (neffect), 5); + assert_equals_uint64 (GES_TIMELINE_ELEMENT_PRIORITY (nsource), 6); + + /* Create a transition ... */ + ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip), GST_SECOND / 2); + + assert_equals_uint64 (GES_TIMELINE_ELEMENT_PRIORITY (effect), 3); + assert_equals_uint64 (GES_TIMELINE_ELEMENT_PRIORITY (source), 4); + assert_equals_uint64 (GES_TIMELINE_ELEMENT_PRIORITY (neffect), 5); + assert_equals_uint64 (GES_TIMELINE_ELEMENT_PRIORITY (nsource), 6); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +#define _NO_ERROR -1 +#define _set_rate(videorate, rate, error_code) \ +{ \ + g_value_set_double (&val, rate); \ + if (error_code != _NO_ERROR) { \ + fail_if (ges_timeline_element_set_child_property_full ( \ + GES_TIMELINE_ELEMENT (videorate), "rate", &val, &error)); \ + assert_GESError (error, error_code); \ + } else { \ + fail_unless (ges_timeline_element_set_child_property_full ( \ + GES_TIMELINE_ELEMENT (videorate), "rate", &val, &error)); \ + fail_if (error); \ + } \ +} + +#define _add_effect(clip, effect, _index, error_code) \ +{ \ + gint index = _index; \ + if (error_code != _NO_ERROR) { \ + fail_if (ges_clip_add_top_effect (clip, effect, index, &error)); \ + assert_GESError (error, error_code); \ + } else { \ + GList *effects; \ + gboolean res = ges_clip_add_top_effect (clip, effect, index, &error); \ + fail_unless (res, "Adding effect " #effect " failed: %s", \ + error ? error->message : "No error produced"); \ + fail_if (error); \ + effects = ges_clip_get_top_effects (clip); \ + fail_unless (g_list_find (effects, effect)); \ + if (index < 0 || index >= g_list_length (effects)) \ + index = g_list_length (effects) - 1; \ + assert_equals_int (ges_clip_get_top_effect_index (clip, effect), index); \ + fail_unless (g_list_nth_data (effects, index) == effect); \ + g_list_free_full (effects, gst_object_unref); \ + } \ +} + +#define _remove_effect(clip, effect, error_code) \ +{ \ + if (error_code != _NO_ERROR) { \ + fail_if (ges_clip_remove_top_effect (clip, effect, &error)); \ + assert_GESError (error, error_code); \ + } else { \ + GList *effects; \ + gboolean res = ges_clip_remove_top_effect (clip, effect, &error); \ + fail_unless (res, "Removing effect " #effect " failed: %s", \ + error ? error->message : "No error produced"); \ + fail_if (error); \ + effects = ges_clip_get_top_effects (clip); \ + fail_if (g_list_find (effects, effect)); \ + g_list_free_full (effects, gst_object_unref); \ + } \ +} + +#define _move_effect(clip, effect, index, error_code) \ +{ \ + if (error_code != _NO_ERROR) { \ + fail_if ( \ + ges_clip_set_top_effect_index_full (clip, effect, index, &error)); \ + assert_GESError (error, error_code); \ + } else { \ + GList *effects; \ + gboolean res = \ + ges_clip_set_top_effect_index_full (clip, effect, index, &error); \ + fail_unless (res, "Moving effect " #effect " failed: %s", \ + error ? error->message : "No error produced"); \ + fail_if (error); \ + effects = ges_clip_get_top_effects (clip); \ + fail_unless (g_list_find (effects, effect)); \ + assert_equals_int (ges_clip_get_top_effect_index (clip, effect), index); \ + fail_unless (g_list_nth_data (effects, index) == effect); \ + g_list_free_full (effects, gst_object_unref); \ + } \ +} + +GST_START_TEST (test_move_time_effect) +{ + GESTimeline *timeline; + GESTrack *track; + GESLayer *layer; + GESAsset *asset; + GESClip *clip; + GESBaseEffect *rate0, *rate1, *overlay; + GError *error = NULL; + GValue val = G_VALUE_INIT; + + ges_init (); + + g_value_init (&val, G_TYPE_DOUBLE); + + + timeline = ges_timeline_new (); + track = GES_TRACK (ges_video_track_new ()); + fail_unless (ges_timeline_add_track (timeline, track)); + + layer = ges_timeline_append_layer (timeline); + + /* add a dummy clip for overlap */ + asset = ges_asset_request (GES_TYPE_TEST_CLIP, "max-duration=16", &error); + fail_unless (asset); + fail_if (error); + + fail_unless (ges_layer_add_asset_full (layer, asset, 0, 0, 16, + GES_TRACK_TYPE_UNKNOWN, &error)); + fail_if (error); + + clip = GES_CLIP (ges_asset_extract (asset, &error)); + fail_unless (clip); + fail_if (error); + assert_set_start (clip, 8); + assert_set_duration (clip, 16); + + rate0 = GES_BASE_EFFECT (ges_effect_new ("videorate")); + rate1 = GES_BASE_EFFECT (ges_effect_new ("videorate")); + overlay = GES_BASE_EFFECT (ges_effect_new ("textoverlay")); + + ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (overlay), TRUE); + /* only has 8ns of content */ + assert_set_inpoint (overlay, 13); + assert_set_max_duration (overlay, 21); + + _set_rate (rate0, 2.0, _NO_ERROR); + _set_rate (rate1, 0.5, _NO_ERROR); + + /* keep alive */ + gst_object_ref (clip); + gst_object_ref (rate0); + gst_object_ref (rate1); + gst_object_ref (overlay); + + /* cannot add to layer with rate effect because it would cause a full + * overlap */ + _add_effect (clip, rate0, 0, _NO_ERROR); + fail_if (ges_layer_add_clip_full (layer, clip, &error)); + assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + _remove_effect (clip, rate0, _NO_ERROR); + + /* same with overlay */ + _add_effect (clip, overlay, 0, _NO_ERROR); + fail_if (ges_layer_add_clip_full (layer, clip, &error)); + assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + _remove_effect (clip, overlay, _NO_ERROR); + + CHECK_OBJECT_PROPS (clip, 8, 0, 16); + + fail_unless (ges_layer_add_clip_full (layer, clip, &error)); + fail_if (error); + + CHECK_OBJECT_PROPS_MAX (clip, 8, 0, 16, 16); + + /* can't add rate0 or overlay in the same way */ + _add_effect (clip, rate0, 0, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + _add_effect (clip, overlay, 0, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + + /* rate1 extends the duration-limit instead */ + _add_effect (clip, rate1, 0, _NO_ERROR); + + /* can't add overlay next to the timeline */ + _add_effect (clip, overlay, 0, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + /* but next to source is ok */ + _add_effect (clip, overlay, 1, _NO_ERROR); + + /* can't add rate0 after overlay */ + _add_effect (clip, rate0, 1, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + /* but before is ok */ + _add_effect (clip, rate0, -1, _NO_ERROR); + + /* can't move rate0 to end */ + _move_effect (clip, rate0, 0, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + /* can't move overlay to start or end */ + _move_effect (clip, overlay, 0, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + _move_effect (clip, overlay, 2, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + + /* can now move: swap places with rate1 */ + _set_rate (rate0, 0.5, _NO_ERROR); + _move_effect (clip, rate0, 0, _NO_ERROR); + _move_effect (clip, rate1, 2, _NO_ERROR); + _set_rate (rate1, 2.0, _NO_ERROR); + + /* cannot speed up either rate too much */ + _set_rate (rate0, 1.0, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + _set_rate (rate1, 4.0, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + + /* cannot remove rate0 which is slowing down */ + _remove_effect (clip, rate0, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + + /* removing the speed-up is fine */ + _remove_effect (clip, rate1, _NO_ERROR); + + /* removing the overlay is fine */ + _remove_effect (clip, overlay, _NO_ERROR); + + CHECK_OBJECT_PROPS_MAX (clip, 8, 0, 16, 16); + assert_set_max_duration (clip, 8); + CHECK_OBJECT_PROPS_MAX (clip, 8, 0, 16, 8); + /* still can't remove the slow down since it is the only thing stopping + * a full overlap */ + _remove_effect (clip, rate0, GES_ERROR_INVALID_OVERLAP_IN_TRACK); + + gst_object_unref (clip); + /* shouldn't have any problems when removing from the layer */ + fail_unless (ges_layer_remove_clip (layer, clip)); + + g_value_reset (&val); + gst_object_unref (rate0); + gst_object_unref (rate1); + gst_object_unref (overlay); + gst_object_unref (asset); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges"); + TCase *tc_chain = tcase_create ("effect"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_effect_basic); + tcase_add_test (tc_chain, test_add_effect_to_clip); + tcase_add_test (tc_chain, test_get_effects_from_tl); + tcase_add_test (tc_chain, test_effect_clip); + tcase_add_test (tc_chain, test_priorities_clip); + tcase_add_test (tc_chain, test_effect_set_properties); + tcase_add_test (tc_chain, test_clip_signals); + tcase_add_test (tc_chain, test_split_clip_effect_priorities); + tcase_add_test (tc_chain, test_move_time_effect); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/group.c b/tests/check/ges/group.c new file mode 100644 index 0000000000..6c6002b243 --- /dev/null +++ b/tests/check/ges/group.c @@ -0,0 +1,880 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2013> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" +#include <ges/ges.h> +#include <gst/check/gstcheck.h> + +GST_START_TEST (test_move_group) +{ + GESAsset *asset; + GESGroup *group; + GESTimeline *timeline; + GESLayer *layer, *layer1; + GESClip *clip, *clip1, *clip2; + + GList *clips = NULL; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + + layer = ges_timeline_append_layer (timeline); + layer1 = ges_timeline_append_layer (timeline); + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + /** + * Our timeline: + * ------------- + * + * 0------------Group1---------------110 + * |-------- | + * layer: | clip | | + * |-------10 | + * |----------------------------------| + * | 0--------- 0-----------| + * layer1: | | clip1 | | clip2 | + * | 10--------20 50----------| + * |----------------------------------| + */ + clip = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + clip1 = + ges_layer_add_asset (layer1, asset, 10, 0, 10, GES_TRACK_TYPE_UNKNOWN); + clip2 = + ges_layer_add_asset (layer1, asset, 50, 0, 60, GES_TRACK_TYPE_UNKNOWN); + clips = g_list_prepend (clips, clip); + clips = g_list_prepend (clips, clip1); + clips = g_list_prepend (clips, clip2); + group = GES_GROUP (ges_container_group (clips)); + g_list_free (clips); + ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); + + fail_unless (GES_IS_GROUP (group)); + ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); + fail_unless (g_list_length (GES_CONTAINER_CHILDREN (group)) == 3); + assert_equals_int (GES_CONTAINER_HEIGHT (group), 2); + + /* Nothing should move */ + ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip1), 5); + + CHECK_OBJECT_PROPS (clip, 0, 0, 10); + CHECK_OBJECT_PROPS (clip1, 10, 0, 10); + CHECK_OBJECT_PROPS (clip2, 50, 0, 60); + CHECK_OBJECT_PROPS (group, 0, 0, 110); + + /* + * 0 10------------Group1---------------120 + * |-------- | + * layer: | clip | | + * |-------20 | + * |----------------------------------| + * | 0--------- 0-----------| + * layer1: | | clip1 | | clip2 | + * | 20--------30 60----------| + * |----------------------------------| + */ + ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip), 10); + CHECK_OBJECT_PROPS (clip, 10, 0, 10); + CHECK_OBJECT_PROPS (clip1, 20, 0, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 60); + CHECK_OBJECT_PROPS (group, 10, 0, 110); + + /* + * 0 10------------Group1---------------120 + * |------ | + * layer: |clip | | + * |-----15 | + * |----------------------------------| + * | 0--------- 0-----------| + * layer1: | | clip1 | | clip2 | + * | 20--------30 60----------| + * |----------------------------------| + */ + ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip), 5); + CHECK_OBJECT_PROPS (clip, 10, 0, 5); + CHECK_OBJECT_PROPS (clip1, 20, 0, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 60); + CHECK_OBJECT_PROPS (group, 10, 0, 110); + ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); + + /* + * 0 10------------Group1---------------110 + * |------ | + * layer: |clip | | + * |-----15 | + * |----------------------------------| + * | 0--------- 0-----------| + * layer1: | | clip1 | | clip2 | + * | 20--------30 60----------| + * |----------------------------------| + */ + ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip2), 50); + CHECK_OBJECT_PROPS (clip, 10, 0, 5); + CHECK_OBJECT_PROPS (clip1, 20, 0, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 10, 0, 100); + + /* + * 0 10------------Group1---------------110 + * |------ | + * layer: |clip | | + * |-----15 | + * |----------------------------------| + * | 5--------- 0-----------| + * layer1: | | clip1 | | clip2 | + * | 20--------30 60----------| + * |----------------------------------| + */ + ges_timeline_element_set_inpoint (GES_TIMELINE_ELEMENT (clip1), 5); + CHECK_OBJECT_PROPS (clip, 10, 0, 5); + CHECK_OBJECT_PROPS (clip1, 20, 5, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 10, 0, 100); + + /* + * 0 10------------Group1---------------110 + * |------ | + * layer: |clip | | + * |-----15 | + * |----------------------------------| + * | 5--------- 0-----------| + * layer1: | | clip1 | | clip2 | + * | 20--------30 60----------| + * |----------------------------------| + */ + ges_timeline_element_set_inpoint (GES_TIMELINE_ELEMENT (clip1), 5); + CHECK_OBJECT_PROPS (clip, 10, 0, 5); + CHECK_OBJECT_PROPS (clip1, 20, 5, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 10, 0, 100); + ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); + fail_if (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 20)); + CHECK_OBJECT_PROPS (clip, 10, 0, 5); + CHECK_OBJECT_PROPS (clip1, 20, 5, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 10, 0, 100); + ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); + + fail_if (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 25)); + CHECK_OBJECT_PROPS (clip, 10, 0, 5); + CHECK_OBJECT_PROPS (clip1, 20, 5, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 10, 0, 100); + ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); + + /* Same thing in the end... */ + ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 10); + CHECK_OBJECT_PROPS (clip, 10, 0, 5); + CHECK_OBJECT_PROPS (clip1, 20, 5, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 10, 0, 100); + ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); + + /* + * 0 12------------Group1---------------110 + * 2------ | + * layer: |clip | | + * |-----15 | + * |----------------------------------| + * | 7--------- 2----------| + * layer1: | | clip1 | | clip2 | + * | 20--------30 60----------| + * |----------------------------------| + */ + ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 12); + CHECK_OBJECT_PROPS (clip, 12, 2, 3); + CHECK_OBJECT_PROPS (clip1, 20, 5, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 12, 0, 98); + ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); + + /* Setting the duration would lead to overlaps */ + fail_if (ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (group), + 10)); + CHECK_OBJECT_PROPS (clip, 12, 2, 3); + CHECK_OBJECT_PROPS (clip1, 20, 5, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 12, 0, 98); + ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (group), 100); + CHECK_OBJECT_PROPS (clip, 12, 2, 3); + CHECK_OBJECT_PROPS (clip1, 20, 5, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 52); + CHECK_OBJECT_PROPS (group, 12, 0, 100); + + ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (group), 20); + CHECK_OBJECT_PROPS (clip, 20, 2, 3); + CHECK_OBJECT_PROPS (clip1, 28, 5, 10); + CHECK_OBJECT_PROPS (clip2, 68, 0, 52); + CHECK_OBJECT_PROPS (group, 20, 0, 100); + + /* Trim fails because clip inpoint would become negative */ + fail_if (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 10)); + CHECK_OBJECT_PROPS (clip, 20, 2, 3); + CHECK_OBJECT_PROPS (clip1, 28, 5, 10); + CHECK_OBJECT_PROPS (clip2, 68, 0, 52); + CHECK_OBJECT_PROPS (group, 20, 0, 100); + + fail_unless (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 18)); + CHECK_OBJECT_PROPS (clip, 18, 0, 5); + CHECK_OBJECT_PROPS (clip1, 28, 5, 10); + CHECK_OBJECT_PROPS (clip2, 68, 0, 52); + CHECK_OBJECT_PROPS (group, 18, 0, 102); + + fail_unless (ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip), + 17)); + CHECK_OBJECT_PROPS (clip, 18, 0, 17); + CHECK_OBJECT_PROPS (clip1, 28, 5, 10); + CHECK_OBJECT_PROPS (clip2, 68, 0, 52); + CHECK_OBJECT_PROPS (group, 18, 0, 102); + + fail_unless (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 30)); + CHECK_OBJECT_PROPS (clip, 30, 12, 5); + CHECK_OBJECT_PROPS (clip1, 30, 7, 8); + CHECK_OBJECT_PROPS (clip2, 68, 0, 52); + CHECK_OBJECT_PROPS (group, 30, 0, 90); + + fail_unless (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 25)); + CHECK_OBJECT_PROPS (clip, 25, 7, 10); + CHECK_OBJECT_PROPS (clip1, 25, 2, 13); + CHECK_OBJECT_PROPS (clip2, 68, 0, 52); + CHECK_OBJECT_PROPS (group, 25, 0, 95); + + ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); + check_destroyed (G_OBJECT (timeline), G_OBJECT (group), NULL); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + + + +static void +_changed_layer_cb (GESTimelineElement * clip, + GParamSpec * arg G_GNUC_UNUSED, guint * nb_calls) +{ + *nb_calls = *nb_calls + 1; +} + +GST_START_TEST (test_group_in_group) +{ + GESAsset *asset; + GESTimeline *timeline; + GESGroup *group, *group1; + GESLayer *layer, *layer1, *layer2, *layer3; + GESClip *c, *c1, *c2, *c3, *c4, *c5; + + guint nb_layer_notifies = 0; + GList *clips = NULL; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + + /* Our timeline + * + * --0------------10-Group-----20---------------30-------Group1----------70 + * | +-----------+ |+-----------50 | + * L | | C | || C3 | | + * | +-----------+ |+-----------+ | + * --|-------------------------------------------|-----40----------------| + * | +------------+ +-------------+ | +--------60 | + * L1 | | C1 | | C2 | | | C4 | | + * | +------------+ +-------------+ | +--------+ | + * --|-------------------------------------------|-----------------------| + * | | +--------+| + * L2 | | | c5 || + * | | +--------+| + * --+-------------------------------------------+-----------------------+ + * + * L3 + * + * ----------------------------------------------------------------------- + */ + + layer = ges_timeline_append_layer (timeline); + layer1 = ges_timeline_append_layer (timeline); + layer2 = ges_timeline_append_layer (timeline); + layer3 = ges_timeline_append_layer (timeline); + assert_equals_int (ges_layer_get_priority (layer3), 3); + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + c = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + c1 = ges_layer_add_asset (layer1, asset, 10, 0, 10, GES_TRACK_TYPE_UNKNOWN); + c2 = ges_layer_add_asset (layer1, asset, 20, 0, 10, GES_TRACK_TYPE_UNKNOWN); + clips = g_list_prepend (clips, c); + clips = g_list_prepend (clips, c1); + clips = g_list_prepend (clips, c2); + group = GES_GROUP (ges_container_group (clips)); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group) == timeline); + g_list_free (clips); + + fail_unless (GES_IS_GROUP (group)); + CHECK_OBJECT_PROPS (c, 0, 0, 10); + CHECK_OBJECT_PROPS (c1, 10, 0, 10); + CHECK_OBJECT_PROPS (c2, 20, 0, 10); + CHECK_OBJECT_PROPS (group, 0, 0, 30); + + c3 = ges_layer_add_asset (layer, asset, 30, 0, 20, GES_TRACK_TYPE_UNKNOWN); + c4 = ges_layer_add_asset (layer1, asset, 40, 0, 20, GES_TRACK_TYPE_UNKNOWN); + c5 = ges_layer_add_asset (layer2, asset, 50, 0, 20, GES_TRACK_TYPE_UNKNOWN); + clips = g_list_prepend (NULL, c3); + clips = g_list_prepend (clips, c4); + clips = g_list_prepend (clips, c5); + group1 = GES_GROUP (ges_container_group (clips)); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group1) == timeline); + g_list_free (clips); + + fail_unless (GES_IS_GROUP (group1)); + CHECK_OBJECT_PROPS (c3, 30, 0, 20); + CHECK_OBJECT_PROPS (c4, 40, 0, 20); + CHECK_OBJECT_PROPS (c5, 50, 0, 20); + CHECK_OBJECT_PROPS (group1, 30, 0, 40); + check_layer (c, 0); + check_layer (c1, 1); + check_layer (c2, 1); + check_layer (c3, 0); + check_layer (c4, 1); + check_layer (c5, 2); + + fail_unless (ges_container_add (GES_CONTAINER (group), + GES_TIMELINE_ELEMENT (group1))); + CHECK_OBJECT_PROPS (c, 0, 0, 10); + CHECK_OBJECT_PROPS (c1, 10, 0, 10); + CHECK_OBJECT_PROPS (c2, 20, 0, 10); + CHECK_OBJECT_PROPS (c3, 30, 0, 20); + CHECK_OBJECT_PROPS (c4, 40, 0, 20); + CHECK_OBJECT_PROPS (c5, 50, 0, 20); + CHECK_OBJECT_PROPS (group, 0, 0, 70); + CHECK_OBJECT_PROPS (group1, 30, 0, 40); + check_layer (c, 0); + check_layer (c1, 1); + check_layer (c2, 1); + check_layer (c3, 0); + check_layer (c4, 1); + check_layer (c5, 2); + + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group) == timeline); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group1) == timeline); + + ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (c4), 50); + CHECK_OBJECT_PROPS (c, 10, 0, 10); + CHECK_OBJECT_PROPS (c1, 20, 0, 10); + CHECK_OBJECT_PROPS (c2, 30, 0, 10); + CHECK_OBJECT_PROPS (c3, 40, 0, 20); + CHECK_OBJECT_PROPS (c4, 50, 0, 20); + CHECK_OBJECT_PROPS (c5, 60, 0, 20); + CHECK_OBJECT_PROPS (group, 10, 0, 70); + CHECK_OBJECT_PROPS (group1, 40, 0, 40); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group) == timeline); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group1) == timeline); + check_layer (c, 0); + check_layer (c1, 1); + check_layer (c2, 1); + check_layer (c3, 0); + check_layer (c4, 1); + check_layer (c5, 2); + + /* Our timeline + * + * L + * ----------------------------------------------------------------------- + * 0------------10-Group-----20---------------30-------Group1----------70 + * | +-----------+ |+-----------50 | + * L1 | | C | || C3 | | + * | +-----------+ |+-----------+ | + * | | | + * --|-------------------------------------------|-----40----------------| + * | +------------+ +-------------+ | +--------60 | + * L2 | | C1 | | C2 | | | C4 | | + * | +------------+ +-------------+ | +--------+ | + * --|-------------------------------------------|-----------------------| + * | | +--------+| + * L3 | | | c5 || + * | | +--------+| + * --+-------------------------------------------+-----------------------+ + */ + fail_unless (ges_clip_move_to_layer (c, layer1)); + check_layer (c, 1); + check_layer (c1, 2); + check_layer (c2, 2); + check_layer (c3, 1); + check_layer (c4, 2); + check_layer (c5, 3); + assert_equals_int (_PRIORITY (group), 1); + assert_equals_int (_PRIORITY (group1), 1); + + /* We can not move so far! */ + g_signal_connect_after (c4, "notify::layer", + (GCallback) _changed_layer_cb, &nb_layer_notifies); + fail_if (ges_clip_move_to_layer (c4, layer)); + assert_equals_int (nb_layer_notifies, 0); + check_layer (c, 1); + check_layer (c1, 2); + check_layer (c2, 2); + check_layer (c3, 1); + check_layer (c4, 2); + check_layer (c5, 3); + assert_equals_int (_PRIORITY (group), 1); + assert_equals_int (_PRIORITY (group1), 1); + + clips = ges_container_ungroup (GES_CONTAINER (group), FALSE); + assert_equals_int (g_list_length (clips), 4); + g_list_free_full (clips, gst_object_unref); + + gst_object_unref (timeline); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_group_in_group_layer_moving) +{ + GESAsset *asset; + GESTimeline *timeline; + GESGroup *group; + GESLayer *layer, *layer1, *layer2, *layer3; + GESClip *c, *c1; + + GList *clips = NULL; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + + /* Our timeline + * + * --0------------10-Group-----20 + * | +-----------+ | + * L | | C | | + * | +-----------+ | + * --|-------------------------- + * | +------------+ + * L1 | | C1 | + * | +------------+ + * ----------------------------- + */ + + layer = ges_timeline_append_layer (timeline); + layer1 = ges_timeline_append_layer (timeline); + layer2 = ges_timeline_append_layer (timeline); + layer3 = ges_timeline_append_layer (timeline); + fail_unless (layer2 && layer3); + assert_equals_int (ges_layer_get_priority (layer3), 3); + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + c = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + c1 = ges_layer_add_asset (layer1, asset, 10, 0, 10, GES_TRACK_TYPE_UNKNOWN); + clips = g_list_prepend (clips, c); + clips = g_list_prepend (clips, c1); + group = GES_GROUP (ges_container_group (clips)); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group) == timeline); + g_list_free (clips); + + fail_unless (GES_IS_GROUP (group)); + CHECK_OBJECT_PROPS (c, 0, 0, 10); + CHECK_OBJECT_PROPS (c1, 10, 0, 10); + CHECK_OBJECT_PROPS (group, 0, 0, 20); + + /* Our timeline + * + * --0--------10-----------20-Group----30 + * | +-----------+ | + * L | | C | | + * | +-----------+ | + * --|----------------------------------- + * | +------------+ + * L1 | | C1 | + * | +------------+ + * ------------------------------------- + */ + fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, + -1, GES_EDIT_MODE_NORMAL, GES_EDGE_NONE, 10)); + + CHECK_OBJECT_PROPS (c, 10, 0, 10); + CHECK_OBJECT_PROPS (c1, 20, 0, 10); + CHECK_OBJECT_PROPS (group, 10, 0, 20); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c), 0); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c1), 1); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (group), 0); + + ges_layer_set_priority (layer2, 0); + /* no change since none of the clips are in layer2 */ + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c), 0); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c1), 1); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (group), 0); + + ges_layer_set_priority (layer, 1); + /* c's layer now has priority 1 (conflicts with layer1) */ + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c), 1); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c1), 1); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (group), 1); + + ges_layer_set_priority (layer1, 2); + /* conflicting layer priorities now resolved */ + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c), 1); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c1), 2); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (group), 1); + + /* Our timeline + * + * --0--------10-----------20-Group----30 + * | +-----------+ | + * L2 | | C | | + * | +-----------+ | + * --|----------------------------------- + * | +------------+ + * L | | C1 | + * | +------------+ + * ------------------------------------- + * + * L1 + * ------------------------------------- + */ + fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, + 0, GES_EDIT_MODE_NORMAL, GES_EDGE_NONE, 10)); + CHECK_OBJECT_PROPS (c, 10, 0, 10); + CHECK_OBJECT_PROPS (c1, 20, 0, 10); + CHECK_OBJECT_PROPS (group, 10, 0, 20); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c), 0); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c1), 1); + + /* Our timeline + * + * --0--------10-----------20-Group----30 + * L2 | | + * -------------------------------------- + * | +-----------+ | + * L | | C | | + * | +-----------+ | + * --|----------------------------------- + * | +------------+ + * L1 | | C1 | + * | +------------+ + * ------------------------------------- + */ + fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, + 1, GES_EDIT_MODE_NORMAL, GES_EDGE_NONE, 10)); + CHECK_OBJECT_PROPS (c, 10, 0, 10); + CHECK_OBJECT_PROPS (c1, 20, 0, 10); + CHECK_OBJECT_PROPS (group, 10, 0, 20); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c), 1); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c1), 2); + + gst_object_unref (timeline); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + + +GST_START_TEST (test_group_in_self) +{ + GESLayer *layer; + GESClip *c, *c1; + GESAsset *asset; + GESTimeline *timeline; + GESGroup *group; + + GList *clips = NULL; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + + layer = ges_timeline_append_layer (timeline); + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + c = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + c1 = ges_layer_add_asset (layer, asset, 10, 0, 10, GES_TRACK_TYPE_UNKNOWN); + clips = g_list_prepend (clips, c); + clips = g_list_prepend (clips, c1); + + + group = GES_GROUP (ges_container_group (clips)); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group) == timeline); + g_list_free (clips); + + fail_if (ges_container_add (GES_CONTAINER (group), + GES_TIMELINE_ELEMENT (group))); + clips = ges_container_get_children (GES_CONTAINER (group), TRUE); + assert_equals_int (g_list_length (clips), 6); + g_list_free_full (clips, g_object_unref); + + gst_object_unref (timeline); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + +static void +project_loaded_cb (GESProject * project, GESTimeline * timeline, + GMainLoop * mainloop) +{ + GST_ERROR ("LOADED!"); + g_main_loop_quit (mainloop); +} + +GST_START_TEST (test_group_serialization) +{ + gchar *tmpuri; + GESLayer *layer; + GESClip *c, *c1, *c2, *c3; + GESAsset *asset; + GESTimeline *timeline; + GESGroup *group; + GESProject *project; + GMainLoop *mainloop; + + GError *err = NULL; + GList *tmp, *clips = NULL; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + + layer = ges_timeline_append_layer (timeline); + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + c = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + + c1 = ges_layer_add_asset (layer, asset, 10, 0, 10, GES_TRACK_TYPE_UNKNOWN); + + clips = g_list_prepend (clips, c); + clips = g_list_prepend (clips, c1); + + c2 = ges_layer_add_asset (layer, asset, 20, 0, 10, GES_TRACK_TYPE_UNKNOWN); + + c3 = ges_layer_add_asset (layer, asset, 30, 0, 10, GES_TRACK_TYPE_UNKNOWN); + + group = GES_GROUP (ges_container_group (clips)); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group) == timeline); + g_list_free (clips); + + + clips = g_list_append (NULL, group); + clips = g_list_append (clips, c2); + group = GES_GROUP (ges_container_group (clips)); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group) == timeline); + g_list_free (clips); + + clips = g_list_append (NULL, group); + clips = g_list_append (clips, c3); + group = GES_GROUP (ges_container_group (clips)); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group) == timeline); + g_list_free (clips); + + project = + GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline))); + + tmpuri = ges_test_get_tmp_uri ("test-auto-transition-save.xges"); + fail_unless (ges_project_save (project, timeline, tmpuri, NULL, TRUE, NULL)); + gst_object_unref (timeline); + gst_object_unref (asset); + + project = ges_project_new (tmpuri); + mainloop = g_main_loop_new (NULL, FALSE); + g_signal_connect (project, "loaded", (GCallback) project_loaded_cb, mainloop); + timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), &err)); + g_main_loop_run (mainloop); + + fail_unless (err == NULL, "%s", err ? err->message : "Nothing"); + fail_unless (timeline != NULL); + + layer = timeline->layers->data; + clips = ges_layer_get_clips (layer); + for (tmp = clips; tmp; tmp = tmp->next) { + fail_unless (GES_IS_GROUP (GES_TIMELINE_ELEMENT_PARENT (tmp->data)), + "%s parent is %p, NOT a group", GES_TIMELINE_ELEMENT_NAME (tmp->data), + GES_TIMELINE_ELEMENT_PARENT (tmp->data)); + } + g_list_free_full (clips, g_object_unref); + + g_free (tmpuri); + gst_object_unref (timeline); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_children_properties_contain) +{ + GESTimeline *timeline; + GESLayer *layer; + GESAsset *asset; + GESTimelineElement *c1, *c2, *c3, *g1, *g2; + GParamSpec **child_props1, **child_props2; + guint num_props1, num_props2; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_timeline_append_layer (timeline); + + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + /* choose one audio and one video to give them different properties */ + c1 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 0, 0, 10, + GES_TRACK_TYPE_AUDIO)); + c2 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 20, 0, 10, + GES_TRACK_TYPE_VIDEO)); + /* but c3 will have the same child properties as c1! */ + c3 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 40, 0, 10, + GES_TRACK_TYPE_AUDIO)); + + fail_unless (c1); + fail_unless (c2); + + g1 = GES_TIMELINE_ELEMENT (ges_group_new ()); + g2 = GES_TIMELINE_ELEMENT (ges_group_new ()); + + /* group should have the same as its children */ + fail_unless (ges_container_add (GES_CONTAINER (g1), c1)); + + num_props1 = 0; + child_props1 = append_children_properties (NULL, c1, &num_props1); + num_props2 = 0; + child_props2 = append_children_properties (NULL, g1, &num_props2); + + assert_property_list_match (child_props1, num_props1, + child_props2, num_props2); + + /* add next child and gain its children properties as well */ + fail_unless (ges_container_add (GES_CONTAINER (g1), c2)); + + /* add the child properties of c2 to the existing list for c1 */ + child_props1 = append_children_properties (child_props1, c2, &num_props1); + + free_children_properties (child_props2, num_props2); + num_props2 = 0; + child_props2 = append_children_properties (NULL, g1, &num_props2); + + assert_property_list_match (child_props1, num_props1, + child_props2, num_props2); + + /* FIXME: if c1 and c3 have the same child properties (they use the + * same GParamSpec) then ges_timeline_element_add_child_property_full + * will fail, even though the corresponding GObject child is not the + * same instance */ + + fail_unless (ges_container_add (GES_CONTAINER (g1), c3)); + + /* FIXME: regarding the above comment, ideally we would append the + * children properties for c3 to child_props1, so that its children + * properties appear twice in the list: + * child_props1 = + * append_children_properties (child_props1, c3, &num_props1); */ + + free_children_properties (child_props2, num_props2); + num_props2 = 0; + child_props2 = append_children_properties (NULL, g1, &num_props2); + + assert_property_list_match (child_props1, num_props1, + child_props2, num_props2); + + /* remove c3 */ + fail_unless (ges_container_remove (GES_CONTAINER (g1), c3)); + + /* FIXME: regarding the above comment, ideally we would reset + * child_props1 to only contain the child properties for c1 and c2 + * Currently, we at least want to make sure that the child properties + * for c1 remain. + * Currently, if we removed c1 first, all its children properties would + * be removed from g1, and this would *not* automatically register the + * children properties for c3. */ + + free_children_properties (child_props2, num_props2); + num_props2 = 0; + child_props2 = append_children_properties (NULL, g1, &num_props2); + + assert_property_list_match (child_props1, num_props1, + child_props2, num_props2); + + /* remove c1 */ + fail_unless (ges_container_remove (GES_CONTAINER (g1), c1)); + + free_children_properties (child_props1, num_props1); + num_props1 = 0; + child_props1 = append_children_properties (NULL, c2, &num_props1); + + free_children_properties (child_props2, num_props2); + num_props2 = 0; + child_props2 = append_children_properties (NULL, g1, &num_props2); + + assert_property_list_match (child_props1, num_props1, + child_props2, num_props2); + + /* add g1 and c1 to g2 */ + fail_unless (ges_container_add (GES_CONTAINER (g2), g1)); + fail_unless (ges_container_add (GES_CONTAINER (g2), c1)); + + free_children_properties (child_props1, num_props1); + num_props1 = 0; + child_props1 = append_children_properties (NULL, g2, &num_props1); + + free_children_properties (child_props2, num_props2); + num_props2 = 0; + child_props2 = append_children_properties (NULL, c1, &num_props2); + child_props2 = append_children_properties (child_props2, g1, &num_props2); + + assert_property_list_match (child_props1, num_props1, + child_props2, num_props2); + + free_children_properties (child_props1, num_props1); + free_children_properties (child_props2, num_props2); + + gst_object_unref (timeline); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + + + + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-group"); + TCase *tc_chain = tcase_create ("group"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_move_group); + tcase_add_test (tc_chain, test_group_in_group); + tcase_add_test (tc_chain, test_group_in_self); + tcase_add_test (tc_chain, test_group_serialization); + tcase_add_test (tc_chain, test_group_in_group_layer_moving); + tcase_add_test (tc_chain, test_children_properties_contain); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/layer.c b/tests/check/ges/layer.c new file mode 100644 index 0000000000..763e78a081 --- /dev/null +++ b/tests/check/ges/layer.c @@ -0,0 +1,1744 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Edward Hervey <bilboed@bilboed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" +#include <ges/ges.h> +#include <gst/check/gstcheck.h> + +#define LAYER_HEIGHT 1000 + +GST_START_TEST (test_layer_properties) +{ + GESTimeline *timeline; + GESLayer *layer, *layer1; + GESTrack *track; + GESTrackElement *trackelement; + GESClip *clip; + + ges_init (); + + /* Timeline and 1 Layer */ + timeline = ges_timeline_new (); + + /* The default priority is 0 */ + fail_unless ((layer = ges_timeline_append_layer (timeline))); + fail_unless_equals_int (ges_layer_get_priority (layer), 0); + + fail_if (g_object_is_floating (layer)); + + fail_unless ((layer1 = ges_timeline_append_layer (timeline))); + fail_unless_equals_int (ges_layer_get_priority (layer1), 1); + + track = GES_TRACK (ges_video_track_new ()); + fail_unless (track != NULL); + fail_unless (ges_timeline_add_track (timeline, track)); + + clip = (GESClip *) ges_test_clip_new (); + fail_unless (clip != NULL); + + /* Set some properties */ + g_object_set (clip, "start", (guint64) 42, "duration", (guint64) 51, + "in-point", (guint64) 12, NULL); + assert_equals_uint64 (_START (clip), 42); + assert_equals_uint64 (_DURATION (clip), 51); + assert_equals_uint64 (_INPOINT (clip), 12); + assert_equals_uint64 (_PRIORITY (clip), 0); + + /* Add the clip to the timeline */ + fail_unless (g_object_is_floating (clip)); + fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip))); + fail_if (g_object_is_floating (clip)); + trackelement = ges_clip_find_track_element (clip, track, G_TYPE_NONE); + fail_unless (trackelement != NULL); + + /* This is not a SimpleLayer, therefore the properties shouldn't have changed */ + assert_equals_uint64 (_START (clip), 42); + assert_equals_uint64 (_DURATION (clip), 51); + assert_equals_uint64 (_INPOINT (clip), 12); + assert_equals_uint64 (_PRIORITY (clip), 1); + ges_timeline_commit (timeline); + nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 51, 12, + 51, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE); + + /* Change the priority of the layer */ + g_object_set (layer, "priority", 1, NULL); + assert_equals_int (ges_layer_get_priority (layer), 1); + assert_equals_uint64 (_PRIORITY (clip), 1); + ges_timeline_commit (timeline); + nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 51, 12, + 51, LAYER_HEIGHT + MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE); + + /* Change it to an insanely high value */ + g_object_set (layer, "priority", 31, NULL); + assert_equals_int (ges_layer_get_priority (layer), 31); + assert_equals_uint64 (_PRIORITY (clip), 1); + ges_timeline_commit (timeline); + nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 51, 12, + 51, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + LAYER_HEIGHT * 31, TRUE); + + /* and back to 0 */ + fail_unless (ges_timeline_move_layer (timeline, layer, 0)); + assert_equals_int (ges_layer_get_priority (layer), 0); + assert_equals_uint64 (_PRIORITY (clip), 1); + ges_timeline_commit (timeline); + nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 51, 12, + 51, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0, TRUE); + + gst_object_unref (trackelement); + fail_unless (ges_layer_remove_clip (layer, clip)); + fail_unless (ges_timeline_remove_track (timeline, track)); + fail_unless (ges_timeline_remove_layer (timeline, layer)); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_layer_priorities) +{ + GESTrack *track; + GESTimeline *timeline; + GESLayer *layer1, *layer2, *layer3; + GESTrackElement *trackelement1, *trackelement2, *trackelement3; + GESClip *clip1, *clip2, *clip3; + GstElement *nleobj1, *nleobj2, *nleobj3; + guint prio1, prio2, prio3; + GList *objs; + + ges_init (); + + /* Timeline and 3 Layer */ + timeline = ges_timeline_new (); + fail_unless ((layer1 = ges_timeline_append_layer (timeline))); + fail_unless ((layer2 = ges_timeline_append_layer (timeline))); + fail_unless ((layer3 = ges_timeline_append_layer (timeline))); + fail_unless_equals_int (ges_layer_get_priority (layer1), 0); + fail_unless_equals_int (ges_layer_get_priority (layer2), 1); + fail_unless_equals_int (ges_layer_get_priority (layer3), 2); + + track = GES_TRACK (ges_video_track_new ()); + fail_unless (track != NULL); + fail_unless (ges_timeline_add_track (timeline, track)); + + clip1 = GES_CLIP (ges_test_clip_new ()); + clip2 = GES_CLIP (ges_test_clip_new ()); + clip3 = GES_CLIP (ges_test_clip_new ()); + fail_unless (clip1 != NULL); + fail_unless (clip2 != NULL); + fail_unless (clip3 != NULL); + + g_object_set (clip1, "start", 0, "duration", 10, NULL); + g_object_set (clip2, "start", 10, "duration", 10, NULL); + g_object_set (clip3, "start", 20, "duration", 10, NULL); + + /* Add objects to the timeline */ + fail_unless (ges_layer_add_clip (layer1, clip1)); + trackelement1 = ges_clip_find_track_element (clip1, track, G_TYPE_NONE); + fail_unless (trackelement1 != NULL); + + fail_unless (ges_layer_add_clip (layer2, clip2)); + trackelement2 = ges_clip_find_track_element (clip2, track, G_TYPE_NONE); + fail_unless (trackelement2 != NULL); + + fail_unless (ges_layer_add_clip (layer3, clip3)); + trackelement3 = ges_clip_find_track_element (clip3, track, G_TYPE_NONE); + fail_unless (trackelement3 != NULL); + + ges_timeline_commit (timeline); + assert_equals_int (_PRIORITY (clip1), 1); + nleobj1 = ges_track_element_get_nleobject (trackelement1); + fail_unless (nleobj1 != NULL); + g_object_get (nleobj1, "priority", &prio1, NULL); + assert_equals_int (prio1, MIN_NLE_PRIO + TRANSITIONS_HEIGHT); + + assert_equals_int (_PRIORITY (clip2), 1); + nleobj2 = ges_track_element_get_nleobject (trackelement2); + fail_unless (nleobj2 != NULL); + g_object_get (nleobj2, "priority", &prio2, NULL); + /* clip2 is on the second layer and has a priority of 1 */ + assert_equals_int (prio2, MIN_NLE_PRIO + LAYER_HEIGHT + 1); + + /* We do not take into account users set priorities */ + assert_equals_int (_PRIORITY (clip3), 1); + + nleobj3 = ges_track_element_get_nleobject (trackelement3); + fail_unless (nleobj3 != NULL); + + /* clip3 is on the third layer and has a priority of LAYER_HEIGHT + 1 + * it priority must have the maximum priority of this layer*/ + g_object_get (nleobj3, "priority", &prio3, NULL); + assert_equals_int (prio3, 1 + MIN_NLE_PRIO + LAYER_HEIGHT * 2); + + /* Move layers around */ + fail_unless (ges_timeline_move_layer (timeline, layer1, 2)); + ges_timeline_commit (timeline); + + /* And check the new priorities */ + assert_equals_int (ges_layer_get_priority (layer1), 2); + assert_equals_int (ges_layer_get_priority (layer2), 0); + assert_equals_int (ges_layer_get_priority (layer3), 1); + assert_equals_int (_PRIORITY (clip1), 1); + assert_equals_int (_PRIORITY (clip2), 1); + assert_equals_int (_PRIORITY (clip3), 1); + g_object_get (nleobj1, "priority", &prio1, NULL); + g_object_get (nleobj2, "priority", &prio2, NULL); + g_object_get (nleobj3, "priority", &prio3, NULL); + assert_equals_int (prio1, + 2 * LAYER_HEIGHT + MIN_NLE_PRIO + TRANSITIONS_HEIGHT); + assert_equals_int (prio2, MIN_NLE_PRIO + 1); + assert_equals_int (prio3, LAYER_HEIGHT + MIN_NLE_PRIO + TRANSITIONS_HEIGHT); + + /* And move objects around */ + fail_unless (ges_clip_move_to_layer (clip2, layer1)); + fail_unless (ges_clip_move_to_layer (clip3, layer1)); + ges_timeline_commit (timeline); + + objs = ges_layer_get_clips (layer1); + assert_equals_int (g_list_length (objs), 3); + fail_unless (ges_layer_get_clips (layer2) == NULL); + fail_unless (ges_layer_get_clips (layer3) == NULL); + g_list_free_full (objs, gst_object_unref); + + /* Check their priorities (layer1 priority is now 2) */ + assert_equals_int (_PRIORITY (clip1), 1); + assert_equals_int (_PRIORITY (clip2), 2); + assert_equals_int (_PRIORITY (clip3), 3); + g_object_get (nleobj1, "priority", &prio1, NULL); + g_object_get (nleobj2, "priority", &prio2, NULL); + g_object_get (nleobj3, "priority", &prio3, NULL); + assert_equals_int (prio1, + 2 * LAYER_HEIGHT + MIN_NLE_PRIO + TRANSITIONS_HEIGHT); + assert_equals_int (prio2, + 2 * LAYER_HEIGHT + 1 + MIN_NLE_PRIO + TRANSITIONS_HEIGHT); + assert_equals_int (prio3, + 2 * LAYER_HEIGHT + 2 + MIN_NLE_PRIO + TRANSITIONS_HEIGHT); + + gst_object_unref (trackelement1); + gst_object_unref (trackelement2); + gst_object_unref (trackelement3); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_timeline_auto_transition) +{ + GESAsset *asset; + GESTimeline *timeline; + GESLayer *layer, *layer1, *layer2; + + ges_init (); + + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + fail_unless (GES_IS_ASSET (asset)); + gst_object_unref (asset); + + GST_DEBUG ("Create timeline"); + timeline = ges_timeline_new_audio_video (); + assert_is_type (timeline, GES_TYPE_TIMELINE); + + GST_DEBUG ("Create layers"); + layer = ges_layer_new (); + assert_is_type (layer, GES_TYPE_LAYER); + layer1 = ges_layer_new (); + assert_is_type (layer, GES_TYPE_LAYER); + layer2 = ges_layer_new (); + assert_is_type (layer, GES_TYPE_LAYER); + + GST_DEBUG ("Set auto-transition to the layers"); + ges_layer_set_auto_transition (layer, TRUE); + ges_layer_set_auto_transition (layer1, TRUE); + ges_layer_set_auto_transition (layer2, TRUE); + + GST_DEBUG ("Add layers to the timeline"); + ges_timeline_add_layer (timeline, layer); + ges_timeline_add_layer (timeline, layer1); + ges_timeline_add_layer (timeline, layer2); + + GST_DEBUG ("Check that auto-transition was properly set to the layers"); + fail_unless (ges_layer_get_auto_transition (layer)); + fail_unless (ges_layer_get_auto_transition (layer1)); + fail_unless (ges_layer_get_auto_transition (layer2)); + + GST_DEBUG ("Set timeline auto-transition property to FALSE"); + ges_timeline_set_auto_transition (timeline, FALSE); + + GST_DEBUG + ("Check that layers auto-transition has the same value as timeline"); + fail_if (ges_layer_get_auto_transition (layer)); + fail_if (ges_layer_get_auto_transition (layer1)); + fail_if (ges_layer_get_auto_transition (layer2)); + + GST_DEBUG ("Set timeline auto-transition property to TRUE"); + ges_timeline_set_auto_transition (timeline, TRUE); + + GST_DEBUG + ("Check that layers auto-transition has the same value as timeline"); + fail_unless (ges_layer_get_auto_transition (layer)); + fail_unless (ges_layer_get_auto_transition (layer1)); + fail_unless (ges_layer_get_auto_transition (layer2)); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_single_layer_automatic_transition) +{ + GESAsset *asset; + GESTimeline *timeline; + GList *objects; + GESClip *transition; + GESLayer *layer; + GESTimelineElement *src, *src1, *src2; + + ges_init (); + + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + fail_unless (GES_IS_ASSET (asset)); + + GST_DEBUG ("Create timeline"); + timeline = ges_timeline_new_audio_video (); + assert_is_type (timeline, GES_TYPE_TIMELINE); + + GST_DEBUG ("Create first layer"); + layer = ges_layer_new (); + assert_is_type (layer, GES_TYPE_LAYER); + + GST_DEBUG ("Add first layer to timeline"); + fail_unless (ges_timeline_add_layer (timeline, layer)); + + GST_DEBUG ("Set auto transition to first layer"); + ges_layer_set_auto_transition (layer, TRUE); + + GST_DEBUG ("Check that auto-transition was properly set"); + fail_unless (ges_layer_get_auto_transition (layer)); + + GST_DEBUG ("Adding assets to first layer"); + GST_DEBUG ("Adding clip from 0 -- 1000 to first layer"); + src = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 0, 0, + 1000, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_CLIP (src)); + + GST_DEBUG ("Adding clip from 500 -- 1000 to first layer"); + src1 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 500, + 0, 1000, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_CLIP (src1)); + + /* + * 500__transition__1000 + * 0___________src_________1000 + * 500___________src1_________1500 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (_START (src), 0); + assert_equals_uint64 (_DURATION (src), 1000); + assert_equals_uint64 (_START (src1), 500); + assert_equals_uint64 (_DURATION (src1), 1500 - 500); + ges_timeline_commit (timeline); + + GST_DEBUG ("Checking that a transition has been added"); + objects = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (objects), 4); + assert_is_type (objects->data, GES_TYPE_TEST_CLIP); + + transition = objects->next->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + + transition = objects->next->next->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + GST_DEBUG ("Moving first source to 250"); + ges_timeline_element_set_start (src, 250); + + /* + * 600_____transition______1500 + * 600___________src_________1600 + * 500___________src1_________1500 + */ + GST_DEBUG ("Checking src timing values"); + CHECK_OBJECT_PROPS (src, 250, 0, 1000); + CHECK_OBJECT_PROPS (src1, 500, 0, 1000); + + objects = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (objects), 4); + transition = objects->next->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + CHECK_OBJECT_PROPS (transition, 500, 0, 750); + + transition = objects->next->next->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + CHECK_OBJECT_PROPS (transition, 500, 0, 750); + g_list_free_full (objects, gst_object_unref); + + fail_if (ges_timeline_element_set_start (src1, 250)); + + fail_if (ges_container_edit (GES_CONTAINER (src), NULL, -1, + GES_EDIT_MODE_TRIM, GES_EDGE_START, 500)); + CHECK_OBJECT_PROPS (src, 250, 0, 1000); + CHECK_OBJECT_PROPS (src1, 500, 0, 1000); + fail_if (ges_timeline_element_trim (src, 500)); + CHECK_OBJECT_PROPS (src, 250, 0, 1000); + CHECK_OBJECT_PROPS (src1, 500, 0, 1000); + fail_if (ges_timeline_element_trim (src, 750)); + CHECK_OBJECT_PROPS (src, 250, 0, 1000); + CHECK_OBJECT_PROPS (src1, 500, 0, 1000); + fail_if (ges_timeline_element_set_start (src, 500)); + CHECK_OBJECT_PROPS (src, 250, 0, 1000); + CHECK_OBJECT_PROPS (src1, 500, 0, 1000); + + /* + * 600_____transition______1500 + * 600___________src_________1600 + * 500___________src1_________1500 + */ + ges_timeline_element_set_start (src, 600); + CHECK_OBJECT_PROPS (src, 600, 0, 1000); + CHECK_OBJECT_PROPS (src1, 500, 0, 1000); + objects = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (objects), 4); + transition = objects->next->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + CHECK_OBJECT_PROPS (transition, 600, 0, 900); + g_list_free_full (objects, gst_object_unref); + + GST_DEBUG ("Adding asset to first layer"); + GST_DEBUG ("Adding clip from 1250 -- 1000 to first layer"); + fail_if (ges_layer_add_asset (layer, asset, 1250, 0, + 1000, GES_TRACK_TYPE_UNKNOWN)); + + /* + * 1500___________src2________2000 + * 1500_trans_1600 + * 600______________src________________1600 + * 600_____transition______1500 + * 500___________src1_________1500 + */ + src2 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 1500, 0, + 500, GES_TRACK_TYPE_UNKNOWN)); + assert_is_type (src2, GES_TYPE_TEST_CLIP); + + CHECK_OBJECT_PROPS (src, 600, 0, 1000); + CHECK_OBJECT_PROPS (src1, 500, 0, 1000); + CHECK_OBJECT_PROPS (src2, 1500, 0, 500); + objects = ges_layer_get_clips (layer); + transition = objects->next->next->data; + assert_equals_int (g_list_length (objects), 7); + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + CHECK_OBJECT_PROPS (transition, 600, 0, 900); + transition = objects->next->next->next->next->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + CHECK_OBJECT_PROPS (transition, 1500, 0, 100); + + g_list_free_full (objects, gst_object_unref); + + gst_object_unref (timeline); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_multi_layer_automatic_transition) +{ + GESAsset *asset; + GESTimeline *timeline; + GList *objects, *current; + GESClip *transition; + GESLayer *layer, *layer1; + GESTimelineElement *src, *src1, *src2, *src3; + + ges_init (); + + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + fail_unless (GES_IS_ASSET (asset)); + + GST_DEBUG ("Create timeline"); + timeline = ges_timeline_new_audio_video (); + assert_is_type (timeline, GES_TYPE_TIMELINE); + + GST_DEBUG ("Create first layer"); + layer = ges_layer_new (); + assert_is_type (layer, GES_TYPE_LAYER); + + GST_DEBUG ("Add first layer to timeline"); + fail_unless (ges_timeline_add_layer (timeline, layer)); + + GST_DEBUG ("Append a new layer to the timeline"); + layer1 = ges_timeline_append_layer (timeline); + assert_is_type (layer1, GES_TYPE_LAYER); + + GST_DEBUG ("Set auto transition to first layer"); + ges_layer_set_auto_transition (layer, TRUE); + + GST_DEBUG ("Check that auto-transition was properly set"); + fail_unless (ges_layer_get_auto_transition (layer)); + fail_if (ges_layer_get_auto_transition (layer1)); + + GST_DEBUG ("Adding assets to first layer"); + GST_DEBUG ("Adding clip from 0 -- 1000 to first layer"); + src = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 0, 0, + 1000, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_CLIP (src)); + + GST_DEBUG ("Adding clip from 500 -- 1000 to first layer"); + src1 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 500, + 0, 1000, GES_TRACK_TYPE_UNKNOWN)); + ges_timeline_commit (timeline); + fail_unless (GES_IS_CLIP (src1)); + + /* + * 500__transition__1000 + * 0___________src_________1000 + * 500___________src1_________1500 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (_START (src), 0); + assert_equals_uint64 (_DURATION (src), 1000); + assert_equals_uint64 (_START (src1), 500); + assert_equals_uint64 (_DURATION (src1), 1500 - 500); + + GST_DEBUG ("Checking that a transition has been added"); + current = objects = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (objects), 4); + assert_is_type (current->data, GES_TYPE_TEST_CLIP); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + GST_DEBUG ("Adding clip 2 from 500 -- 1000 to second layer"); + src2 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer1, asset, 0, + 0, 1000, GES_TRACK_TYPE_UNKNOWN)); + GST_DEBUG ("Adding clip 3 from 500 -- 1000 to second layer"); + src3 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer1, asset, 500, + 0, 1000, GES_TRACK_TYPE_UNKNOWN)); + assert_is_type (src3, GES_TYPE_TEST_CLIP); + + /* 500__transition__1000 + * 0___________src_________1000 + * 500___________src1_________1500 + *---------------------------------------------------- + * 0___________src2_________1000 + * 500___________src3_________1500 Layer1 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (_START (src), 0); + assert_equals_uint64 (_DURATION (src), 1000); + assert_equals_uint64 (_START (src1), 500); + assert_equals_uint64 (_DURATION (src1), 1500 - 500); + assert_equals_uint64 (_START (src2), 0); + assert_equals_uint64 (_DURATION (src2), 1000); + assert_equals_uint64 (_START (src3), 500); + assert_equals_uint64 (_DURATION (src3), 1500 - 500); + + GST_DEBUG ("Checking transitions on first layer"); + current = objects = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (objects), 4); + assert_is_type (current->data, GES_TYPE_TEST_CLIP); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + GST_DEBUG ("Checking transitions on second layer"); + current = objects = ges_layer_get_clips (layer1); + assert_equals_int (g_list_length (objects), 2); + fail_unless (current->data == src2); + fail_unless (current->next->data == src3); + g_list_free_full (objects, gst_object_unref); + + GST_DEBUG + ("Set auto transition to second layer, a new transition should be added"); + ges_layer_set_auto_transition (layer1, TRUE); + + /* 500__transition__1000 + * 0___________src_________1000 + * 500___________src1_________1500 + *---------------------------------------------------- + * 500__transition__1000 + * 0__________src2_________1000 + * 500___________src3_________1500 Layer1 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (_START (src), 0); + assert_equals_uint64 (_DURATION (src), 1000); + assert_equals_uint64 (_START (src1), 500); + assert_equals_uint64 (_DURATION (src1), 1500 - 500); + assert_equals_uint64 (_START (src2), 0); + assert_equals_uint64 (_DURATION (src2), 1000); + assert_equals_uint64 (_START (src3), 500); + assert_equals_uint64 (_DURATION (src3), 1500 - 500); + + GST_DEBUG ("Checking transitions on first layer"); + current = objects = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (objects), 4); + assert_is_type (current->data, GES_TYPE_TEST_CLIP); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + GST_DEBUG ("Checking transitions has been added on second layer"); + current = objects = ges_layer_get_clips (layer1); + assert_equals_int (g_list_length (objects), 4); + assert_is_type (current->data, GES_TYPE_TEST_CLIP); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + GST_DEBUG ("Moving src3 to 1000. should remove transition"); + ges_timeline_element_set_start (src3, 1000); + + /* 500__transition__1000 + * 0___________src_________1000 + * 500___________src1_________1500 Layer + *---------------------------------------------------- + * 0__________src2_________1000 + * 1000___________src3_________2000 Layer1 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (_START (src), 0); + assert_equals_uint64 (_DURATION (src), 1000); + assert_equals_uint64 (_START (src1), 500); + assert_equals_uint64 (_DURATION (src1), 1500 - 500); + assert_equals_uint64 (_START (src2), 0); + assert_equals_uint64 (_DURATION (src2), 1000); + assert_equals_uint64 (_START (src3), 1000); + assert_equals_uint64 (_DURATION (src3), 2000 - 1000); + + GST_DEBUG ("Checking transitions on first layer"); + current = objects = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (objects), 4); + assert_is_type (current->data, GES_TYPE_TEST_CLIP); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + GST_DEBUG ("Checking transitions has been removed on second layer"); + current = objects = ges_layer_get_clips (layer1); + assert_equals_int (g_list_length (objects), 2); + fail_unless (current->data == src2); + fail_unless (current->next->data == src3); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + GST_DEBUG ("Moving src3 to first layer, should add a transition"); + ges_clip_move_to_layer (GES_CLIP (src3), layer); + + /* 500__transition__1000 + * 0___________src_________1000 + * 500___________src1_________1500 + * 1000___________src3_________2000 Layer + * 1000__tr__1500 + *---------------------------------------------------- + * 0__________src2_________1000 Layer1 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (_START (src), 0); + assert_equals_uint64 (_DURATION (src), 1000); + assert_equals_uint64 (_START (src1), 500); + assert_equals_uint64 (_DURATION (src1), 1500 - 500); + assert_equals_uint64 (_START (src2), 0); + assert_equals_uint64 (_DURATION (src2), 1000); + assert_equals_uint64 (_START (src3), 1000); + assert_equals_uint64 (_DURATION (src3), 2000 - 1000); + + GST_DEBUG ("Checking transitions on first layer"); + current = objects = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (objects), 7); + fail_unless (current->data == src); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + fail_unless (current->data == src1); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 1000); + assert_equals_uint64 (_DURATION (transition), 1500 - 1000); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 1000); + assert_equals_uint64 (_DURATION (transition), 1500 - 1000); + + current = current->next; + fail_unless (current->data == src3); + + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + GST_DEBUG ("Checking second layer"); + current = objects = ges_layer_get_clips (layer1); + assert_equals_int (g_list_length (objects), 1); + fail_unless (current->data == src2); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + GST_DEBUG + ("Moving src to second layer, should remove first transition on first layer"); + fail_if (ges_clip_move_to_layer (GES_CLIP (src), layer1)); + + /* 500___________src1_________1500 + * 1000___________src3_________2000 Layer + * 1000__tr__1500 + *---------------------------------------------------- + * 0___________src_________1000 + * 0__________src2_________1000 Layer1 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (_START (src), 0); + assert_equals_uint64 (_DURATION (src), 1000); + assert_equals_uint64 (_START (src1), 500); + assert_equals_uint64 (_DURATION (src1), 1500 - 500); + assert_equals_uint64 (_START (src2), 0); + assert_equals_uint64 (_DURATION (src2), 1000); + assert_equals_uint64 (_START (src3), 1000); + assert_equals_uint64 (_DURATION (src3), 2000 - 1000); + + GST_DEBUG ("Checking transitions on first layer"); + current = objects = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (objects), 7); + + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + GST_DEBUG ("Edit src to first layer start=1500"); + ges_container_edit (GES_CONTAINER (src), NULL, 0, + GES_EDIT_MODE_NORMAL, GES_EDGE_NONE, 1500); + /* 1500___________src_________2500 + * 1500______tr______2000 + * 500___________src1_________1500 ^ + * 1000_________^_src3_________2000 Layer + * 1000__tr__1500 + *--------------------------------------------------------------------------- + * 0__________src2_________1000 Layer1 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (_START (src), 1500); + assert_equals_uint64 (_DURATION (src), 1000); + assert_equals_uint64 (_START (src1), 500); + assert_equals_uint64 (_DURATION (src1), 1500 - 500); + assert_equals_uint64 (_START (src2), 0); + assert_equals_uint64 (_DURATION (src2), 1000); + assert_equals_uint64 (_START (src3), 1000); + assert_equals_uint64 (_DURATION (src3), 2000 - 1000); + + GST_DEBUG ("Checking transitions on first layer"); + current = objects = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (objects), 7); + fail_unless (current->data == src1); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 1000); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 1000); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + fail_unless (current->data == src3); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 1500); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 1500); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + fail_unless (current->data == src); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + GST_DEBUG ("Checking second layer"); + current = objects = ges_layer_get_clips (layer1); + assert_equals_int (g_list_length (objects), 1); + assert_is_type (current->data, GES_TYPE_TEST_CLIP); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + GST_DEBUG ("Ripple src1 to 700"); + ges_container_edit (GES_CONTAINER (src1), NULL, 0, + GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, 700); + + /* 1700___________src_________2700 + * 1700__tr__2000 + * 700___________src1_________1700 + * 1200___________src3_________2200 Layer + * 1200___tr__1700 + *--------------------------------------------------------------------------- + * 0__________src2_________1000 Layer1 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (_START (src), 1700); + assert_equals_uint64 (_DURATION (src), 1000); + assert_equals_uint64 (_START (src1), 700); + assert_equals_uint64 (_DURATION (src1), 1700 - 700); + assert_equals_uint64 (_START (src2), 0); + assert_equals_uint64 (_DURATION (src2), 1000); + assert_equals_uint64 (_START (src3), 1200); + assert_equals_uint64 (_DURATION (src3), 2200 - 1200); + + GST_DEBUG ("Checking transitions on first layer"); + current = objects = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (objects), 7); + fail_unless (current->data == src1); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 1200); + assert_equals_uint64 (_DURATION (transition), 1700 - 1200); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 1200); + assert_equals_uint64 (_DURATION (transition), 1700 - 1200); + + current = current->next; + fail_unless (current->data == src3); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 1700); + assert_equals_uint64 (_DURATION (transition), 2200 - 1700); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 1700); + assert_equals_uint64 (_DURATION (transition), 2200 - 1700); + + current = current->next; + fail_unless (current->data == src); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + GST_DEBUG ("Checking second layer"); + current = objects = ges_layer_get_clips (layer1); + assert_equals_int (g_list_length (objects), 1); + assert_is_type (current->data, GES_TYPE_TEST_CLIP); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + gst_object_unref (timeline); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_layer_activate_automatic_transition) +{ + GESAsset *asset, *transition_asset; + GESTimeline *timeline; + GESLayer *layer; + GList *objects, *current; + GESClip *transition; + GESTimelineElement *src, *src1, *src2, *src3; + + ges_init (); + + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + transition_asset = + ges_asset_request (GES_TYPE_TRANSITION_CLIP, "crossfade", NULL); + fail_unless (GES_IS_ASSET (asset)); + + GST_DEBUG ("Create timeline"); + timeline = ges_timeline_new_audio_video (); + assert_is_type (timeline, GES_TYPE_TIMELINE); + + GST_DEBUG ("Append a layer to the timeline"); + layer = ges_timeline_append_layer (timeline); + assert_is_type (layer, GES_TYPE_LAYER); + + GST_DEBUG ("Adding clip from 0 -- 1000 to layer"); + src = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 0, 0, + 1000, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_CLIP (src)); + + GST_DEBUG ("Adding clip from 500 -- 1000 to first layer"); + src1 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 500, + 0, 1000, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_CLIP (src1)); + + GST_DEBUG ("Adding clip from 1000 -- 2000 to layer"); + src2 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 1000, + 0, 1000, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_CLIP (src2)); + + GST_DEBUG ("Adding clip from 2000 -- 2500 to layer"); + src3 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 2000, + 0, 500, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_CLIP (src3)); + + /* + * 0___________src_________1000 + * 500___________src1_________1500 + * 1000____src2_______2000 + * 2000_______src2_____2500 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (_START (src), 0); + assert_equals_uint64 (_DURATION (src), 1000); + assert_equals_uint64 (_START (src1), 500); + assert_equals_uint64 (_DURATION (src1), 1500 - 500); + assert_equals_uint64 (_START (src2), 1000); + assert_equals_uint64 (_DURATION (src2), 1000); + assert_equals_uint64 (_START (src3), 2000); + assert_equals_uint64 (_DURATION (src3), 500); + + GST_DEBUG ("Checking that no transition has been added"); + current = objects = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (objects), 4); + assert_is_type (current->data, GES_TYPE_TEST_CLIP); + g_list_free_full (objects, gst_object_unref); + + GST_DEBUG ("Adding transition from 1000 -- 1500 to layer"); + transition = + GES_CLIP (ges_layer_add_asset (layer, + transition_asset, 1000, 0, 500, GES_TRACK_TYPE_VIDEO)); + g_object_unref (transition_asset); + fail_unless (GES_IS_TRANSITION_CLIP (transition)); + objects = GES_CONTAINER_CHILDREN (transition); + assert_equals_int (g_list_length (objects), 1); + + GST_DEBUG ("Checking the transitions"); + /* + * 0___________src_________1000 + * 500___________src1_________1500 + * 1000__tr__1500 (1 of the 2 tracks only) + * 1000____src2_______2000 + * 2000_______src3_____2500 + */ + current = objects = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (objects), 5); + current = current->next; + assert_is_type (current->data, GES_TYPE_TEST_CLIP); + current = current->next; + assert_is_type (current->data, GES_TYPE_TRANSITION_CLIP); + current = current->next; + assert_is_type (current->data, GES_TYPE_TEST_CLIP); + current = current->next; + assert_is_type (current->data, GES_TYPE_TEST_CLIP); + g_list_free_full (objects, gst_object_unref); + + ges_layer_set_auto_transition (layer, TRUE); + /* + * 0___________src_________1000 + * 500______tr______1000 + * 500___________src1_________1500 + * 1000__tr__1500 + * 1000____src2_______2000 + * 2000_______src3_____2500 + */ + current = objects = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (objects), 8); + assert_equals_uint64 (_START (src), 0); + assert_equals_uint64 (_DURATION (src), 1000); + assert_equals_uint64 (_START (src1), 500); + assert_equals_uint64 (_DURATION (src1), 1500 - 500); + assert_equals_uint64 (_START (src2), 1000); + assert_equals_uint64 (_DURATION (src2), 1000); + assert_equals_uint64 (_START (src3), 2000); + assert_equals_uint64 (_DURATION (src3), 500); + + GST_DEBUG ("Checking transitions"); + fail_unless (current->data == src); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + fail_unless (current->data == src1); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 1000); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 1000); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + fail_unless (current->data == src2); + + current = current->next; + fail_unless (current->data == src3); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + GST_DEBUG ("Moving src2 to 1200, check everything updates properly"); + ges_timeline_element_set_start (src2, 1200); + ges_timeline_commit (timeline); + /* + * 0___________src_________1000 + * 500______tr______1000 + * 500___________src1_________1500 + * 1200_tr_1500 + * 1200____src2_______2200 + * !__tr__^ + * 2000_______src3_____2500 + */ + current = objects = ges_layer_get_clips (layer); + assert_equals_int (g_list_length (objects), 10); + assert_equals_uint64 (_START (src), 0); + assert_equals_uint64 (_DURATION (src), 1000); + assert_equals_uint64 (_START (src1), 500); + assert_equals_uint64 (_DURATION (src1), 1500 - 500); + assert_equals_uint64 (_START (src2), 1200); + assert_equals_uint64 (_DURATION (src2), 1000); + assert_equals_uint64 (_START (src3), 2000); + assert_equals_uint64 (_DURATION (src3), 500); + + GST_DEBUG ("Checking transitions"); + fail_unless (current->data == src); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 500); + assert_equals_uint64 (_DURATION (transition), 500); + + current = current->next; + fail_unless (current->data == src1); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 1200); + assert_equals_uint64 (_DURATION (transition), 300); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 1200); + assert_equals_uint64 (_DURATION (transition), 300); + + current = current->next; + fail_unless (current->data == src2); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 2000); + assert_equals_uint64 (_DURATION (transition), 200); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + assert_equals_uint64 (_START (transition), 2000); + assert_equals_uint64 (_DURATION (transition), 200); + + current = current->next; + fail_unless (current->data == src3); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); + + + gst_object_unref (timeline); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_layer_meta_string) +{ + GESTimeline *timeline; + GESLayer *layer; + const gchar *result; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + + ges_meta_container_set_string (GES_META_CONTAINER (layer), + "ges-test", "blub"); + + fail_unless ((result = ges_meta_container_get_string (GES_META_CONTAINER + (layer), "ges-test")) != NULL); + + assert_equals_string (result, "blub"); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_layer_meta_boolean) +{ + GESTimeline *timeline; + GESLayer *layer; + gboolean result; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + + ges_meta_container_set_boolean (GES_META_CONTAINER (layer), "ges-test", TRUE); + + fail_unless (ges_meta_container_get_boolean (GES_META_CONTAINER + (layer), "ges-test", &result)); + + fail_unless (result); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_layer_meta_int) +{ + GESTimeline *timeline; + GESLayer *layer; + gint result; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + + ges_meta_container_set_int (GES_META_CONTAINER (layer), "ges-test", 1234); + + fail_unless (ges_meta_container_get_int (GES_META_CONTAINER (layer), + "ges-test", &result)); + + assert_equals_int (result, 1234); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_layer_meta_uint) +{ + GESTimeline *timeline; + GESLayer *layer; + guint result; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + + ges_meta_container_set_uint (GES_META_CONTAINER (layer), "ges-test", 42); + + fail_unless (ges_meta_container_get_uint (GES_META_CONTAINER + (layer), "ges-test", &result)); + + assert_equals_int (result, 42); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_layer_meta_int64) +{ + GESTimeline *timeline; + GESLayer *layer; + gint64 result; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + + ges_meta_container_set_int64 (GES_META_CONTAINER (layer), "ges-test", 1234); + + fail_unless (ges_meta_container_get_int64 (GES_META_CONTAINER (layer), + "ges-test", &result)); + + assert_equals_int64 (result, 1234); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_layer_meta_uint64) +{ + GESTimeline *timeline; + GESLayer *layer; + guint64 result; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + + ges_meta_container_set_uint64 (GES_META_CONTAINER (layer), "ges-test", 42); + + fail_unless (ges_meta_container_get_uint64 (GES_META_CONTAINER + (layer), "ges-test", &result)); + + assert_equals_uint64 (result, 42); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_layer_meta_float) +{ + GESTimeline *timeline; + GESLayer *layer; + gfloat result; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + + fail_unless (ges_meta_container_set_float (GES_META_CONTAINER (layer), + "ges-test", 23.456)); + + fail_unless (ges_meta_container_get_float (GES_META_CONTAINER (layer), + "ges-test", &result)); + + assert_equals_float (result, 23.456f); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_layer_meta_double) +{ + GESTimeline *timeline; + GESLayer *layer; + gdouble result; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + + ges_meta_container_set_double (GES_META_CONTAINER (layer), + "ges-test", 23.456); + + fail_unless (ges_meta_container_get_double (GES_META_CONTAINER + (layer), "ges-test", &result)); + fail_unless (result == 23.456); + + //TODO CHECK + assert_equals_float (result, 23.456); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_layer_meta_date) +{ + GESTimeline *timeline; + GESLayer *layer; + GDate *input; + GDate *result; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + + input = g_date_new_dmy (1, 1, 2012); + + ges_meta_container_set_date (GES_META_CONTAINER (layer), "ges-test", input); + + fail_unless (ges_meta_container_get_date (GES_META_CONTAINER + (layer), "ges-test", &result)); + + fail_unless (g_date_compare (result, input) == 0); + + g_date_free (input); + g_date_free (result); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_layer_meta_date_time) +{ + GESTimeline *timeline; + GESLayer *layer; + GstDateTime *input; + GstDateTime *result = NULL; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + + input = gst_date_time_new_from_unix_epoch_local_time (123456789); + + fail_unless (ges_meta_container_set_date_time (GES_META_CONTAINER + (layer), "ges-test", input)); + + fail_unless (ges_meta_container_get_date_time (GES_META_CONTAINER + (layer), "ges-test", &result)); + + fail_unless (result != NULL); + fail_unless (gst_date_time_get_day (input) == gst_date_time_get_day (result)); + fail_unless (gst_date_time_get_hour (input) == + gst_date_time_get_hour (result)); + + gst_date_time_unref (input); + gst_date_time_unref (result); + + ges_deinit (); +} + +GST_END_TEST; + + +GST_START_TEST (test_layer_meta_value) +{ + GESTimeline *timeline; + GESLayer *layer; + GValue data = G_VALUE_INIT; + const GValue *result; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + + g_value_init (&data, G_TYPE_STRING); + g_value_set_string (&data, "Hello world!"); + + ges_meta_container_set_meta (GES_META_CONTAINER (layer), + "ges-test-value", &data); + + result = + ges_meta_container_get_meta (GES_META_CONTAINER (layer), + "ges-test-value"); + assert_equals_string (g_value_get_string (result), "Hello world!"); + + g_value_unset (&data); + + ges_deinit (); +} + +GST_END_TEST; + + +GST_START_TEST (test_layer_meta_marker_list) +{ + GESTimeline *timeline; + GESLayer *layer, *layer2; + GESMarkerList *mlist, *mlist2; + GESMarker *marker; + gchar *metas1, *metas2; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + layer2 = ges_layer_new (); + ges_timeline_add_layer (timeline, layer2); + + mlist = ges_marker_list_new (); + marker = ges_marker_list_add (mlist, 42); + ges_meta_container_set_string (GES_META_CONTAINER (marker), "bar", "baz"); + marker = ges_marker_list_add (mlist, 84); + ges_meta_container_set_string (GES_META_CONTAINER (marker), "lorem", + "ip\tsu\"m;"); + + ASSERT_OBJECT_REFCOUNT (mlist, "local ref", 1); + + fail_unless (ges_meta_container_set_marker_list (GES_META_CONTAINER (layer), + "foo", mlist)); + + ASSERT_OBJECT_REFCOUNT (mlist, "GstStructure + local ref", 2); + + mlist2 = + ges_meta_container_get_marker_list (GES_META_CONTAINER (layer), "foo"); + + fail_unless (mlist == mlist2); + + ASSERT_OBJECT_REFCOUNT (mlist, "GstStructure + getter + local ref", 3); + + g_object_unref (mlist2); + + ASSERT_OBJECT_REFCOUNT (mlist, "GstStructure + local ref", 2); + + metas1 = ges_meta_container_metas_to_string (GES_META_CONTAINER (layer)); + ges_meta_container_add_metas_from_string (GES_META_CONTAINER (layer2), + metas1); + metas2 = ges_meta_container_metas_to_string (GES_META_CONTAINER (layer2)); + + fail_unless_equals_string (metas1, metas2); + g_free (metas1); + g_free (metas2); + + fail_unless (ges_meta_container_set_marker_list (GES_META_CONTAINER (layer), + "foo", NULL)); + + ASSERT_OBJECT_REFCOUNT (mlist, "local ref", 1); + + g_object_unref (mlist); + g_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_layer_meta_register) +{ + GESTimeline *timeline; + GESLayer *layer; + const gchar *result; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + + fail_unless (ges_meta_container_register_meta_string (GES_META_CONTAINER + (layer), GES_META_READABLE, "ges-test-value", "Hello world!")); + + result = ges_meta_container_get_string (GES_META_CONTAINER (layer), + "ges-test-value"); + assert_equals_string (result, "Hello world!"); + + fail_if (ges_meta_container_set_int (GES_META_CONTAINER (layer), + "ges-test-value", 123456)); + + result = ges_meta_container_get_string (GES_META_CONTAINER (layer), + "ges-test-value"); + assert_equals_string (result, "Hello world!"); + + ges_deinit (); +} + +GST_END_TEST; + +static void +test_foreach (const GESMetaContainer * container, const gchar * key, + GValue * value, gpointer user_data) +{ + fail_unless ((0 == g_strcmp0 (key, "some-string")) || + (0 == g_strcmp0 (key, "some-int")) || (0 == g_strcmp0 (key, "volume"))); +} + +GST_START_TEST (test_layer_meta_foreach) +{ + GESTimeline *timeline; + GESLayer *layer; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + + ges_meta_container_set_string (GES_META_CONTAINER (layer), + "some-string", "some-content"); + + ges_meta_container_set_int (GES_META_CONTAINER (layer), "some-int", 123456); + + ges_meta_container_foreach (GES_META_CONTAINER (layer), + (GESMetaForeachFunc) test_foreach, NULL); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_layer_get_clips_in_interval) +{ + GESTimeline *timeline; + GESLayer *layer; + GESClip *clip, *clip2, *clip3; + GList *objects, *current; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + + clip = (GESClip *) ges_test_clip_new (); + fail_unless (clip != NULL); + g_object_set (clip, "start", 10, "duration", 30, NULL); + assert_equals_uint64 (_START (clip), 10); + assert_equals_uint64 (_DURATION (clip), 30); + + ges_layer_add_clip (layer, GES_CLIP (clip)); + + /* Clip's start lies between the interval */ + current = objects = ges_layer_get_clips_in_interval (layer, 0, 30); + assert_equals_int (g_list_length (objects), 1); + fail_unless (current->data == GES_TIMELINE_ELEMENT (clip)); + g_list_free_full (objects, gst_object_unref); + + current = objects = ges_layer_get_clips_in_interval (layer, 0, 11); + assert_equals_int (g_list_length (objects), 1); + fail_unless (current->data == GES_TIMELINE_ELEMENT (clip)); + g_list_free_full (objects, gst_object_unref); + + /* Clip's end lies between the interval */ + current = objects = ges_layer_get_clips_in_interval (layer, 30, 50); + assert_equals_int (g_list_length (objects), 1); + fail_unless (current->data == GES_TIMELINE_ELEMENT (clip)); + g_list_free_full (objects, gst_object_unref); + + current = objects = ges_layer_get_clips_in_interval (layer, 39, 50); + assert_equals_int (g_list_length (objects), 1); + fail_unless (current->data == GES_TIMELINE_ELEMENT (clip)); + g_list_free_full (objects, gst_object_unref); + + /* Clip exactly overlaps the interval */ + current = objects = ges_layer_get_clips_in_interval (layer, 10, 40); + assert_equals_int (g_list_length (objects), 1); + fail_unless (current->data == GES_TIMELINE_ELEMENT (clip)); + g_list_free_full (objects, gst_object_unref); + + /* Clip completely inside the interval */ + current = objects = ges_layer_get_clips_in_interval (layer, 0, 50); + assert_equals_int (g_list_length (objects), 1); + fail_unless (current->data == GES_TIMELINE_ELEMENT (clip)); + g_list_free_full (objects, gst_object_unref); + + /* Interval completely inside the clip duration */ + current = objects = ges_layer_get_clips_in_interval (layer, 20, 30); + assert_equals_int (g_list_length (objects), 1); + fail_unless (current->data == GES_TIMELINE_ELEMENT (clip)); + g_list_free_full (objects, gst_object_unref); + + /* No intersecting clip */ + objects = ges_layer_get_clips_in_interval (layer, 0, 10); + assert_equals_int (g_list_length (objects), 0); + + objects = ges_layer_get_clips_in_interval (layer, 40, 50); + assert_equals_int (g_list_length (objects), 0); + + /* Multiple intersecting clips */ + clip2 = (GESClip *) ges_test_clip_new (); + fail_unless (clip2 != NULL); + g_object_set (clip2, "start", 50, "duration", 10, NULL); + assert_equals_uint64 (_START (clip2), 50); + assert_equals_uint64 (_DURATION (clip2), 10); + + ges_layer_add_clip (layer, GES_CLIP (clip2)); + + clip3 = (GESClip *) ges_test_clip_new (); + fail_unless (clip3 != NULL); + g_object_set (clip3, "start", 0, "duration", 5, NULL); + assert_equals_uint64 (_START (clip3), 0); + assert_equals_uint64 (_DURATION (clip3), 5); + + ges_layer_add_clip (layer, GES_CLIP (clip3)); + + /** + * Our timeline: + * ------------- + * + * |-------- 0--------------- 0--------- | + * layer: | clip3 | | clip | | clip2 | | + * |-------05 10-------------40 50--------60 | + * |--------------------------------------------------| + * + */ + + current = objects = ges_layer_get_clips_in_interval (layer, 4, 52); + assert_equals_int (g_list_length (objects), 3); + fail_unless (current->data == GES_TIMELINE_ELEMENT (clip3)); + current = current->next; + fail_unless (current->data == GES_TIMELINE_ELEMENT (clip)); + current = current->next; + fail_unless (current->data == GES_TIMELINE_ELEMENT (clip2)); + g_list_free_full (objects, gst_object_unref); + + current = objects = ges_layer_get_clips_in_interval (layer, 39, 65); + assert_equals_int (g_list_length (objects), 2); + fail_unless (current->data == GES_TIMELINE_ELEMENT (clip)); + current = current->next; + fail_unless (current->data == GES_TIMELINE_ELEMENT (clip2)); + g_list_free_full (objects, gst_object_unref); + + ges_deinit (); +} + +GST_END_TEST; + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-layer"); + TCase *tc_chain = tcase_create ("timeline-layer"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_layer_properties); + tcase_add_test (tc_chain, test_layer_priorities); + tcase_add_test (tc_chain, test_timeline_auto_transition); + tcase_add_test (tc_chain, test_single_layer_automatic_transition); + tcase_add_test (tc_chain, test_multi_layer_automatic_transition); + tcase_add_test (tc_chain, test_layer_activate_automatic_transition); + tcase_add_test (tc_chain, test_layer_meta_string); + tcase_add_test (tc_chain, test_layer_meta_boolean); + tcase_add_test (tc_chain, test_layer_meta_int); + tcase_add_test (tc_chain, test_layer_meta_uint); + tcase_add_test (tc_chain, test_layer_meta_int64); + tcase_add_test (tc_chain, test_layer_meta_uint64); + tcase_add_test (tc_chain, test_layer_meta_float); + tcase_add_test (tc_chain, test_layer_meta_double); + tcase_add_test (tc_chain, test_layer_meta_date); + tcase_add_test (tc_chain, test_layer_meta_date_time); + tcase_add_test (tc_chain, test_layer_meta_value); + tcase_add_test (tc_chain, test_layer_meta_marker_list); + tcase_add_test (tc_chain, test_layer_meta_register); + tcase_add_test (tc_chain, test_layer_meta_foreach); + tcase_add_test (tc_chain, test_layer_get_clips_in_interval); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/markerlist.c b/tests/check/ges/markerlist.c new file mode 100644 index 0000000000..e26dfbb0b0 --- /dev/null +++ b/tests/check/ges/markerlist.c @@ -0,0 +1,483 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2019> Mathieu Duponchelle <mathieu@centricular.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" +#include <ges/ges.h> +#include <gst/check/gstcheck.h> + +GST_START_TEST (test_add) +{ + GESMarkerList *markerlist; + GESMarker *marker; + ges_init (); + + markerlist = ges_marker_list_new (); + marker = ges_marker_list_add (markerlist, 42); + + ASSERT_OBJECT_REFCOUNT (marker, "marker list", 1); + + g_object_ref (marker); + + ASSERT_OBJECT_REFCOUNT (marker, "marker list + local ref", 2); + + g_object_unref (markerlist); + + ASSERT_OBJECT_REFCOUNT (marker, "local ref", 1); + + g_object_unref (marker); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_remove) +{ + GESMarkerList *markerlist; + GESMarker *marker; + ges_init (); + + markerlist = ges_marker_list_new (); + marker = ges_marker_list_add (markerlist, 42); + + g_object_ref (marker); + + fail_unless_equals_int (ges_marker_list_size (markerlist), 1); + + fail_unless (ges_marker_list_remove (markerlist, marker)); + fail_unless_equals_int (ges_marker_list_size (markerlist), 0); + + ASSERT_OBJECT_REFCOUNT (marker, "local ref", 1); + + fail_if (ges_marker_list_remove (markerlist, marker)); + + g_object_unref (marker); + + g_object_unref (markerlist); + + ges_deinit (); +} + +GST_END_TEST; + + +static void +marker_added_cb (GESMarkerList * mlist, GstClockTime position, + GESMarker * marker, gboolean * called) +{ + fail_unless_equals_int (position, 42); + + ASSERT_OBJECT_REFCOUNT (marker, "local ref + signal", 2); + *called = TRUE; +} + +GST_START_TEST (test_signal_marker_added) +{ + GESMarkerList *mlist; + GESMarker *marker; + gboolean called = FALSE; + + ges_init (); + + mlist = ges_marker_list_new (); + g_signal_connect (mlist, "marker-added", G_CALLBACK (marker_added_cb), + &called); + marker = ges_marker_list_add (mlist, 42); + ASSERT_OBJECT_REFCOUNT (marker, "local ref", 1); + fail_unless (called == TRUE); + g_signal_handlers_disconnect_by_func (mlist, marker_added_cb, &called); + + g_object_unref (mlist); + + ges_deinit (); +} + +GST_END_TEST; + + +static void +marker_removed_cb (GESMarkerList * mlist, GESMarker * marker, gboolean * called) +{ + ASSERT_OBJECT_REFCOUNT (marker, "local ref + signal", 2); + *called = TRUE; +} + +GST_START_TEST (test_signal_marker_removed) +{ + GESMarkerList *mlist; + GESMarker *marker; + gboolean called = FALSE; + + ges_init (); + + mlist = ges_marker_list_new (); + marker = ges_marker_list_add (mlist, 42); + + ASSERT_OBJECT_REFCOUNT (marker, "local ref", 1); + + g_signal_connect (mlist, "marker-removed", G_CALLBACK (marker_removed_cb), + &called); + + fail_unless (ges_marker_list_remove (mlist, marker)); + + fail_unless (called == TRUE); + + g_signal_handlers_disconnect_by_func (mlist, marker_removed_cb, &called); + + g_object_unref (mlist); + + ges_deinit (); +} + +GST_END_TEST; + + +static void +marker_moved_cb (GESMarkerList * mlist, GstClockTime prev_position, + GstClockTime position, GESMarker * marker, gboolean * called) +{ + fail_unless_equals_int (prev_position, 10); + fail_unless_equals_int (position, 42); + + ASSERT_OBJECT_REFCOUNT (marker, "local ref + signal", 2); + *called = TRUE; +} + +GST_START_TEST (test_signal_marker_moved) +{ + GESMarkerList *mlist; + GESMarker *marker; + gboolean called = FALSE; + + ges_init (); + + mlist = ges_marker_list_new (); + g_signal_connect (mlist, "marker-moved", G_CALLBACK (marker_moved_cb), + &called); + + marker = ges_marker_list_add (mlist, 10); + ASSERT_OBJECT_REFCOUNT (marker, "local ref", 1); + + fail_unless (ges_marker_list_move (mlist, marker, 42)); + + fail_unless (called == TRUE); + + g_signal_handlers_disconnect_by_func (mlist, marker_moved_cb, &called); + + g_object_unref (mlist); + + ges_deinit (); +} + +GST_END_TEST; + + +GST_START_TEST (test_get_markers) +{ + GESMarkerList *markerlist; + GESMarker *marker1, *marker2, *marker3, *marker4; + GList *markers; + + ges_init (); + + markerlist = ges_marker_list_new (); + marker1 = ges_marker_list_add (markerlist, 0); + marker2 = ges_marker_list_add (markerlist, 10); + marker3 = ges_marker_list_add (markerlist, 20); + marker4 = ges_marker_list_add (markerlist, 30); + + markers = ges_marker_list_get_markers (markerlist); + + ASSERT_OBJECT_REFCOUNT (marker1, "local ref + markers", 2); + ASSERT_OBJECT_REFCOUNT (marker2, "local ref + markers", 2); + ASSERT_OBJECT_REFCOUNT (marker3, "local ref + markers", 2); + ASSERT_OBJECT_REFCOUNT (marker4, "local ref + markers", 2); + + fail_unless (g_list_index (markers, marker1) == 0); + fail_unless (g_list_index (markers, marker2) == 1); + fail_unless (g_list_index (markers, marker3) == 2); + fail_unless (g_list_index (markers, marker4) == 3); + + g_list_foreach (markers, (GFunc) gst_object_unref, NULL); + g_list_free (markers); + + g_object_unref (markerlist); + + ges_deinit (); +} + +GST_END_TEST; + + +GST_START_TEST (test_move_marker) +{ + GESMarkerList *markerlist; + GESMarker *marker_a, *marker_b; + GstClockTime position; + GList *range; + + ges_init (); + + markerlist = ges_marker_list_new (); + + marker_a = ges_marker_list_add (markerlist, 10); + marker_b = ges_marker_list_add (markerlist, 30); + + fail_unless (ges_marker_list_move (markerlist, marker_a, 20)); + + g_object_get (marker_a, "position", &position, NULL); + fail_unless_equals_int (position, 20); + + range = ges_marker_list_get_markers (markerlist); + + fail_unless (g_list_index (range, marker_a) == 0); + fail_unless (g_list_index (range, marker_b) == 1); + + g_list_foreach (range, (GFunc) gst_object_unref, NULL); + g_list_free (range); + + fail_unless (ges_marker_list_move (markerlist, marker_a, 35)); + + range = ges_marker_list_get_markers (markerlist); + + fail_unless (g_list_index (range, marker_b) == 0); + fail_unless (g_list_index (range, marker_a) == 1); + + g_list_foreach (range, (GFunc) gst_object_unref, NULL); + g_list_free (range); + + fail_unless (ges_marker_list_move (markerlist, marker_a, 30)); + + g_object_get (marker_a, "position", &position, NULL); + fail_unless_equals_int (position, 30); + + g_object_get (marker_b, "position", &position, NULL); + fail_unless_equals_int (position, 30); + + fail_unless_equals_int (ges_marker_list_size (markerlist), 2); + + ges_marker_list_remove (markerlist, marker_a); + + fail_unless (!ges_marker_list_move (markerlist, marker_a, 20)); + + g_object_unref (markerlist); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_serialize_deserialize_in_timeline) +{ + GESMarkerList *markerlist1, *markerlist2; + gchar *metas1, *metas2; + GESTimeline *timeline1, *timeline2; + + ges_init (); + + timeline1 = ges_timeline_new_audio_video (); + + markerlist1 = ges_marker_list_new (); + ges_marker_list_add (markerlist1, 0); + ges_marker_list_add (markerlist1, 10); + + ASSERT_OBJECT_REFCOUNT (markerlist1, "local ref", 1); + + fail_unless (ges_meta_container_set_marker_list (GES_META_CONTAINER + (timeline1), "ges-test", markerlist1)); + + ASSERT_OBJECT_REFCOUNT (markerlist1, "GstStructure + local ref", 2); + + markerlist2 = + ges_meta_container_get_marker_list (GES_META_CONTAINER (timeline1), + "ges-test"); + + fail_unless (markerlist1 == markerlist2); + + ASSERT_OBJECT_REFCOUNT (markerlist1, "GstStructure + getter + local ref", 3); + + g_object_unref (markerlist2); + + ASSERT_OBJECT_REFCOUNT (markerlist1, "GstStructure + local ref", 2); + + timeline2 = ges_timeline_new_audio_video (); + + metas1 = ges_meta_container_metas_to_string (GES_META_CONTAINER (timeline1)); + ges_meta_container_add_metas_from_string (GES_META_CONTAINER (timeline2), + metas1); + metas2 = ges_meta_container_metas_to_string (GES_META_CONTAINER (timeline2)); + + fail_unless_equals_string (metas1, metas2); + + g_free (metas1); + g_free (metas2); + + fail_unless (ges_meta_container_set_marker_list (GES_META_CONTAINER + (timeline1), "ges-test", NULL)); + + ASSERT_OBJECT_REFCOUNT (markerlist1, "local ref", 1); + + g_object_unref (markerlist1); + g_object_unref (timeline1); + g_object_unref (timeline2); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_serialize_deserialize_in_value) +{ + GESMarkerList *markerlist1, *markerlist2; + GESMarker *marker; + gchar *serialized, *cmp; + const gchar *str_val; + guint uint_val; + const gchar *test_string = "test \" string"; + GList *markers; + guint64 position; + GValue val1 = G_VALUE_INIT, val2 = G_VALUE_INIT; + GESMarkerFlags flags; + + ges_init (); + + g_value_init (&val1, GES_TYPE_MARKER_LIST); + g_value_init (&val2, GES_TYPE_MARKER_LIST); + + markerlist1 = ges_marker_list_new (); + g_object_set (markerlist1, "flags", GES_MARKER_FLAG_SNAPPABLE, NULL); + marker = ges_marker_list_add (markerlist1, 0); + fail_unless (ges_meta_container_set_string (GES_META_CONTAINER (marker), + "str-val", test_string)); + marker = ges_marker_list_add (markerlist1, 10); + fail_unless (ges_meta_container_set_string (GES_META_CONTAINER (marker), + "first", test_string)); + fail_unless (ges_meta_container_set_uint (GES_META_CONTAINER (marker), + "second", 43)); + + ASSERT_OBJECT_REFCOUNT (markerlist1, "local ref", 1); + + g_value_set_instance (&val1, markerlist1); + ASSERT_OBJECT_REFCOUNT (markerlist1, "GValue + local ref", 2); + + serialized = gst_value_serialize (&val1); + fail_unless (serialized != NULL); + GST_DEBUG ("serialized to %s", serialized); + fail_unless (gst_value_deserialize (&val2, serialized)); + cmp = gst_value_serialize (&val2); + fail_unless_equals_string (cmp, serialized); + + markerlist2 = GES_MARKER_LIST (g_value_get_object (&val2)); + ASSERT_OBJECT_REFCOUNT (markerlist2, "GValue", 1); + + g_object_get (markerlist2, "flags", &flags, NULL); + fail_unless (flags == GES_MARKER_FLAG_SNAPPABLE); + + fail_unless_equals_int (ges_marker_list_size (markerlist2), 2); + markers = ges_marker_list_get_markers (markerlist2); + marker = GES_MARKER (markers->data); + fail_unless (marker != NULL); + + g_object_get (marker, "position", &position, NULL); + fail_unless_equals_uint64 (position, 0); + str_val = + ges_meta_container_get_string (GES_META_CONTAINER (marker), "str-val"); + fail_unless_equals_string (str_val, test_string); + + marker = GES_MARKER (markers->next->data); + fail_unless (marker != NULL); + fail_unless (markers->next->next == NULL); + + g_object_get (marker, "position", &position, NULL); + fail_unless_equals_uint64 (position, 10); + str_val = + ges_meta_container_get_string (GES_META_CONTAINER (marker), "first"); + fail_unless_equals_string (str_val, test_string); + fail_unless (ges_meta_container_get_uint (GES_META_CONTAINER (marker), + "second", &uint_val)); + fail_unless_equals_int (uint_val, 43); + + g_list_free_full (markers, g_object_unref); + g_value_unset (&val1); + g_value_unset (&val2); + ASSERT_OBJECT_REFCOUNT (markerlist1, "local ref", 1); + g_object_unref (markerlist1); + g_free (serialized); + g_free (cmp); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_marker_color) +{ + GESMarkerList *mlist; + GESMarker *marker; + const guint yellow_rgb = 16776960; + guint color; + + ges_init (); + + mlist = ges_marker_list_new (); + marker = ges_marker_list_add (mlist, 0); + /* getting the color should fail since no value should be set yet */ + fail_unless (ges_meta_container_get_meta (GES_META_CONTAINER (marker), + GES_META_MARKER_COLOR) == NULL); + /* trying to set the color field to something other than a uint should + * fail */ + fail_unless (ges_meta_container_set_float (GES_META_CONTAINER (marker), + GES_META_MARKER_COLOR, 0.0) == FALSE); + fail_unless (ges_meta_container_set_uint (GES_META_CONTAINER (marker), + GES_META_MARKER_COLOR, yellow_rgb)); + fail_unless (ges_meta_container_get_uint (GES_META_CONTAINER (marker), + GES_META_MARKER_COLOR, &color)); + fail_unless_equals_int (color, yellow_rgb); + + g_object_unref (mlist); + + ges_deinit (); +} + +GST_END_TEST; + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-marker-list"); + TCase *tc_chain = tcase_create ("markerlist"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_add); + tcase_add_test (tc_chain, test_remove); + tcase_add_test (tc_chain, test_signal_marker_added); + tcase_add_test (tc_chain, test_signal_marker_removed); + tcase_add_test (tc_chain, test_signal_marker_moved); + tcase_add_test (tc_chain, test_get_markers); + tcase_add_test (tc_chain, test_move_marker); + tcase_add_test (tc_chain, test_serialize_deserialize_in_timeline); + tcase_add_test (tc_chain, test_serialize_deserialize_in_value); + tcase_add_test (tc_chain, test_marker_color); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/mixers.c b/tests/check/ges/mixers.c new file mode 100644 index 0000000000..15f7a4c061 --- /dev/null +++ b/tests/check/ges/mixers.c @@ -0,0 +1,245 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ +/* + * gst-editing-services + * + * Copyright (C) 2013 Thibault Saunier <tsaunier@gnome.org> + * + * gst-editing-services 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 3 of the License, or + * (at your option) any later version. + * + * gst-editing-services 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 program. If not, see <http://www.gnu.org/licenses/>."; + * + */ +#include "test-utils.h" +#include <ges/ges.h> +#include <gst/check/gstcheck.h> + +#include <ges/ges-smart-adder.h> + +static GMainLoop *main_loop; + +GST_START_TEST (simple_smart_adder_test) +{ + GstPad *requested_pad; + GstPadTemplate *template = NULL; + GESTrack *track; + GstElement *smart_adder; + + ges_init (); + + track = GES_TRACK (ges_audio_track_new ()); + smart_adder = ges_smart_adder_new (track); + + fail_unless (GES_IS_SMART_ADDER (smart_adder)); + fail_unless (GST_IS_ELEMENT (smart_adder)); + fail_unless (GST_IS_ELEMENT (GES_SMART_ADDER (smart_adder)->adder)); + fail_unless (GST_IS_PAD (GES_SMART_ADDER (smart_adder)->srcpad)); + + template = + gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (smart_adder), + "sink_%u"); + fail_unless (template != NULL); + requested_pad = gst_element_request_pad (GST_ELEMENT (smart_adder), + template, NULL, NULL); + fail_unless (GST_IS_PAD (requested_pad)); + + gst_object_unref (requested_pad); + gst_object_unref (smart_adder); + gst_object_unref (track); + + ges_deinit (); +} + +GST_END_TEST; + +static void +message_received_cb (GstBus * bus, GstMessage * message, GstPipeline * pipeline) +{ + GST_INFO ("bus message from \"%" GST_PTR_FORMAT "\": %" GST_PTR_FORMAT, + GST_MESSAGE_SRC (message), message); + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + g_main_loop_quit (main_loop); + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + g_main_loop_quit (main_loop); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + g_main_loop_quit (main_loop); + default: + break; + } +} + +GST_START_TEST (simple_audio_mixed_with_pipeline) +{ + GstBus *bus; + GESAsset *asset; + GESClip *tmpclip; + GstMessage *message; + GESLayer *layer, *layer1; + GESTrack *track; + GESTimeline *timeline; + GESPipeline *pipeline; + + ges_init (); + + track = GES_TRACK (ges_audio_track_new ()); + timeline = ges_timeline_new (); + pipeline = ges_test_create_pipeline (timeline); + + ges_timeline_add_track (timeline, track); + layer = ges_timeline_append_layer (timeline); + layer1 = ges_timeline_append_layer (timeline); + + asset = GES_ASSET (ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL)); + + GST_DEBUG ("Setting volume on the layer"); + ges_meta_container_set_float (GES_META_CONTAINER (layer), GES_META_VOLUME, + 1.5); + + tmpclip = ges_layer_add_asset (layer, asset, 0, 0, 1 * GST_SECOND, + GES_TRACK_TYPE_AUDIO); + ges_audio_test_source_set_volume (GES_CONTAINER_CHILDREN (tmpclip)->data, + 1.0); + ges_audio_test_source_set_freq (GES_CONTAINER_CHILDREN (tmpclip)->data, 550); + + tmpclip = ges_layer_add_asset (layer1, asset, 0, 0, 2 * GST_SECOND, + GES_TRACK_TYPE_AUDIO); + g_object_unref (asset); + + ges_audio_test_source_set_volume (GES_CONTAINER_CHILDREN (tmpclip)->data, 1); + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + main_loop = g_main_loop_new (NULL, FALSE); + + gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); + g_signal_connect (bus, "message", (GCallback) message_received_cb, pipeline); + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING) + == GST_STATE_CHANGE_FAILURE); + message = gst_bus_timed_pop_filtered (bus, 5 * GST_SECOND, + GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR); + + if (message == NULL) { + fail_unless ("No message after 5 seconds" == NULL); + goto done; + } else if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) + fail_error_message (message); + + gst_message_unref (message); + GST_INFO ("running main loop"); + g_main_loop_run (main_loop); + g_main_loop_unref (main_loop); + +done: + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + gst_object_unref (pipeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (audio_video_mixed_with_pipeline) +{ + GstBus *bus; + GESAsset *asset; + GESClip *tmpclip; + GstMessage *message; + GESLayer *layer, *layer1; + GESTrack *track; + GESTrack *track_audio; + GESTimeline *timeline; + GESPipeline *pipeline; + + ges_init (); + + track = GES_TRACK (ges_video_track_new ()); + track_audio = GES_TRACK (ges_audio_track_new ()); + timeline = ges_timeline_new (); + pipeline = ges_test_create_pipeline (timeline); + + ges_timeline_add_track (timeline, track); + ges_timeline_add_track (timeline, track_audio); + layer = ges_timeline_append_layer (timeline); + layer1 = ges_timeline_append_layer (timeline); + + asset = GES_ASSET (ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL)); + + tmpclip = + ges_layer_add_asset (layer, asset, 0 * GST_SECOND, 0, 2 * GST_SECOND, + GES_TRACK_TYPE_UNKNOWN); + + ges_test_clip_set_vpattern (GES_TEST_CLIP (tmpclip), 18); + + tmpclip = + ges_layer_add_asset (layer1, asset, 1 * GST_SECOND, 0, 5 * GST_SECOND, + GES_TRACK_TYPE_UNKNOWN); + g_object_unref (asset); + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + main_loop = g_main_loop_new (NULL, FALSE); + + gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); + g_signal_connect (bus, "message", (GCallback) message_received_cb, pipeline); + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING) + == GST_STATE_CHANGE_FAILURE); + + message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR); + + if (message == NULL) { + fail_unless ("No message after 5 seconds" == NULL); + goto done; + } else if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) + fail_error_message (message); + + gst_message_unref (message); + GST_INFO ("running main loop"); + g_main_loop_run (main_loop); + g_main_loop_unref (main_loop); + +done: + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + gst_object_unref (pipeline); + + ges_deinit (); +} + +GST_END_TEST; + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("Smart mixers"); + TCase *tc_chain = tcase_create ("smart-mixers"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, simple_smart_adder_test); + tcase_add_test (tc_chain, simple_audio_mixed_with_pipeline); + tcase_add_test (tc_chain, audio_video_mixed_with_pipeline); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/negative.c b/tests/check/ges/negative.c new file mode 100644 index 0000000000..92f21ff1db --- /dev/null +++ b/tests/check/ges/negative.c @@ -0,0 +1,73 @@ +/* GStreamer Editing Services + * Copyright (C) 2019 Seungha Yang <seungha.yang@navercorp.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <ges/ges.h> +#include <gst/check/gstcheck.h> +#include <signal.h> + +static void +sigabrt_handler (int signum) +{ + /* expected abort */ + exit (0); +} + +static gpointer +deinit_thread_func (gpointer user_data) +{ + signal (SIGABRT, sigabrt_handler); + ges_deinit (); + + /* shouldn't be reached */ + exit (1); + + return NULL; +} + +GST_START_TEST (test_inconsistent_init_deinit_thread) +{ + GThread *thread; + + fail_unless (ges_init ()); + + /* test assertion, when trying to call ges_deinit() in a thread different + * from that of ges_init() called. + */ + thread = g_thread_new ("test-ges-deinit-thread", + (GThreadFunc) deinit_thread_func, NULL); + + g_thread_join (thread); +} + +GST_END_TEST; + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-negative"); + TCase *tc_chain = tcase_create ("negative"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_inconsistent_init_deinit_thread); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/overlays.c b/tests/check/ges/overlays.c new file mode 100644 index 0000000000..b169e41636 --- /dev/null +++ b/tests/check/ges/overlays.c @@ -0,0 +1,226 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" +#include <ges/ges.h> +#include <gst/check/gstcheck.h> + +GST_START_TEST (test_overlay_basic) +{ + GESTextOverlayClip *source; + + ges_init (); + + source = ges_text_overlay_clip_new (); + fail_unless (source != NULL); + + gst_object_unref (source); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_overlay_properties) +{ + GESClip *clip; + GESTrack *track; + GESTimeline *timeline; + GESLayer *layer; + GESTrackElement *trackelement; + + ges_init (); + + track = ges_track_new (GES_TRACK_TYPE_VIDEO, gst_caps_ref (GST_CAPS_ANY)); + fail_unless (track != NULL); + layer = ges_layer_new (); + fail_unless (layer != NULL); + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + fail_unless (ges_timeline_add_layer (timeline, layer)); + fail_unless (ges_timeline_add_track (timeline, track)); + ASSERT_OBJECT_REFCOUNT (timeline, "timeline", 1); + + clip = (GESClip *) ges_text_overlay_clip_new (); + fail_unless (clip != NULL); + + /* Set some properties */ + g_object_set (clip, "start", (guint64) 42, "duration", (guint64) 51, + "in-point", (guint64) 12, NULL); + assert_equals_uint64 (_START (clip), 42); + assert_equals_uint64 (_DURATION (clip), 51); + assert_equals_uint64 (_INPOINT (clip), 12); + + ges_layer_add_clip (layer, GES_CLIP (clip)); + ges_timeline_commit (timeline); + assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1); + trackelement = GES_CONTAINER_CHILDREN (clip)->data; + fail_unless (trackelement != NULL); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) == + GES_TIMELINE_ELEMENT (clip)); + fail_unless (ges_track_element_get_track (trackelement) == track); + + /* Check that trackelement has the same properties */ + assert_equals_uint64 (_START (trackelement), 42); + assert_equals_uint64 (_DURATION (trackelement), 51); + /* in-point is 0 since it does not have has-internal-source */ + assert_equals_uint64 (_INPOINT (trackelement), 0); + + /* And let's also check that it propagated correctly to GNonLin */ + nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 51, 0, + 51, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE); + + /* Change more properties, see if they propagate */ + g_object_set (clip, "start", (guint64) 420, "duration", (guint64) 510, + "in-point", (guint64) 120, NULL); + ges_timeline_commit (timeline); + assert_equals_uint64 (_START (clip), 420); + assert_equals_uint64 (_DURATION (clip), 510); + assert_equals_uint64 (_INPOINT (clip), 120); + assert_equals_uint64 (_START (trackelement), 420); + assert_equals_uint64 (_DURATION (trackelement), 510); + assert_equals_uint64 (_INPOINT (trackelement), 0); + + /* And let's also check that it propagated correctly to GNonLin */ + nle_object_check (ges_track_element_get_nleobject (trackelement), 420, 510, + 0, 510, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0, TRUE); + + ges_container_remove (GES_CONTAINER (clip), + GES_TIMELINE_ELEMENT (trackelement)); + gst_object_unref (clip); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_overlay_in_layer) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *a, *v; + GESTrackElement *track_element; + GESTextOverlayClip *source; + gchar *text; + gint halign, valign; + guint32 color; + gdouble xpos; + gdouble ypos; + + ges_init (); + + timeline = ges_timeline_new (); + layer = (GESLayer *) ges_layer_new (); + a = GES_TRACK (ges_audio_track_new ()); + v = GES_TRACK (ges_video_track_new ()); + + ges_timeline_add_track (timeline, a); + ges_timeline_add_track (timeline, v); + ges_timeline_add_layer (timeline, layer); + + source = ges_text_overlay_clip_new (); + + g_object_set (source, "duration", (guint64) GST_SECOND, NULL); + + ges_layer_add_clip (layer, (GESClip *) source); + + /* specifically test the text property */ + g_object_set (source, "text", (gchar *) "some text", NULL); + g_object_get (source, "text", &text, NULL); + assert_equals_string ("some text", text); + g_free (text); + + track_element = + ges_clip_find_track_element (GES_CLIP (source), v, G_TYPE_NONE); + + /* test the font-desc property */ + g_object_set (source, "font-desc", (gchar *) "sans 72", NULL); + g_object_get (source, "font-desc", &text, NULL); + assert_equals_string ("sans 72", text); + g_free (text); + + assert_equals_string ("sans 72", + ges_text_overlay_get_font_desc (GES_TEXT_OVERLAY (track_element))); + + /* test halign and valign */ + g_object_set (source, "halignment", (gint) + GES_TEXT_HALIGN_LEFT, "valignment", (gint) GES_TEXT_VALIGN_TOP, NULL); + g_object_get (source, "halignment", &halign, "valignment", &valign, NULL); + assert_equals_int (halign, GES_TEXT_HALIGN_LEFT); + assert_equals_int (valign, GES_TEXT_VALIGN_TOP); + + halign = ges_text_overlay_get_halignment (GES_TEXT_OVERLAY (track_element)); + valign = ges_text_overlay_get_valignment (GES_TEXT_OVERLAY (track_element)); + assert_equals_int (halign, GES_TEXT_HALIGN_LEFT); + assert_equals_int (valign, GES_TEXT_VALIGN_TOP); + + /* test color */ + g_object_set (source, "color", (gint) 2147483647, NULL); + g_object_get (source, "color", &color, NULL); + assert_equals_int (color, 2147483647); + + color = ges_text_overlay_get_color (GES_TEXT_OVERLAY (track_element)); + assert_equals_int (color, 2147483647); + + /* test xpos */ + g_object_set (source, "xpos", (gdouble) 0.5, NULL); + g_object_get (source, "xpos", &xpos, NULL); + assert_equals_float (xpos, 0.5); + + xpos = ges_text_overlay_get_xpos (GES_TEXT_OVERLAY (track_element)); + assert_equals_float (xpos, 0.5); + + /* test ypos */ + g_object_set (source, "ypos", (gdouble) 0.33, NULL); + g_object_get (source, "ypos", &ypos, NULL); + assert_equals_float (ypos, 0.33); + + ypos = ges_text_overlay_get_ypos (GES_TEXT_OVERLAY (track_element)); + assert_equals_float (ypos, 0.33); + + GST_DEBUG ("removing the source"); + + ges_layer_remove_clip (layer, (GESClip *) source); + + GST_DEBUG ("removing the layer"); + + gst_object_unref (track_element); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-overlays"); + TCase *tc_chain = tcase_create ("overlays"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_overlay_basic); + tcase_add_test (tc_chain, test_overlay_properties); + tcase_add_test (tc_chain, test_overlay_in_layer); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/project.c b/tests/check/ges/project.c new file mode 100644 index 0000000000..d5d7c73234 --- /dev/null +++ b/tests/check/ges/project.c @@ -0,0 +1,776 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2012> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "test-utils.h" +#include <ges/ges.h> +#include <gst/check/gstcheck.h> +#include <gst/controller/gstdirectcontrolbinding.h> +#include <gst/controller/gstinterpolationcontrolsource.h> + +GMainLoop *mainloop; + +static void +project_loaded_cb (GESProject * project, GESTimeline * timeline, + GMainLoop * mainloop) +{ + g_main_loop_quit (mainloop); +} + +GST_START_TEST (test_project_simple) +{ + gchar *id; + GESProject *project; + GESTimeline *timeline; + + ges_init (); + + mainloop = g_main_loop_new (NULL, FALSE); + project = GES_PROJECT (ges_asset_request (GES_TYPE_TIMELINE, NULL, NULL)); + fail_unless (GES_IS_PROJECT (project)); + assert_equals_string (ges_asset_get_id (GES_ASSET (project)), "project-0"); + g_signal_connect (project, "loaded", (GCallback) project_loaded_cb, mainloop); + + timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), NULL)); + g_main_loop_run (mainloop); + + fail_unless (GES_IS_TIMELINE (timeline)); + id = ges_extractable_get_id (GES_EXTRACTABLE (timeline)); + assert_equals_string (id, "project-0"); + ASSERT_OBJECT_REFCOUNT (timeline, "We own the only ref", 1); + + g_free (id); + gst_object_unref (project); + gst_object_unref (timeline); + g_main_loop_unref (mainloop); + g_signal_handlers_disconnect_by_func (project, (GCallback) project_loaded_cb, + mainloop); + + ges_deinit (); +} + +GST_END_TEST; + +static void +asset_removed_add_cb (GESProject * project, GESAsset * asset, gboolean * called) +{ + *called = TRUE; +} + +static void +asset_created_cb (GObject * source, GAsyncResult * res, GESAsset ** asset) +{ + GError *error = NULL; + *asset = ges_asset_request_finish (res, &error); + + fail_unless (error == NULL); + g_main_loop_quit (mainloop); +} + +GST_START_TEST (test_project_add_assets) +{ + GESProject *project; + GESAsset *asset; + gboolean added_cb_called = FALSE; + gboolean removed_cb_called = FALSE; + + ges_init (); + + mainloop = g_main_loop_new (NULL, FALSE); + project = GES_PROJECT (ges_asset_request (GES_TYPE_TIMELINE, NULL, NULL)); + fail_unless (GES_IS_PROJECT (project)); + + g_signal_connect (project, "asset-added", + (GCallback) asset_removed_add_cb, &added_cb_called); + g_signal_connect (project, "asset-removed", + (GCallback) asset_removed_add_cb, &removed_cb_called); + + ges_asset_request_async (GES_TYPE_TEST_CLIP, NULL, NULL, + (GAsyncReadyCallback) asset_created_cb, &asset); + g_main_loop_run (mainloop); + g_main_loop_unref (mainloop); + + fail_unless (GES_IS_ASSET (asset)); + + fail_unless (ges_project_add_asset (project, asset)); + fail_unless (added_cb_called); + ASSERT_OBJECT_REFCOUNT (project, "The project", 2); + ASSERT_OBJECT_REFCOUNT (asset, "The asset (1 for project and one for " + "us + 1 cache)", 3); + + fail_unless (ges_project_remove_asset (project, asset)); + fail_unless (removed_cb_called); + + g_signal_handlers_disconnect_by_func (project, + (GCallback) asset_removed_add_cb, &added_cb_called); + g_signal_handlers_disconnect_by_func (project, + (GCallback) asset_removed_add_cb, &removed_cb_called); + + gst_object_unref (asset); + gst_object_unref (project); + ASSERT_OBJECT_REFCOUNT (asset, "The asset (1 ref in cache)", 1); + ASSERT_OBJECT_REFCOUNT (project, "The project (1 ref in cache)", 1); + + ges_deinit (); +} + +GST_END_TEST; + +static void +error_loading_asset_cb (GESProject * project, GError * error, gchar * id, + GType extractable_type, GMainLoop * mainloop) +{ + fail_unless (g_error_matches (error, GST_PARSE_ERROR, + GST_PARSE_ERROR_NO_SUCH_ELEMENT)); + g_main_loop_quit (mainloop); +} + +GST_START_TEST (test_project_unexistant_effect) +{ + GESProject *project; + gboolean added_cb_called = FALSE; + gboolean removed_cb_called = FALSE; + + ges_init (); + + project = GES_PROJECT (ges_asset_request (GES_TYPE_TIMELINE, NULL, NULL)); + fail_unless (GES_IS_PROJECT (project)); + + mainloop = g_main_loop_new (NULL, FALSE); + g_signal_connect (project, "asset-added", + (GCallback) asset_removed_add_cb, &added_cb_called); + g_signal_connect (project, "asset-removed", + (GCallback) asset_removed_add_cb, &removed_cb_called); + g_signal_connect (project, "error-loading-asset", + (GCallback) error_loading_asset_cb, mainloop); + + fail_unless (ges_project_create_asset (project, "nowaythiselementexists", + GES_TYPE_EFFECT)); + g_main_loop_run (mainloop); + + /* And.... try again! */ + fail_if (ges_project_create_asset (project, "nowaythiselementexists", + GES_TYPE_EFFECT)); + + fail_if (added_cb_called); + fail_if (removed_cb_called); + + ASSERT_OBJECT_REFCOUNT (project, "The project", 2); + gst_object_unref (project); + g_main_loop_unref (mainloop); + + ASSERT_OBJECT_REFCOUNT (project, "The project (1 ref in cache)", 1); + + ges_deinit (); +} + +GST_END_TEST; + +static void +asset_added_cb (GESProject * project, GESAsset * asset) +{ + gchar *uri = ges_test_file_uri ("audio_video.ogg"); + GstDiscovererInfo *info; + + if (ges_asset_get_extractable_type (asset) == GES_TYPE_EFFECT) { + assert_equals_string (ges_asset_get_id (asset), "video agingtv"); + } else { + info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset)); + fail_unless (GST_IS_DISCOVERER_INFO (info)); + assert_equals_string (ges_asset_get_id (asset), uri); + } + + g_free (uri); +} + +static gchar * +_set_new_uri (GESProject * project, GError * error, GESAsset * wrong_asset) +{ + fail_unless (!g_strcmp0 (ges_asset_get_id (wrong_asset), + "file:///test/not/exisiting")); + + return ges_test_file_uri ("audio_video.ogg"); +} + +static void +_test_project (GESProject * project, GESTimeline * timeline) +{ + guint a_meta; + gchar *media_uri; + GESTrack *track; + const GList *profiles; + GstEncodingContainerProfile *profile; + GList *tracks, *tmp, *tmptrackelement, *clips; + + fail_unless (GES_IS_TIMELINE (timeline)); + assert_equals_int (g_list_length (timeline->layers), 2); + + assert_equals_string (ges_meta_container_get_string (GES_META_CONTAINER + (project), "name"), "Example project"); + clips = ges_layer_get_clips (GES_LAYER (timeline->layers->data)); + fail_unless (ges_meta_container_get_uint (GES_META_CONTAINER + (timeline->layers->data), "a", &a_meta)); + assert_equals_int (a_meta, 3); + assert_equals_int (g_list_length (clips), 1); + media_uri = ges_test_file_uri ("audio_video.ogg"); + assert_equals_string (ges_asset_get_id (ges_extractable_get_asset + (GES_EXTRACTABLE (clips->data))), media_uri); + g_free (media_uri); + g_list_free_full (clips, gst_object_unref); + + /* Check tracks and the objects they contain */ + tracks = ges_timeline_get_tracks (timeline); + assert_equals_int (g_list_length (tracks), 2); + for (tmp = tracks; tmp; tmp = tmp->next) { + GList *trackelements; + track = GES_TRACK (tmp->data); + + trackelements = ges_track_get_elements (track); + GST_DEBUG_OBJECT (track, "Testing track"); + switch (track->type) { + case GES_TRACK_TYPE_VIDEO: + assert_equals_int (g_list_length (trackelements), 2); + for (tmptrackelement = trackelements; tmptrackelement; + tmptrackelement = tmptrackelement->next) { + GESTrackElement *trackelement = + GES_TRACK_ELEMENT (tmptrackelement->data); + + if (GES_IS_BASE_EFFECT (trackelement)) { + guint nb_scratch_lines; + + ges_timeline_element_get_child_properties (tmptrackelement->data, + "scratch-lines", &nb_scratch_lines, NULL); + assert_equals_int (nb_scratch_lines, 12); + + nle_object_check (ges_track_element_get_nleobject (trackelement), + 0, 1000000000, 0, 1000000000, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, + TRUE); + } else { + nle_object_check (ges_track_element_get_nleobject (trackelement), + 0, 1000000000, 0, 1000000000, + MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 1, TRUE); + } + } + break; + case GES_TRACK_TYPE_AUDIO: + assert_equals_int (g_list_length (trackelements), 2); + break; + default: + g_assert (1); + } + + g_list_free_full (trackelements, gst_object_unref); + + } + g_list_free_full (tracks, gst_object_unref); + + /* Now test the encoding profile */ + profiles = ges_project_list_encoding_profiles (project); + assert_equals_int (g_list_length ((GList *) profiles), 1); + profile = profiles->data; + fail_unless (GST_IS_ENCODING_CONTAINER_PROFILE (profile)); + profiles = gst_encoding_container_profile_get_profiles (profile); + assert_equals_int (g_list_length ((GList *) profiles), 2); +} + +static void +_add_properties (GESTimeline * timeline) +{ + GList *tracks; + GList *tmp; + + tracks = ges_timeline_get_tracks (timeline); + for (tmp = tracks; tmp; tmp = tmp->next) { + GESTrack *track; + GList *track_elements; + GList *tmp_tck; + + track = GES_TRACK (tmp->data); + switch (track->type) { + case GES_TRACK_TYPE_VIDEO: + track_elements = ges_track_get_elements (track); + + for (tmp_tck = track_elements; tmp_tck; tmp_tck = tmp_tck->next) { + GESTrackElement *element = GES_TRACK_ELEMENT (tmp_tck->data); + + /* Adding keyframes */ + if (GES_IS_EFFECT (element)) { + GstControlSource *source; + GstControlBinding *tmp_binding, *binding; + + source = gst_interpolation_control_source_new (); + + /* Check binding creation and replacement */ + binding = + ges_track_element_get_control_binding (element, + "scratch-lines"); + fail_unless (binding == NULL); + ges_track_element_set_control_source (element, + source, "scratch-lines", "direct"); + tmp_binding = + ges_track_element_get_control_binding (element, + "scratch-lines"); + fail_unless (tmp_binding != NULL); + ges_track_element_set_control_source (element, + source, "scratch-lines", "direct"); + binding = + ges_track_element_get_control_binding (element, + "scratch-lines"); + fail_unless (binding != tmp_binding); + + + g_object_set (source, "mode", GST_INTERPOLATION_MODE_LINEAR, NULL); + gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE + (source), 0 * GST_SECOND, 0.); + gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE + (source), 5 * GST_SECOND, 0.); + gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE + (source), 10 * GST_SECOND, 1.); + + gst_object_unref (source); + } else if (GES_IS_VIDEO_SOURCE (element)) { + /* Adding children properties */ + gint64 posx = 42; + ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT + (element), "posx", posx, NULL); + ges_timeline_element_get_child_properties (GES_TIMELINE_ELEMENT + (element), "posx", &posx, NULL); + fail_unless_equals_int64 (posx, 42); + } + + } + g_list_free_full (track_elements, g_object_unref); + break; + default: + break; + } + } + + g_list_free_full (tracks, g_object_unref); +} + +static void +_check_properties (GESTimeline * timeline) +{ + GList *tracks; + GList *tmp; + + tracks = ges_timeline_get_tracks (timeline); + for (tmp = tracks; tmp; tmp = tmp->next) { + GESTrack *track; + GList *track_elements; + GList *tmp_tck; + + track = GES_TRACK (tmp->data); + switch (track->type) { + case GES_TRACK_TYPE_VIDEO: + track_elements = ges_track_get_elements (track); + + for (tmp_tck = track_elements; tmp_tck; tmp_tck = tmp_tck->next) { + GESTrackElement *element = GES_TRACK_ELEMENT (tmp_tck->data); + /* Checking keyframes */ + if (GES_IS_EFFECT (element)) { + GstControlBinding *binding; + GstControlSource *source; + GList *timed_values, *tmpvalue; + GstTimedValue *value; + + binding = + ges_track_element_get_control_binding (element, + "scratch-lines"); + fail_unless (binding != NULL); + g_object_get (binding, "control-source", &source, NULL); + fail_unless (source != NULL); + + /* Now check keyframe position */ + tmpvalue = timed_values = + gst_timed_value_control_source_get_all + (GST_TIMED_VALUE_CONTROL_SOURCE (source)); + value = tmpvalue->data; + fail_unless (value->value == 0.); + fail_unless (value->timestamp == 0 * GST_SECOND); + tmpvalue = tmpvalue->next; + value = tmpvalue->data; + fail_unless (value->value == 0.); + fail_unless (value->timestamp == 5 * GST_SECOND); + tmpvalue = tmpvalue->next; + value = tmpvalue->data; + fail_unless (value->value == 1.); + fail_unless (value->timestamp == 10 * GST_SECOND); + g_list_free (timed_values); + gst_object_unref (source); + } + /* Checking children properties */ + else if (GES_IS_VIDEO_SOURCE (element)) { + /* Init 'posx' with a wrong value */ + gint64 posx = 27; + ges_timeline_element_get_child_properties (GES_TIMELINE_ELEMENT + (element), "posx", &posx, NULL); + fail_unless_equals_int64 (posx, 42); + } + } + g_list_free_full (track_elements, g_object_unref); + break; + default: + break; + } + } + + g_list_free_full (tracks, g_object_unref); +} + +GST_START_TEST (test_project_add_properties) +{ + GESProject *project; + GESTimeline *timeline; + gchar *uri; + + ges_init (); + + uri = ges_test_file_uri ("test-properties.xges"); + project = ges_project_new (uri); + g_free (uri); + mainloop = g_main_loop_new (NULL, FALSE); + + /* Connect the signals */ + g_signal_connect (project, "loaded", (GCallback) project_loaded_cb, mainloop); + g_signal_connect (project, "missing-uri", (GCallback) _set_new_uri, NULL); + + /* Now extract a timeline from it */ + GST_LOG ("Loading project"); + timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), NULL)); + + g_main_loop_run (mainloop); + + GST_LOG ("Test first loading"); + + + _add_properties (timeline); + + uri = ges_test_get_tmp_uri ("test-properties-save.xges"); + fail_unless (ges_project_save (project, timeline, uri, NULL, TRUE, NULL)); + gst_object_unref (timeline); + gst_object_unref (project); + + project = ges_project_new (uri); + g_free (uri); + + ASSERT_OBJECT_REFCOUNT (project, "Our + cache", 2); + + g_signal_connect (project, "loaded", (GCallback) project_loaded_cb, mainloop); + + GST_LOG ("Loading saved project"); + timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), NULL)); + fail_unless (GES_IS_TIMELINE (timeline)); + + g_main_loop_run (mainloop); + + _check_properties (timeline); + + gst_object_unref (timeline); + gst_object_unref (project); + + g_main_loop_unref (mainloop); + g_signal_handlers_disconnect_by_func (project, (GCallback) project_loaded_cb, + mainloop); + g_signal_handlers_disconnect_by_func (project, (GCallback) asset_added_cb, + NULL); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_project_load_xges) +{ + gboolean saved; + GESProject *loaded_project, *saved_project; + GESTimeline *timeline; + GESAsset *formatter_asset; + gchar *uri; + GList *tmp; + + ges_init (); + + uri = ges_test_file_uri ("test-project.xges"); + loaded_project = ges_project_new (uri); + mainloop = g_main_loop_new (NULL, FALSE); + fail_unless (GES_IS_PROJECT (loaded_project)); + + /* Connect the signals */ + g_signal_connect (loaded_project, "asset-added", (GCallback) asset_added_cb, + NULL); + g_signal_connect (loaded_project, "loaded", (GCallback) project_loaded_cb, + mainloop); + + /* Make sure we update the project's dummy URL to some actual URL */ + g_signal_connect (loaded_project, "missing-uri", (GCallback) _set_new_uri, + NULL); + + /* Now extract a timeline from it */ + GST_LOG ("Loading project"); + timeline = + GES_TIMELINE (ges_asset_extract (GES_ASSET (loaded_project), NULL)); + fail_unless (GES_IS_TIMELINE (timeline)); + tmp = ges_project_get_loading_assets (loaded_project); + assert_equals_int (g_list_length (tmp), 1); + g_list_free_full (tmp, g_object_unref); + + g_main_loop_run (mainloop); + GST_LOG ("Test first loading"); + _test_project (loaded_project, timeline); + g_free (uri); + + uri = ges_test_get_tmp_uri ("test-project_TMP.xges"); + formatter_asset = ges_asset_request (GES_TYPE_FORMATTER, "ges", NULL); + saved = + ges_project_save (loaded_project, timeline, uri, formatter_asset, TRUE, + NULL); + fail_unless (saved); + gst_object_unref (timeline); + + saved_project = ges_project_new (uri); + ASSERT_OBJECT_REFCOUNT (saved_project, "Our + cache", 2); + g_signal_connect (saved_project, "asset-added", (GCallback) asset_added_cb, + NULL); + g_signal_connect (saved_project, "loaded", (GCallback) project_loaded_cb, + mainloop); + + GST_LOG ("Loading saved project"); + timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (saved_project), NULL)); + fail_unless (GES_IS_TIMELINE (timeline)); + g_main_loop_run (mainloop); + _test_project (saved_project, timeline); + + fail_unless (ges_meta_container_get_string (GES_META_CONTAINER + (loaded_project), GES_META_FORMAT_VERSION)); + fail_unless_equals_string (ges_meta_container_get_string (GES_META_CONTAINER + (loaded_project), GES_META_FORMAT_VERSION), + ges_meta_container_get_string (GES_META_CONTAINER (loaded_project), + GES_META_FORMAT_VERSION)); + gst_object_unref (timeline); + gst_object_unref (saved_project); + gst_object_unref (loaded_project); + g_free (uri); + + ASSERT_OBJECT_REFCOUNT (saved_project, "Still 1 ref for asset cache", 1); + + g_main_loop_unref (mainloop); + g_signal_handlers_disconnect_by_func (saved_project, + (GCallback) project_loaded_cb, mainloop); + g_signal_handlers_disconnect_by_func (saved_project, + (GCallback) asset_added_cb, NULL); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_project_auto_transition) +{ + GList *layers, *tmp; + GESProject *project; + GESTimeline *timeline; + GESLayer *layer = NULL; + GESAsset *formatter_asset; + gboolean saved; + gchar *tmpuri, *uri; + + ges_init (); + + uri = ges_test_file_uri ("test-auto-transition.xges"); + project = ges_project_new (uri); + mainloop = g_main_loop_new (NULL, FALSE); + fail_unless (GES_IS_PROJECT (project)); + + /* Connect the signals */ + g_signal_connect (project, "loaded", (GCallback) project_loaded_cb, mainloop); + g_signal_connect (project, "missing-uri", (GCallback) _set_new_uri, NULL); + + /* Now extract a timeline from it */ + GST_LOG ("Loading project"); + timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), NULL)); + + g_main_loop_run (mainloop); + + /* Check timeline and layers auto-transition, must be FALSE */ + fail_if (ges_timeline_get_auto_transition (timeline)); + layers = ges_timeline_get_layers (timeline); + for (tmp = layers; tmp; tmp = tmp->next) { + layer = tmp->data; + fail_if (ges_layer_get_auto_transition (layer)); + } + + g_list_free_full (layers, gst_object_unref); + g_free (uri); + + /* Set timeline and layers auto-transition to TRUE */ + ges_timeline_set_auto_transition (timeline, TRUE); + + tmpuri = ges_test_get_tmp_uri ("test-auto-transition-save.xges"); + formatter_asset = ges_asset_request (GES_TYPE_FORMATTER, "ges", NULL); + saved = + ges_project_save (project, timeline, tmpuri, formatter_asset, TRUE, NULL); + fail_unless (saved); + + gst_object_unref (timeline); + gst_object_unref (project); + + project = ges_project_new (tmpuri); + + ASSERT_OBJECT_REFCOUNT (project, "Our + cache", 2); + + g_signal_connect (project, "loaded", (GCallback) project_loaded_cb, mainloop); + + GST_LOG ("Loading saved project"); + timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), NULL)); + fail_unless (GES_IS_TIMELINE (timeline)); + + g_main_loop_run (mainloop); + + /* Check timeline and layers auto-transition, must be TRUE */ + fail_unless (ges_timeline_get_auto_transition (timeline)); + layers = ges_timeline_get_layers (timeline); + for (tmp = layers; tmp; tmp = tmp->next) { + layer = tmp->data; + fail_unless (ges_layer_get_auto_transition (layer)); + } + + g_list_free_full (layers, gst_object_unref); + gst_object_unref (timeline); + gst_object_unref (project); + g_free (tmpuri); + + g_main_loop_unref (mainloop); + g_signal_handlers_disconnect_by_func (project, (GCallback) project_loaded_cb, + mainloop); + g_signal_handlers_disconnect_by_func (project, (GCallback) asset_added_cb, + NULL); + + ges_deinit (); +} + +GST_END_TEST; + +/* FIXME This test does not pass for some bad reason */ +#if 0 +static void +project_loaded_now_play_cb (GESProject * project, GESTimeline * timeline) +{ + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + + GESPipeline *pipeline = ges_pipeline_new (); + + fail_unless (ges_pipeline_set_timeline (pipeline, timeline)); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + GST_ERROR ("GOT MESSAGE: %" GST_PTR_FORMAT, message); + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS, we did not even start!"); + carry_on = FALSE; + fail_if (TRUE); + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + break; + case GST_MESSAGE_ASYNC_DONE: + GST_DEBUG ("prerolling done"); + carry_on = FALSE; + break; + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + gst_object_unref (pipeline); + g_main_loop_quit (mainloop); +} + + +GST_START_TEST (test_load_xges_and_play) +{ + GESProject *project; + GESTimeline *timeline; + gchar *uri = ges_test_file_uri ("test-project_TMP.xges"); + + project = ges_project_new (uri); + fail_unless (GES_IS_PROJECT (project)); + + mainloop = g_main_loop_new (NULL, FALSE); + /* Connect the signals */ + g_signal_connect (project, "loaded", (GCallback) project_loaded_now_play_cb, + NULL); + + /* Now extract a timeline from it */ + timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), NULL)); + fail_unless (GES_IS_TIMELINE (timeline)); + + g_main_loop_run (mainloop); + + g_free (uri); + gst_object_unref (project); + gst_object_unref (timeline); + g_main_loop_unref (mainloop); +} + +GST_END_TEST; +#endif + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-project"); + TCase *tc_chain = tcase_create ("project"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_project_simple); + tcase_add_test (tc_chain, test_project_add_assets); + tcase_add_test (tc_chain, test_project_load_xges); + tcase_add_test (tc_chain, test_project_add_properties); + tcase_add_test (tc_chain, test_project_auto_transition); + /*tcase_add_test (tc_chain, test_load_xges_and_play); */ + tcase_add_test (tc_chain, test_project_unexistant_effect); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/tempochange.c b/tests/check/ges/tempochange.c new file mode 100644 index 0000000000..4cc776e558 --- /dev/null +++ b/tests/check/ges/tempochange.c @@ -0,0 +1,104 @@ +/* GStreamer Editing Services + * Copyright (C) 2016 Sjors Gielen <mixml-ges@sjorsgielen.nl> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" +#include <ges/ges.h> +#include <gst/check/gstcheck.h> +#include <plugins/nle/nleobject.h> + +GST_START_TEST (test_tempochange) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *track_audio; + GESEffect *effect; + GESTestClip *clip; + GESClip *clip2, *clip3; + + ges_init (); + + timeline = ges_timeline_new (); + layer = ges_layer_new (); + track_audio = GES_TRACK (ges_audio_track_new ()); + + ges_timeline_add_track (timeline, track_audio); + ges_timeline_add_layer (timeline, layer); + + /* Add a 9-second clip */ + clip = ges_test_clip_new (); + g_object_set (clip, "duration", 9 * GST_SECOND, NULL); + ges_layer_add_clip (layer, (GESClip *) clip); + + /* Split it after 3 seconds */ + clip2 = ges_clip_split ((GESClip *) clip, 3 * GST_SECOND); + + /* Add pitch effect to play 1.5 times faster */ + effect = ges_effect_new ("pitch tempo=1.5"); + + fail_unless (GES_IS_EFFECT (effect)); + fail_unless (ges_container_add (GES_CONTAINER (clip2), + GES_TIMELINE_ELEMENT (effect))); + fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect)) != + NULL); + + assert_equals_int (GES_TRACK_ELEMENT (effect)->active, TRUE); + + /* Split clip again after 6 seconds (note: this is timeline time) */ + clip3 = ges_clip_split (clip2, 6 * GST_SECOND); + + // note: start and duration are counted in timeline time, while + // inpoint is counted in media time. + fail_unless_equals_int64 (_START (clip), 0 * GST_SECOND); + fail_unless_equals_int64 (_INPOINT (clip), 0 * GST_SECOND); + fail_unless_equals_int64 (_DURATION (clip), 3 * GST_SECOND); + + fail_unless_equals_int64 (_START (clip2), 3 * GST_SECOND); + fail_unless_equals_int64 (_INPOINT (clip2), 3 * GST_SECOND); + fail_unless_equals_int64 (_DURATION (clip2), 3 * GST_SECOND); + + fail_unless_equals_int64 (_START (clip3), 6 * GST_SECOND); + fail_unless_equals_int64 (_INPOINT (clip3), 7.5 * GST_SECOND); + fail_unless_equals_int64 (_DURATION (clip3), 3 * GST_SECOND); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges"); + GstPluginFeature *pitch = gst_registry_find_feature (gst_registry_get (), + "pitch", GST_TYPE_ELEMENT_FACTORY); + + if (pitch) { + TCase *tc_chain = tcase_create ("tempochange"); + gst_object_unref (pitch); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_tempochange); + } + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/test-utils.c b/tests/check/ges/test-utils.c new file mode 100644 index 0000000000..de38ae37dd --- /dev/null +++ b/tests/check/ges/test-utils.c @@ -0,0 +1,332 @@ +/** + * Gstreamer Editing Services + * + * Copyright (C) <2012> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" +#include <gio/gio.h> + +typedef struct _DestroyedObjectStruct +{ + GObject *object; + gboolean destroyed; +} DestroyedObjectStruct; + +gchar * +ges_test_get_audio_only_uri (void) +{ + return ges_test_file_uri ("audio_only.ogg"); +} + +gchar * +ges_test_get_audio_video_uri (void) +{ + return ges_test_file_uri ("audio_video.ogg"); +} + +gchar * +ges_test_get_image_uri (void) +{ + return ges_test_file_uri ("image.png"); +} + +gchar * +ges_test_file_uri (const gchar * filename) +{ + gchar *path, *uri; + + path = g_build_filename (GES_TEST_FILES_PATH, filename, NULL); + uri = gst_filename_to_uri (path, NULL); + + g_free (path); + + return uri; +} + +GESPipeline * +ges_test_create_pipeline (GESTimeline * timeline) +{ + GESPipeline *pipeline; + + pipeline = ges_pipeline_new (); + fail_unless (ges_pipeline_set_timeline (pipeline, timeline)); + + g_object_set (pipeline, "audio-sink", + gst_element_factory_make ("fakeaudiosink", "test-audiofakesink"), + "video-sink", gst_element_factory_make ("fakevideosink", + "test-videofakesink"), NULL); + + return pipeline; +} + +gchar * +ges_test_file_name (const gchar * filename) +{ + return g_strjoin ("/", "file:/", g_get_current_dir (), filename, NULL); +} + +gboolean +ges_generate_test_file_audio_video (const gchar * filedest, + const gchar * audio_enc, + const gchar * video_enc, + const gchar * mux, const gchar * video_pattern, const gchar * audio_wave) +{ + GError *error = NULL; + GstElement *pipeline; + GstBus *bus; + GstMessage *message; + gchar *pipeline_str; + gboolean done = FALSE; + gboolean ret = FALSE; + + if (g_file_test (filedest, G_FILE_TEST_EXISTS)) { + GST_INFO ("The file %s already existed.", filedest); + return TRUE; + } + + pipeline_str = g_strdup_printf ("audiotestsrc num-buffers=430 wave=%s " + "%c %s ! %s name=m ! filesink location= %s/%s " + "videotestsrc pattern=%s num-buffers=300 ! %s ! m.", + audio_wave, + audio_enc ? '!' : ' ', + audio_enc ? audio_enc : "", + mux, g_get_current_dir (), filedest, video_pattern, video_enc); + + pipeline = gst_parse_launch (pipeline_str, &error); + + if (pipeline == NULL) + return FALSE; + + g_free (pipeline_str); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + gst_bus_add_signal_watch (bus); + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + + while (!done) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_CLOCK_TIME_NONE); + if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_EOS) { + done = TRUE; + ret = TRUE; + } else if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) { + gchar *debug = NULL; + GError *err = NULL; + + gst_message_parse_error (message, &err, &debug); + done = TRUE; + ret = FALSE; + GST_ERROR ("Got error %s from %s fron the bus while generation: %s" + "debug infos: %s", GST_OBJECT_NAME (message->src), err->message, + debug ? debug : "none", filedest); + g_clear_error (&err); + g_free (debug); + } + } + + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); + + return ret; +} + +static void +weak_notify (DestroyedObjectStruct * destroyed, GObject ** object) +{ + destroyed->destroyed = TRUE; +} + +void +check_destroyed (GObject * object_to_unref, GObject * first_object, ...) +{ + GObject *object; + GList *objs = NULL, *tmp; + DestroyedObjectStruct *destroyed = g_slice_new0 (DestroyedObjectStruct); + + destroyed->object = object_to_unref; + g_object_weak_ref (object_to_unref, (GWeakNotify) weak_notify, destroyed); + objs = g_list_prepend (objs, destroyed); + + if (first_object) { + va_list varargs; + + object = first_object; + + va_start (varargs, first_object); + while (object) { + destroyed = g_slice_new0 (DestroyedObjectStruct); + destroyed->object = object; + g_object_weak_ref (object, (GWeakNotify) weak_notify, destroyed); + objs = g_list_prepend (objs, destroyed); + object = va_arg (varargs, GObject *); + } + va_end (varargs); + } + gst_object_unref (object_to_unref); + + for (tmp = objs; tmp; tmp = tmp->next) { + fail_unless (((DestroyedObjectStruct *) tmp->data)->destroyed == TRUE, + "%p is not destroyed", ((DestroyedObjectStruct *) tmp->data)->object); + g_slice_free (DestroyedObjectStruct, tmp->data); + } + g_list_free (objs); + +} + +static gboolean +my_bus_callback (GstBus * bus, GstMessage * message, GMainLoop * loop) +{ + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR:{ + GError *err; + gchar *debug; + + gst_message_parse_error (message, &err, &debug); + + g_assert_no_error (err); + g_error_free (err); + g_free (debug); + g_main_loop_quit (loop); + break; + } + case GST_MESSAGE_EOS: + GST_INFO ("EOS\n"); + g_main_loop_quit (loop); + break; + default: + /* unhandled message */ + break; + } + return TRUE; +} + +gboolean +play_timeline (GESTimeline * timeline) +{ + GstBus *bus; + GESPipeline *pipeline; + GMainLoop *loop = g_main_loop_new (NULL, FALSE); + + ges_timeline_commit (timeline); + pipeline = ges_pipeline_new (); + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + gst_bus_add_watch (bus, (GstBusFunc) my_bus_callback, loop); + gst_object_unref (bus); + + ges_pipeline_set_timeline (pipeline, timeline); + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); + gst_element_get_state (GST_ELEMENT (pipeline), NULL, NULL, -1); + + g_main_loop_run (loop); + + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + gst_element_get_state (GST_ELEMENT (pipeline), NULL, NULL, -1); + + gst_object_unref (pipeline); + + return TRUE; +} + +gchar * +ges_test_get_tmp_uri (const gchar * filename) +{ + gchar *location, *uri; + + location = g_build_filename (g_get_tmp_dir (), filename, NULL); + + uri = g_strconcat ("file://", location, NULL); + g_free (location); + + return uri; +} + +void +print_timeline (GESTimeline * timeline) +{ + GList *layer, *clip, *clips, *group; + + gst_printerr + ("\n\n=========================== GESTimeline: %p ==================\n", + timeline); + for (layer = timeline->layers; layer; layer = layer->next) { + clips = ges_layer_get_clips (layer->data); + + gst_printerr ("layer %04d: ", ges_layer_get_priority (layer->data)); + for (clip = clips; clip; clip = clip->next) { + gst_printerr ("{ %s [ %" G_GUINT64_FORMAT "(%" G_GUINT64_FORMAT ") %" + G_GUINT64_FORMAT "] } ", GES_TIMELINE_ELEMENT_NAME (clip->data), + GES_TIMELINE_ELEMENT_START (clip->data), + GES_TIMELINE_ELEMENT_INPOINT (clip->data), + GES_TIMELINE_ELEMENT_END (clip->data)); + } + if (layer->next) + gst_printerr ("\n--------------------------------------------------\n"); + + g_list_free_full (clips, gst_object_unref); + } + + if (ges_timeline_get_groups (timeline)) { + gst_printerr ("\n--------------------------------------------------\n"); + gst_printerr ("\nGROUPS:"); + gst_printerr ("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); + } + + for (group = ges_timeline_get_groups (timeline); group; group = group->next) { + gst_printerr ("%" GES_FORMAT ": ", GES_ARGS (group->data)); + for (clip = GES_CONTAINER_CHILDREN (group->data); clip; clip = clip->next) + gst_printerr ("[ %s ]", GES_TIMELINE_ELEMENT_NAME (clip->data)); + } + + gst_printerr + ("\n=====================================================================\n"); +} + +/* append the properties found in element to list, num_props should point + * to the current list length. + */ +GParamSpec ** +append_children_properties (GParamSpec ** list, GESTimelineElement * element, + guint * num_props) +{ + guint i, num; + GParamSpec **props = + ges_timeline_element_list_children_properties (element, &num); + fail_unless (props); + list = g_realloc_n (list, num + *num_props, sizeof (GParamSpec *)); + + for (i = 0; i < num; i++) + list[*num_props + i] = props[i]; + + g_free (props); + *num_props += num; + return list; +} + +void +free_children_properties (GParamSpec ** list, guint num_props) +{ + guint i; + for (i = 0; i < num_props; i++) + g_param_spec_unref (list[i]); + g_free (list); +} diff --git a/tests/check/ges/test-utils.h b/tests/check/ges/test-utils.h new file mode 100644 index 0000000000..85ee03a9e3 --- /dev/null +++ b/tests/check/ges/test-utils.h @@ -0,0 +1,416 @@ +/** + * Gstreamer Editing Services + * + * Copyright (C) <2012> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#pragma once + +#include <ges/ges.h> +#include <gst/check/gstcheck.h> +#include "../../../ges/ges-internal.h" + +GESPipeline * ges_test_create_pipeline (GESTimeline *timeline); +/* The first 2 NLE priorities are used for: + * 0- The Mixing element + * 1- The Gaps + */ +#define MIN_NLE_PRIO 2 +#define TRANSITIONS_HEIGHT 1 +#define LAYER_HEIGHT 1000 + +gchar * ges_test_get_tmp_uri (const gchar * filename); +gchar * ges_test_get_audio_only_uri (void); +gchar * ges_test_get_audio_video_uri (void); +gchar * ges_test_get_image_uri (void); +gchar * ges_test_file_uri (const gchar *filename); + +void check_destroyed (GObject *object_to_unref, GObject *first_object, ...) G_GNUC_NULL_TERMINATED; +gchar * ges_test_file_name (const gchar *filename); +gboolean +ges_generate_test_file_audio_video (const gchar * filedest, + const gchar * audio_enc, + const gchar * video_enc, + const gchar * mux, const gchar * video_pattern, const gchar * audio_wave); +gboolean +play_timeline (GESTimeline * timeline); + +GParamSpec ** +append_children_properties (GParamSpec ** list, GESTimelineElement * element, guint * num_props); +void +free_children_properties (GParamSpec ** list, guint num_props); + +#define nle_object_check(nleobj, start, duration, mstart, mduration, priority, active) { \ + guint64 pstart, pdur, inpoint, pprio, pact; \ + g_object_get (nleobj, "start", &pstart, "duration", &pdur, \ + "inpoint", &inpoint, "priority", &pprio, "active", &pact, \ + NULL); \ + assert_equals_uint64 (pstart, start); \ + assert_equals_uint64 (pdur, duration); \ + assert_equals_uint64 (inpoint, mstart); \ + assert_equals_int (pprio, priority); \ + assert_equals_int (pact, active); \ + } + +/* copied from nle */ +#define fail_error_message(msg) \ + G_STMT_START { \ + GError *error; \ + gst_message_parse_error(msg, &error, NULL); \ + fail_unless(FALSE, "Error Message from %s : %s", \ + GST_OBJECT_NAME (GST_MESSAGE_SRC(msg)), error->message); \ + g_error_free (error); \ + } G_STMT_END; + +#define assert_is_type(object, type) \ +G_STMT_START { \ + fail_unless (g_type_is_a(G_OBJECT_TYPE(object), type), \ + "%s is not a %s", G_OBJECT_TYPE_NAME(object), \ + g_type_name (type)); \ +} G_STMT_END; + +#define _START(obj) GES_TIMELINE_ELEMENT_START (obj) +#define _DURATION(obj) GES_TIMELINE_ELEMENT_DURATION (obj) +#define _INPOINT(obj) GES_TIMELINE_ELEMENT_INPOINT (obj) +#define _MAX_DURATION(obj) GES_TIMELINE_ELEMENT_MAX_DURATION (obj) +#define _PRIORITY(obj) GES_TIMELINE_ELEMENT_PRIORITY (obj) +#ifndef _END +#define _END(obj) (_START(obj) + _DURATION(obj)) +#endif + +#define CHECK_OBJECT_PROPS(obj, start, inpoint, duration) {\ + fail_unless (_START (obj) == start, "%s start is %" GST_TIME_FORMAT " != %" GST_TIME_FORMAT, GES_TIMELINE_ELEMENT_NAME(obj), GST_TIME_ARGS (_START(obj)), GST_TIME_ARGS (start));\ + fail_unless (_INPOINT (obj) == inpoint, "%s inpoint is %" GST_TIME_FORMAT " != %" GST_TIME_FORMAT, GES_TIMELINE_ELEMENT_NAME(obj), GST_TIME_ARGS (_INPOINT(obj)), GST_TIME_ARGS (inpoint));\ + fail_unless (_DURATION (obj) == duration, "%s duration is %" GST_TIME_FORMAT " != %" GST_TIME_FORMAT, GES_TIMELINE_ELEMENT_NAME(obj), GST_TIME_ARGS (_DURATION(obj)), GST_TIME_ARGS (duration));\ +} + +#define CHECK_OBJECT_PROPS_MAX(obj, start, inpoint, duration, max_duration) {\ + CHECK_OBJECT_PROPS (obj, start, inpoint, duration); \ + fail_unless (_MAX_DURATION(obj) == max_duration, "%s max-duration is " \ + "%" GST_TIME_FORMAT " != %" GST_TIME_FORMAT, \ + GES_TIMELINE_ELEMENT_NAME(obj), \ + GST_TIME_ARGS (_MAX_DURATION(obj)), GST_TIME_ARGS (max_duration)); \ +} + +#define __assert_timeline_element_set(obj, prop, val) \ + fail_unless (ges_timeline_element_set_ ## prop ( \ + GES_TIMELINE_ELEMENT (obj), val), "Could not set the " # prop \ + " of " #obj "(%s) to " #val, GES_TIMELINE_ELEMENT_NAME (obj)) + +#define __fail_timeline_element_set(obj, prop, val) \ + fail_if (ges_timeline_element_set_ ## prop ( \ + GES_TIMELINE_ELEMENT (obj), val), "Setting the " # prop \ + " of " #obj "(%s) to " #val " did not fail as expected", \ + GES_TIMELINE_ELEMENT_NAME (obj)) + + +#define assert_set_start(obj, val) \ + __assert_timeline_element_set (obj, start, val) + +#define assert_set_duration(obj, val) \ + __assert_timeline_element_set (obj, duration, val) + +#define assert_set_inpoint(obj, val) \ + __assert_timeline_element_set (obj, inpoint, val) + +#define assert_set_max_duration(obj, val) \ + __assert_timeline_element_set (obj, max_duration, val) + + +#define assert_fail_set_start(obj, val) \ + __fail_timeline_element_set (obj, start, val) + +#define assert_fail_set_duration(obj, val) \ + __fail_timeline_element_set (obj, duration, val) + +#define assert_fail_set_inpoint(obj, val) \ + __fail_timeline_element_set (obj, inpoint, val) + +#define assert_fail_set_max_duration(obj, val) \ + __fail_timeline_element_set (obj, max_duration, val) + + +#define assert_num_in_track(track, val) \ +{ \ + GList *tmp = ges_track_get_elements (track); \ + guint length = g_list_length (tmp); \ + fail_unless (length == val, "Track %" GST_PTR_FORMAT \ + " contains %u track elements, rather than %u", track, length, val); \ + g_list_free_full (tmp, gst_object_unref); \ +} + +#define assert_num_children(clip, cmp) \ +{ \ + guint num_children = g_list_length (GES_CONTAINER_CHILDREN (clip)); \ + fail_unless (cmp == num_children, \ + "clip %s contains %u children rather than %u", \ + GES_TIMELINE_ELEMENT_NAME (clip), num_children, cmp); \ +} + +/* assert that the time property (start, duration or in-point) is the + * same as @cmp for the clip and all its children */ +#define assert_clip_children_time_val(clip, property, cmp) \ +{ \ + GList *tmp; \ + GstClockTime read_val; \ + gchar *name = GES_TIMELINE_ELEMENT (clip)->name; \ + gboolean is_inpoint = (g_strcmp0 (property, "in-point") == 0); \ + g_object_get (clip, property, &read_val, NULL); \ + fail_unless (read_val == cmp, "The %s property for clip %s is %" \ + GST_TIME_FORMAT ", rather than the expected value of %" \ + GST_TIME_FORMAT, property, name, GST_TIME_ARGS (read_val), \ + GST_TIME_ARGS (cmp)); \ + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp != NULL; \ + tmp = tmp->next) { \ + GESTimelineElement *child = tmp->data; \ + g_object_get (child, property, &read_val, NULL); \ + if (!is_inpoint || ges_track_element_has_internal_source ( \ + GES_TRACK_ELEMENT (child))) \ + fail_unless (read_val == cmp, "The %s property for the child %s " \ + "of clip %s is %" GST_TIME_FORMAT ", rather than the expected" \ + " value of %" GST_TIME_FORMAT, property, child->name, name, \ + GST_TIME_ARGS (read_val), GST_TIME_ARGS (cmp)); \ + else \ + fail_unless (read_val == 0, "The %s property for the child %s " \ + "of clip %s is %" GST_TIME_FORMAT ", rather than 0", \ + property, child->name, name, GST_TIME_ARGS (read_val)); \ + } \ +} + +#define check_layer(clip, layer_prio) { \ + fail_unless (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip) == (layer_prio), \ + "%s in layer %d instead of %d", GES_TIMELINE_ELEMENT_NAME (clip), \ + GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), layer_prio); \ +} + +#define assert_layer(clip, layer) \ +{ \ + GESLayer *tmp_layer = ges_clip_get_layer (GES_CLIP (clip)); \ + fail_unless (tmp_layer == GES_LAYER (layer), "clip %s belongs to " \ + "layer %u (timeline %" GST_PTR_FORMAT ") rather than layer %u " \ + "(timeline %" GST_PTR_FORMAT ")", \ + tmp_layer ? ges_layer_get_priority (tmp_layer) : 0, \ + tmp_layer ? tmp_layer->timeline : NULL, \ + layer ? ges_layer_get_priority (GES_LAYER (layer)) : 0, \ + layer ? GES_LAYER (layer)->timeline : NULL); \ + if (tmp_layer) \ + gst_object_unref (tmp_layer); \ + if (layer) { \ + GList *layer_clips = ges_layer_get_clips (GES_LAYER (layer)); \ + fail_unless (g_list_find (layer_clips, clip), "clip %s not found " \ + "in layer %u (timeline %" GST_PTR_FORMAT ")", \ + ges_layer_get_priority (GES_LAYER (layer)), layer->timeline); \ + g_list_free_full (layer_clips, gst_object_unref); \ + } \ +} + +/* test that the two property lists contain the same properties the same + * number of times */ +#define assert_property_list_match(list1, len1, list2, len2) \ + { \ + gboolean *found_count_in_list2; \ + guint *count_list1; \ + guint i, j; \ + found_count_in_list2 = g_new0 (gboolean, len1); \ + count_list1 = g_new0 (guint, len1); \ + for (i = 0; i < len1; i++) { \ + found_count_in_list2[i] = 0; \ + count_list1[i] = 0; \ + for (j = 0; j < len1; j++) { \ + if (list1[i] == list1[j]) \ + count_list1[i] ++; \ + } \ + } \ + for (j = 0; j < len2; j++) { \ + guint count_list2 = 0; \ + guint found_count_in_list1 = 0; \ + GParamSpec *prop = list2[j]; \ + for (i = 0; i < len2; i++) { \ + if (list2[i] == prop) \ + count_list2 ++; \ + } \ + for (i = 0; i < len1; i++) { \ + if (list1[i] == prop) { \ + found_count_in_list2[i] ++; \ + found_count_in_list1 ++; \ + } \ + } \ + fail_unless (found_count_in_list1 == count_list2, \ + "Found property '%s' %u times, rather than %u times, in " #list1, \ + prop->name, found_count_in_list1, count_list2); \ + } \ + /* make sure we found each one once */ \ + for (i = 0; i < len1; i++) { \ + GParamSpec *prop = list1[i]; \ + fail_unless (found_count_in_list2[i] == count_list1[i], \ + "Found property '%s' %u times, rather than %u times, in " #list2, \ + prop->name, found_count_in_list2[i], count_list1[i]); \ + } \ + g_free (found_count_in_list2); \ + g_free (count_list1); \ + } + +#define assert_equal_children_properties(el1, el2) \ +{ \ + guint i, num1, num2; \ + const gchar *name1 = GES_TIMELINE_ELEMENT_NAME (el1); \ + const gchar *name2 = GES_TIMELINE_ELEMENT_NAME (el2); \ + GParamSpec **el_props1 = ges_timeline_element_list_children_properties ( \ + GES_TIMELINE_ELEMENT (el1), &num1); \ + GParamSpec **el_props2 = ges_timeline_element_list_children_properties ( \ + GES_TIMELINE_ELEMENT (el2), &num2); \ + assert_property_list_match (el_props1, num1, el_props2, num2); \ + \ + for (i = 0; i < num1; i++) { \ + gchar *ser1, *ser2; \ + GParamSpec *prop = el_props1[i]; \ + GValue val1 = G_VALUE_INIT, val2 = G_VALUE_INIT; \ + /* name property can be different */ \ + if (g_strcmp0 (prop->name, "name") == 0) \ + continue; \ + if (g_strcmp0 (prop->name, "parent") == 0) \ + continue; \ + g_value_init (&val1, prop->value_type); \ + g_value_init (&val2, prop->value_type); \ + ges_timeline_element_get_child_property_by_pspec ( \ + GES_TIMELINE_ELEMENT (el1), prop, &val1); \ + ges_timeline_element_get_child_property_by_pspec ( \ + GES_TIMELINE_ELEMENT (el2), prop, &val2); \ + ser1 = gst_value_serialize (&val1); \ + ser2 = gst_value_serialize (&val2); \ + fail_unless (gst_value_compare (&val1, &val2) == GST_VALUE_EQUAL, \ + "Child property '%s' for %s does not match that for %s (%s vs %s)", \ + prop->name, name1, name2, ser1, ser2); \ + g_free (ser1); \ + g_free (ser2); \ + g_value_unset (&val1); \ + g_value_unset (&val2); \ + } \ + free_children_properties (el_props1, num1); \ + free_children_properties (el_props2, num2); \ +} + +#define assert_equal_bindings(el1, el2) \ +{ \ + guint i, num1, num2; \ + const gchar *name1 = GES_TIMELINE_ELEMENT_NAME (el1); \ + const gchar *name2 = GES_TIMELINE_ELEMENT_NAME (el2); \ + GParamSpec **props1 = ges_timeline_element_list_children_properties ( \ + GES_TIMELINE_ELEMENT (el1), &num1); \ + GParamSpec **props2 = ges_timeline_element_list_children_properties ( \ + GES_TIMELINE_ELEMENT (el2), &num2); \ + assert_property_list_match (props1, num1, props2, num2); \ + \ + for (i = 0; i < num1; i++) { \ + const gchar *prop = props1[i]->name; \ + GList *tmp1, *tmp2; \ + GList *timed_vals1, *timed_vals2; \ + GObject *object1, *object2; \ + gboolean abs1, abs2; \ + GstControlSource *source1, *source2; \ + GstInterpolationMode mode1, mode2; \ + GstControlBinding *binding1, *binding2; \ + guint j; \ + \ + binding1 = ges_track_element_get_control_binding ( \ + GES_TRACK_ELEMENT (el1), prop); \ + binding2 = ges_track_element_get_control_binding ( \ + GES_TRACK_ELEMENT (el2), prop); \ + if (binding1 == NULL) { \ + fail_unless (binding2 == NULL, "%s has a binding for property " \ + " '%s', whilst %s does not", name2, prop, name1); \ + continue; \ + } \ + if (binding2 == NULL) { \ + fail_unless (binding1 == NULL, "%s has a binding for property " \ + "'%s', whilst %s does not", name1, prop, name2); \ + continue; \ + } \ + \ + fail_unless (G_OBJECT_TYPE (binding1) == GST_TYPE_DIRECT_CONTROL_BINDING, \ + "%s binding for property '%s' is not a direct control binding, " \ + "so cannot be handled", prop, name1); \ + fail_unless (G_OBJECT_TYPE (binding2) == GST_TYPE_DIRECT_CONTROL_BINDING, \ + "%s binding for property '%s' is not a direct control binding, " \ + "so cannot be handled", prop, name2); \ + \ + g_object_get (G_OBJECT (binding1), "control-source", &source1, \ + "absolute", &abs1, "object", &object1, NULL); \ + g_object_get (G_OBJECT (binding2), "control-source", &source2, \ + "absolute", &abs2, "object", &object2, NULL); \ + \ + fail_unless (G_OBJECT_TYPE (object1) == G_OBJECT_TYPE (object2), \ + "The child object for property '%s' for %s and %s correspond " \ + "to different object types (%s vs %s)", prop, name1, name2, \ + G_OBJECT_TYPE_NAME (object1), G_OBJECT_TYPE_NAME (object2)); \ + gst_object_unref (object1); \ + gst_object_unref (object2); \ + \ + fail_unless (abs1 == abs2, "control biding for property '%s' " \ + " is %s absolute for %s, but %s absolute for %s", prop, \ + abs1 ? "" : "not", name1, abs2 ? "" : "not", name2); \ + \ + fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source1), \ + "%s does not have an interpolation control source for " \ + "property '%s', so cannot be handled", name1, prop); \ + fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source2), \ + "%s does not have an interpolation control source for " \ + "property '%s', so cannot be handled", name2, prop); \ + g_object_get (G_OBJECT (source1), "mode", &mode1, NULL); \ + g_object_get (G_OBJECT (source2), "mode", &mode2, NULL); \ + fail_unless (mode1 == mode2, "control source for property '%s' " \ + "has different modes for %s and %s (%i vs %i)", prop, \ + name1, name2, mode1, mode2); \ + \ + timed_vals1 = gst_timed_value_control_source_get_all ( \ + GST_TIMED_VALUE_CONTROL_SOURCE (source1)); \ + timed_vals2 = gst_timed_value_control_source_get_all ( \ + GST_TIMED_VALUE_CONTROL_SOURCE (source2)); \ + \ + for (j = 0, tmp1 = timed_vals1, tmp2 = timed_vals2; tmp1 && tmp2; \ + j++, tmp1 = tmp1->next, tmp2 = tmp2->next) { \ + GstTimedValue *val1 = tmp1->data, *val2 = tmp2->data; \ + fail_unless (val1->timestamp == val2->timestamp && \ + val1->value == val2->value, "The %uth timed value for property " \ + "'%s' is different for %s and %s: (%" G_GUINT64_FORMAT ": %g) vs " \ + "(%" G_GUINT64_FORMAT ": %g)", j, prop, name1, name2, \ + val1->timestamp, val1->value, val2->timestamp, val2->value); \ + } \ + fail_unless (tmp1 == NULL, "Found too many timed values for " \ + "property '%s' for %s", prop, name1); \ + fail_unless (tmp2 == NULL, "Found too many timed values for " \ + "property '%s' for %s", prop, name2); \ + \ + g_list_free (timed_vals1); \ + g_list_free (timed_vals2); \ + gst_object_unref (source1); \ + gst_object_unref (source2); \ + } \ + free_children_properties (props1, num1); \ + free_children_properties (props2, num2); \ +} + +#define assert_GESError(error, error_code) \ +{ \ + fail_unless (error); \ + fail_unless (error->domain == GES_ERROR); \ + assert_equals_int (error->code, error_code); \ + g_error_free (error); \ + error = NULL; \ +} + +void print_timeline(GESTimeline *timeline); diff --git a/tests/check/ges/timelineedition.c b/tests/check/ges/timelineedition.c new file mode 100644 index 0000000000..2d4e5dd73b --- /dev/null +++ b/tests/check/ges/timelineedition.c @@ -0,0 +1,1297 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2012> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" +#include <ges/ges.h> +#include <gst/check/gstcheck.h> + +#define DEEP_CHECK(element, start, inpoint, duration) \ +{ \ + GList *track_elements, *tmp; \ + CHECK_OBJECT_PROPS (element, start, inpoint, duration) \ + \ + track_elements = GES_CONTAINER_CHILDREN (element); \ + for (tmp = track_elements; tmp; tmp = tmp->next) { \ + CHECK_OBJECT_PROPS (tmp->data, start, inpoint, duration) \ + } \ +} + +#define CHECK_CLIP(element, start, inpoint, duration, layer_prio) \ +{ \ + DEEP_CHECK(element, start, inpoint, duration);\ + check_layer (element, layer_prio); \ +}\ + + +GST_START_TEST (test_basic_timeline_edition) +{ + GESAsset *asset; + GESTrack *track; + GESTimeline *timeline; + GESLayer *layer; + GESTrackElement *trackelement, *trackelement1, *trackelement2; + GESContainer *clip, *clip1, *clip2; + + ges_init (); + + track = GES_TRACK (ges_audio_track_new ()); + fail_unless (track != NULL); + + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + fail_unless (ges_timeline_add_track (timeline, track)); + + layer = ges_layer_new (); + fail_unless (layer != NULL); + fail_unless (ges_timeline_add_layer (timeline, layer)); + + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + fail_unless (GES_IS_ASSET (asset)); + + /** + * Our timeline + * + * inpoints 0------- 0-------- 0----------- + * | clip | | clip1 | | clip2 | + * time 0------- 10 --------20 50---------60 + */ + clip = GES_CONTAINER (ges_layer_add_asset (layer, asset, 0, 0, 10, + GES_TRACK_TYPE_UNKNOWN)); + trackelement = GES_CONTAINER_CHILDREN (clip)->data; + fail_unless (GES_IS_TRACK_ELEMENT (trackelement)); + + clip1 = GES_CONTAINER (ges_layer_add_asset (layer, asset, 10, 0, 10, + GES_TRACK_TYPE_UNKNOWN)); + trackelement1 = GES_CONTAINER_CHILDREN (clip1)->data; + fail_unless (GES_IS_TRACK_ELEMENT (trackelement1)); + + clip2 = GES_CONTAINER (ges_layer_add_asset (layer, asset, 50, 0, 60, + GES_TRACK_TYPE_UNKNOWN)); + g_object_unref (asset); + trackelement2 = GES_CONTAINER_CHILDREN (clip2)->data; + fail_unless (GES_IS_TRACK_ELEMENT (trackelement2)); + + CHECK_OBJECT_PROPS (trackelement, 0, 0, 10); + CHECK_OBJECT_PROPS (trackelement1, 10, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 50, 0, 60); + + /** + * Simple rippling clip to: 10 + * + * New timeline: + * ------------ + * + * inpoints 0------- 0-------- 0----------- + * | clip | | clip1 | | clip2 | + * time 10------- 20 --------30 60---------120 + */ + fail_unless (ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_NONE, 10) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 10, 0, 10); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 60, 0, 60); + + + /* FIXME find a way to check that we are using the same MovingContext + * inside the GESTrack */ + fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_NONE, 40) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 10, 0, 10); + CHECK_OBJECT_PROPS (trackelement1, 40, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 80, 0, 60); + + /** + * Rippling clip1 back to: 20 (getting to the exact same timeline as before + */ + fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_NONE, 20) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 10, 0, 10); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 60, 0, 60); + + /** + * Simple move clip to: 27 and clip2 to 35 + * + * New timeline: + * ------------ + * 0------------ + * inpoints 0-------|--- clip 0--|---------- + * | clip1 27 -|-----|-37 clip2 | + * time 20-----------30 35-------------120 + */ + fail_unless (ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 27) == TRUE); + fail_unless (ges_container_edit (clip2, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 35) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 27, 0, 10); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 35, 0, 60); + + /** + * Trim start clip to: 32 and clip2 to 35 + * + * New timeline: + * ------------ + * 5-------- + * inpoints 0----------- | clip 0--|---------- + * | clip1 | 32----|-37 clip2 | + * time 20-----------30 35-------------120 + */ + fail_unless (ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_START, 32) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 32, 5, 5); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 35, 0, 60); + + /* Ripple end clip to 42 + * New timeline: + * ------------ + * 5-------- + * inpoints 0----------- | clip 0--|---------- + * | clip1 | 32----|-42 clip2 | + * time 20-----------30 35-------------120 + */ + fail_unless (ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_END, 42) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 32, 5, 10); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 35, 0, 60); + + /** + * New timeline: + * ------------ + * inpoints 0------- 5-------- 0----------- + * | clip1 | | clip1 || clip2 | + * time 20-------30 32--------52 ---------112 + */ + fail_unless (ges_container_edit (clip2, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 42) == TRUE); + fail_unless (ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_END, 52) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 32, 5, 20); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 52, 0, 60); + + /** + * New timeline: + * ------------ + * inpoints 0------- 5-------- 0------------ + * | clip1 | | clip || clip2 | + * time 20-------40 42--------62 ---------122 + */ + fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_END, 40) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 42, 5, 20); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 20); + CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + + /** + * New timeline: + * ------------ + * inpoints 0------- 3-------- 0------------ + * | clip1 || clip || clip2 | + * time 20-------40 --------62 ---------122 + */ + fail_unless (ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_START, 40) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 40, 3, 22); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 20); + CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + /** + * New timeline: + * ------------ + * inpoints 0------- 0-------- 0----------- + * | clip1 || clip || clip2 | + * time 20------ 25 ------ 62 ---------122 + */ + fail_if (ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_START, 25)); + CHECK_OBJECT_PROPS (trackelement, 40, 3, 22); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 20); + CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + + /* Make sure that not doing anything when not able to roll */ + fail_if (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_END, 65) == TRUE, 0); + CHECK_OBJECT_PROPS (trackelement, 40, 3, 22); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 20); + CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_snapping) +{ + GESTrack *track; + GESTimeline *timeline; + GESTrackElement *trackelement, *trackelement1, *trackelement2; + GESContainer *clip, *clip1, *clip2; + GESLayer *layer; + GList *trackelements; + + ges_init (); + + track = GES_TRACK (ges_video_track_new ()); + fail_unless (track != NULL); + + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + + fail_unless (ges_timeline_add_track (timeline, track)); + + clip = GES_CONTAINER (ges_test_clip_new ()); + clip1 = GES_CONTAINER (ges_test_clip_new ()); + clip2 = GES_CONTAINER (ges_test_clip_new ()); + + fail_unless (clip && clip1 && clip2); + + /** + * Our timeline + * ------------ + * inpoints 0------- 0-------- 0----------- + * | clip1 || clip || clip2 | + * time 20------ 25 ------ 62 ---------122 + */ + g_object_set (clip, "start", (guint64) 25, "duration", (guint64) 37, + "in-point", (guint64) 0, NULL); + g_object_set (clip1, "start", (guint64) 20, "duration", (guint64) 15, + "in-point", (guint64) 0, NULL); + g_object_set (clip2, "start", (guint64) 62, "duration", (guint64) 60, + "in-point", (guint64) 0, NULL); + + fail_unless ((layer = ges_timeline_append_layer (timeline)) != NULL); + assert_equals_int (ges_layer_get_priority (layer), 0); + + + fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip))); + fail_unless ((trackelements = GES_CONTAINER_CHILDREN (clip)) != NULL); + fail_unless ((trackelement = + GES_TRACK_ELEMENT (trackelements->data)) != NULL); + fail_unless (ges_track_element_get_track (trackelement) == track); + assert_equals_uint64 (_DURATION (trackelement), 37); + + ASSERT_OBJECT_REFCOUNT (trackelement, "track + timeline + clip", 3); + ASSERT_OBJECT_REFCOUNT (clip, "layer + timeline", 2); + + fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip1))); + fail_unless ((trackelements = GES_CONTAINER_CHILDREN (clip1)) != NULL); + fail_unless ((trackelement1 = + GES_TRACK_ELEMENT (trackelements->data)) != NULL); + fail_unless (ges_track_element_get_track (trackelement1) == track); + assert_equals_uint64 (_DURATION (trackelement1), 15); + + /* Same ref logic */ + ASSERT_OBJECT_REFCOUNT (trackelement1, "First trackelement", 3); + ASSERT_OBJECT_REFCOUNT (clip1, "First clip", 2); + + fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip2))); + fail_unless ((trackelements = GES_CONTAINER_CHILDREN (clip2)) != NULL); + fail_unless ((trackelement2 = + GES_TRACK_ELEMENT (trackelements->data)) != NULL); + fail_unless (ges_track_element_get_track (trackelement2) == track); + assert_equals_uint64 (_DURATION (trackelement2), 60); + + /* Same ref logic */ + ASSERT_OBJECT_REFCOUNT (trackelement2, "First trackelement", 3); + ASSERT_OBJECT_REFCOUNT (clip2, "First clip", 2); + + /* Snaping to edge, so no move */ + g_object_set (timeline, "snapping-distance", (guint64) 3, NULL); + fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_END, 27) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 5); + CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + + /* Snaping to edge, so no move */ + ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 27); + CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 5); + CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + + /** + * New timeline: + * ------------ + * 0----------- 0------------- + * inpoints 0-------|-- clip || clip2 | + * | clip1 25-|------- 62 -----------122 + * time 20----------30 + */ + g_object_set (timeline, "snapping-distance", (guint64) 0, NULL); + ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip1), 10); + DEEP_CHECK (clip, 25, 0, 37); + DEEP_CHECK (clip1, 20, 0, 10); + DEEP_CHECK (clip2, 62, 0, 60); + + /* clip and clip1 would fully overlap ... forbiden */ + fail_if (ges_timeline_element_roll_end (GES_TIMELINE_ELEMENT (clip1), 62)); + DEEP_CHECK (clip, 25, 0, 37); + DEEP_CHECK (clip1, 20, 0, 10); + DEEP_CHECK (clip2, 62, 0, 60); + fail_if (ges_timeline_element_roll_end (GES_TIMELINE_ELEMENT (clip1), + 72) == TRUE); + DEEP_CHECK (clip, 25, 0, 37); + DEEP_CHECK (clip1, 20, 0, 10); + DEEP_CHECK (clip2, 62, 0, 60); + + /** + * 30-------+0-------------+ + * inpoints 0-----------5 clip || clip2 | + * | clip1 |------- 62 -----------122 + * time 20----------30 + */ + g_object_set (timeline, "snapping-distance", (guint64) 4, NULL); + fail_unless (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (clip), + 28) == TRUE); + DEEP_CHECK (clip, 30, 5, 32); + DEEP_CHECK (clip1, 20, 0, 10); + DEEP_CHECK (clip2, 62, 0, 60); + + /** + * 30-------+0-------------+ + * inpoints 0-----------5 clip || clip2 | + * | clip1 |------- 62 -----------122 + * time 20----------30 + */ + fail_unless (ges_timeline_element_set_inpoint (GES_TIMELINE_ELEMENT (clip2), + 5)); + DEEP_CHECK (clip, 30, 5, 32); + DEEP_CHECK (clip1, 20, 0, 10); + DEEP_CHECK (clip2, 62, 5, 60); + + /** + * 30-------+0-------------+ + * inpoints 0-----------5 clip || clip2 | + * | clip1 |------- 62 -----------122 + * time 20----------30 + */ + /* Moving clip1 to 26 would lead to snapping to 30, and clip1 and clip + * would fully overlap */ + fail_if (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 26) == TRUE); + DEEP_CHECK (clip, 30, 5, 32); + DEEP_CHECK (clip1, 20, 0, 10); + DEEP_CHECK (clip2, 62, 5, 60); + + /** + * 30-------+0-------------+ + * inpoints 5 clip || clip2 |-------------+ + * +------- 62 -----------122 clip1 | + * time +------------132 + * Check that clip1 snaps with the end of clip2 */ + fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 125) == TRUE); + DEEP_CHECK (clip, 30, 5, 32); + DEEP_CHECK (clip1, 122, 0, 10); + DEEP_CHECK (clip2, 62, 5, 60); + + /* Check we didn't lose/screwed any references */ + ASSERT_OBJECT_REFCOUNT (trackelement, "First trackelement", 3); + ASSERT_OBJECT_REFCOUNT (trackelement1, "Second trackelement", 3); + ASSERT_OBJECT_REFCOUNT (trackelement2, "Third trackelement", 3); + ASSERT_OBJECT_REFCOUNT (clip, "First clip", 2); + ASSERT_OBJECT_REFCOUNT (clip1, "Second clip", 2); + ASSERT_OBJECT_REFCOUNT (clip2, "Third clip", 2); + + check_destroyed (G_OBJECT (timeline), G_OBJECT (trackelement), + trackelement1, trackelement2, clip, clip1, clip2, layer, NULL); + + ges_deinit (); +} + +GST_END_TEST; + +static void +asset_added_cb (GESProject * project, GESAsset * asset, void *mainloop) +{ + GstDiscovererInfo *info; + + info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset)); + fail_unless (GST_IS_DISCOVERER_INFO (info)); + + g_main_loop_quit ((GMainLoop *) mainloop); +} + +GST_START_TEST (test_simple_triming) +{ + GList *assets, *tmp; + GMainLoop *mainloop; + GESClipAsset *asset; + GESProject *project; + GESTimeline *timeline; + GESLayer *layer; + GESTimelineElement *element; + gchar *uri; + + ges_init (); + + uri = ges_test_file_uri ("audio_video.ogg"); + + project = ges_project_new (NULL); + + mainloop = g_main_loop_new (NULL, FALSE); + + g_signal_connect (project, "asset-added", (GCallback) asset_added_cb, + mainloop); + ges_project_create_asset (project, uri, GES_TYPE_URI_CLIP); + g_free (uri); + + g_main_loop_run (mainloop); + + /* the asset is now loaded */ + timeline = ges_timeline_new_audio_video (); + assets = ges_project_list_assets (project, GES_TYPE_CLIP); + + assert_equals_int (g_list_length (assets), 1); + asset = assets->data; + + layer = ges_layer_new (); + ges_timeline_add_layer (timeline, layer); + + ges_layer_add_asset (layer, GES_ASSET (asset), 0, 0, 10, + ges_clip_asset_get_supported_formats (asset)); + g_list_free_full (assets, g_object_unref); + + tmp = ges_layer_get_clips (layer); + element = tmp->data; + + DEEP_CHECK (element, 0, 0, 10); + ges_container_edit (GES_CONTAINER (element), NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_START, 5); + DEEP_CHECK (element, 5, 5, 5); + g_list_free_full (tmp, g_object_unref); + + g_main_loop_unref (mainloop); + gst_object_unref (timeline); + gst_object_unref (project); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_timeline_edition_mode) +{ + GESTrack *track; + GESTimeline *timeline; + GESTrackElement *trackelement, *trackelement1, *trackelement2; + GESContainer *clip, *clip1, *clip2; + GESLayer *layer, *layer1, *layer2; + GList *trackelements, *layers, *tmp; + + ges_init (); + + track = GES_TRACK (ges_video_track_new ()); + fail_unless (track != NULL); + + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + + fail_unless (ges_timeline_add_track (timeline, track)); + + clip = GES_CONTAINER (ges_test_clip_new ()); + clip1 = GES_CONTAINER (ges_test_clip_new ()); + clip2 = GES_CONTAINER (ges_test_clip_new ()); + + fail_unless (clip && clip1 && clip2); + + /** + * Our timeline + * + * 0------- + * layer: | clip | + * 0-------10 + * + * 0-------- 0----------- + * layer1: | clip1 | | clip2 | + * 10--------20 50---------60 + */ + g_object_set (clip, "start", (guint64) 0, "duration", (guint64) 10, + "in-point", (guint64) 0, NULL); + g_object_set (clip1, "start", (guint64) 10, "duration", (guint64) 10, + "in-point", (guint64) 0, NULL); + g_object_set (clip2, "start", (guint64) 50, "duration", (guint64) 60, + "in-point", (guint64) 0, NULL); + + fail_unless ((layer = ges_timeline_append_layer (timeline)) != NULL); + assert_equals_int (ges_layer_get_priority (layer), 0); + + + fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip))); + fail_unless ((trackelements = GES_CONTAINER_CHILDREN (clip)) != NULL); + fail_unless ((trackelement = + GES_TRACK_ELEMENT (trackelements->data)) != NULL); + fail_unless (ges_track_element_get_track (trackelement) == track); + assert_equals_uint64 (_DURATION (trackelement), 10); + + /* Add a new layer and add clipects to it */ + fail_unless ((layer1 = ges_timeline_append_layer (timeline)) != NULL); + fail_unless (layer != layer1); + assert_equals_int (ges_layer_get_priority (layer1), 1); + + fail_unless (ges_layer_add_clip (layer1, GES_CLIP (clip1))); + fail_unless ((trackelements = GES_CONTAINER_CHILDREN (clip1)) != NULL); + fail_unless ((trackelement1 = + GES_TRACK_ELEMENT (trackelements->data)) != NULL); + fail_unless (ges_track_element_get_track (trackelement1) == track); + assert_equals_uint64 (_DURATION (trackelement1), 10); + + fail_unless (ges_layer_add_clip (layer1, GES_CLIP (clip2))); + fail_unless ((trackelements = GES_CONTAINER_CHILDREN (clip2)) != NULL); + fail_unless ((trackelement2 = + GES_TRACK_ELEMENT (trackelements->data)) != NULL); + fail_unless (ges_track_element_get_track (trackelement2) == track); + assert_equals_uint64 (_DURATION (trackelement2), 60); + + /** + * Simple rippling clip to: 10 + * + * New timeline: + * ------------ + * + * inpoints 0------- + * | clip | + * time 10-------20 + * + * 0-------- 0----------- + * | clip1 | | clip2 | + * 20--------30 60--------120 + */ + fail_unless (ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_NONE, 10) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 10, 0, 10); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 60, 0, 60); + + + /* FIXME find a way to check that we are using the same MovingContext + * inside the GESTimeline */ + fail_unless (ges_container_edit (clip1, NULL, 3, GES_EDIT_MODE_RIPPLE, + GES_EDGE_NONE, 40) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 10, 0, 10); + CHECK_OBJECT_PROPS (trackelement1, 40, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 80, 0, 60); + layer2 = ges_clip_get_layer (GES_CLIP (clip1)); + assert_equals_int (ges_layer_get_priority (layer2), 3); + /* clip2 should have moved layer too */ + fail_unless (ges_clip_get_layer (GES_CLIP (clip2)) == layer2); + /* We got 2 reference to the same clipect, unref them */ + gst_object_unref (layer2); + gst_object_unref (layer2); + + /** + * Rippling clip1 back to: 20 (getting to the exact same timeline as before + */ + fail_unless (ges_container_edit (clip1, NULL, 1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_NONE, 20) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 10, 0, 10); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 60, 0, 60); + layer2 = ges_clip_get_layer (GES_CLIP (clip1)); + assert_equals_int (ges_layer_get_priority (layer2), 1); + /* clip2 should have moved layer too */ + fail_unless (ges_clip_get_layer (GES_CLIP (clip2)) == layer2); + /* We got 2 reference to the same clipect, unref them */ + gst_object_unref (layer2); + gst_object_unref (layer2); + + /** + * Simple move clip to 27 and clip2 to 35 + * + * New timeline: + * ------------ + * + * inpoints 0------- + * | clip | + * time 27-------37 + * + * 0-------- 0----------- + * | clip1 | | clip2 | + * 20--------30 35---------95 + */ + fail_unless (ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 27) == TRUE); + fail_unless (ges_container_edit (clip2, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 35) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 27, 0, 10); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 35, 0, 60); + + /** + * Simple trimming start clip to: 32 + * + * New timeline: + * ------------ + * + * 5------- + * layer 0: | clip | + * 32-------37 + * + * 0-------- 0----------- + * layer 1 | clip1 | | clip2 | + * 20--------30 35---------95 + */ + fail_unless (ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_START, 32) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 32, 5, 5); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 35, 0, 60); + + /* Ripple end clip to 35 and move to layer 2 + * New timeline: + * ------------ + * + * 0-------- 0----------- + * layer 1: | clip1 | | clip2 | + * 20--------30 35---------95 + * + * 5------ + * layer 2: | clip | + * 32------35 + */ + fail_unless (ges_container_edit (clip, NULL, 2, GES_EDIT_MODE_RIPPLE, + GES_EDGE_END, 35) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 32, 5, 3); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 35, 0, 60); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), 2); + + /* Roll end clip to 50 + * New timeline: + * ------------ + * + * 0-------- 0----------- + * layer 1: | clip1 | | clip2 | + * 20--------30 50---------95 + * + * 5------ + * layer 2: | clip | + * 32------50 + */ + fail_unless (ges_container_edit (clip, NULL, 2, GES_EDIT_MODE_ROLL, + GES_EDGE_END, 50) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 32, 5, 18); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 50, 15, 45); + layer = ges_clip_get_layer (GES_CLIP (clip)); + assert_equals_int (ges_layer_get_priority (layer), 2); + gst_object_unref (layer); + + /* Roll end clip back to 35 + * New timeline: + * ------------ + * + * 0-------- 0----------- + * layer 1: | clip1 | | clip2 | + * 20--------30 35---------95 + * + * 5------ + * layer 2: | clip | + * 32------35 + */ + fail_unless (ges_container_edit (clip, NULL, 2, GES_EDIT_MODE_ROLL, + GES_EDGE_END, 35) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 32, 5, 3); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 35, 0, 60); + layer = ges_clip_get_layer (GES_CLIP (clip)); + assert_equals_int (ges_layer_get_priority (layer), 2); + gst_object_unref (layer); + + /* Roll end clip back to 35 */ + /* Can not move to the first layer as clip2 should move to a layer with priority < 0 */ + fail_if (ges_container_edit (clip, NULL, 0, GES_EDIT_MODE_RIPPLE, + GES_EDGE_END, 52)); + CHECK_OBJECT_PROPS (trackelement, 32, 5, 3); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 35, 0, 60); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), 2); + + /* Ripple clip end to 52 + * New timeline: + * ------------ + * + * 0-------- 0---------- + * layer 1: | clip1 | | clip2 | + * 20-------30 52---------112 + * + * 5------ + * layer 2: | clip | + * 32------52 + * + */ + fail_unless (ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_END, 52) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 32, 5, 20); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 52, 0, 60); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), 2); + + + /* Little check that we have 4 layers in the timeline */ + layers = ges_timeline_get_layers (timeline); + assert_equals_int (g_list_length (layers), 4); + + /* Some refcount checkings */ + /* We have a reference to each layer in layers */ + for (tmp = layers; tmp; tmp = tmp->next) + ASSERT_OBJECT_REFCOUNT (layer, "Layer", 2); + g_list_free_full (layers, gst_object_unref); + + /* We have 3 references: + * track + timeline + clip + */ + ASSERT_OBJECT_REFCOUNT (trackelement, "First trackelement", 3); + ASSERT_OBJECT_REFCOUNT (trackelement1, "Second trackelement", 3); + ASSERT_OBJECT_REFCOUNT (trackelement2, "Third trackelement", 3); + ASSERT_OBJECT_REFCOUNT (clip, "First clip", 2); + ASSERT_OBJECT_REFCOUNT (clip1, "Second clip", 2); + ASSERT_OBJECT_REFCOUNT (clip2, "Third clip", 2); + + /* Ripple clip end to 52 + * New timeline: + * ------------ + * + * 0-------- 0----------- + * layer 0: | clip1 | | clip2 | + * 20-------40 62----------112 + * + * 5------ + * layer 1: | clip | + * 42------60 + * + */ + fail_unless (ges_container_edit (clip1, NULL, 0, GES_EDIT_MODE_RIPPLE, + GES_EDGE_END, 40) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 42, 5, 20); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 20); + CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + + /* Check that movement between layer has been done properly */ + layer1 = ges_clip_get_layer (GES_CLIP (clip)); + layer = ges_clip_get_layer (GES_CLIP (clip1)); + assert_equals_int (ges_layer_get_priority (layer1), 1); + assert_equals_int (ges_layer_get_priority (layer), 0); + fail_unless (ges_clip_get_layer (GES_CLIP (clip2)) == layer); + gst_object_unref (layer1); + /* We have 2 references to @layer that we do not need anymore */ ; + gst_object_unref (layer); + gst_object_unref (layer); + + /* Trim clip start to 40 + * New timeline: + * ------------ + * + * 0-------- 0----------- + * layer 0: | clip1 | | clip2 | + * 20-------40 62---------112 + * + * 0------ + * layer 1: | clip | + * 40------62 + * + */ + fail_unless (ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_START, 40) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 40, 3, 22); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 20); + CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + + /* Roll clip end to 25 + * New timeline: + * ------------ + * + * 0-------- 0----------- + * layer 0: | clip1 | | clip2 | + * 20-------25 62---------112 + * + * 0------ + * layer 1: | clip | + * 25------62 + * + */ + ges_timeline_element_set_inpoint (GES_TIMELINE_ELEMENT (clip), 15); + fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_END, 25) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 5); + CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + + /* Make sure that not doing anything when not able to roll */ + fail_if (ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_START, 65)); + fail_if (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_END, 65)); + CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 5); + CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + + /* Snaping to edge, so no move */ + g_object_set (timeline, "snapping-distance", (guint64) 3, NULL); + ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 27); + CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 5); + CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + + /* Snaping to edge, so no move */ + ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 27); + CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 5); + CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + + /** + * New timeline: + * ------------ + * 0----------- 0------------- + * inpoints 0-------|-- clip || clip2 | + * | clip1 25-|------- 62 -----------122 + * time 20----------30 + */ + g_object_set (timeline, "snapping-distance", (guint64) 0, NULL); + fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_END, 30) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + + /** + * New timeline + * ------------ + * 0---------- + * | clip | + * 25---------62 + * ------------------------------------------------- + * inpoints 0----------------------- 10-------- + * | clip1 || clip2 | + * time 20---------------------- 72 --------122 + */ + /* Rolling involves only neighbours that are currently snapping */ + ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_ROLL, GES_EDGE_END, 62); + fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_END, 72) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 52); + CHECK_OBJECT_PROPS (trackelement2, 72, 10, 50); + + /* Test Snapping */ + /** + * 0---------- + * | clip | + * 25---------62 + * inpoints 5--------------- 10-------- + * | clip1 || clip2 | + * time 25------------- 72 --------122 + */ + g_object_set (timeline, "snapping-distance", (guint64) 4, NULL); + fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_START, 28) == TRUE); + CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); + CHECK_OBJECT_PROPS (trackelement1, 25, 5, 47); + CHECK_OBJECT_PROPS (trackelement2, 72, 10, 50); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_groups) +{ + GESAsset *asset; + GESTimeline *timeline; + GESGroup *group; + GESLayer *layer, *layer1, *layer2, *layer3; + GESClip *c, *c1, *c2, *c3, *c4, *c5; + + GList *clips = NULL; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + + /* Our timeline + * + * --0------------10-Group-----20---------------30-----------------------70 + * | +-----------+ |+-----------50 | + * L | | C | || C3 | | + * | +-----------+ |+-----------+ | + * --|-------------------------------------------|-----40----------------| + * | +------------+ +-------------+ | +--------60 | + * L1 | | C1 | | C2 | | | C4 | | + * | +------------+ +-------------+ | +--------+ | + * --|-------------------------------------------|-----------------------| + * | | +--------+| + * L2 | | | c5 || + * | | +--------+| + * --+-------------------------------------------+-----------------------+ + * + * L3 + * + * ----------------------------------------------------------------------- + */ + + layer = ges_timeline_append_layer (timeline); + layer1 = ges_timeline_append_layer (timeline); + layer2 = ges_timeline_append_layer (timeline); + layer3 = ges_timeline_append_layer (timeline); + assert_equals_int (ges_layer_get_priority (layer3), 3); + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + c = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + c1 = ges_layer_add_asset (layer1, asset, 10, 0, 10, GES_TRACK_TYPE_UNKNOWN); + c2 = ges_layer_add_asset (layer1, asset, 20, 0, 10, GES_TRACK_TYPE_UNKNOWN); + clips = g_list_prepend (clips, c); + clips = g_list_prepend (clips, c1); + clips = g_list_prepend (clips, c2); + group = GES_GROUP (ges_container_group (clips)); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group) == timeline); + g_list_free (clips); + + fail_unless (GES_IS_GROUP (group)); + CHECK_CLIP (c, 0, 0, 10, 0); + CHECK_CLIP (c1, 10, 0, 10, 1); + CHECK_CLIP (c2, 20, 0, 10, 1); + CHECK_OBJECT_PROPS (group, 0, 0, 30); + + c3 = ges_layer_add_asset (layer, asset, 30, 0, 20, GES_TRACK_TYPE_UNKNOWN); + c4 = ges_layer_add_asset (layer1, asset, 40, 0, 20, GES_TRACK_TYPE_UNKNOWN); + c5 = ges_layer_add_asset (layer2, asset, 50, 0, 20, GES_TRACK_TYPE_UNKNOWN); + + CHECK_CLIP (c3, 30, 0, 20, 0); + CHECK_CLIP (c4, 40, 0, 20, 1); + CHECK_CLIP (c5, 50, 0, 20, 2); + check_layer (c, 0); + check_layer (c1, 1); + check_layer (c2, 1); + + fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, -1, + GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, 10) == TRUE); + + CHECK_CLIP (c, 10, 0, 10, 0); + CHECK_CLIP (c1, 20, 0, 10, 1); + CHECK_CLIP (c2, 30, 0, 10, 1); + CHECK_CLIP (c3, 40, 0, 20, 0); + CHECK_CLIP (c4, 50, 0, 20, 1); + CHECK_CLIP (c5, 60, 0, 20, 2); + + fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, 1, + GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, 10) == TRUE); + CHECK_CLIP (c, 10, 0, 10, 1); + CHECK_CLIP (c1, 20, 0, 10, 2); + CHECK_CLIP (c2, 30, 0, 10, 2); + CHECK_CLIP (c3, 40, 0, 20, 1); + CHECK_CLIP (c4, 50, 0, 20, 2); + CHECK_CLIP (c5, 60, 0, 20, 3); + + fail_if (ges_container_edit (GES_CONTAINER (c1), NULL, 2, + GES_EDIT_MODE_RIPPLE, GES_EDGE_END, 40) == TRUE); + CHECK_CLIP (c, 10, 0, 10, 1); + CHECK_CLIP (c1, 20, 0, 10, 2); + CHECK_CLIP (c2, 30, 0, 10, 2); + CHECK_CLIP (c3, 40, 0, 20, 1); + CHECK_CLIP (c4, 50, 0, 20, 2); + CHECK_CLIP (c5, 60, 0, 20, 3); + fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, 0, + GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, 0) == TRUE); + CHECK_CLIP (c, 0, 0, 10, 0); + CHECK_CLIP (c1, 10, 0, 10, 1); + CHECK_CLIP (c2, 20, 0, 10, 1); + CHECK_CLIP (c3, 30, 0, 20, 0); + CHECK_CLIP (c4, 40, 0, 20, 1); + CHECK_CLIP (c5, 50, 0, 20, 2); + CHECK_OBJECT_PROPS (group, 0, 0, 30); + + fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, 0, + GES_EDIT_MODE_TRIM, GES_EDGE_START, 5) == TRUE); + CHECK_CLIP (c, 5, 5, 5, 0); + CHECK_CLIP (c1, 10, 0, 10, 1); + CHECK_CLIP (c2, 20, 0, 10, 1); + CHECK_CLIP (c3, 30, 0, 20, 0); + CHECK_CLIP (c4, 40, 0, 20, 1); + CHECK_CLIP (c5, 50, 0, 20, 2); + CHECK_OBJECT_PROPS (group, 5, 0, 25); + + gst_object_unref (timeline); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_snapping_groups) +{ + GESAsset *asset; + GESTimeline *timeline; + GESGroup *group; + GESLayer *layer, *layer1, *layer2, *layer3; + GESClip *c, *c1, *c2, *c3, *c4, *c5; + + GList *clips = NULL; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + g_object_set (timeline, "snapping-distance", (guint64) 3, NULL); + + /* Our timeline + * + * --0------------10-Group-----20---------25-----30----------------------70 + * | +-----------+ | +-----------50 | + * L | | C | | | C3 | | + * | +-----------+ | +-----------+ | + * --|------------------------------------|------------40----------------| + * | +------------+ +--------+ +--------60 | + * L1 | | C1 | | C2 | | C4 | | + * | +------------+ +--------+ +--------+ | + * --|------------------------------------+------------------------------| + * | +--------+| + * L2 | | c5 || + * | +--------+| + * --+-------------------------------------------------------------------+ + * + * L3 + * + * ----------------------------------------------------------------------- + */ + + layer = ges_timeline_append_layer (timeline); + layer1 = ges_timeline_append_layer (timeline); + layer2 = ges_timeline_append_layer (timeline); + layer3 = ges_timeline_append_layer (timeline); + assert_equals_int (ges_layer_get_priority (layer3), 3); + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + c = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + c1 = ges_layer_add_asset (layer1, asset, 10, 0, 10, GES_TRACK_TYPE_UNKNOWN); + c2 = ges_layer_add_asset (layer1, asset, 20, 0, 5, GES_TRACK_TYPE_UNKNOWN); + clips = g_list_prepend (clips, c); + clips = g_list_prepend (clips, c1); + clips = g_list_prepend (clips, c2); + group = GES_GROUP (ges_container_group (clips)); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group) == timeline); + g_list_free (clips); + + fail_unless (GES_IS_GROUP (group)); + CHECK_OBJECT_PROPS (c, 0, 0, 10); + CHECK_OBJECT_PROPS (c1, 10, 0, 10); + CHECK_OBJECT_PROPS (c2, 20, 0, 5); + CHECK_OBJECT_PROPS (group, 0, 0, 25); + + c3 = ges_layer_add_asset (layer, asset, 30, 0, 20, GES_TRACK_TYPE_UNKNOWN); + c4 = ges_layer_add_asset (layer1, asset, 40, 0, 20, GES_TRACK_TYPE_UNKNOWN); + c5 = ges_layer_add_asset (layer2, asset, 50, 0, 20, GES_TRACK_TYPE_UNKNOWN); + + CHECK_OBJECT_PROPS (c3, 30, 0, 20); + CHECK_OBJECT_PROPS (c4, 40, 0, 20); + CHECK_OBJECT_PROPS (c5, 50, 0, 20); + check_layer (c, 0); + check_layer (c1, 1); + check_layer (c2, 1); + check_layer (c3, 0); + check_layer (c4, 1); + check_layer (c5, 2); + + /* c2 should snap with C3 and make the group moving to 5 */ + fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, -1, + GES_EDIT_MODE_NORMAL, GES_EDGE_NONE, 3) == TRUE); + + DEEP_CHECK (c, 5, 0, 10); + DEEP_CHECK (c1, 15, 0, 10); + DEEP_CHECK (c2, 25, 0, 5); + DEEP_CHECK (c2, 25, 0, 5); + DEEP_CHECK (c4, 40, 0, 20); + DEEP_CHECK (c5, 50, 0, 20); + CHECK_OBJECT_PROPS (group, 5, 0, 25); + check_layer (c, 0); + check_layer (c1, 1); + check_layer (c2, 1); + check_layer (c3, 0); + check_layer (c4, 1); + check_layer (c5, 2); + + + gst_object_unref (timeline); + gst_object_unref (asset); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_marker_snapping) +{ + GESTrack *track; + GESTimeline *timeline; + GESTrackElement *trackelement1, *trackelement2; + GESContainer *clip1, *clip2; + GESLayer *layer; + GList *trackelements; + GESMarkerList *marker_list1, *marker_list2; + + ges_init (); + + track = GES_TRACK (ges_video_track_new ()); + fail_unless (track != NULL); + + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + + fail_unless (ges_timeline_add_track (timeline, track)); + + clip1 = GES_CONTAINER (ges_test_clip_new ()); + clip2 = GES_CONTAINER (ges_test_clip_new ()); + + fail_unless (clip1 && clip2); + + /** + * Our timeline + * ------------ + * 30 + * markers -----|---------------- + * | clip1 || clip2 | + * time 20 ------- 50 ------ 110 + * + */ + g_object_set (clip1, "start", (guint64) 20, "duration", (guint64) 30, + "in-point", (guint64) 0, NULL); + g_object_set (clip2, "start", (guint64) 50, "duration", (guint64) 60, + "in-point", (guint64) 0, NULL); + + fail_unless ((layer = ges_timeline_append_layer (timeline)) != NULL); + assert_equals_int (ges_layer_get_priority (layer), 0); + + fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip1))); + fail_unless ((trackelements = GES_CONTAINER_CHILDREN (clip1)) != NULL); + fail_unless ((trackelement1 = + GES_TRACK_ELEMENT (trackelements->data)) != NULL); + fail_unless (ges_track_element_get_track (trackelement1) == track); + + fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip2))); + fail_unless ((trackelements = GES_CONTAINER_CHILDREN (clip2)) != NULL); + fail_unless ((trackelement2 = + GES_TRACK_ELEMENT (trackelements->data)) != NULL); + fail_unless (ges_track_element_get_track (trackelement2) == track); + + marker_list1 = ges_marker_list_new (); + g_object_set (marker_list1, "flags", GES_MARKER_FLAG_SNAPPABLE, NULL); + ges_marker_list_add (marker_list1, 10); + ges_marker_list_add (marker_list1, 20); + fail_unless (ges_meta_container_set_marker_list (GES_META_CONTAINER + (trackelement1), "ges-test", marker_list1)); + + /** + * Snapping clip2 to a marker on clip1 + * ------------ + * 30 40 + * markers -----|--|-- + * | clip1 | + * time 20 ------ 50 + * ----------- + * | clip2 | + * 30 ------ 90 + */ + g_object_set (timeline, "snapping-distance", (guint64) 3, NULL); + /* Move within 2 units of marker timestamp */ + fail_unless (ges_container_edit (clip2, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 32) == TRUE); + /* Clip nr. 2 should snap to marker at timestamp 30 */ + DEEP_CHECK (clip1, 20, 0, 30); + DEEP_CHECK (clip2, 30, 0, 60); + + /** + * Snapping clip1 to a marker on clip2 + * ------------ + * 90 + * markers --|-------- + * | clip1 | + * time 80 ------ 110 + * markers ----------|-- + * | clip2 | + * 30 -------- 90 + */ + marker_list2 = ges_marker_list_new (); + g_object_set (marker_list2, "flags", GES_MARKER_FLAG_SNAPPABLE, NULL); + ges_marker_list_add (marker_list2, 40); + ges_marker_list_add (marker_list2, 50); + fail_unless (ges_meta_container_set_marker_list (GES_META_CONTAINER + (trackelement2), "ges-test", marker_list2)); + + fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 77) == TRUE); + DEEP_CHECK (clip1, 80, 0, 30); + DEEP_CHECK (clip2, 30, 0, 60); + + /** + * Checking if clip's own markers are properly ignored when snapping + * (moving clip1 close to where one of its markers is) + * ------------ + * 100 112 122 + * markers | --|-------|-- + * old m.pos. | clip1 | + * time 102 ------- 132 + */ + fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 102) == TRUE); + DEEP_CHECK (clip1, 102, 0, 30); + DEEP_CHECK (clip2, 30, 0, 60); + + /** + * Checking if non-snappable marker lists are correctly ignored. + * (moving clip1 close to clip2's non-snappable marker) + */ + g_object_set (marker_list2, "flags", GES_MARKER_FLAG_NONE, NULL); + fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 82) == TRUE); + DEEP_CHECK (clip1, 82, 0, 30); + DEEP_CHECK (clip2, 30, 0, 60); + + g_object_unref (marker_list1); + g_object_unref (marker_list2); + check_destroyed (G_OBJECT (timeline), G_OBJECT (trackelement1), + trackelement2, clip1, clip2, layer, marker_list1, marker_list2, NULL); + ges_deinit (); +} + +GST_END_TEST; + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-timeline-edition"); + TCase *tc_chain = tcase_create ("timeline-edition"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_basic_timeline_edition); + tcase_add_test (tc_chain, test_snapping); + tcase_add_test (tc_chain, test_timeline_edition_mode); + tcase_add_test (tc_chain, test_simple_triming); + tcase_add_test (tc_chain, test_groups); + tcase_add_test (tc_chain, test_snapping_groups); + tcase_add_test (tc_chain, test_marker_snapping); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/titles.c b/tests/check/ges/titles.c new file mode 100644 index 0000000000..e5b19dfc6f --- /dev/null +++ b/tests/check/ges/titles.c @@ -0,0 +1,225 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" +#include <ges/ges.h> +#include <gst/check/gstcheck.h> + +GST_START_TEST (test_title_source_basic) +{ + GESTitleClip *source; + + ges_init (); + + source = ges_title_clip_new (); + fail_unless (source != NULL); + + gst_object_unref (source); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_title_source_properties) +{ + GESClip *clip; + GESTrack *track; + GESTimeline *timeline; + GESLayer *layer; + GESTrackElement *trackelement; + + ges_init (); + + track = GES_TRACK (ges_video_track_new ()); + fail_unless (track != NULL); + layer = ges_layer_new (); + fail_unless (layer != NULL); + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + fail_unless (ges_timeline_add_layer (timeline, layer)); + fail_unless (ges_timeline_add_track (timeline, track)); + ASSERT_OBJECT_REFCOUNT (timeline, "timeline", 1); + + clip = (GESClip *) ges_title_clip_new (); + fail_unless (clip != NULL); + + /* Set some properties */ + g_object_set (clip, "start", (guint64) 42, "duration", (guint64) 51, + "in-point", (guint64) 12, NULL); + assert_equals_uint64 (_START (clip), 42); + assert_equals_uint64 (_DURATION (clip), 51); + /* the clip can have a non-zero in-point, but this won't affect any + * of the core children because they have their has-internal-source set + * to FALSE */ + assert_equals_uint64 (_INPOINT (clip), 12); + + ges_layer_add_clip (layer, GES_CLIP (clip)); + ges_timeline_commit (timeline); + assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1); + trackelement = GES_CONTAINER_CHILDREN (clip)->data; + fail_unless (trackelement != NULL); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) == + GES_TIMELINE_ELEMENT (clip)); + fail_unless (ges_track_element_get_track (trackelement) == track); + + /* Check that trackelement has the same properties */ + assert_equals_uint64 (_START (trackelement), 42); + assert_equals_uint64 (_DURATION (trackelement), 51); + assert_equals_uint64 (_INPOINT (trackelement), 0); + + /* And let's also check that it propagated correctly to GNonLin */ + nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 51, 0, + 51, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE); + + /* Change more properties, see if they propagate */ + g_object_set (clip, "start", (guint64) 420, "duration", (guint64) 510, + "in-point", (guint64) 120, NULL); + ges_timeline_commit (timeline); + assert_equals_uint64 (_START (clip), 420); + assert_equals_uint64 (_DURATION (clip), 510); + assert_equals_uint64 (_INPOINT (clip), 120); + assert_equals_uint64 (_START (trackelement), 420); + assert_equals_uint64 (_DURATION (trackelement), 510); + assert_equals_uint64 (_INPOINT (trackelement), 0); + + /* And let's also check that it propagated correctly to GNonLin */ + nle_object_check (ges_track_element_get_nleobject (trackelement), 420, 510, + 0, 510, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0, TRUE); + + ges_container_remove (GES_CONTAINER (clip), + GES_TIMELINE_ELEMENT (trackelement)); + gst_object_unref (clip); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_title_source_in_layer) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *a, *v; + GESTrackElement *track_element; + GESTitleClip *source; + gchar *text; + gint halign, valign; + guint32 color; + gdouble xpos; + gdouble ypos; + + ges_init (); + + timeline = ges_timeline_new (); + layer = ges_layer_new (); + a = GES_TRACK (ges_audio_track_new ()); + v = GES_TRACK (ges_video_track_new ()); + + ges_timeline_add_track (timeline, a); + ges_timeline_add_track (timeline, v); + ges_timeline_add_layer (timeline, layer); + + source = ges_title_clip_new (); + + g_object_set (source, "duration", (guint64) GST_SECOND, NULL); + + ges_layer_add_clip (layer, (GESClip *) source); + + /* specifically test the text property */ + g_object_set (source, "text", (gchar *) "some text", NULL); + g_object_get (source, "text", &text, NULL); + assert_equals_string ("some text", text); + g_free (text); + + track_element = + ges_clip_find_track_element (GES_CLIP (source), v, GES_TYPE_TITLE_SOURCE); + + /* test the font-desc property */ + g_object_set (source, "font-desc", (gchar *) "sans 72", NULL); + g_object_get (source, "font-desc", &text, NULL); + assert_equals_string ("sans 72", text); + g_free (text); + + /* test halign and valign */ + g_object_set (source, "halignment", (gint) + GES_TEXT_HALIGN_LEFT, "valignment", (gint) GES_TEXT_VALIGN_TOP, NULL); + g_object_get (source, "halignment", &halign, "valignment", &valign, NULL); + assert_equals_int (halign, GES_TEXT_HALIGN_LEFT); + assert_equals_int (valign, GES_TEXT_VALIGN_TOP); + + assert_equals_int (ges_title_source_get_halignment + (GES_TITLE_SOURCE (track_element)), GES_TEXT_HALIGN_LEFT); + assert_equals_int (ges_title_source_get_valignment + (GES_TITLE_SOURCE (track_element)), GES_TEXT_VALIGN_TOP); + + /* test color */ + g_object_set (source, "color", (gint) 2147483647, NULL); + g_object_get (source, "color", &color, NULL); + assert_equals_int (color, 2147483647); + + color = ges_title_source_get_text_color (GES_TITLE_SOURCE (track_element)); + assert_equals_int (color, 2147483647); + + /* test xpos */ + g_object_set (source, "xpos", (gdouble) 0.25, NULL); + g_object_get (source, "xpos", &xpos, NULL); + assert_equals_float (xpos, 0.25); + + xpos = ges_title_source_get_xpos (GES_TITLE_SOURCE (track_element)); + assert_equals_float (xpos, 0.25); + + /* test ypos */ + g_object_set (source, "ypos", (gdouble) 0.66, NULL); + g_object_get (source, "ypos", &ypos, NULL); + assert_equals_float (ypos, 0.66); + + xpos = ges_title_source_get_xpos (GES_TITLE_SOURCE (track_element)); + assert_equals_float (ypos, 0.66); + + GST_DEBUG ("removing the source"); + + ges_layer_remove_clip (layer, (GESClip *) source); + + GST_DEBUG ("removing the layer"); + + gst_object_unref (track_element); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-titles"); + TCase *tc_chain = tcase_create ("titles"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_title_source_basic); + tcase_add_test (tc_chain, test_title_source_properties); + tcase_add_test (tc_chain, test_title_source_in_layer); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/track.c b/tests/check/ges/track.c new file mode 100644 index 0000000000..e8c01e389e --- /dev/null +++ b/tests/check/ges/track.c @@ -0,0 +1,107 @@ +/* GStreamer Editing Services + * Copyright (C) 2014 Mathieu Duponchelle <mathieu.duponchelle@opencreed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" +#include <ges/ges.h> +#include <gst/check/gstcheck.h> + +static gboolean +compare_caps_from_string (GstCaps * caps, const gchar * desc) +{ + GstCaps *new_caps = gst_caps_from_string (desc); + gboolean ret = TRUE; + + if (!gst_caps_is_strictly_equal (caps, new_caps)) + ret = FALSE; + + gst_caps_unref (new_caps); + return ret; +} + +GST_START_TEST (test_update_restriction_caps) +{ + GESTrack *track; + GstCaps *original; + GstCaps *new; + GstCaps *current; + + ges_init (); + + track = GES_TRACK (ges_audio_track_new ()); + + original = gst_caps_from_string ("audio/x-raw, format=S32LE"); + ges_track_set_restriction_caps (track, original); + + new = gst_caps_from_string ("audio/x-raw, format=S16LE, width=720"); + ges_track_update_restriction_caps (track, new); + g_object_get (track, "restriction-caps", ¤t, NULL); + + /* Assuming the format for to_string doesn't change */ + fail_unless (compare_caps_from_string (current, + "audio/x-raw, format=(string)S16LE, width=(int)720")); + + gst_caps_unref (new); + new = gst_caps_from_string ("audio/x-raw, width=360"); + ges_track_update_restriction_caps (track, new); + gst_caps_unref (current); + g_object_get (track, "restriction-caps", ¤t, NULL); + fail_unless (compare_caps_from_string (current, + "audio/x-raw, format=(string)S16LE, width=(int)360")); + + gst_caps_append_structure (new, + gst_structure_new_from_string ("audio/x-raw, format=S16LE")); + ges_track_update_restriction_caps (track, new); + gst_caps_unref (current); + g_object_get (track, "restriction-caps", ¤t, NULL); + fail_unless (compare_caps_from_string (current, + "audio/x-raw, format=(string)S16LE, width=(int)360; audio/x-raw, format=S16LE")); + + gst_caps_unref (new); + new = + gst_caps_from_string + ("audio/x-raw, width=240; audio/x-raw, format=S32LE"); + ges_track_update_restriction_caps (track, new); + gst_caps_unref (current); + g_object_get (track, "restriction-caps", ¤t, NULL); + fail_unless (compare_caps_from_string (current, + "audio/x-raw, format=(string)S16LE, width=(int)240; audio/x-raw, format=S32LE")); + + gst_caps_unref (new); + gst_caps_unref (original); + gst_caps_unref (current); + + ges_deinit (); +} + +GST_END_TEST; + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-track"); + TCase *tc_chain = tcase_create ("track"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_update_restriction_caps); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/transition.c b/tests/check/ges/transition.c new file mode 100644 index 0000000000..16b2880be4 --- /dev/null +++ b/tests/check/ges/transition.c @@ -0,0 +1,195 @@ + +/* GStreamer Editing Services + * Copyright (C) 2010 Edward Hervey <brandon@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" +#include <ges/ges.h> +#include <gst/check/gstcheck.h> + +/* This test uri will eventually have to be fixed */ +#define TEST_URI "blahblahblah" + +GST_START_TEST (test_transition_basic) +{ + GESTrack *track; + GESTimeline *timeline; + GESLayer *layer; + GESTransitionClip *tr1, *tr2; + GESTrackElement *trackelement; + + ges_init (); + + track = GES_TRACK (ges_video_track_new ()); + layer = ges_layer_new (); + timeline = ges_timeline_new (); + fail_unless (track != NULL); + fail_unless (layer != NULL); + fail_unless (timeline != NULL); + fail_unless (ges_timeline_add_layer (timeline, layer)); + fail_unless (ges_timeline_add_track (timeline, track)); + ASSERT_OBJECT_REFCOUNT (timeline, "timeline", 1); + + tr1 = ges_transition_clip_new (GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE); + fail_unless (tr1 != 0); + fail_unless (tr1->vtype == GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE); + + tr2 = ges_transition_clip_new_for_nick ((gchar *) "bar-wipe-lr"); + fail_unless (tr2 != 0); + fail_unless (tr2->vtype == 1); + + /* Make sure track element is created and vtype is set */ + fail_unless (ges_layer_add_clip (layer, GES_CLIP (tr2))); + assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (tr2)), 1); + trackelement = GES_CONTAINER_CHILDREN (tr2)->data; + fail_unless (trackelement != NULL); + fail_unless (ges_video_transition_get_transition_type + (GES_VIDEO_TRANSITION (trackelement)) == 1); + + gst_object_unref (tr1); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_transition_properties) +{ + GESClip *clip; + GESTrack *track; + GESTimeline *timeline; + GESLayer *layer; + GESTrackElement *trackelement; + + ges_init (); + + clip = GES_CLIP (ges_transition_clip_new + (GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE)); + + track = GES_TRACK (ges_video_track_new ()); + layer = ges_layer_new (); + timeline = ges_timeline_new (); + fail_unless (track != NULL); + fail_unless (layer != NULL); + fail_unless (timeline != NULL); + fail_unless (ges_timeline_add_layer (timeline, layer)); + fail_unless (ges_timeline_add_track (timeline, track)); + ASSERT_OBJECT_REFCOUNT (timeline, "timeline", 1); + + /* Set some properties */ + g_object_set (clip, "start", (guint64) 42, "duration", (guint64) 51, + "in-point", (guint64) 12, NULL); + + assert_equals_uint64 (_START (clip), 42); + assert_equals_uint64 (_DURATION (clip), 51); + assert_equals_uint64 (_INPOINT (clip), 12); + + fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip))); + ges_timeline_commit (timeline); + assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1); + trackelement = GES_CONTAINER_CHILDREN (clip)->data; + fail_unless (trackelement != NULL); + + /* Check that trackelement has the same properties */ + assert_equals_uint64 (_START (trackelement), 42); + assert_equals_uint64 (_DURATION (trackelement), 51); + /* in-point is 0 since it does not have has-internal-source */ + assert_equals_uint64 (_INPOINT (trackelement), 0); + + /* And let's also check that it propagated correctly to GNonLin */ + nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 51, 0, + 51, MIN_NLE_PRIO, TRUE); + + /* Change more properties, see if they propagate */ + g_object_set (clip, "start", (guint64) 420, "duration", (guint64) 510, + "in-point", (guint64) 120, NULL); + ges_timeline_commit (timeline); + assert_equals_uint64 (_START (clip), 420); + assert_equals_uint64 (_DURATION (clip), 510); + assert_equals_uint64 (_INPOINT (clip), 120); + assert_equals_uint64 (_START (trackelement), 420); + assert_equals_uint64 (_DURATION (trackelement), 510); + assert_equals_uint64 (_INPOINT (trackelement), 0); + + /* And let's also check that it propagated correctly to GNonLin */ + nle_object_check (ges_track_element_get_nleobject (trackelement), 420, 510, + 0, 510, MIN_NLE_PRIO + 0, TRUE); + + /* test changing vtype */ + GST_DEBUG ("Setting to crossfade"); + g_object_set (clip, "vtype", GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE, + NULL); + assert_equals_int (GES_TRANSITION_CLIP (clip)->vtype, + GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE); + assert_equals_int (ges_video_transition_get_transition_type + (GES_VIDEO_TRANSITION (trackelement)), + GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE); + + /* Check that changing from crossfade to anything else fails (it should + * still be using crossfade */ + GST_DEBUG ("Setting back to 1 (should fail)"); + g_object_set (clip, "vtype", 1, NULL); + + assert_equals_int (GES_TRANSITION_CLIP (clip)->vtype, 1); + assert_equals_int (ges_video_transition_get_transition_type + (GES_VIDEO_TRANSITION (trackelement)), 1); + + GST_DEBUG ("Removing clip from layer"); + gst_object_ref (clip); /* We do not want it to be destroyed */ + ges_layer_remove_clip (layer, clip); + + g_object_set (clip, "vtype", 1, NULL); + GST_DEBUG ("Read it to the layer"); + fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip))); + g_object_unref (clip); + assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1); + trackelement = GES_CONTAINER_CHILDREN (clip)->data; + fail_unless (trackelement != NULL); + + /* The new track element should have taken the previously set transition + * type (in this case 1) */ + GST_DEBUG ("Setting to vtype:1"); + assert_equals_int (ges_video_transition_get_transition_type + (GES_VIDEO_TRANSITION (trackelement)), 1); + assert_equals_int (GES_TRANSITION_CLIP (clip)->vtype, 1); + + check_destroyed (G_OBJECT (timeline), G_OBJECT (track), clip, NULL); + + ges_deinit (); +} + +GST_END_TEST; + + + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-transition"); + TCase *tc_chain = tcase_create ("transition"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_transition_basic); + tcase_add_test (tc_chain, test_transition_properties); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/uriclip.c b/tests/check/ges/uriclip.c new file mode 100644 index 0000000000..229808909e --- /dev/null +++ b/tests/check/ges/uriclip.c @@ -0,0 +1,312 @@ +/* GStreamer Editing Services + * Copyright (C) 2009 Edward Hervey <bilboed@bilboed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" +#include <ges/ges.h> +#include <gst/check/gstcheck.h> + +/* This test uri will eventually have to be fixed */ +#define TEST_URI "http://nowhere/blahblahblah" + + +static gchar *av_uri; +static gchar *image_uri; +GMainLoop *mainloop; + +typedef struct _AssetUri +{ + const gchar *uri; + GESAsset *asset; +} AssetUri; + +static void +asset_created_cb (GObject * source, GAsyncResult * res, gpointer udata) +{ + GList *tracks, *tmp; + GESAsset *asset; + GESLayer *layer; + GESUriClip *tlfs; + + GError *error = NULL; + + asset = ges_asset_request_finish (res, &error); + ASSERT_OBJECT_REFCOUNT (asset, "1 for us + for the cache + 1 taken " + "by g_task", 3); + fail_unless (error == NULL); + fail_if (asset == NULL); + fail_if (g_strcmp0 (ges_asset_get_id (asset), av_uri)); + + layer = GES_LAYER (g_async_result_get_user_data (res)); + tlfs = GES_URI_CLIP (ges_layer_add_asset (layer, + asset, 0, 0, GST_CLOCK_TIME_NONE, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_URI_CLIP (tlfs)); + fail_if (g_strcmp0 (ges_uri_clip_get_uri (tlfs), av_uri)); + assert_equals_uint64 (_DURATION (tlfs), GST_SECOND); + + fail_unless (ges_clip_get_supported_formats + (GES_CLIP (tlfs)) & GES_TRACK_TYPE_VIDEO); + fail_unless (ges_clip_get_supported_formats + (GES_CLIP (tlfs)) & GES_TRACK_TYPE_AUDIO); + + tracks = ges_timeline_get_tracks (ges_layer_get_timeline (layer)); + for (tmp = tracks; tmp; tmp = tmp->next) { + GList *trackelements = ges_track_get_elements (GES_TRACK (tmp->data)); + + assert_equals_int (g_list_length (trackelements), 1); + fail_unless (GES_IS_VIDEO_URI_SOURCE (trackelements->data) + || GES_IS_AUDIO_URI_SOURCE (trackelements->data)); + g_list_free_full (trackelements, gst_object_unref); + } + g_list_free_full (tracks, gst_object_unref); + + gst_object_unref (asset); + g_main_loop_quit (mainloop); +} + +GST_START_TEST (test_filesource_basic) +{ + GESTimeline *timeline; + GESLayer *layer; + + mainloop = g_main_loop_new (NULL, FALSE); + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + fail_unless (timeline != NULL); + + layer = ges_layer_new (); + fail_unless (layer != NULL); + fail_unless (ges_timeline_add_layer (timeline, layer)); + + ges_asset_request_async (GES_TYPE_URI_CLIP, + av_uri, NULL, asset_created_cb, layer); + + g_main_loop_run (mainloop); + g_main_loop_unref (mainloop); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +static gboolean +create_asset (AssetUri * asset_uri) +{ + asset_uri->asset = + GES_ASSET (ges_uri_clip_asset_request_sync (asset_uri->uri, NULL)); + g_main_loop_quit (mainloop); + + return FALSE; +} + +GST_START_TEST (test_filesource_properties) +{ + GESClip *clip; + GESTrack *track; + AssetUri asset_uri; + GESTimeline *timeline; + GESUriClipAsset *asset; + GESLayer *layer; + GESTrackElement *trackelement; + + ges_init (); + + track = ges_track_new (GES_TRACK_TYPE_AUDIO, gst_caps_ref (GST_CAPS_ANY)); + fail_unless (track != NULL); + + layer = ges_layer_new (); + fail_unless (layer != NULL); + timeline = ges_timeline_new (); + fail_unless (GES_IS_TIMELINE (timeline)); + fail_unless (ges_timeline_add_layer (timeline, layer)); + fail_unless (ges_timeline_add_track (timeline, track)); + ASSERT_OBJECT_REFCOUNT (timeline, "timeline", 1); + + mainloop = g_main_loop_new (NULL, FALSE); + asset_uri.uri = av_uri; + /* Right away request the asset synchronously */ + g_timeout_add (1, (GSourceFunc) create_asset, &asset_uri); + g_main_loop_run (mainloop); + + asset = GES_URI_CLIP_ASSET (asset_uri.asset); + fail_unless (GES_IS_ASSET (asset)); + clip = ges_layer_add_asset (layer, GES_ASSET (asset), + 42, 12, 51, GES_TRACK_TYPE_AUDIO); + ges_timeline_commit (timeline); + assert_is_type (clip, GES_TYPE_URI_CLIP); + assert_equals_uint64 (_START (clip), 42); + assert_equals_uint64 (_DURATION (clip), 51); + assert_equals_uint64 (_INPOINT (clip), 12); + + assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1); + trackelement = GES_CONTAINER_CHILDREN (clip)->data; + fail_unless (trackelement != NULL); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) == + GES_TIMELINE_ELEMENT (clip)); + fail_unless (ges_track_element_get_track (trackelement) == track); + + /* Check that trackelement has the same properties */ + assert_equals_uint64 (_START (trackelement), 42); + assert_equals_uint64 (_DURATION (trackelement), 51); + assert_equals_uint64 (_INPOINT (trackelement), 12); + + /* And let's also check that it propagated correctly to GNonLin */ + nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 51, 12, + 51, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE); + + /* Change more properties, see if they propagate */ + g_object_set (clip, "start", (guint64) 420, "duration", (guint64) 510, + "in-point", (guint64) 120, NULL); + ges_timeline_commit (timeline); + assert_equals_uint64 (_START (clip), 420); + assert_equals_uint64 (_DURATION (clip), 510); + assert_equals_uint64 (_INPOINT (clip), 120); + assert_equals_uint64 (_START (trackelement), 420); + assert_equals_uint64 (_DURATION (trackelement), 510); + assert_equals_uint64 (_INPOINT (trackelement), 120); + + /* And let's also check that it propagated correctly to GNonLin */ + nle_object_check (ges_track_element_get_nleobject (trackelement), 420, 510, + 120, 510, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0, TRUE); + + /* Test mute support */ + g_object_set (clip, "mute", TRUE, NULL); + ges_timeline_commit (timeline); + nle_object_check (ges_track_element_get_nleobject (trackelement), 420, 510, + 120, 510, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0, FALSE); + g_object_set (clip, "mute", FALSE, NULL); + ges_timeline_commit (timeline); + nle_object_check (ges_track_element_get_nleobject (trackelement), 420, 510, + 120, 510, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0, TRUE); + + ges_container_remove (GES_CONTAINER (clip), + GES_TIMELINE_ELEMENT (trackelement)); + + gst_object_unref (asset); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_filesource_images) +{ + GESClip *clip; + GESAsset *asset; + GESTrack *a, *v; + GESUriClip *uriclip; + AssetUri asset_uri; + GESTimeline *timeline; + GESLayer *layer; + GESTrackElement *track_element; + + ges_init (); + + a = GES_TRACK (ges_audio_track_new ()); + v = GES_TRACK (ges_video_track_new ()); + + layer = ges_layer_new (); + fail_unless (layer != NULL); + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + fail_unless (ges_timeline_add_layer (timeline, layer)); + fail_unless (ges_timeline_add_track (timeline, a)); + fail_unless (ges_timeline_add_track (timeline, v)); + ASSERT_OBJECT_REFCOUNT (timeline, "timeline", 1); + + mainloop = g_main_loop_new (NULL, FALSE); + /* Right away request the asset synchronously */ + asset_uri.uri = image_uri; + g_timeout_add (1, (GSourceFunc) create_asset, &asset_uri); + g_main_loop_run (mainloop); + + asset = asset_uri.asset; + fail_unless (GES_IS_ASSET (asset)); + fail_unless (ges_uri_clip_asset_is_image (GES_URI_CLIP_ASSET (asset))); + uriclip = GES_URI_CLIP (ges_asset_extract (asset, NULL)); + fail_unless (GES_IS_URI_CLIP (uriclip)); + fail_unless (ges_clip_get_supported_formats (GES_CLIP (uriclip)) == + GES_TRACK_TYPE_VIDEO); + clip = GES_CLIP (uriclip); + fail_unless (ges_uri_clip_is_image (uriclip)); + ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip), + 1 * GST_SECOND); + + /* the returned track element should be an image source */ + /* the clip should not create any TrackElement in the audio track */ + ges_layer_add_clip (layer, GES_CLIP (clip)); + assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1); + track_element = GES_CONTAINER_CHILDREN (clip)->data; + fail_unless (track_element != NULL); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (track_element) == + GES_TIMELINE_ELEMENT (clip)); + fail_unless (ges_track_element_get_track (track_element) == v); + fail_unless (GES_IS_VIDEO_URI_SOURCE (track_element)); + + ASSERT_OBJECT_REFCOUNT (track_element, "1 in track, 1 in clip 2 in timeline", + 3); + + gst_object_unref (asset); + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; + + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-filesource"); + TCase *tc_chain = tcase_create ("filesource"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_filesource_basic); + tcase_add_test (tc_chain, test_filesource_images); + tcase_add_test (tc_chain, test_filesource_properties); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s; + + gst_check_init (&argc, &argv); + + s = ges_suite (); + + av_uri = ges_test_get_audio_video_uri (); + image_uri = ges_test_get_image_uri (); + + nf = gst_check_run_suite (s, "ges", __FILE__); + + g_free (av_uri); + g_free (image_uri); + + return nf; +} diff --git a/tests/check/ges/wrong_test.xptv b/tests/check/ges/wrong_test.xptv new file mode 100644 index 0000000000..fa55f5b285 --- /dev/null +++ b/tests/check/ges/wrong_test.xptv @@ -0,0 +1 @@ +<Nothing/> diff --git a/tests/check/meson.build b/tests/check/meson.build new file mode 100644 index 0000000000..4b5c398558 --- /dev/null +++ b/tests/check/meson.build @@ -0,0 +1,138 @@ +# tests and condition when to skip the test +ges_tests = [ + ['ges/asset'], + ['ges/backgroundsource'], + ['ges/basic'], + ['ges/layer'], + ['ges/effects'], + ['ges/uriclip'], + ['ges/clip'], + ['ges/timelineedition'], + ['ges/titles'], + ['ges/transition'], + ['ges/overlays'], + ['ges/mixers'], + ['ges/group'], + ['ges/project'], + ['ges/track'], + ['ges/tempochange'], + ['ges/negative'], + ['ges/markerlist'], + ['nle/simple'], + ['nle/complex'], + ['nle/nleoperation'], + ['nle/nlecomposition'], + ['nle/tempochange'] +] + +test_defines = [ + '-UG_DISABLE_ASSERT', + '-UG_DISABLE_CAST_CHECKS', + '-DGES_TEST_FILES_PATH="@0@"'.format(join_paths(meson.current_source_dir(), 'assets')), + '-DGST_CHECK_TEST_ENVIRONMENT_BEACON="GST_STATE_IGNORE_ELEMENTS"', + '-DTESTFILE="' + meson.current_source_dir() + '/meson.build"', + '-DGST_USE_UNSTABLE_API', +] + +pluginsdirs = [] +if gst_dep.type_name() == 'pkgconfig' + pbase = dependency('gstreamer-plugins-base-' + apiversion, required : false) + pbad = dependency('gstreamer-plugins-bad-' + apiversion, required : false) + + pluginsdirs = [gst_dep.get_pkgconfig_variable('pluginsdir'), + pbase.get_pkgconfig_variable('pluginsdir'), + pbad.get_pkgconfig_variable('pluginsdir')] + gst_plugin_scanner_dir = gst_dep.get_pkgconfig_variable('pluginscannerdir') +else + gst_plugin_scanner_dir = subproject('gstreamer').get_variable('gst_scanner_dir') +endif +gst_plugin_scanner_path = join_paths(gst_plugin_scanner_dir, 'gst-plugin-scanner') + +foreach t : ges_tests + fname = '@0@.c'.format(t.get(0)) + test_name = t.get(0).underscorify() + if t.length() == 2 + skip_test = t.get(1) + else + skip_test = false + endif + + if not skip_test + env = environment() + env.set('GST_PLUGIN_SYSTEM_PATH_1_0', '') + env.set('GST_STATE_IGNORE_ELEMENTS', '') + env.set('CK_DEFAULT_TIMEOUT', '20') + env.set('GST_REGISTRY', '@0@/@1@.registry'.format(meson.current_build_dir(), test_name)) + env.set('GST_PLUGIN_PATH_1_0', [meson.build_root()] + pluginsdirs) + + exe = executable(test_name, fname, + 'ges/test-utils.c', 'nle/common.c', + c_args : ges_c_args + test_defines, + include_directories : [configinc], + dependencies : libges_deps + [gstcheck_dep, ges_dep], + ) + test(test_name, exe, env: env, timeout : 3 * 60) + endif +endforeach + +if gstvalidate_dep.found() + # filename: is .validatetest + scenarios = { + 'check_video_track_restriction_scale': false, + 'check_video_track_restriction_scale_with_keyframes': false, + 'check_edit_in_frames': false, + 'check_edit_in_frames_with_framerate_mismatch': false, + 'check_layer_activness_gaps': false, + 'seek_with_stop': true, + 'seek_with_stop.check_clock_sync': true, + 'edit_while_seeked_with_stop': true, + 'complex_effect_bin_desc': true, + 'check_keyframes_in_compositor_two_sources': true, + 'check-clip-positioning': true, + 'set-layer-on-command-line': true, + } + + foreach scenario, is_validatetest: scenarios + + env = environment() + env.set('GST_PLUGIN_SYSTEM_PATH_1_0', '') + env.set('GST_STATE_IGNORE_ELEMENTS', '') + env.set('CK_DEFAULT_TIMEOUT', '20') + env.set('GST_REGISTRY', '@0@/@1@.registry'.format(meson.current_build_dir(), 'scenarios')) + env.set('GST_PLUGIN_PATH_1_0', [meson.build_root()] + pluginsdirs) + env.set('GST_VALIDATE_LOGSDIR', meson.current_build_dir() / scenario) + env.set('GST_PLUGIN_SCANNER_1_0', gst_plugin_scanner_path) + + if is_validatetest + testfile = meson.current_source_dir() / 'scenarios' / scenario + '.validatetest' + test(scenario, ges_launch, env: env, args: ['--no-interactive', '--set-test-file', testfile, '--mute']) + else + scenario_file = meson.current_source_dir() / 'scenarios' / scenario + '.scenario' + test(scenario, ges_launch, env: env, args: ['--no-interactive', '--set-scenario', scenario_file]) + endif + + endforeach + test('simple_playback_test', ges_launch, env: env, args: ['+test-clip', 'blue', 'd=0.1', '--videosink=fakevideosink', '--audiosink=fakeaudiosink']) +endif + +if build_gir + # Make sure to use the subproject gst-validate-launcher if available. + if gstvalidate_dep.found() and gstvalidate_dep.type_name() == 'internal' + runtests = subproject('gst-devtools').get_variable('launcher') + else + runtests = find_program('gst-validate-launcher', required : false) + endif + + if runtests.found() + env = environment() + env.set('GST_PLUGIN_SYSTEM_PATH_1_0', '') + env.set('GST_STATE_IGNORE_ELEMENTS', '') + env.set('CK_DEFAULT_TIMEOUT', '20') + env.set('GST_REGISTRY', '@0@/@1@.registry'.format(meson.current_build_dir(), 'scenarios')) + env.set('GST_PLUGIN_PATH_1_0', [meson.build_root()] + pluginsdirs) + env.set('GI_TYPELIB_PATH', meson.current_build_dir() / '..' / '..' / 'ges') + + test('pythontests', runtests, args: ['--pyunittest-dir', meson.current_source_dir(), 'pyunittest', '--dump-on-failure'], + env: env) + endif +endif diff --git a/tests/check/nle/common.c b/tests/check/nle/common.c new file mode 100644 index 0000000000..ba05132a53 --- /dev/null +++ b/tests/check/nle/common.c @@ -0,0 +1,417 @@ +#include "common.h" + +void +poll_the_bus (GstBus * bus) +{ + GstMessage *message; + gboolean carry_on = TRUE; + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_DEBUG ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } +} + +static gboolean +nle_object_commit (GstElement * nlesource, gboolean recurse) +{ + gboolean ret; + + g_signal_emit_by_name (nlesource, "commit", recurse, &ret); + + return ret; +} + +GstElement * +gst_element_factory_make_or_warn (const gchar * factoryname, const gchar * name) +{ + GstElement *element; + + element = gst_element_factory_make (factoryname, name); + fail_unless (element != NULL, "Failed to make element %s", factoryname); + return element; +} + +void +composition_pad_added_cb (GstElement * composition, GstPad * pad, + CollectStructure * collect) +{ + fail_if (!(gst_element_link_pads_full (composition, GST_OBJECT_NAME (pad), + collect->sink, "sink", GST_PAD_LINK_CHECK_NOTHING))); +} + +/* return TRUE to discard the Segment */ +static gboolean +compare_segments (CollectStructure * collect, Segment * segment, + GstEvent * event) +{ + const GstSegment *received_segment; + guint64 running_stop, running_start, running_duration; + + gst_event_parse_segment (event, &received_segment); + + GST_DEBUG ("Got Segment rate:%f, format:%s, start:%" GST_TIME_FORMAT + ", stop:%" GST_TIME_FORMAT ", time:%" GST_TIME_FORMAT + ", base:%" GST_TIME_FORMAT ", offset:%" GST_TIME_FORMAT, + received_segment->rate, gst_format_get_name (received_segment->format), + GST_TIME_ARGS (received_segment->start), + GST_TIME_ARGS (received_segment->stop), + GST_TIME_ARGS (received_segment->time), + GST_TIME_ARGS (received_segment->base), + GST_TIME_ARGS (received_segment->offset)); + GST_DEBUG ("[RUNNING] start:%" GST_TIME_FORMAT " [STREAM] start:%" + GST_TIME_FORMAT, + GST_TIME_ARGS (gst_segment_to_running_time (received_segment, + GST_FORMAT_TIME, received_segment->start)), + GST_TIME_ARGS (gst_segment_to_stream_time (received_segment, + GST_FORMAT_TIME, received_segment->start))); + + GST_DEBUG ("Expecting rate:%f, format:%s, start:%" GST_TIME_FORMAT + ", stop:%" GST_TIME_FORMAT ", position:%" GST_TIME_FORMAT ", base:%" + GST_TIME_FORMAT, segment->rate, gst_format_get_name (segment->format), + GST_TIME_ARGS (segment->start), GST_TIME_ARGS (segment->stop), + GST_TIME_ARGS (segment->position), + GST_TIME_ARGS (collect->expected_base)); + + running_start = + gst_segment_to_running_time (received_segment, GST_FORMAT_TIME, + received_segment->start); + running_stop = + gst_segment_to_running_time (received_segment, GST_FORMAT_TIME, + received_segment->stop); + running_duration = running_stop - running_start; + fail_if (received_segment->rate != segment->rate); + fail_if (received_segment->format != segment->format); + fail_unless_equals_int64 (received_segment->time, segment->position); + fail_unless_equals_int64 (received_segment->base, collect->expected_base); + fail_unless_equals_uint64 (received_segment->stop - received_segment->start, + segment->stop - segment->start); + + collect->expected_base += running_duration; + + GST_DEBUG ("Segment was valid, discarding expected Segment"); + + return TRUE; +} + +static GstPadProbeReturn +sinkpad_event_probe (GstPad * sinkpad, GstEvent * event, + CollectStructure * collect) +{ + Segment *segment; + + GST_DEBUG_OBJECT (sinkpad, "event:%p (%s seqnum:%d) , collect:%p", event, + GST_EVENT_TYPE_NAME (event), GST_EVENT_SEQNUM (event), collect); + + if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) { + fail_if (collect->expected_segments == NULL, + "Received unexpected segment on pad: %s:%s", + GST_DEBUG_PAD_NAME (sinkpad)); + + if (!collect->gotsegment) + collect->seen_segments = + g_list_append (NULL, GINT_TO_POINTER (GST_EVENT_SEQNUM (event))); + else { + fail_if (g_list_find (collect->seen_segments, + GINT_TO_POINTER (GST_EVENT_SEQNUM (event))), + "Got a segment event we already saw before !"); + collect->seen_segments = + g_list_append (collect->seen_segments, + GINT_TO_POINTER (GST_EVENT_SEQNUM (event))); + } + + segment = (Segment *) collect->expected_segments->data; + + if (compare_segments (collect, segment, event) && + collect->keep_expected_segments == FALSE) { + collect->expected_segments = + g_list_remove (collect->expected_segments, segment); + g_free (segment); + } + + collect->gotsegment = TRUE; + } + + return GST_PAD_PROBE_OK; +} + +static GstPadProbeReturn +sinkpad_buffer_probe (GstPad * sinkpad, GstBuffer * buffer, + CollectStructure * collect) +{ + GST_LOG_OBJECT (sinkpad, "buffer:%p (%" GST_TIME_FORMAT ") , collect:%p", + buffer, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), collect); + fail_if (!collect->gotsegment, + "Received a buffer without a preceding segment"); + return GST_PAD_PROBE_OK; +} + +GstPadProbeReturn +sinkpad_probe (GstPad * sinkpad, GstPadProbeInfo * info, + CollectStructure * collect) +{ + if (info->type & GST_PAD_PROBE_TYPE_BUFFER) + return sinkpad_buffer_probe (sinkpad, (GstBuffer *) info->data, collect); + if (info->type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM) + return sinkpad_event_probe (sinkpad, (GstEvent *) info->data, collect); + return GST_PAD_PROBE_OK; +} + +static GstElement * +new_nle_src (const gchar * name, guint64 start, gint64 duration, gint priority) +{ + GstElement *nlesource = NULL; + + nlesource = gst_element_factory_make_or_warn ("nlesource", name); + fail_if (nlesource == NULL); + + g_object_set (G_OBJECT (nlesource), + "start", start, + "duration", duration, "inpoint", start, "priority", priority, NULL); + nle_object_commit (nlesource, FALSE); + + return nlesource; +} + +GstElement * +videotest_nle_src (const gchar * name, guint64 start, gint64 duration, + gint pattern, guint priority) +{ + GstElement *nlesource = NULL; + GstElement *videotestsrc = NULL; + GstCaps *caps = + gst_caps_from_string + ("video/x-raw,format=(string)I420,framerate=(fraction)3/2"); + + fail_if (caps == NULL); + + videotestsrc = gst_element_factory_make_or_warn ("videotestsrc", NULL); + g_object_set (G_OBJECT (videotestsrc), "pattern", pattern, NULL); + + nlesource = new_nle_src (name, start, duration, priority); + g_object_set (G_OBJECT (nlesource), "caps", caps, NULL); + gst_caps_unref (caps); + + gst_bin_add (GST_BIN (nlesource), videotestsrc); + + return nlesource; +} + +GstElement * +videotest_nle_src_full (const gchar * name, guint64 start, gint64 duration, + guint64 inpoint, gint pattern, guint priority) +{ + GstElement *nles; + + nles = videotest_nle_src (name, start, duration, pattern, priority); + if (nles) { + g_object_set (G_OBJECT (nles), "inpoint", inpoint, NULL); + } + + + return nles; +} + +GstElement * +videotest_in_bin_nle_src (const gchar * name, guint64 start, gint64 duration, + gint pattern, guint priority) +{ + GstElement *nlesource = NULL; + GstElement *videotestsrc = NULL; + GstElement *bin = NULL; + GstElement *alpha = NULL; + GstPad *srcpad = NULL; + + alpha = gst_element_factory_make ("alpha", NULL); + if (alpha == NULL) + return NULL; + + videotestsrc = gst_element_factory_make_or_warn ("videotestsrc", NULL); + g_object_set (G_OBJECT (videotestsrc), "pattern", pattern, NULL); + bin = gst_bin_new (NULL); + + nlesource = new_nle_src (name, start, duration, priority); + + gst_bin_add (GST_BIN (bin), videotestsrc); + gst_bin_add (GST_BIN (bin), alpha); + + gst_element_link_pads_full (videotestsrc, "src", alpha, "sink", + GST_PAD_LINK_CHECK_NOTHING); + + gst_bin_add (GST_BIN (nlesource), bin); + + srcpad = gst_element_get_static_pad (alpha, "src"); + + gst_element_add_pad (bin, gst_ghost_pad_new ("src", srcpad)); + + gst_object_unref (srcpad); + + return nlesource; +} + +GstElement * +audiotest_bin_src (const gchar * name, guint64 start, + gint64 duration, guint priority, gboolean intaudio) +{ + GstElement *source = NULL; + GstElement *identity = NULL; + GstElement *audiotestsrc = NULL; + GstElement *audioconvert = NULL; + GstElement *bin = NULL; + GstCaps *caps; + GstPad *srcpad = NULL; + + audiotestsrc = gst_element_factory_make_or_warn ("audiotestsrc", NULL); + identity = gst_element_factory_make_or_warn ("identity", NULL); + bin = gst_bin_new (NULL); + source = new_nle_src (name, start, duration, priority); + audioconvert = gst_element_factory_make_or_warn ("audioconvert", NULL); + + if (intaudio) + caps = gst_caps_from_string ("audio/x-raw,format=(string)S16LE"); + else + caps = gst_caps_from_string ("audio/x-raw,format=(string)F32LE"); + + gst_bin_add_many (GST_BIN (bin), audiotestsrc, audioconvert, identity, NULL); + gst_element_link_pads_full (audiotestsrc, "src", audioconvert, "sink", + GST_PAD_LINK_CHECK_NOTHING); + fail_if ((gst_element_link_filtered (audioconvert, identity, caps)) != TRUE); + + gst_caps_unref (caps); + + gst_bin_add (GST_BIN (source), bin); + + srcpad = gst_element_get_static_pad (identity, "src"); + + gst_element_add_pad (bin, gst_ghost_pad_new ("src", srcpad)); + + gst_object_unref (srcpad); + + return source; +} + +GstElement * +new_operation (const gchar * name, const gchar * factory, guint64 start, + gint64 duration, guint priority) +{ + GstElement *nleoperation = NULL; + GstElement *operation = NULL; + + operation = gst_element_factory_make_or_warn (factory, NULL); + nleoperation = gst_element_factory_make_or_warn ("nleoperation", name); + + g_object_set (G_OBJECT (nleoperation), + "start", start, "duration", duration, "priority", priority, NULL); + + gst_bin_add (GST_BIN (nleoperation), operation); + + return nleoperation; +} + + +Segment * +segment_new (gdouble rate, GstFormat format, gint64 start, gint64 stop, + gint64 position) +{ + Segment *segment; + + segment = g_new0 (Segment, 1); + + segment->rate = rate; + segment->format = format; + segment->start = start; + segment->stop = stop; + segment->position = position; + + return segment; +} + +GList * +copy_segment_list (GList * list) +{ + GList *res = NULL; + + while (list) { + Segment *pdata = (Segment *) list->data; + + res = + g_list_append (res, segment_new (pdata->rate, pdata->format, + pdata->start, pdata->stop, pdata->position)); + + list = list->next; + } + + return res; +} + +static GMutex lock; +static GCond cond; +static void +commited_cb (GstElement * comp, gboolean changed) +{ + g_mutex_lock (&lock); + g_cond_signal (&cond); + g_mutex_unlock (&lock); +} + +void +commit_and_wait (GstElement * comp, gboolean * ret) +{ + gulong handler_id = + g_signal_connect (comp, "commited", (GCallback) commited_cb, NULL); + g_mutex_lock (&lock); + *ret = nle_object_commit (comp, TRUE); + g_cond_wait (&cond, &lock); + g_mutex_unlock (&lock); + g_signal_handler_disconnect (comp, handler_id); +} + +gboolean +nle_composition_remove (GstBin * comp, GstElement * object) +{ + gboolean ret; + + ret = gst_bin_remove (comp, object); + if (!ret) + return ret; + + commit_and_wait ((GstElement *) comp, &ret); + + return ret; +} + +gboolean +nle_composition_add (GstBin * comp, GstElement * object) +{ + return gst_bin_add (comp, object); +} + +void +collect_free (CollectStructure * collect) +{ + if (collect->seen_segments) + g_list_free (collect->seen_segments); + if (collect->expected_segments) + g_list_free_full (collect->expected_segments, (GDestroyNotify) g_free); + + g_free (collect); +} diff --git a/tests/check/nle/common.h b/tests/check/nle/common.h new file mode 100644 index 0000000000..03157ef5b5 --- /dev/null +++ b/tests/check/nle/common.h @@ -0,0 +1,80 @@ + +#include <gst/check/gstcheck.h> +#include <ges/ges.h> + +#define fail_error_message(msg) \ + G_STMT_START { \ + GError *error; \ + gst_message_parse_error(msg, &error, NULL); \ + fail_unless(FALSE, "Error Message from %s : %s", \ + GST_OBJECT_NAME (GST_MESSAGE_SRC(msg)), error->message); \ + g_error_free (error); \ + } G_STMT_END; + +#define check_start_stop_duration(object, startval, stopval, durval) \ + G_STMT_START { guint64 start, stop; \ + gint64 duration; \ + GST_DEBUG_OBJECT (object, "Checking for valid start/stop/duration values"); \ + g_object_get (object, "start", &start, "stop", &stop, \ + "duration", &duration, NULL); \ + fail_unless_equals_uint64(start, startval); \ + fail_unless_equals_uint64(stop, stopval); \ + fail_unless_equals_int64(duration, durval); \ + GST_DEBUG_OBJECT (object, "start/stop/duration values valid"); \ + } G_STMT_END; + +#define check_state_simple(object, expected_state) \ + G_STMT_START { \ + GstStateChangeReturn ret; \ + GstState state, pending; \ + ret = gst_element_get_state(GST_ELEMENT_CAST(object), &state, &pending, 5 * GST_SECOND); \ + fail_if (ret == GST_STATE_CHANGE_FAILURE); \ + fail_unless (state == expected_state, "Element state (%s) is not the expected one (%s)", \ + gst_element_state_get_name(state), gst_element_state_get_name(expected_state)); \ + } G_STMT_END; + +typedef struct _Segment { + gdouble rate; + GstFormat format; + guint64 start, stop, position; +} Segment; + +typedef struct _CollectStructure { + GstElement *comp; + GstElement *sink; + guint64 last_time; + gboolean gotsegment; + GList *seen_segments; + GList *expected_segments; + guint64 expected_base; + + gboolean keep_expected_segments; +} CollectStructure; + +void poll_the_bus(GstBus *bus); +void composition_pad_added_cb (GstElement *composition, GstPad *pad, CollectStructure * collect); +GstPadProbeReturn sinkpad_probe (GstPad *sinkpad, GstPadProbeInfo * info, CollectStructure * collect); +GstElement *videotest_nle_src (const gchar * name, guint64 start, gint64 duration, + gint pattern, guint priority); +GstElement * videotest_nle_src_full (const gchar * name, guint64 start, gint64 duration, + guint64 inpoint, + gint pattern, guint priority); +GstElement * +videotest_in_bin_nle_src (const gchar * name, guint64 start, gint64 duration, gint pattern, guint priority); +GstElement * +audiotest_bin_src (const gchar * name, guint64 start, + gint64 duration, guint priority, gboolean intaudio); +GstElement * +new_operation (const gchar * name, const gchar * factory, guint64 start, gint64 duration, guint priority); +GList * +copy_segment_list (GList *list); +GstElement * +gst_element_factory_make_or_warn (const gchar * factoryname, const gchar * name); +Segment * +segment_new (gdouble rate, GstFormat format, gint64 start, gint64 stop, gint64 position); + +void commit_and_wait (GstElement *comp, gboolean *ret); +gboolean nle_composition_remove (GstBin * comp, GstElement * object); +gboolean nle_composition_add (GstBin * comp, GstElement * object); + +void collect_free (CollectStructure *collect); diff --git a/tests/check/nle/complex.c b/tests/check/nle/complex.c new file mode 100644 index 0000000000..b7358fbc64 --- /dev/null +++ b/tests/check/nle/complex.c @@ -0,0 +1,872 @@ +#include "common.h" + +static void +fill_pipeline_and_check (GstElement * comp, GList * segments, + gint expected_error_domain) +{ + GstElement *pipeline, *sink; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + GList *listcopy = copy_segment_list (segments); + + pipeline = gst_pipeline_new ("test_pipeline"); + sink = gst_element_factory_make_or_warn ("fakevideosink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = segments; + + gst_element_link (comp, sink); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + { + GError *error = NULL; + + gst_message_parse_error (message, &error, NULL); + if (comp == GST_ELEMENT (GST_MESSAGE_SRC (message)) && + expected_error_domain == error->domain) { + GST_DEBUG ("Expected Error Message from %s : %s", + GST_OBJECT_NAME (GST_MESSAGE_SRC (message)), error->message); + carry_on = FALSE; + } else { + fail_unless (FALSE, "Error Message from %s : %s", + GST_OBJECT_NAME (GST_MESSAGE_SRC (message)), error->message); + } + g_clear_error (&error); + } + break; + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to READY"); + + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + + + fail_if (collect->expected_segments != NULL); + + GST_DEBUG ("Resetted pipeline to READY"); + + if (collect->seen_segments) + g_list_free (collect->seen_segments); + + collect->seen_segments = NULL; + collect->expected_segments = listcopy; + collect->gotsegment = FALSE; + collect->expected_base = 0; + + if (expected_error_domain) + goto done; + + GST_DEBUG ("Setting pipeline to PLAYING again"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + carry_on = TRUE; + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } else { + GST_DEBUG ("bus_poll responded, but there wasn't any message..."); + } + } + + fail_if (collect->expected_segments != NULL); + +done: + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + gst_object_unref (GST_OBJECT (sinkpad)); + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + collect_free (collect); +} + +GST_START_TEST (test_one_space_another) +{ + GstElement *comp, *source1, *source2; + gboolean ret = FALSE; + GList *segments = NULL; + + ges_init (); + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + fail_if (comp == NULL); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 | Priority + * ---------------------------------------------------------------------------- + * [-source1--] [-source2--] | 1 + * */ + + /* + Source 1 + Start : 0s + Duration : 1s + Priority : 1 + */ + source1 = videotest_nle_src ("source1", 0, 1 * GST_SECOND, 2, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* + Source 2 + Start : 2s + Duration : 1s + Priority : 1 + */ + source2 = videotest_nle_src ("source2", 2 * GST_SECOND, 1 * GST_SECOND, 3, 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 2 * GST_SECOND, 3 * GST_SECOND, + 1 * GST_SECOND); + + /* Add one source */ + + nle_composition_add (GST_BIN (comp), source1); + GST_ERROR ("doing one commit"); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Second source */ + + nle_composition_add (GST_BIN (comp), source2); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + commit_and_wait (comp, &ret); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Remove first source */ + + gst_object_ref (source1); + nle_composition_remove (GST_BIN (comp), source1); + check_start_stop_duration (comp, 2 * GST_SECOND, 3 * GST_SECOND, + 1 * GST_SECOND); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Re-add first source */ + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + gst_object_unref (source1); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + + fill_pipeline_and_check (comp, segments, GST_STREAM_ERROR); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_one_default_another) +{ + gboolean ret = FALSE; + GstElement *comp, *source1, *source2, *source3, *defaultsrc; + GList *segments = NULL; + + ges_init (); + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + fail_if (comp == NULL); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 | Priority + * ---------------------------------------------------------------------------- + * [-source1--] [-source2--][-source3-] | 1 + * [--------------------------defaultsource------------------] | MAXUINT32 + * */ + + + /* + defaultsrc source + Start : 0s + Duration : 5s + Priority : 2 + */ + + defaultsrc = + videotest_nle_src ("defaultsrc", 0, 5 * GST_SECOND, 2, G_MAXUINT32); + g_object_set (defaultsrc, "expandable", TRUE, NULL); + fail_if (defaultsrc == NULL); + check_start_stop_duration (defaultsrc, 0, 5 * GST_SECOND, 5 * GST_SECOND); + + /* + Source 1 + Start : 1s + Duration : 1s + Priority : 1 + */ + source1 = videotest_nle_src ("source1", 1 * GST_SECOND, 1 * GST_SECOND, 3, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + /* + Source 2 + Start : 3s + Duration : 1s + Priority : 1 + */ + source2 = videotest_nle_src ("source2", 3 * GST_SECOND, 1 * GST_SECOND, 2, 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 3 * GST_SECOND, 4 * GST_SECOND, + 1 * GST_SECOND); + + /* + Source 3 + Start : 4s + Duration : 1s + Priority : 1 + */ + source3 = videotest_nle_src ("source3", 4 * GST_SECOND, 1 * GST_SECOND, 2, 1); + fail_if (source3 == NULL); + check_start_stop_duration (source3, 4 * GST_SECOND, 5 * GST_SECOND, + 1 * GST_SECOND); + + + /* Add one source */ + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* defaultsrc source */ + nle_composition_add (GST_BIN (comp), defaultsrc); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + check_start_stop_duration (defaultsrc, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (defaultsrc, "defaultsrc", 1); + + /* Second source */ + + nle_composition_add (GST_BIN (comp), source2); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + /* Third source */ + nle_composition_add (GST_BIN (comp), source3); + commit_and_wait (comp, &ret); + fail_unless (ret); + check_start_stop_duration (comp, 0, 5 * GST_SECOND, 5 * GST_SECOND); + check_start_stop_duration (defaultsrc, 0, 5 * GST_SECOND, 5 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source3, "source3", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 3 * GST_SECOND, 4 * GST_SECOND, 3 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 4 * GST_SECOND, 5 * GST_SECOND, 4 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments, GST_STREAM_ERROR); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_one_expandable_another) +{ + GstElement *comp, *source1, *source2, *source3, *defaultsrc; + GList *segments = NULL; + gboolean ret = FALSE; + + ges_init (); + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + fail_if (comp == NULL); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 | Priority + * ---------------------------------------------------------------------------- + * [ source1 ] [ source2 ][ source3 ] | 1 + * [--------------------- defaultsrc ------------------------] | 1000 EXPANDABLE + * */ + + /* + defaultsrc source + Start : 0s + Duration : 5s + Priority : 1000 + */ + + defaultsrc = videotest_nle_src ("defaultsrc", 0, 5 * GST_SECOND, 2, 1000); + g_object_set (defaultsrc, "expandable", TRUE, NULL); + fail_if (defaultsrc == NULL); + check_start_stop_duration (defaultsrc, 0, 5 * GST_SECOND, 5 * GST_SECOND); + + /* + Source 1 + Start : 1s + Duration : 1s + Priority : 1 + */ + source1 = videotest_nle_src ("source1", 1 * GST_SECOND, 1 * GST_SECOND, 3, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + /* + Source 2 + Start : 3s + Duration : 1s + Priority : 1 + */ + source2 = videotest_nle_src ("source2", 3 * GST_SECOND, 1 * GST_SECOND, 2, 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 3 * GST_SECOND, 4 * GST_SECOND, + 1 * GST_SECOND); + + /* + Source 3 + Start : 4s + Duration : 1s + Priority : 1 + */ + source3 = videotest_nle_src ("source3", 4 * GST_SECOND, 1 * GST_SECOND, 2, 1); + fail_if (source3 == NULL); + check_start_stop_duration (source3, 4 * GST_SECOND, 5 * GST_SECOND, + 1 * GST_SECOND); + + + /* Add one source */ + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* defaultsrc source */ + + nle_composition_add (GST_BIN (comp), defaultsrc); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + check_start_stop_duration (defaultsrc, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (defaultsrc, "defaultsrc", 1); + + /* Second source */ + + nle_composition_add (GST_BIN (comp), source2); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 4 * GST_SECOND, 4 * GST_SECOND); + check_start_stop_duration (defaultsrc, 0, 4 * GST_SECOND, 4 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + + /* Third source */ + + nle_composition_add (GST_BIN (comp), source3); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 5 * GST_SECOND, 5 * GST_SECOND); + check_start_stop_duration (defaultsrc, 0, 5 * GST_SECOND, 5 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source3, "source3", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 3 * GST_SECOND, 4 * GST_SECOND, 3 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 4 * GST_SECOND, 5 * GST_SECOND, 4 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments, 0); + + ges_deinit (); +} + +GST_END_TEST; + + + +GST_START_TEST (test_renegotiation) +{ + gboolean ret; + GstElement *pipeline; + GstElement *comp, *sink, *source1, *source2, *source3; + GstElement *audioconvert; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + GstCaps *caps; + + ges_init (); + + pipeline = gst_pipeline_new ("test_pipeline"); + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + fail_if (comp == NULL); + + /* + Source 1 + Start : 1s + Duration : 1s + Priority : 1 + */ + source1 = + audiotest_bin_src ("source1", 0 * GST_SECOND, 1 * GST_SECOND, 1, FALSE); + check_start_stop_duration (source1, 0 * GST_SECOND, 1 * GST_SECOND, + 1 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 1s + Priority : 1 + */ + source2 = + audiotest_bin_src ("source2", 1 * GST_SECOND, 1 * GST_SECOND, 1, TRUE); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + /* + Source 3 + Start : 2s + Duration : 1s + Priority : 1 + */ + source3 = + audiotest_bin_src ("source3", 2 * GST_SECOND, 1 * GST_SECOND, 1, FALSE); + check_start_stop_duration (source3, 2 * GST_SECOND, 3 * GST_SECOND, + 1 * GST_SECOND); + + /* Add one source */ + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Second source */ + + nle_composition_add (GST_BIN (comp), source2); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + + /* Third source */ + + nle_composition_add (GST_BIN (comp), source3); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source3, "source3", 1); + + + sink = gst_element_factory_make_or_warn ("fakeaudiosink", "sink"); + audioconvert = gst_element_factory_make_or_warn ("audioconvert", "aconv"); + + gst_bin_add_many (GST_BIN (pipeline), comp, audioconvert, sink, NULL); + caps = gst_caps_from_string ("audio/x-raw,format=(string)S16LE"); + fail_unless (gst_element_link_filtered (audioconvert, sink, caps)); + gst_caps_unref (caps); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = audioconvert; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND)); + + gst_element_link (comp, audioconvert); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to NULL"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + GST_DEBUG ("Resetted pipeline to READY"); + + if (collect->seen_segments) + g_list_free (collect->seen_segments); + collect->seen_segments = NULL; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND)); + collect->gotsegment = FALSE; + collect->expected_base = 0; + + + GST_DEBUG ("Setting pipeline to PLAYING again"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + carry_on = TRUE; + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } else { + GST_DEBUG ("bus_poll responded, but there wasn't any message..."); + } + } + + fail_if (collect->expected_segments != NULL); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + gst_object_unref (GST_OBJECT (sinkpad)); + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + collect_free (collect); + + ges_deinit (); +} + +GST_END_TEST; + + +GST_START_TEST (test_one_bin_space_another) +{ + GstElement *comp, *source1, *source2; + gboolean ret = FALSE; + GList *segments = NULL; + + ges_init (); + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + fail_if (comp == NULL); + + /* + Source 1 + Start : 0s + Duration : 1s + Priority : 1 + */ + source1 = videotest_in_bin_nle_src ("source1", 0, 1 * GST_SECOND, 3, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* + Source 2 + Start : 2s + Duration : 1s + Priority : 1 + */ + source2 = + videotest_in_bin_nle_src ("source2", 2 * GST_SECOND, 1 * GST_SECOND, 2, + 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 2 * GST_SECOND, 3 * GST_SECOND, + 1 * GST_SECOND); + + /* Add one source */ + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* Second source */ + + nle_composition_add (GST_BIN (comp), source2); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + + /* Remove second source */ + + gst_object_ref (source1); + nle_composition_remove (GST_BIN (comp), source1); + check_start_stop_duration (comp, 2 * GST_SECOND, 3 * GST_SECOND, + 1 * GST_SECOND); + + /* Re-add second source */ + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + gst_object_unref (source1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + + fill_pipeline_and_check (comp, segments, GST_STREAM_ERROR); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_one_above_another) +{ + GstElement *comp, *source1, *source2; + gboolean ret = FALSE; + GList *segments = NULL; + + ges_init (); + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + fail_if (comp == NULL); + + /* + Source 1 + Start : 0s + Duration : 2s + Priority : 2 + */ + source1 = videotest_nle_src ("source1", 0, 2 * GST_SECOND, 3, 2); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + /* + Source 2 + Start : 2s + Duration : 2s + Priority : 1 + */ + source2 = videotest_nle_src ("source2", 1 * GST_SECOND, 2 * GST_SECOND, 2, 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 1 * GST_SECOND, 3 * GST_SECOND, + 2 * GST_SECOND); + + /* Add one source */ + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + /* Second source */ + + nle_composition_add (GST_BIN (comp), source2); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + + /* Remove second source */ + + gst_object_ref (source1); + nle_composition_remove (GST_BIN (comp), source1); + check_start_stop_duration (comp, 1 * GST_SECOND, 3 * GST_SECOND, + 2 * GST_SECOND); + + /* Re-add second source */ + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + gst_object_unref (source1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 3 * GST_SECOND, 1 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments, 0); + + ges_deinit (); +} + +GST_END_TEST; + +static Suite * +gnonlin_suite (void) +{ + Suite *s = suite_create ("gnonlin-complex"); + TCase *tc_chain = tcase_create ("complex"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_one_space_another); + tcase_add_test (tc_chain, test_one_default_another); + tcase_add_test (tc_chain, test_one_expandable_another); + tcase_add_test (tc_chain, test_renegotiation); + tcase_add_test (tc_chain, test_one_bin_space_another); + tcase_add_test (tc_chain, test_one_above_another); + return s; +} + +GST_CHECK_MAIN (gnonlin) diff --git a/tests/check/nle/nlecomposition.c b/tests/check/nle/nlecomposition.c new file mode 100644 index 0000000000..3607ddb3dc --- /dev/null +++ b/tests/check/nle/nlecomposition.c @@ -0,0 +1,778 @@ +/* Gnonlin + * Copyright (C) <2009> Alessandro Decina <alessandro.decina@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#include "common.h" + +typedef struct +{ + GstElement *composition; + GstElement *source3; +} TestClosure; + +static int seek_events; + +static GstPadProbeReturn +on_source1_pad_event_cb (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + if (GST_EVENT_TYPE (info->data) == GST_EVENT_SEEK) + ++seek_events; + + return GST_PAD_PROBE_OK; +} + +GST_START_TEST (test_change_object_start_stop_in_current_stack) +{ + GstPad *srcpad; + GstElement *pipeline; + GstElement *comp, *source1, *def, *sink; + GstBus *bus; + GstMessage *message; + gboolean carry_on, ret = FALSE; + + ges_init (); + + pipeline = gst_pipeline_new ("test_pipeline"); + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + + gst_element_set_state (comp, GST_STATE_READY); + + sink = gst_element_factory_make_or_warn ("fakevideosink", "sink"); + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + gst_element_link (comp, sink); + + /* + source1 + Start : 0s + Duration : 2s + Priority : 2 + */ + + source1 = videotest_nle_src ("source1", 0, 2 * GST_SECOND, 2, 2); + srcpad = gst_element_get_static_pad (source1, "src"); + gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, + (GstPadProbeCallback) on_source1_pad_event_cb, NULL, NULL); + gst_object_unref (srcpad); + + /* + def (default source) + Priority = G_MAXUINT32 + */ + def = + videotest_nle_src ("default", 0 * GST_SECOND, 0 * GST_SECOND, 2, + G_MAXUINT32); + g_object_set (def, "expandable", TRUE, NULL); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (def, "default", 1); + + /* Add source 1 */ + + nle_composition_add (GST_BIN (comp), source1); + nle_composition_add (GST_BIN (comp), def); + commit_and_wait (comp, &ret); + check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PAUSED"); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + carry_on = TRUE; + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ASYNC_DONE: + { + carry_on = FALSE; + GST_DEBUG ("Pipeline reached PAUSED, stopping polling"); + break; + } + case GST_MESSAGE_EOS: + { + GST_WARNING ("Saw EOS"); + + fail_if (TRUE); + } + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + + /* pipeline is paused at this point */ + + /* move source1 out of the active segment */ + g_object_set (source1, "start", (guint64) 4 * GST_SECOND, NULL); + commit_and_wait (comp, &ret); + + /* remove source1 from the composition, which will become empty and remove the + * ghostpad */ + + + /* keep an extra ref to source1 as we remove it from the bin */ + gst_object_ref (source1); + fail_unless (nle_composition_remove (GST_BIN (comp), source1)); + g_object_set (source1, "start", (guint64) 0 * GST_SECOND, NULL); + /* add the source again and check that the ghostpad is added again */ + nle_composition_add (GST_BIN (comp), source1); + gst_object_unref (source1); + commit_and_wait (comp, &ret); + + + g_object_set (source1, "duration", (guint64) 1 * GST_SECOND, NULL); + commit_and_wait (comp, &ret); + + GST_DEBUG ("Setting pipeline to NULL"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + gst_element_set_state (source1, GST_STATE_NULL); + + GST_DEBUG ("Resetted pipeline to NULL"); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_check_objects_destroyed_on_unref (pipeline, comp, def, NULL); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_remove_invalid_object) +{ + GstBin *composition; + GstElement *source1, *source2; + + ges_init (); + + composition = GST_BIN (gst_element_factory_make ("nlecomposition", + "composition")); + gst_element_set_state (GST_ELEMENT (composition), GST_STATE_READY); + + source1 = gst_element_factory_make ("nlesource", "source1"); + source2 = gst_element_factory_make ("nlesource", "source2"); + + nle_composition_add (composition, source1); + nle_composition_remove (composition, source2); + fail_unless (nle_composition_remove (composition, source1)); + + gst_element_set_state (GST_ELEMENT (composition), GST_STATE_NULL); + gst_object_unref (composition); + gst_object_unref (source2); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_remove_last_object) +{ + GstBin *composition; + GstElement *source1, *audiotestsrc, *source2, *audiotestsrc2, *fakesink, + *pipeline; + GstBus *bus; + GstMessage *message; + gboolean ret; + gint64 position = 0; + GstClockTime duration; + + ges_init (); + + pipeline = GST_ELEMENT (gst_pipeline_new (NULL)); + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + + composition = GST_BIN (gst_element_factory_make ("nlecomposition", + "composition")); + + gst_element_set_state (GST_ELEMENT (composition), GST_STATE_READY); + + fakesink = gst_element_factory_make ("fakeaudiosink", NULL); + gst_bin_add_many (GST_BIN (pipeline), GST_ELEMENT (composition), fakesink, + NULL); + gst_element_link (GST_ELEMENT (composition), fakesink); + + source1 = gst_element_factory_make ("nlesource", "source1"); + audiotestsrc = gst_element_factory_make ("audiotestsrc", "audiotestsrc1"); + gst_bin_add (GST_BIN (source1), audiotestsrc); + g_object_set (source1, "start", (guint64) 0 * GST_SECOND, + "duration", 10 * GST_SECOND, "inpoint", (guint64) 0, "priority", 1, NULL); + + nle_composition_add (composition, source1); + + source2 = gst_element_factory_make ("nlesource", "source1"); + audiotestsrc2 = gst_element_factory_make ("audiotestsrc", "audiotestsrc1"); + gst_bin_add (GST_BIN (source2), audiotestsrc2); + g_object_set (source2, "start", (guint64) 10 * GST_SECOND, + "duration", 10 * GST_SECOND, "inpoint", (guint64) 0, "priority", 1, NULL); + + nle_composition_add (composition, source2); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PAUSED) + == GST_STATE_CHANGE_FAILURE); + message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR); + gst_mini_object_unref (GST_MINI_OBJECT (message)); + + commit_and_wait (GST_ELEMENT (composition), &ret); + + message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR); + gst_mini_object_unref (GST_MINI_OBJECT (message)); + + gst_element_seek_simple (pipeline, + GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, 15 * GST_SECOND); + + message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR); + gst_mini_object_unref (GST_MINI_OBJECT (message)); + + ret = + gst_element_query_position (GST_ELEMENT (pipeline), GST_FORMAT_TIME, + &position); + fail_unless_equals_uint64 (position, 15 * GST_SECOND); + + gst_element_seek_simple (pipeline, + GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, 18 * GST_SECOND); + + message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR); + gst_mini_object_unref (GST_MINI_OBJECT (message)); + + ret = + gst_element_query_position (GST_ELEMENT (pipeline), GST_FORMAT_TIME, + &position); + fail_unless_equals_uint64 (position, 18 * GST_SECOND); + + nle_composition_remove (composition, source2); + + commit_and_wait (GST_ELEMENT (composition), &ret); + g_object_get (composition, "duration", &duration, NULL); + fail_unless_equals_uint64 (duration, 10 * GST_SECOND); + + ret = + gst_element_query_position (GST_ELEMENT (pipeline), GST_FORMAT_TIME, + &position); + fail_unless_equals_uint64 (position, 10 * GST_SECOND - 1); + + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + gst_object_unref (pipeline); + gst_object_unref (bus); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_dispose_on_commit) +{ + GstElement *composition; + GstElement *nlesource; + GstElement *audiotestsrc; + GstElement *pipeline, *fakesink; + gboolean ret; + + ges_init (); + + composition = gst_element_factory_make ("nlecomposition", "composition"); + pipeline = GST_ELEMENT (gst_pipeline_new (NULL)); + fakesink = gst_element_factory_make ("fakevideosink", NULL); + + nlesource = gst_element_factory_make ("nlesource", "nlesource1"); + audiotestsrc = gst_element_factory_make ("audiotestsrc", "audiotestsrc1"); + gst_bin_add (GST_BIN (nlesource), audiotestsrc); + g_object_set (nlesource, "start", (guint64) 0 * GST_SECOND, + "duration", 10 * GST_SECOND, "inpoint", (guint64) 0, "priority", 1, NULL); + fail_unless (nle_composition_add (GST_BIN (composition), nlesource)); + + gst_bin_add_many (GST_BIN (pipeline), composition, fakesink, NULL); + fail_unless (gst_element_link (composition, fakesink) == TRUE); + + + ASSERT_OBJECT_REFCOUNT (composition, "composition", 1); + g_signal_emit_by_name (composition, "commit", TRUE, &ret); + + gst_object_unref (pipeline); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_simple_audiomixer) +{ + GstBus *bus; + GstMessage *message; + GstElement *pipeline; + GstElement *nle_audiomixer; + GstElement *composition; + GstElement *audiomixer, *fakesink; + GstElement *nlesource1, *nlesource2; + GstElement *audiotestsrc1, *audiotestsrc2; + + gboolean carry_on = TRUE, ret; + GstClockTime total_time = 10 * GST_SECOND; + + ges_init (); + + pipeline = GST_ELEMENT (gst_pipeline_new (NULL)); + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + + composition = gst_element_factory_make ("nlecomposition", "composition"); + gst_element_set_state (composition, GST_STATE_READY); + fakesink = gst_element_factory_make ("fakeaudiosink", NULL); + + /* nle_audiomixer */ + nle_audiomixer = gst_element_factory_make ("nleoperation", "nle_audiomixer"); + audiomixer = gst_element_factory_make ("audiomixer", "audiomixer"); + fail_unless (audiomixer != NULL); + gst_bin_add (GST_BIN (nle_audiomixer), audiomixer); + g_object_set (nle_audiomixer, "start", (guint64) 0 * GST_SECOND, + "duration", total_time, "inpoint", (guint64) 0 * GST_SECOND, + "priority", 0, NULL); + nle_composition_add (GST_BIN (composition), nle_audiomixer); + + /* source 1 */ + nlesource1 = gst_element_factory_make ("nlesource", "nlesource1"); + audiotestsrc1 = gst_element_factory_make ("audiotestsrc", "audiotestsrc1"); + gst_bin_add (GST_BIN (nlesource1), audiotestsrc1); + g_object_set (nlesource1, "start", (guint64) 0 * GST_SECOND, + "duration", total_time / 2, "inpoint", (guint64) 0, "priority", 1, NULL); + fail_unless (nle_composition_add (GST_BIN (composition), nlesource1)); + + /* nlesource2 */ + nlesource2 = gst_element_factory_make ("nlesource", "nlesource2"); + audiotestsrc2 = gst_element_factory_make ("audiotestsrc", "audiotestsrc2"); + gst_bin_add (GST_BIN (nlesource2), GST_ELEMENT (audiotestsrc2)); + g_object_set (nlesource2, "start", (guint64) 0 * GST_SECOND, + "duration", total_time, "inpoint", (guint64) 0 * GST_SECOND, "priority", + 2, NULL); + + GST_DEBUG ("Adding composition to pipeline"); + gst_bin_add_many (GST_BIN (pipeline), composition, fakesink, NULL); + + fail_unless (nle_composition_add (GST_BIN (composition), nlesource2)); + fail_unless (gst_element_link (composition, fakesink) == TRUE); + + GST_DEBUG ("Setting pipeline to PLAYING"); + + commit_and_wait (composition, &ret); + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING) + == GST_STATE_CHANGE_FAILURE); + + message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR); + + if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) + fail_error_message (message); + gst_mini_object_unref (GST_MINI_OBJECT (message)); + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "nle-simple-audiomixer-test-play"); + + /* Now play the 10 second composition */ + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + GST_LOG ("poll: %" GST_PTR_FORMAT, message); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + carry_on = FALSE; + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + gst_object_unref (bus); + gst_object_unref (pipeline); + + ges_deinit (); +} + +GST_END_TEST; + +static GstElement * +create_nested_source (gint nesting_depth) +{ + gint i; + GstElement *source, *nested_comp, *bin; + + source = videotest_nle_src ("source", 0, 2 * GST_SECOND, 2, 2); + for (i = 0; i < nesting_depth; i++) { + gchar *name = g_strdup_printf ("nested_comp%d", i); + gchar *desc = g_strdup_printf ("nlecomposition name=%s ! queue", name); + bin = gst_parse_bin_from_description (desc, TRUE, NULL); + nested_comp = gst_bin_get_by_name (GST_BIN (bin), name); + g_free (name); + g_free (desc); + + nle_composition_add (GST_BIN (nested_comp), source); + gst_object_unref (nested_comp); + + name = g_strdup_printf ("nested_src%d", i); + source = gst_element_factory_make_or_warn ("nlesource", name); + g_free (name); + g_object_set (source, "start", 0, "duration", 2 * GST_SECOND, NULL); + gst_bin_add (GST_BIN (source), bin); + } + + + return source; +} + +GST_START_TEST (test_seek_on_nested) +{ + GstBus *bus; + GstPad *srcpad; + GstElement *pipeline, *comp, *nested_source, *sink; + GstMessage *message; + gboolean carry_on, ret = FALSE; + GstClockTime position; + + ges_init (); + + pipeline = gst_pipeline_new ("test_pipeline"); + comp = gst_element_factory_make_or_warn ("nlecomposition", NULL); + + gst_element_set_state (comp, GST_STATE_READY); + sink = gst_element_factory_make_or_warn ("fakevideosink", "sink"); + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + gst_element_link (comp, sink); + + nested_source = create_nested_source (1); + srcpad = gst_element_get_static_pad (nested_source, "src"); + gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, + (GstPadProbeCallback) on_source1_pad_event_cb, NULL, NULL); + gst_object_unref (srcpad); + + /* Add nested source */ + nle_composition_add (GST_BIN (comp), nested_source); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + carry_on = TRUE; + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ASYNC_DONE: + { + carry_on = FALSE; + GST_DEBUG ("Pipeline reached PAUSED, stopping polling"); + break; + } + case GST_MESSAGE_EOS: + { + GST_WARNING ("Saw EOS"); + + fail_if (TRUE); + } + case GST_MESSAGE_ERROR: + GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "error"); + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + gst_element_seek_simple (pipeline, + GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, 1 * GST_SECOND); + + message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR); + gst_mini_object_unref (GST_MINI_OBJECT (message)); + + ret = + gst_element_query_position (GST_ELEMENT (pipeline), GST_FORMAT_TIME, + (gint64 *) & position); + fail_unless_equals_uint64 (position, 1 * GST_SECOND); + + GST_DEBUG ("Setting pipeline to NULL"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Resetted pipeline to NULL"); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + gst_check_objects_destroyed_on_unref (pipeline, comp, nested_source, NULL); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_error_in_nested_timeline) +{ + GstBus *bus; + GstPad *srcpad; + GstElement *pipeline, *comp, *nested_source, *sink; + GstMessage *message; + gboolean carry_on, ret = FALSE; + + ges_init (); + + pipeline = gst_pipeline_new ("test_pipeline"); + comp = gst_element_factory_make_or_warn ("nlecomposition", NULL); + + gst_element_set_state (comp, GST_STATE_READY); + sink = gst_element_factory_make_or_warn ("fakevideosink", "sink"); + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + gst_element_link (comp, sink); + + nested_source = create_nested_source (1); + srcpad = gst_element_get_static_pad (nested_source, "src"); + gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, + (GstPadProbeCallback) on_source1_pad_event_cb, NULL, NULL); + gst_object_unref (srcpad); + + /* Add nested source */ + nle_composition_add (GST_BIN (comp), nested_source); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + carry_on = TRUE; + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ASYNC_DONE: + { + carry_on = FALSE; + GST_DEBUG ("Pipeline reached PAUSED, stopping polling"); + break; + } + case GST_MESSAGE_EOS: + { + GST_WARNING ("Saw EOS"); + + fail_if (TRUE); + } + case GST_MESSAGE_ERROR: + GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "error"); + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_ELEMENT_ERROR (nested_source, STREAM, FAILED, + ("Faking an error message"), ("Nothing")); + + message = + gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR); + gst_mini_object_unref (GST_MINI_OBJECT (message)); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Resetted pipeline to NULL"); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_nest_deep) +{ + GstBus *bus; + GstPad *srcpad; + GstElement *pipeline, *comp, *nested_source, *sink; + GstMessage *message; + gboolean carry_on, ret = FALSE; + GstClockTime position; + + ges_init (); + + pipeline = gst_pipeline_new ("test_pipeline"); + comp = gst_element_factory_make_or_warn ("nlecomposition", NULL); + + gst_element_set_state (comp, GST_STATE_READY); + sink = gst_element_factory_make_or_warn ("fakevideosink", "sink"); + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + gst_element_link (comp, sink); + + nested_source = create_nested_source (2); + srcpad = gst_element_get_static_pad (nested_source, "src"); + gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, + (GstPadProbeCallback) on_source1_pad_event_cb, NULL, NULL); + gst_object_unref (srcpad); + + /* Add nested source */ + nle_composition_add (GST_BIN (comp), nested_source); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + carry_on = TRUE; + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "nothing"); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ASYNC_DONE: + { + carry_on = FALSE; + GST_DEBUG ("Pipeline reached PAUSED, stopping polling"); + break; + } + case GST_MESSAGE_EOS: + { + GST_WARNING ("Saw EOS"); + + fail_if (TRUE); + } + case GST_MESSAGE_ERROR: + GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "error"); + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + gst_element_seek_simple (pipeline, + GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, 1 * GST_SECOND); + + message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR); + gst_mini_object_unref (GST_MINI_OBJECT (message)); + + ret = + gst_element_query_position (GST_ELEMENT (pipeline), GST_FORMAT_TIME, + (gint64 *) & position); + fail_unless_equals_uint64 (position, 1 * GST_SECOND); + + GST_DEBUG ("Setting pipeline to NULL"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Resetted pipeline to NULL"); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_check_objects_destroyed_on_unref (pipeline, comp, nested_source, NULL); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + ges_deinit (); +} + +GST_END_TEST; + + + +static Suite * +gnonlin_suite (void) +{ + Suite *s = suite_create ("nlecomposition"); + TCase *tc_chain = tcase_create ("nlecomposition"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_change_object_start_stop_in_current_stack); + tcase_add_test (tc_chain, test_remove_invalid_object); + tcase_add_test (tc_chain, test_remove_last_object); + tcase_add_test (tc_chain, test_seek_on_nested); + tcase_add_test (tc_chain, test_error_in_nested_timeline); + tcase_add_test (tc_chain, test_nest_deep); + + tcase_add_test (tc_chain, test_dispose_on_commit); + + if (gst_registry_check_feature_version (gst_registry_get (), "audiomixer", 1, + 0, 0)) { + tcase_add_test (tc_chain, test_simple_audiomixer); + } else { + GST_WARNING ("audiomixer element not available, skipping 1 test"); + } + + return s; +} + +GST_CHECK_MAIN (gnonlin) diff --git a/tests/check/nle/nleoperation.c b/tests/check/nle/nleoperation.c new file mode 100644 index 0000000000..2e8be8b0f8 --- /dev/null +++ b/tests/check/nle/nleoperation.c @@ -0,0 +1,735 @@ +#include "common.h" + +static void +fill_pipeline_and_check (GstElement * comp, GList * segments) +{ + GstElement *pipeline, *sink; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + GList *listcopy = copy_segment_list (segments); + + pipeline = gst_pipeline_new ("test_pipeline"); + sink = gst_element_factory_make_or_warn ("fakevideosink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = segments; + + gst_element_link (comp, sink); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to READY"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + GST_DEBUG ("Resetted pipeline to READY"); + + if (collect->seen_segments) + g_list_free (collect->seen_segments); + + collect->seen_segments = NULL; + collect->expected_base = 0; + collect->expected_segments = listcopy; + collect->gotsegment = FALSE; + + GST_DEBUG ("Setting pipeline to PLAYING again"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + carry_on = TRUE; + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } else { + GST_DEBUG ("bus_poll responded, but there wasn't any message..."); + } + } + + fail_if (collect->expected_segments != NULL); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + gst_object_unref (GST_OBJECT (sinkpad)); + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + collect_free (collect); +} + +GST_START_TEST (test_simple_operation) +{ + gboolean ret = FALSE; + GstElement *comp, *oper, *source; + GList *segments = NULL; + + ges_init (); + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 | Priority + * ---------------------------------------------------------------------------- + * [-- oper --] | 0 + * [------------- source -------------] | 1 + * */ + + /* + source + Start : 0s + Duration : 3s + Priority : 1 + */ + + source = videotest_nle_src ("source", 0, 3 * GST_SECOND, 2, 1); + fail_if (source == NULL); + + /* + operation + Start : 1s + Duration : 1s + Priority : 0 + */ + + oper = new_operation ("oper", "identity", 1 * GST_SECOND, 1 * GST_SECOND, 0); + fail_if (oper == NULL); + + /* Add source */ + ASSERT_OBJECT_REFCOUNT (source, "source", 1); + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + nle_composition_add (GST_BIN (comp), source); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source, "source", 1); + + /* Add operaton */ + + nle_composition_add (GST_BIN (comp), oper); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* remove source */ + + gst_object_ref (source); + nle_composition_remove (GST_BIN (comp), source); + check_start_stop_duration (comp, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source, "source", 1); + + /* re-add source */ + nle_composition_add (GST_BIN (comp), source); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + gst_object_unref (source); + + ASSERT_OBJECT_REFCOUNT (source, "source", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_pyramid_operations) +{ + GstElement *comp, *oper1, *oper2, *source; + gboolean ret = FALSE; + GList *segments = NULL; + + ges_init (); + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + + /* + source + Start : 0s + Duration : 10s + Priority : 2 + */ + + source = videotest_nle_src ("source", 0, 10 * GST_SECOND, 2, 2); + + /* + operation1 + Start : 4s + Duration : 2s + Priority : 1 + */ + + oper1 = + new_operation ("oper1", "identity", 4 * GST_SECOND, 2 * GST_SECOND, 1); + + /* + operation2 + Start : 2s + Duration : 6s + Priority : 0 + */ + + oper2 = + new_operation ("oper2", "identity", 2 * GST_SECOND, 6 * GST_SECOND, 0); + + /* Add source */ + ASSERT_OBJECT_REFCOUNT (source, "source", 1); + ASSERT_OBJECT_REFCOUNT (oper1, "oper1", 1); + ASSERT_OBJECT_REFCOUNT (oper2, "oper2", 1); + + nle_composition_add (GST_BIN (comp), source); + commit_and_wait (comp, &ret); + check_start_stop_duration (source, 0, 10 * GST_SECOND, 10 * GST_SECOND); + check_start_stop_duration (comp, 0, 10 * GST_SECOND, 10 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source, "source", 1); + + /* Add operation 1 */ + + nle_composition_add (GST_BIN (comp), oper1); + commit_and_wait (comp, &ret); + check_start_stop_duration (oper1, 4 * GST_SECOND, 6 * GST_SECOND, + 2 * GST_SECOND); + check_start_stop_duration (comp, 0, 10 * GST_SECOND, 10 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (oper1, "oper1", 1); + + /* Add operation 2 */ + + nle_composition_add (GST_BIN (comp), oper2); + commit_and_wait (comp, &ret); + check_start_stop_duration (oper2, 2 * GST_SECOND, 8 * GST_SECOND, + 6 * GST_SECOND); + check_start_stop_duration (comp, 0, 10 * GST_SECOND, 10 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (oper1, "oper2", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 4 * GST_SECOND, 2 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 4 * GST_SECOND, 6 * GST_SECOND, 4 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 6 * GST_SECOND, 8 * GST_SECOND, 6 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 8 * GST_SECOND, 10 * GST_SECOND, 8 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_pyramid_operations2) +{ + gboolean ret; + GstElement *comp, *oper, *source1, *source2, *def; + GList *segments = NULL; + + ges_init (); + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + + /* + source1 + Start : 0s + Duration : 2s + Priority : 2 + */ + + source1 = videotest_nle_src ("source1", 0, 2 * GST_SECOND, 2, 2); + + /* + operation + Start : 1s + Duration : 4s + Priority : 1 + */ + + oper = new_operation ("oper", "identity", 1 * GST_SECOND, 4 * GST_SECOND, 1); + + /* + source2 + Start : 4s + Duration : 2s + Priority : 2 + */ + + source2 = videotest_nle_src ("source2", 4 * GST_SECOND, 2 * GST_SECOND, 2, 2); + + /* + def (default source) + Priority = G_MAXUINT32 + */ + def = + videotest_nle_src ("default", 0 * GST_SECOND, 0 * GST_SECOND, 2, + G_MAXUINT32); + g_object_set (def, "expandable", TRUE, NULL); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + ASSERT_OBJECT_REFCOUNT (def, "default", 1); + + /* Add source 1 */ + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + /* Add source 2 */ + + nle_composition_add (GST_BIN (comp), source2); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + /* Add operation */ + + nle_composition_add (GST_BIN (comp), oper); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + /* Add default */ + + nle_composition_add (GST_BIN (comp), def); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 4 * GST_SECOND, 2 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 4 * GST_SECOND, 5 * GST_SECOND, 4 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 5 * GST_SECOND, 6 * GST_SECOND, 5 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments); + + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_pyramid_operations_expandable) +{ + GstElement *comp, *oper, *source1, *source2, *def; + gboolean ret = FALSE; + GList *segments = NULL; + + ges_init (); + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + + /* + source1 + Start : 0s + Duration : 2s + Priority : 2 + */ + + source1 = videotest_nle_src ("source1", 0, 2 * GST_SECOND, 2, 2); + + /* + operation (expandable) + Start : XX + Duration : XX + Priority : 1 + */ + + oper = new_operation ("oper", "identity", 1 * GST_SECOND, 4 * GST_SECOND, 1); + g_object_set (oper, "expandable", TRUE, NULL); + + /* + source2 + Start : 4s + Duration : 2s + Priority : 2 + */ + + source2 = videotest_nle_src ("source2", 4 * GST_SECOND, 2 * GST_SECOND, 2, 2); + + /* + def (default source) + Priority = G_MAXUINT32 + */ + def = + videotest_nle_src ("default", 0 * GST_SECOND, 0 * GST_SECOND, 2, + G_MAXUINT32); + g_object_set (def, "expandable", TRUE, NULL); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + ASSERT_OBJECT_REFCOUNT (def, "default", 1); + + /* Add source 1 */ + nle_composition_add (GST_BIN (comp), source1); + /* Add source 2 */ + nle_composition_add (GST_BIN (comp), source2); + /* Add operation */ + nle_composition_add (GST_BIN (comp), oper); + /* Add default */ + nle_composition_add (GST_BIN (comp), def); + + commit_and_wait (comp, &ret); + check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND); + check_start_stop_duration (oper, 0 * GST_SECOND, 6 * GST_SECOND, + 6 * GST_SECOND); + check_start_stop_duration (source2, 4 * GST_SECOND, 6 * GST_SECOND, + 2 * GST_SECOND); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 4 * GST_SECOND, 2 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 4 * GST_SECOND, 6 * GST_SECOND, 4 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments); + + ges_deinit (); +} + +GST_END_TEST; + + +GST_START_TEST (test_complex_operations) +{ + GstElement *comp, *oper, *source1, *source2; + gboolean ret = FALSE; + GList *segments = NULL; + + ges_init (); + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 6 | Priority + * ---------------------------------------------------------------------------- + * [ -oper- ] | 1 + * [ -source2- -] | 2 + * [ -source1- -] | 3 + * */ + + /* + source1 + Start : 0s + Duration : 4s + Priority : 3 + */ + + source1 = videotest_in_bin_nle_src ("source1", 0, 4 * GST_SECOND, 2, 3); + fail_if (source1 == NULL); + + /* + source2 + Start : 2s + Duration : 4s + Priority : 2 + */ + + source2 = + videotest_in_bin_nle_src ("source2", 2 * GST_SECOND, 4 * GST_SECOND, 2, + 2); + fail_if (source2 == NULL); + + /* + operation + Start : 2s + Duration : 2s + Priority : 1 + */ + + oper = + new_operation ("oper", "compositor", 2 * GST_SECOND, 2 * GST_SECOND, 1); + fail_if (oper == NULL); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Add source1 */ + nle_composition_add (GST_BIN (comp), source1); + check_start_stop_duration (comp, 0, 0, 0); + /* If the composition already processed the source, the refcount + * might be 2 */ + ASSERT_OBJECT_REFCOUNT_BETWEEN (source1, "source1", 1, 2); + + /* Add source2 */ + nle_composition_add (GST_BIN (comp), source2); + check_start_stop_duration (comp, 0, 0, 0); + /* If the composition already processed the source, the refcount + * might be 2 */ + ASSERT_OBJECT_REFCOUNT_BETWEEN (source2, "source2", 1, 2); + + /* Add operaton */ + nle_composition_add (GST_BIN (comp), oper); + check_start_stop_duration (comp, 0, 0, 0); + + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 0 * GST_SECOND, 2 * GST_SECOND, 2 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 4 * GST_SECOND, 6 * GST_SECOND, 4 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments); + + ges_deinit (); +} + +GST_END_TEST; + + +GST_START_TEST (test_complex_operations_bis) +{ + GstElement *comp, *oper, *source1, *source2; + gboolean ret; + GList *segments = NULL; + + ges_init (); + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + + /* TOPOLOGY + * + * 0 1 2 3 4 .. 6 | Priority + * ---------------------------------------------------------------------------- + * [ ......................[------ oper ----------]..........] | 1 EXPANDABLE + * [--------------------- source1 ----------------] | 2 + * [------------ source2 ------------] | 3 + * */ + + + /* + source1 + Start : 0s + Duration : 4s + Priority : 2 + */ + + source1 = videotest_in_bin_nle_src ("source1", 0, 4 * GST_SECOND, 3, 2); + fail_if (source1 == NULL); + + /* + source2 + Start : 2s + Duration : 4s + Priority : 3 + */ + + source2 = + videotest_in_bin_nle_src ("source2", 2 * GST_SECOND, 4 * GST_SECOND, 2, + 3); + fail_if (source2 == NULL); + + /* + operation + Start : 2s + Duration : 2s + Priority : 1 + EXPANDABLE + */ + + oper = + new_operation ("oper", "compositor", 2 * GST_SECOND, 2 * GST_SECOND, 1); + fail_if (oper == NULL); + g_object_set (oper, "expandable", TRUE, NULL); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Add source1 */ + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 4 * GST_SECOND, 4 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Add source2 */ + nle_composition_add (GST_BIN (comp), source2); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Add operaton */ + + nle_composition_add (GST_BIN (comp), oper); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + /* Since it's expandable, it should have changed to full length */ + check_start_stop_duration (oper, 0 * GST_SECOND, 6 * GST_SECOND, + 6 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 0 * GST_SECOND, 2 * GST_SECOND, 2 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 0 * GST_SECOND, 2 * GST_SECOND, 4 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments); + + ges_deinit (); +} + +GST_END_TEST; + + + +static Suite * +gnonlin_suite (void) +{ + Suite *s = suite_create ("nleoperation"); + TCase *tc_chain = tcase_create ("nleoperation"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_simple_operation); + tcase_add_test (tc_chain, test_pyramid_operations); + tcase_add_test (tc_chain, test_pyramid_operations2); + tcase_add_test (tc_chain, test_pyramid_operations_expandable); + if (gst_registry_check_feature_version (gst_registry_get (), "compositor", 0, + 11, 0)) { + tcase_add_test (tc_chain, test_complex_operations); + tcase_add_test (tc_chain, test_complex_operations_bis); + } else + GST_WARNING ("compositor element not available, skipping 1 test"); + + return s; +} + +GST_CHECK_MAIN (gnonlin) diff --git a/tests/check/nle/nlesource.c b/tests/check/nle/nlesource.c new file mode 100644 index 0000000000..72aee071ce --- /dev/null +++ b/tests/check/nle/nlesource.c @@ -0,0 +1,219 @@ +#include "common.h" + +GST_START_TEST (test_simple_videotestsrc) +{ + GstElement *pipeline; + GstElement *nlesource, *sink; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + + pipeline = gst_pipeline_new ("test_pipeline"); + + /* + Source 1 + Start : 1s + Duration : 1s + Priority : 1 + */ + nlesource = + videotest_nle_src ("source1", 1 * GST_SECOND, 1 * GST_SECOND, 2, 1); + fail_if (nlesource == NULL); + check_start_stop_duration (nlesource, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + sink = gst_element_factory_make_or_warn ("fakevideosink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), nlesource, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = nlesource; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + + gst_element_link (nlesource, sink); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + fail_if (sinkpad == NULL); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (pipeline); + + GST_DEBUG ("Setting pipeline to PLAYING"); + ASSERT_OBJECT_REFCOUNT (nlesource, "nlesource", 1); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + GST_LOG ("poll"); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (FALSE); + carry_on = FALSE; + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to NULL"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + gst_object_unref (GST_OBJECT (sinkpad)); + + GST_DEBUG ("Resetted pipeline to NULL"); + + gst_object_unref (pipeline); + gst_object_unref (bus); + + g_free (collect); +} + +GST_END_TEST; + +GST_START_TEST (test_videotestsrc_in_bin) +{ + GstElement *pipeline; + GstElement *nlesource, *sink; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + + pipeline = gst_pipeline_new ("test_pipeline"); + + /* + Source 1 + Start : 1s + Duration : 1s + Priority : 1 + */ + nlesource = videotest_in_bin_nle_src ("source1", 0, 1 * GST_SECOND, 2, 1); + /* Handle systems which don't have alpha available */ + if (nlesource == NULL) + return; + + sink = gst_element_factory_make_or_warn ("fakevideosink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), nlesource, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = nlesource; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + + gst_element_link (nlesource, sink); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + fail_if (sinkpad == NULL); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (pipeline); + + GST_DEBUG ("Setting pipeline to PLAYING"); + ASSERT_OBJECT_REFCOUNT (nlesource, "nlesource", 1); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + GST_LOG ("poll"); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (FALSE); + carry_on = FALSE; + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to NULL"); + + gst_object_unref (GST_OBJECT (sinkpad)); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + GST_DEBUG ("Resetted pipeline to NULL"); + + gst_object_unref (pipeline); + gst_object_unref (bus); + + g_free (collect); +} + +GST_END_TEST; + +static Suite * +gnonlin_suite (void) +{ + Suite *s = suite_create ("nlesource"); + TCase *tc_chain = tcase_create ("nlesource"); + + ges_init (); + suite_add_tcase (s, tc_chain); + + if (0) + tcase_add_test (tc_chain, test_simple_videotestsrc); + tcase_add_test (tc_chain, test_videotestsrc_in_bin); + + return s; +} + +GST_CHECK_MAIN (gnonlin) diff --git a/tests/check/nle/seek.c b/tests/check/nle/seek.c new file mode 100644 index 0000000000..8bd530c145 --- /dev/null +++ b/tests/check/nle/seek.c @@ -0,0 +1,780 @@ +#include "common.h" +static const gchar *compositor_element = NULL; + +typedef struct _SeekInfo +{ + GstClockTime position; /* Seek value and segment position */ + GstClockTime start; /* Segment start */ + GstClockTime stop; /* Segment stop */ + gboolean expect_failure; /* Whether we expect the seek to fail or not */ +} SeekInfo; + +static SeekInfo * +new_seek_info (GstClockTime position, GstClockTime start, GstClockTime stop, + gboolean expect_failure) +{ + SeekInfo *info = g_new0 (SeekInfo, 1); + + info->position = position; + info->start = start; + info->stop = stop; + info->expect_failure = expect_failure; + + return info; +} + +static void +fill_pipeline_and_check (GstElement * comp, GList * segments, GList * seeks) +{ + GstElement *pipeline, *sink; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE, expected_failure; + GstPad *sinkpad; + GList *ltofree = seeks; + + pipeline = gst_pipeline_new ("test_pipeline"); + sink = gst_element_factory_make_or_warn ("fakevideosink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = segments; + collect->keep_expected_segments = TRUE; + + gst_element_link (comp, sink); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + break; + case GST_MESSAGE_ASYNC_DONE: + GST_DEBUG ("prerolling done"); + + if (seeks == NULL) { + carry_on = FALSE; + g_list_free_full (collect->expected_segments, g_free); + collect->expected_segments = NULL; + GST_DEBUG ("Done seeking"); + break; + } + + g_list_free_full (collect->expected_segments, g_free); + collect->expected_segments = NULL; + expected_failure = TRUE; + while (expected_failure && carry_on) { + SeekInfo *sinfo = (SeekInfo *) seeks->data; + + seeks = seeks->next; + + if (!sinfo->expect_failure) { + collect->gotsegment = FALSE; + collect->expected_base = 0; + collect->expected_segments = + g_list_append (collect->expected_segments, segment_new (1.0, + GST_FORMAT_TIME, sinfo->start, sinfo->stop, + sinfo->position)); + + expected_failure = FALSE; + } + + GST_DEBUG ("Seeking to %" GST_TIME_FORMAT ", Expecting (%" + GST_TIME_FORMAT " %" GST_TIME_FORMAT ")", + GST_TIME_ARGS (sinfo->position), GST_TIME_ARGS (sinfo->start), + GST_TIME_ARGS (sinfo->stop)); + + fail_unless_equals_int (gst_element_seek_simple (pipeline, + GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, sinfo->position), + !sinfo->expect_failure); + + if (!sinfo->expect_failure) { + g_free (sinfo); + break; + } + + if (seeks == NULL) + carry_on = FALSE; + g_free (sinfo); + } + break; + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to READY"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + g_list_free (ltofree); + g_free (collect); +} + +static void +test_simplest_full (void) +{ + gboolean ret; + GstElement *comp, *source1; + GList *segments = NULL; + GList *seeks = NULL; + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + fail_if (comp == NULL); + + /* + Source 1 + Start : 0s + Duration : 1s + Media start : 5s + Priority : 1 + */ + source1 = + videotest_nle_src_full ("source1", 0, 1 * GST_SECOND, 5 * GST_SECOND, 3, + 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* Add one source */ + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0)); + + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 5.5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (0 * GST_SECOND, 5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND - 1, 6 * GST_SECOND - 1, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND, 6 * GST_SECOND, + 6 * GST_SECOND, TRUE)); + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 5.5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (0 * GST_SECOND, 5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND - 1, 6 * GST_SECOND - 1, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND, 6 * GST_SECOND, + 6 * GST_SECOND, TRUE)); + + fill_pipeline_and_check (comp, segments, seeks); +} + +static void +test_one_after_other_full (void) +{ + gboolean ret; + GstElement *comp, *source1, *source2; + GList *segments = NULL, *seeks = NULL; + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + fail_if (comp == NULL); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 | Priority + * ---------------------------------------------------------------------------- + * [5 source1 ][2 source2 ] | 1 + * + * */ + + /* + Source 1 + Start : 0s + Duration : 1s + Media start : 5s + Priority : 1 + */ + source1 = + videotest_nle_src_full ("source1", 0, 1 * GST_SECOND, 5 * GST_SECOND, 3, + 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 1s + Media start : 2s + Priority : 1 + */ + source2 = videotest_nle_src_full ("source2", 1 * GST_SECOND, 1 * GST_SECOND, + 2 * GST_SECOND, 2, 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + /* Add sources */ + nle_composition_add (GST_BIN (comp), source1); + nle_composition_add (GST_BIN (comp), source2); + commit_and_wait (comp, &ret); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0)); + + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 5.5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (0 * GST_SECOND, 5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND - 1, 6 * GST_SECOND - 1, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND, 2 * GST_SECOND, + 3 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (2 * GST_SECOND - 1, + 3 * GST_SECOND - 1, 3 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (2 * GST_SECOND, 3 * GST_SECOND, + 3 * GST_SECOND, TRUE)); + + + fill_pipeline_and_check (comp, segments, seeks); +} + +static void +test_one_under_another_full (void) +{ + gboolean ret; + GstElement *comp, *source1, *source2; + GList *segments = NULL, *seeks = NULL; + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + fail_if (comp == NULL); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 | Priority + * ---------------------------------------------------------------------------- + * [ source1 ] | 1 + * [ source2 ] | 2 + * + * */ + + /* + Source 1 + Start : 0s + Duration : 2s + Priority : 1 + */ + source1 = videotest_nle_src ("source1", 0, 2 * GST_SECOND, 3, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 2s + Priority : 2 + */ + source2 = videotest_nle_src ("source2", 1 * GST_SECOND, 2 * GST_SECOND, 2, 2); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 1 * GST_SECOND, 3 * GST_SECOND, + 2 * GST_SECOND); + + /* Add two sources */ + + nle_composition_add (GST_BIN (comp), source1); + nle_composition_add (GST_BIN (comp), source2); + commit_and_wait (comp, &ret); + check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND); + check_start_stop_duration (source2, 1 * GST_SECOND, 3 * GST_SECOND, + 2 * GST_SECOND); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, GST_SECOND, 0)); + + + /* Hit source1 */ + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0.5 * GST_SECOND, + 1 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (0 * GST_SECOND, 0 * GST_SECOND, + 1 * GST_SECOND, FALSE)); + /* Hit source1 over source2 */ + seeks = + g_list_append (seeks, new_seek_info (1 * GST_SECOND, 1 * GST_SECOND, + 2 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (1.5 * GST_SECOND, 1.5 * GST_SECOND, + 2 * GST_SECOND, FALSE)); + /* Hit source2 */ + seeks = + g_list_append (seeks, new_seek_info (2 * GST_SECOND, 2 * GST_SECOND, + 3 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 2.5 * GST_SECOND, + 3 * GST_SECOND, FALSE)); + + fill_pipeline_and_check (comp, segments, seeks); +} + +static void +test_one_bin_after_other_full (void) +{ + gboolean ret = FALSE; + GstElement *comp, *source1, *source2; + GList *segments = NULL, *seeks = NULL; + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + fail_if (comp == NULL); + + /* + Source 1 + Start : 0s + Duration : 1s + Priority : 1 + */ + source1 = videotest_in_bin_nle_src ("source1", 0, 1 * GST_SECOND, 3, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 1s + Priority : 1 + */ + source2 = + videotest_in_bin_nle_src ("source2", 1 * GST_SECOND, 1 * GST_SECOND, 2, + 1); + fail_if (source2 == NULL); + + /* Add one source */ + + nle_composition_add (GST_BIN (comp), source1); + + /* Second source */ + + nle_composition_add (GST_BIN (comp), source2); + commit_and_wait (comp, &ret); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + + /* Hit source1 */ + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0.5 * GST_SECOND, + GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (0 * GST_SECOND, 0 * GST_SECOND, + GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND - 1, GST_SECOND - 1, + GST_SECOND, FALSE)); + /* Hit source2 */ + seeks = + g_list_append (seeks, new_seek_info (1.5 * GST_SECOND, 1.5 * GST_SECOND, + 2 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND, GST_SECOND, + 2 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (2 * GST_SECOND - 1, + 2 * GST_SECOND - 1, 2 * GST_SECOND, FALSE)); + /* Should fail */ + seeks = + g_list_append (seeks, new_seek_info (2 * GST_SECOND, GST_SECOND, + GST_SECOND, TRUE)); + + fill_pipeline_and_check (comp, segments, seeks); +} + + +GST_START_TEST (test_complex_operations) +{ + gboolean ret = FALSE; + GstElement *comp, *oper, *source1, *source2; + GList *segments = NULL, *seeks = NULL; + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + + /* TOPOLOGY + * + * 0 1 2 3 4 .. 6 | Priority + * ---------------------------------------------------------------------------- + * [------ oper ----------] | 1 + * [--------------------- source1 ----------------] | 2 + * [------------ source2 ------] | 3 + * */ + + /* + source1 + Start : 0s + Duration : 4s + Priority : 3 + */ + + source1 = videotest_in_bin_nle_src ("source1", 0, 4 * GST_SECOND, 2, 3); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 4 * GST_SECOND, 4 * GST_SECOND); + + /* + source2 + Start : 2s + Duration : 4s + Priority : 2 + */ + + source2 = + videotest_in_bin_nle_src ("source2", 2 * GST_SECOND, 4 * GST_SECOND, 2, + 2); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 2 * GST_SECOND, 6 * GST_SECOND, + 4 * GST_SECOND); + + /* + operation + Start : 2s + Duration : 2s + Priority : 1 + */ + + oper = + new_operation ("oper", compositor_element, 2 * GST_SECOND, 2 * GST_SECOND, + 1); + fail_if (oper == NULL); + check_start_stop_duration (oper, 2 * GST_SECOND, 4 * GST_SECOND, + 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Add source1 */ + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 4 * GST_SECOND, 4 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Add source2 */ + nle_composition_add (GST_BIN (comp), source2); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Add operaton */ + + nle_composition_add (GST_BIN (comp), oper); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0)); + + /* Seeks */ + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0.5 * GST_SECOND, + 2 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (4.5 * GST_SECOND, 4.5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + /* and backwards */ + seeks = + g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0.5 * GST_SECOND, + 2 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (4.5 * GST_SECOND, 4.5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + + fill_pipeline_and_check (comp, segments, seeks); +} + +GST_END_TEST; + + +GST_START_TEST (test_complex_operations_bis) +{ + gboolean ret = FALSE; + GstElement *comp, *oper, *source1, *source2; + GList *segments = NULL, *seeks = NULL; + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + + /* TOPOLOGY + * + * 0 1 2 3 4 .. 6 | Priority + * ---------------------------------------------------------------------------- + * [ ......................[------ oper ----------]..........] | 1 EXPANDABLE + * [--------------------- source1 ----------------] | 2 + * [------------ source2 ------] | 3 + * */ + + + /* + source1 + Start : 0s + Duration : 4s + Priority : 2 + */ + + source1 = videotest_in_bin_nle_src ("source1", 0, 4 * GST_SECOND, 3, 2); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 4 * GST_SECOND, 4 * GST_SECOND); + + /* + source2 + Start : 2s + Duration : 4s + Priority : 3 + */ + + source2 = + videotest_in_bin_nle_src ("source2", 2 * GST_SECOND, 4 * GST_SECOND, 2, + 3); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 2 * GST_SECOND, 6 * GST_SECOND, + 4 * GST_SECOND); + + /* + operation + Start : 2s + Duration : 2s + Priority : 1 + EXPANDABLE + */ + + oper = + new_operation ("oper", compositor_element, 2 * GST_SECOND, 2 * GST_SECOND, + 1); + fail_if (oper == NULL); + check_start_stop_duration (oper, 2 * GST_SECOND, 4 * GST_SECOND, + 2 * GST_SECOND); + g_object_set (oper, "expandable", TRUE, NULL); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Add source1 */ + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 4 * GST_SECOND, 4 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Add source2 */ + nle_composition_add (GST_BIN (comp), source2); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Add operaton */ + + nle_composition_add (GST_BIN (comp), oper); + commit_and_wait (comp, &ret); + check_start_stop_duration (source1, 0, 4 * GST_SECOND, 4 * GST_SECOND); + check_start_stop_duration (source2, 2 * GST_SECOND, 6 * GST_SECOND, + 4 * GST_SECOND); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + check_start_stop_duration (oper, 0 * GST_SECOND, 6 * GST_SECOND, + 6 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0)); + + /* Seeks */ + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (4.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + /* and backwards */ + seeks = + g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + + seeks = + g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (4.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + + fill_pipeline_and_check (comp, segments, seeks); +} + +GST_END_TEST; + + +GST_START_TEST (test_simplest) +{ + test_simplest_full (); +} + +GST_END_TEST; + + +GST_START_TEST (test_one_after_other) +{ + test_one_after_other_full (); +} + +GST_END_TEST; + + +GST_START_TEST (test_one_under_another) +{ + test_one_under_another_full (); +} + +GST_END_TEST; + + +GST_START_TEST (test_one_bin_after_other) +{ + test_one_bin_after_other_full (); +} + +GST_END_TEST; + + +static Suite * +gnonlin_suite (void) +{ + Suite *s = suite_create ("gnonlin-seek"); + TCase *tc_chain = tcase_create ("general"); + + ges_init (); + suite_add_tcase (s, tc_chain); + + if (gst_registry_check_feature_version (gst_registry_get (), "compositor", 1, + 0, 0)) { + compositor_element = "compositor"; + } else if (gst_registry_check_feature_version (gst_registry_get (), + "videomixer", 1, 0, 0)) { + compositor_element = "videomixer"; + + } + + tcase_add_test (tc_chain, test_simplest); + tcase_add_test (tc_chain, test_one_after_other); + tcase_add_test (tc_chain, test_one_under_another); + tcase_add_test (tc_chain, test_one_bin_after_other); + + if (compositor_element) { + tcase_add_test (tc_chain, test_complex_operations); + tcase_add_test (tc_chain, test_complex_operations_bis); + } else { + GST_WARNING ("No compositor element, can not run operations tests"); + } + + return s; +} + +GST_CHECK_MAIN (gnonlin) diff --git a/tests/check/nle/simple.c b/tests/check/nle/simple.c new file mode 100644 index 0000000000..e6cbfe786d --- /dev/null +++ b/tests/check/nle/simple.c @@ -0,0 +1,801 @@ +#include "common.h" + +static void +test_simplest_full (void) +{ + gboolean ret = FALSE; + GstElement *pipeline; + GstElement *comp, *sink, *source1; + CollectStructure *collect; + GstBus *bus; + GstPad *sinkpad; + + pipeline = gst_pipeline_new ("test_pipeline"); + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + fail_if (comp == NULL); + + /* + Source 1 + Start : 0s + Duration : 1s + Media start : 5s + Media Duartion : 1s + Priority : 1 + */ + source1 = + videotest_nle_src_full ("source1", 0, 1 * GST_SECOND, 5 * GST_SECOND, 3, + 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* Add one source */ + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + fail_unless (ret); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + sink = gst_element_factory_make_or_warn ("fakevideosink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0)); + + gst_element_link (comp, sink); + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_ERROR ("Setting pipeline to PLAYING"); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + poll_the_bus (bus); + + GST_DEBUG ("Setting pipeline to NULL"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + GST_ERROR ("Resetted pipeline to READY"); + + if (collect->seen_segments) + g_list_free (collect->seen_segments); + + collect->seen_segments = NULL; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0)); + collect->expected_base = 0; + collect->gotsegment = FALSE; + + GST_ERROR ("Setting pipeline to PLAYING again"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus AGAIN"); + + poll_the_bus (bus); + + fail_if (collect->expected_segments != NULL); + + gst_object_unref (GST_OBJECT (sinkpad)); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_check_objects_destroyed_on_unref (pipeline, comp, source1, NULL); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + + collect_free (collect); +} + +static void +test_time_duration_full (void) +{ + gboolean ret = FALSE; + GstElement *comp, *source1, *source2; + + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + + /* + Source 1 + Start : 0s + Duration : 1s + Priority : 1 + */ + source1 = videotest_nle_src ("source1", 0, 1 * GST_SECOND, 3, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 1s + Priority : 1 + */ + source2 = videotest_nle_src ("source2", 1 * GST_SECOND, 1 * GST_SECOND, 2, 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + /* Add one source */ + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + fail_unless (ret == TRUE); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Second source */ + + ret = FALSE; + nle_composition_add (GST_BIN (comp), source2); + commit_and_wait (comp, &ret); + fail_unless (ret == TRUE); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Remove first source */ + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + gst_object_ref (source1); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 2); + GST_ERROR_OBJECT (source1, "Num refs : %i", ((GObject *) source1)->ref_count); + nle_composition_remove (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + GST_ERROR_OBJECT (source1, "Num refs : %i", ((GObject *) source1)->ref_count); + check_start_stop_duration (comp, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Re-add first source */ + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + gst_object_unref (source1); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + gst_element_set_state (comp, GST_STATE_NULL); + gst_object_unref (comp); +} + +static void +test_one_after_other_full (void) +{ + GstElement *pipeline; + GstElement *comp, *sink, *source1, *source2; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + + gboolean ret = FALSE; + + pipeline = gst_pipeline_new ("test_pipeline"); + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + fail_if (comp == NULL); + + /* + Source 1 + Start : 0s + Duration : 1s + Media start : 5s + Priority : 1 + */ + source1 = + videotest_nle_src_full ("source1", 0, 1 * GST_SECOND, 5 * GST_SECOND, 3, + 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 1s + Media start : 2s + Priority : 1 + */ + source2 = videotest_nle_src_full ("source2", 1 * GST_SECOND, 1 * GST_SECOND, + 2 * GST_SECOND, 2, 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + /* Add one source */ + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Second source */ + nle_composition_add (GST_BIN (comp), source2); + commit_and_wait (comp, &ret); + fail_unless (ret); + check_start_stop_duration (source1, 0 * GST_SECOND, 1 * GST_SECOND, + 1 * GST_SECOND); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + sink = gst_element_factory_make_or_warn ("fakevideosink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 1 * GST_SECOND)); + + gst_element_link (comp, sink); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to NULL"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + GST_DEBUG ("Resetted pipeline to READY"); + + if (collect->seen_segments) + g_list_free (collect->seen_segments); + + collect->seen_segments = NULL; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 1 * GST_SECOND)); + collect->gotsegment = FALSE; + collect->expected_base = 0; + + + GST_DEBUG ("Setting pipeline to PLAYING again"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + carry_on = TRUE; + + GST_DEBUG ("Let's poll the bus AGAIN"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } else { + GST_DEBUG ("bus_poll responded, but there wasn't any message..."); + } + } + + fail_if (collect->expected_segments != NULL); + + gst_object_unref (GST_OBJECT (sinkpad)); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + collect_free (collect); +} + +static void +test_one_under_another_full (void) +{ + gboolean ret = FALSE; + GstElement *pipeline; + GstElement *comp, *sink, *source1, *source2; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + + pipeline = gst_pipeline_new ("test_pipeline"); + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + fail_if (comp == NULL); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 | Priority + * ---------------------------------------------------------------------------- + * [- source1 -] | 1 + * [- source2 -] | 2 + * */ + + /* + Source 1 + Start : 0s + Duration : 2s + Priority : 1 + */ + source1 = videotest_nle_src ("source1", 0, 2 * GST_SECOND, 18, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 2s + Priority : 2 + */ + source2 = videotest_nle_src ("source2", 1 * GST_SECOND, 2 * GST_SECOND, 0, 2); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 1 * GST_SECOND, 3 * GST_SECOND, + 2 * GST_SECOND); + + /* Add two sources */ + + nle_composition_add (GST_BIN (comp), source1); + nle_composition_add (GST_BIN (comp), source2); + check_start_stop_duration (comp, 0, 0 * GST_SECOND, 0 * GST_SECOND); + /* Now commiting changes */ + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND); + check_start_stop_duration (source2, 1 * GST_SECOND, 3 * GST_SECOND, + 2 * GST_SECOND); + + /* Remove second source */ + + gst_object_ref (source1); + nle_composition_remove (GST_BIN (comp), source1); + check_start_stop_duration (comp, 1 * GST_SECOND, 3 * GST_SECOND, + 2 * GST_SECOND); + + /* Re-add second source */ + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + gst_object_unref (source1); + + sink = gst_element_factory_make_or_warn ("fakevideosink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 0, GST_SECOND, 0)); + + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, GST_SECOND, 2 * GST_SECOND, + GST_SECOND)); + + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND)); + + gst_element_link (comp, sink); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* check if the segment is the correct one (0s-4s) */ + carry_on = FALSE; + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_message_unref (message); + } + } + + fail_if (collect->expected_segments != NULL); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + gst_object_unref (GST_OBJECT (sinkpad)); + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + collect_free (collect); +} + +static void +test_one_bin_after_other_full (void) +{ + gboolean ret = FALSE; + GstElement *pipeline; + GstElement *comp, *sink, *source1, *source2; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + + pipeline = gst_pipeline_new ("test_pipeline"); + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + fail_if (comp == NULL); + + /* + Source 1 + Start : 0s + Duration : 1s + Priority : 1 + */ + source1 = videotest_in_bin_nle_src ("source1", 0, 1 * GST_SECOND, 3, 1); + if (source1 == NULL) { + gst_object_unref (pipeline); + gst_object_unref (comp); + return; + } + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 1s + Priority : 1 + */ + source2 = + videotest_in_bin_nle_src ("source2", 1 * GST_SECOND, 1 * GST_SECOND, 2, + 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + /* Add one source */ + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + fail_unless (ret); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Second source */ + + nle_composition_add (GST_BIN (comp), source2); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Remove first source */ + + gst_object_ref (source1); + nle_composition_remove (GST_BIN (comp), source1); + check_start_stop_duration (comp, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Re-add first source */ + + nle_composition_add (GST_BIN (comp), source1); + commit_and_wait (comp, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + gst_object_unref (source1); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + sink = gst_element_factory_make_or_warn ("fakevideosink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + + gst_element_link (comp, sink); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (FALSE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to NULL"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + GST_DEBUG ("Resetted pipeline to READY"); + + if (collect->seen_segments) + g_list_free (collect->seen_segments); + + collect->seen_segments = NULL; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + collect->gotsegment = FALSE; + collect->expected_base = 0; + + GST_DEBUG ("Setting pipeline to PLAYING again"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + carry_on = TRUE; + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (FALSE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + gst_object_unref (GST_OBJECT (sinkpad)); + + fail_if (collect->expected_segments != NULL); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + collect_free (collect); +} + +GST_START_TEST (test_simplest) +{ + ges_init (); + test_simplest_full (); + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_time_duration) +{ + ges_init (); + test_time_duration_full (); + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_one_after_other) +{ + ges_init (); + test_one_after_other_full (); + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_one_under_another) +{ + ges_init (); + test_one_under_another_full (); + ges_deinit (); +} + +GST_END_TEST; + +GST_START_TEST (test_one_bin_after_other) +{ + ges_init (); + test_one_bin_after_other_full (); + ges_deinit (); +} + +GST_END_TEST; + +static Suite * +gnonlin_suite (void) +{ + Suite *s = suite_create ("gnonlin-simple"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_time_duration); + tcase_add_test (tc_chain, test_simplest); + tcase_add_test (tc_chain, test_one_after_other); + tcase_add_test (tc_chain, test_one_under_another); + tcase_add_test (tc_chain, test_one_bin_after_other); + return s; +} + +GST_CHECK_MAIN (gnonlin) diff --git a/tests/check/nle/tempochange.c b/tests/check/nle/tempochange.c new file mode 100644 index 0000000000..e3d8208cc4 --- /dev/null +++ b/tests/check/nle/tempochange.c @@ -0,0 +1,655 @@ +/* GStreamer Editing Services + * Copyright (C) 2016 Sjors Gielen <mixml-ges@sjorsgielen.nl> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "common.h" +#include "plugins/nle/nleobject.h" + +typedef struct _PadEventData +{ + gchar *name; + guint expect_num_segments; + guint num_segments; + GArray *expect_segment_time; + GArray *expect_segment_num_seeks; + guint expect_num_seeks; + guint num_seeks; + GArray *expect_seek_start; + GArray *expect_seek_stop; + GArray *expect_seek_num_segments; + guint num_eos; + guint expect_num_eos; +} PadEventData; + +#define _SEGMENT_FORMAT "flags: %i, rate: %g, applied_rate: %g, format: %i" \ + ", base: %" G_GUINT64_FORMAT ", offset: %" G_GUINT64_FORMAT ", start: %" \ + G_GUINT64_FORMAT ", stop: %" G_GUINT64_FORMAT ", time: %" G_GUINT64_FORMAT \ + ", position: %" G_GUINT64_FORMAT ", duration: %" G_GUINT64_FORMAT + +#define _SEGMENT_ARGS(seg) (seg).flags, (seg).rate, (seg).applied_rate, \ + (seg).format, (seg).base, (seg).offset, (seg).start, (seg).stop, \ + (seg).time, (seg).position, (seg).duration + +static GstPadProbeReturn +_test_pad_events (GstPad * pad, GstPadProbeInfo * info, PadEventData * data) +{ + guint num; + GstEvent *event = info->data; + if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) { + guint expect_num_seeks; + const GstSegment *segment; + /* copy the segment start, stop, position and duration since these are + * not yet translated by nleghostpad. Also, don't care about flags. */ + GstSegment expect_segment; + + gst_event_parse_segment (event, &segment); + GST_DEBUG ("%s segment: " _SEGMENT_FORMAT, data->name, + _SEGMENT_ARGS (*segment)); + + if (!GST_CLOCK_TIME_IS_VALID (segment->stop)) { + GST_DEBUG ("%s: ignoring pre-roll segment", data->name); + return GST_PAD_PROBE_OK; + } + + data->num_segments++; + num = data->num_segments; + + fail_unless (num <= data->expect_num_segments, "%s received %u " + "segments, more than the expected %u segments", data->name, num, + data->expect_num_segments); + + expect_num_seeks = + g_array_index (data->expect_segment_num_seeks, gint, num - 1); + + fail_unless (data->num_seeks == expect_num_seeks, "%s has received %u " + "segments, compared to %u seeks, but expected %u seeks", + data->name, num, data->num_seeks, expect_num_seeks); + + /* copy the segment start, stop, position, duration, offset, base + * since these are not yet translated by nleghostpad. */ + gst_segment_copy_into (segment, &expect_segment); + expect_segment.rate = 1.0; + expect_segment.applied_rate = 1.0; + expect_segment.format = GST_FORMAT_TIME; + expect_segment.time = g_array_index (data->expect_segment_time, + GstClockTime, num - 1); + + fail_unless (gst_segment_is_equal (segment, &expect_segment), + "%s %uth segment is not equal to the expected. Received:\n" + _SEGMENT_FORMAT "\nExpected\n" _SEGMENT_FORMAT, data->name, + num - 1, _SEGMENT_ARGS (*segment), _SEGMENT_ARGS (expect_segment)); + + } else if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK) { + gdouble rate; + GstFormat format; + GstClockTime expect; + gint64 start, stop; + GstSeekType start_type, stop_type; + guint expect_num_segments; + + gst_event_parse_seek (event, &rate, &format, NULL, &start_type, &start, + &stop_type, &stop); + + GST_DEBUG ("%s seek: rate: %g, start: %" G_GINT64_FORMAT ", stop: %" + G_GINT64_FORMAT, data->name, rate, start, stop); + + data->num_seeks++; + num = data->num_seeks; + + fail_unless (num <= data->expect_num_seeks, "%s received %u " + "seeks, more than the expected %u seeks", data->name, num, + data->expect_num_seeks); + + expect_num_segments = + g_array_index (data->expect_seek_num_segments, gint, num - 1); + + fail_unless (data->num_segments == expect_num_segments, "%s has " + "received %u seeks, compared to %u segments, but expected %u " + "segments", data->name, num, data->num_segments, expect_num_segments); + + fail_unless (rate == 1.0, "%s %uth seek has a rate of %g rather than 1.0", + data->name, num - 1, rate); + fail_unless (format == GST_FORMAT_TIME, "%s %uth seek has a format of %i " + " than a time format", data->name, num - 1, format); + + /* expect seek-set or seek-none */ + fail_if (start_type == GST_SEEK_TYPE_END, "%s %uth seek-start is " + "seek-end", data->name, num - 1); + fail_if (stop_type == GST_SEEK_TYPE_END, "%s %uth seek-stop is " + "seek-end", data->name, num - 1); + + expect = g_array_index (data->expect_seek_start, GstClockTime, num - 1); + fail_unless (start == expect, "%s %uth seek start is %" GST_TIME_FORMAT + ", rather than the expected %" GST_TIME_FORMAT, data->name, num - 1, + GST_TIME_ARGS (start), GST_TIME_ARGS (expect)); + + expect = g_array_index (data->expect_seek_stop, GstClockTime, num - 1); + fail_unless (stop == expect, "%s %uth seek stop is %" GST_TIME_FORMAT + ", rather than the expected %" GST_TIME_FORMAT, data->name, num - 1, + GST_TIME_ARGS (stop), GST_TIME_ARGS (expect)); + + } else if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { + data->num_eos++; + fail_unless (data->num_eos <= data->expect_num_eos, "%s received %u " + "EOS, more than the expected %u EOS", data->name, data->num_eos, + data->expect_num_seeks); + } + + return GST_PAD_PROBE_OK; +} + +static void +_pad_event_data_check_received (PadEventData * data) +{ + fail_unless (data->num_eos == data->expect_num_eos, "%s received %u " + "EOS, rather than %u", data->num_eos, data->expect_num_eos); + fail_unless (data->num_segments == data->expect_num_segments, + "%s received %u segments, rather than %u", data->name, + data->num_segments, data->expect_num_segments); + fail_unless (data->num_seeks == data->expect_num_seeks, + "%s received %u seeks, rather than %u", data->name, data->num_seeks, + data->expect_num_seeks); +} + +static void +_pad_event_data_free (PadEventData * data) +{ + g_free (data->name); + g_array_unref (data->expect_segment_time); + g_array_unref (data->expect_seek_start); + g_array_unref (data->expect_seek_stop); + g_array_unref (data->expect_segment_num_seeks); + g_array_unref (data->expect_seek_num_segments); + g_free (data); +} + +static PadEventData * +_pad_event_data_new (GstElement * element, const gchar * pad_name, + const gchar * suffix) +{ + GstPad *pad; + PadEventData *data = g_new0 (PadEventData, 1); + data->expect_segment_time = g_array_new (FALSE, FALSE, sizeof (GstClockTime)); + data->expect_seek_start = g_array_new (FALSE, FALSE, sizeof (GstClockTime)); + data->expect_seek_stop = g_array_new (FALSE, FALSE, sizeof (GstClockTime)); + data->expect_seek_num_segments = g_array_new (FALSE, FALSE, sizeof (guint)); + data->expect_segment_num_seeks = g_array_new (FALSE, FALSE, sizeof (guint)); + data->name = g_strdup_printf ("%s:%s(%s):%s", G_OBJECT_TYPE_NAME (element), + GST_ELEMENT_NAME (element), pad_name, suffix); + + pad = gst_element_get_static_pad (element, pad_name); + fail_unless (pad, "%s not found", data->name); + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | + GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, (GstPadProbeCallback) _test_pad_events, + data, (GDestroyNotify) _pad_event_data_free); + gst_object_unref (pad); + + return data; +} + +static void +_pad_event_data_add_expect_segment (PadEventData * data, GstClockTime time) +{ + data->expect_num_segments++; + g_array_append_val (data->expect_segment_time, time); + g_array_append_val (data->expect_segment_num_seeks, data->expect_num_seeks); +} + +static void +_pad_event_data_add_expect_seek (PadEventData * data, GstClockTime start, + GstClockTime stop) +{ + data->expect_num_seeks++; + g_array_append_val (data->expect_seek_start, start); + g_array_append_val (data->expect_seek_stop, stop); + g_array_append_val (data->expect_seek_num_segments, + data->expect_num_segments); +} + +static void +_pad_event_data_add_expect_seek_then_segment (PadEventData * data, + GstClockTime start, GstClockTime stop) +{ + _pad_event_data_add_expect_seek (data, start, stop); + _pad_event_data_add_expect_segment (data, start); +} + +#define _EXPECT_SEEK_SEGMENT(data, start, stop) \ + _pad_event_data_add_expect_seek_then_segment (data, start, stop) + +static GstElement * +_get_source (GstElement * nle_source) +{ + GList *tmp; + GstElement *bin, *src = NULL; + + fail_unless (g_list_length (GST_BIN_CHILDREN (nle_source)), 1); + bin = GST_BIN_CHILDREN (nle_source)->data; + fail_unless (GST_IS_BIN (bin)); + + for (tmp = GST_BIN_CHILDREN (bin); src == NULL && tmp; tmp = tmp->next) { + if (g_strrstr (GST_ELEMENT_NAME (tmp->data), "audiotestsrc")) + src = tmp->data; + } + fail_unless (src); + return src; +} + +enum +{ + NLE_PREV_SRC, + NLE_POST_SRC, + NLE_SOURCE_SRC, + NLE_OPER_SRC, + NLE_OPER_SINK, + NLE_IDENTITY_SRC, + PREV_SRC, + POST_SRC, + SOURCE_SRC, + PITCH_SRC, + PITCH_SINK, + IDENTITY_SRC, + SINK_SINK, + NUM_DATA +}; + +static PadEventData ** +_setup_test (GstElement * pipeline, gdouble rate) +{ + GstElement *sink, *pitch, *src, *prev, *post, *identity; + GstElement *comp, *nle_source, *nle_prev, *nle_post, *nle_oper, *nle_identity; + gboolean ret; + gchar *suffix; + PadEventData **data = g_new0 (PadEventData *, NUM_DATA); + + /* composition */ + comp = + gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); + gst_element_set_state (comp, GST_STATE_READY); + + /* sink */ + sink = gst_element_factory_make_or_warn ("fakeaudiosink", "sink"); + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + gst_element_link (comp, sink); + + /* sources */ + nle_source = + audiotest_bin_src ("nle_source", 3 * GST_SECOND, 4 * GST_SECOND, 3, + FALSE); + g_object_set (nle_source, "inpoint", 7 * GST_SECOND, NULL); + src = _get_source (nle_source); + g_object_set (src, "name", "middle-source", NULL); + + nle_prev = + audiotest_bin_src ("nle_previous", 0 * GST_SECOND, 3 * GST_SECOND, 2, + FALSE); + g_object_set (nle_prev, "inpoint", 99 * GST_SECOND, NULL); + prev = _get_source (nle_prev); + g_object_set (src, "name", "previous-source", NULL); + + nle_post = + audiotest_bin_src ("post", 7 * GST_SECOND, 5 * GST_SECOND, 2, FALSE); + g_object_set (nle_post, "inpoint", 20 * GST_SECOND, NULL); + post = _get_source (nle_post); + g_object_set (src, "name", "post-source", NULL); + + /* Operation, must share the same start and duration as the upstream + * source */ + nle_oper = + new_operation ("nle_oper", "pitch", 3 * GST_SECOND, 4 * GST_SECOND, 2); + fail_unless (g_list_length (GST_BIN_CHILDREN (nle_oper)) == 1); + pitch = GST_ELEMENT (GST_BIN_CHILDREN (nle_oper)->data); + g_object_set (pitch, "rate", rate, NULL); + + /* cover with an identity operation + * rate effect has lower priority, so we don't need the same start or + * duration */ + nle_identity = + new_operation ("nle_identity", "identity", 0, 12 * GST_SECOND, 1); + g_object_set (nle_identity, "inpoint", 5 * GST_SECOND, NULL); + fail_unless (g_list_length (GST_BIN_CHILDREN (nle_oper)) == 1); + identity = GST_ELEMENT (GST_BIN_CHILDREN (nle_identity)->data); + + nle_composition_add (GST_BIN (comp), nle_source); + nle_composition_add (GST_BIN (comp), nle_prev); + nle_composition_add (GST_BIN (comp), nle_post); + nle_composition_add (GST_BIN (comp), nle_oper); + nle_composition_add (GST_BIN (comp), nle_identity); + ret = FALSE; + commit_and_wait (comp, &ret); + fail_unless (ret); + + check_start_stop_duration (nle_source, 3 * GST_SECOND, 7 * GST_SECOND, + 4 * GST_SECOND); + check_start_stop_duration (nle_oper, 3 * GST_SECOND, 7 * GST_SECOND, + 4 * GST_SECOND); + check_start_stop_duration (nle_prev, 0, 3 * GST_SECOND, 3 * GST_SECOND); + check_start_stop_duration (nle_post, 7 * GST_SECOND, 12 * GST_SECOND, + 5 * GST_SECOND); + check_start_stop_duration (nle_identity, 0, 12 * GST_SECOND, 12 * GST_SECOND); + check_start_stop_duration (comp, 0, 12 * GST_SECOND, 12 * GST_SECOND); + + /* create data */ + suffix = g_strdup_printf ("rate=%g", rate); + + /* source */ + data[NLE_SOURCE_SRC] = _pad_event_data_new (nle_source, "src", suffix); + data[NLE_PREV_SRC] = _pad_event_data_new (nle_prev, "src", suffix); + data[NLE_POST_SRC] = _pad_event_data_new (nle_post, "src", suffix); + data[SOURCE_SRC] = _pad_event_data_new (src, "src", suffix); + data[PREV_SRC] = _pad_event_data_new (prev, "src", suffix); + data[POST_SRC] = _pad_event_data_new (post, "src", suffix); + + /* rate operation */ + data[NLE_OPER_SRC] = _pad_event_data_new (nle_oper, "src", suffix); + data[NLE_OPER_SINK] = _pad_event_data_new (nle_oper, "sink", suffix); + data[PITCH_SRC] = _pad_event_data_new (pitch, "src", suffix); + data[PITCH_SINK] = _pad_event_data_new (pitch, "sink", suffix); + + /* identity: only care about the source pads */ + data[NLE_IDENTITY_SRC] = _pad_event_data_new (nle_identity, "src", suffix); + data[IDENTITY_SRC] = _pad_event_data_new (identity, "src", suffix); + + /* sink */ + data[SINK_SINK] = _pad_event_data_new (sink, "sink", suffix); + + g_free (suffix); + + return data; +} + + +GST_START_TEST (test_tempochange_play) +{ + GstElement *pipeline; + GstBus *bus; + GstMessage *message; + gboolean carry_on; + PadEventData **data; + gdouble rates[3] = { 0.5, 4.0, 1.0 }; + guint i, j; + + for (i = 0; i < G_N_ELEMENTS (rates); i++) { + gdouble rate = rates[i]; + GST_DEBUG ("rate = %g", rate); + + pipeline = gst_pipeline_new ("test_pipeline"); + + data = _setup_test (pipeline, rate); + + /* initial seek */ + _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 0, 3 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 0, 3 * GST_SECOND); + /* nleobject will convert the seek by removing start and adding inpoint */ + _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 5 * GST_SECOND, 8 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_PREV_SRC], 0, 3 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[PREV_SRC], 99 * GST_SECOND, 102 * GST_SECOND); + + /* rate-stack seek */ + _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 3 * GST_SECOND, 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 3 * GST_SECOND, + 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 8 * GST_SECOND, 12 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SRC], 3 * GST_SECOND, 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[PITCH_SRC], 0, 4 * GST_SECOND); + /* pitch element will change the stop time, e.g. if rate=2.0, then we + * want to use up twice as much source, so the stop time doubles */ + _EXPECT_SEEK_SEGMENT (data[PITCH_SINK], 0, rate * 4 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SINK], 3 * GST_SECOND, + (GstClockTime) (rate * 4 * GST_SECOND) + 3 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_SOURCE_SRC], 3 * GST_SECOND, + (GstClockTime) (rate * 4 * GST_SECOND) + 3 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[SOURCE_SRC], 7 * GST_SECOND, + (GstClockTime) (rate * 4 * GST_SECOND) + 7 * GST_SECOND); + + /* final part only involves post source */ + _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 7 * GST_SECOND, 12 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 7 * GST_SECOND, + 12 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 12 * GST_SECOND, 17 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_POST_SRC], 7 * GST_SECOND, 12 * GST_SECOND); + /* nleobject will convert the seek by removing start and adding + * inpoint */ + _EXPECT_SEEK_SEGMENT (data[POST_SRC], 20 * GST_SECOND, 25 * GST_SECOND); + + /* expect 1 EOS from each, apart from identity, which will get 3 since + * part of 3 stacks */ + for (j = 0; j < NUM_DATA; j++) + data[j]->expect_num_eos = 1; + + data[IDENTITY_SRC]->expect_num_eos = 3; + data[NLE_IDENTITY_SRC]->expect_num_eos = 3; + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + carry_on = TRUE; + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + if (message->src == GST_OBJECT (pipeline)) { + GST_DEBUG ("Setting pipeline to NULL"); + fail_unless (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS); + carry_on = FALSE; + } + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + break; + default: + break; + } + gst_message_unref (message); + } + } + + for (j = 0; j < NUM_DATA; j++) + _pad_event_data_check_received (data[j]); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + g_free (data); + } +} + +GST_END_TEST; + +#define _WAIT_UNTIL_ASYNC_DONE \ +{ \ + GST_DEBUG ("Let's poll the bus"); \ + carry_on = TRUE; \ + while (carry_on) { \ + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); \ + if (message) { \ + switch (GST_MESSAGE_TYPE (message)) { \ + case GST_MESSAGE_EOS: \ + fail_if (TRUE, "Received EOS"); \ + break; \ + case GST_MESSAGE_ERROR: \ + fail_error_message (message); \ + break; \ + case GST_MESSAGE_ASYNC_DONE: \ + carry_on = FALSE; \ + break; \ + default: \ + break; \ + } \ + gst_message_unref (message); \ + } \ + } \ +} + +GST_START_TEST (test_tempochange_seek) +{ + GstElement *pipeline; + GstBus *bus; + GstMessage *message; + gboolean carry_on; + PadEventData **data; + gdouble rates[3] = { 2.0, 0.25, 1.0 }; + guint i, j; + GstClockTime offset = 0.1 * GST_SECOND; + + for (i = 0; i < G_N_ELEMENTS (rates); i++) { + gdouble rate = rates[i]; + GST_DEBUG ("rate = %g", rate); + + pipeline = gst_pipeline_new ("test_pipeline"); + + data = _setup_test (pipeline, rate); + + /* initial seek from the pause */ + _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 0, 3 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 0, 3 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 5 * GST_SECOND, 8 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_PREV_SRC], 0, 3 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[PREV_SRC], 99 * GST_SECOND, 102 * GST_SECOND); + + GST_DEBUG ("Setting pipeline to PAUSED"); + fail_unless (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PAUSED) == GST_STATE_CHANGE_ASYNC); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + _WAIT_UNTIL_ASYNC_DONE; + + for (j = 0; j < NUM_DATA; j++) + _pad_event_data_check_received (data[j]); + + /* first seek for just after the start of the rate effect */ + /* NOTE: neither prev nor post should receive anything */ + + /* sink will receive two seeks: one that initiates the pre-roll, and + * then the seek with the stop set */ + /* expect no segment for the first seek */ + _pad_event_data_add_expect_seek (data[SINK_SINK], 3 * GST_SECOND + offset, + GST_CLOCK_TIME_NONE); + _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 3 * GST_SECOND + offset, + 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 3 * GST_SECOND + offset, + 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 8 * GST_SECOND + offset, + 12 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SRC], 3 * GST_SECOND + offset, + 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[PITCH_SRC], offset, 4 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[PITCH_SINK], rate * offset, + rate * 4 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SINK], + 3 * GST_SECOND + (GstClockTime) (rate * offset), + 3 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND)); + _EXPECT_SEEK_SEGMENT (data[NLE_SOURCE_SRC], + 3 * GST_SECOND + (GstClockTime) (rate * offset), + 3 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND)); + _EXPECT_SEEK_SEGMENT (data[SOURCE_SRC], + 7 * GST_SECOND + (GstClockTime) (rate * offset), + 7 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND)); + + /* perform seek */ + fail_unless (gst_element_seek_simple (pipeline, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, 3 * GST_SECOND + offset)); + + _WAIT_UNTIL_ASYNC_DONE; + + for (j = 0; j < NUM_DATA; j++) + _pad_event_data_check_received (data[j]); + + /* now seek to just before the end */ + _pad_event_data_add_expect_seek (data[SINK_SINK], 7 * GST_SECOND - offset, + GST_CLOCK_TIME_NONE); + _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 7 * GST_SECOND - offset, + 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 7 * GST_SECOND - offset, + 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 12 * GST_SECOND - offset, + 12 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SRC], 7 * GST_SECOND - offset, + 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[PITCH_SRC], 4 * GST_SECOND - offset, + 4 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[PITCH_SINK], + rate * (4 * GST_SECOND) - rate * offset, rate * 4 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SINK], + 3 * GST_SECOND + (GstClockTime) (rate * (4 * GST_SECOND - offset)), + 3 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND)); + _EXPECT_SEEK_SEGMENT (data[NLE_SOURCE_SRC], + 3 * GST_SECOND + (GstClockTime) (rate * (4 * GST_SECOND - offset)), + 3 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND)); + _EXPECT_SEEK_SEGMENT (data[SOURCE_SRC], + 7 * GST_SECOND + (GstClockTime) (rate * (4 * GST_SECOND - offset)), + 7 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND)); + + /* perform seek */ + fail_unless (gst_element_seek_simple (pipeline, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, 7 * GST_SECOND - offset)); + + _WAIT_UNTIL_ASYNC_DONE; + + for (j = 0; j < NUM_DATA; j++) + _pad_event_data_check_received (data[j]); + + GST_DEBUG ("Setting pipeline to NULL"); + fail_unless (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + g_free (data); + } +} + +GST_END_TEST; + +static Suite * +gnonlin_suite (void) +{ + Suite *s = suite_create ("nle"); + TCase *tc_chain = tcase_create ("tempochange"); + + if (atexit (ges_deinit) != 0) { + GST_ERROR ("failed to set ges_deinit as exit function"); + } + + ges_init (); + suite_add_tcase (s, tc_chain); + + /* give the tests a little more time than the default + * CK_DEFAULT_TIMEOUT=20, this is sometimes needed for running under + * valgrind */ + tcase_set_timeout (tc_chain, 40.0); + + tcase_add_test (tc_chain, test_tempochange_play); + tcase_add_test (tc_chain, test_tempochange_seek); + + return s; +} + +GST_CHECK_MAIN (gnonlin) diff --git a/tests/check/nose2-junit-xml.cfg.in b/tests/check/nose2-junit-xml.cfg.in new file mode 100644 index 0000000000..335e33de57 --- /dev/null +++ b/tests/check/nose2-junit-xml.cfg.in @@ -0,0 +1,3 @@ +[junit-xml] +always-on = True +path = @path@ diff --git a/tests/check/python/__init__.py b/tests/check/python/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/check/python/common.py b/tests/check/python/common.py new file mode 100644 index 0000000000..1593d68982 --- /dev/null +++ b/tests/check/python/common.py @@ -0,0 +1,767 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016 Alexandru Băluț <alexandru.balut@gmail.com> +# Copyright (c) 2016, Thibault Saunier +# +# This program 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 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +import gi + +gi.require_version("Gst", "1.0") +gi.require_version("GES", "1.0") + +from gi.repository import Gst # noqa +from gi.repository import GES # noqa +from gi.repository import GLib # noqa +from gi.repository import GObject # noqa +import contextlib # noqa +import os #noqa +import unittest # noqa +import tempfile # noqa + +try: + gi.require_version("GstTranscoder", "1.0") + from gi.repository import GstTranscoder +except ValueError: + GstTranscoder = None + +Gst.init(None) +GES.init() + + +def create_main_loop(): + """Creates a MainLoop with a timeout.""" + mainloop = GLib.MainLoop() + timed_out = False + + def timeout_cb(unused): + nonlocal timed_out + timed_out = True + mainloop.quit() + + def run(timeout_seconds=5, until_empty=False): + source = GLib.timeout_source_new_seconds(timeout_seconds) + source.set_callback(timeout_cb) + source.attach() + if until_empty: + GLib.idle_add(mainloop.quit) + GLib.MainLoop.run(mainloop) + source.destroy() + if timed_out: + raise Exception("Timed out after %s seconds" % timeout_seconds) + + mainloop.run = run + return mainloop + + +def create_project(with_group=False, saved=False): + """Creates a project with two clips in a group.""" + timeline = GES.Timeline.new_audio_video() + layer = timeline.append_layer() + + if with_group: + clip1 = GES.TitleClip() + clip1.set_start(0) + clip1.set_duration(10*Gst.SECOND) + layer.add_clip(clip1) + clip2 = GES.TitleClip() + clip2.set_start(100 * Gst.SECOND) + clip2.set_duration(10*Gst.SECOND) + layer.add_clip(clip2) + group = GES.Container.group([clip1, clip2]) + + if saved: + if isinstance(saved, str): + suffix = "-%s.xges" % saved + else: + suffix = ".xges" + uri = "file://%s" % tempfile.NamedTemporaryFile(suffix=suffix).name + timeline.get_asset().save(timeline, uri, None, overwrite=True) + + return timeline + + +@contextlib.contextmanager +def created_project_file(xges): + _, xges_path = tempfile.mkstemp(suffix=".xges") + with open(xges_path, "w") as f: + f.write(xges) + + yield Gst.filename_to_uri(os.path.abspath(xges_path)) + + os.remove(xges_path) + + +def can_generate_assets(): + if GstTranscoder is None: + return False, "GstTranscoder is not available" + + if not Gst.ElementFactory.make("testsrcbin"): + return False, "testbinsrc is not available" + + return True, None + + +@contextlib.contextmanager +def created_video_asset(uri=None, num_bufs=30): + with tempfile.NamedTemporaryFile(suffix=".ogg") as f: + if not uri: + uri = Gst.filename_to_uri(f.name) + transcoder = GstTranscoder.Transcoder.new("testbin://video,num-buffers=%s" % num_bufs, + uri, "application/ogg:video/x-theora:audio/x-vorbis") + transcoder.run() + + yield uri + + +def get_asset_uri(name): + python_tests_dir = os.path.dirname(os.path.abspath(__file__)) + assets_dir = os.path.join(python_tests_dir, "..", "assets") + return Gst.filename_to_uri(os.path.join(assets_dir, name)) + + +class GESTest(unittest.TestCase): + + def _log(self, func, format, *args): + string = format + if args: + string = string % args[0] + func(string) + + def log(self, format, *args): + self._log(Gst.log, format, *args) + + def debug(self, format, *args): + self._log(Gst.debug, format, *args) + + def info(self, format, *args): + self._log(Gst.info, format, *args) + + def fixme(self, format, *args): + self._log(Gst.fixme, format, *args) + + def warning(self, format, *args): + self._log(Gst.warning, format, *args) + + def error(self, format, *args): + self._log(Gst.error, format, *args) + + def check_clip_values(self, clip, start, in_point, duration): + for elem in [clip] + clip.get_children(False): + self.check_element_values(elem, start, in_point, duration) + + def check_element_values(self, element, start, in_point, duration): + self.assertEqual(element.props.start, start, element) + self.assertEqual(element.props.in_point, in_point, element) + self.assertEqual(element.props.duration, duration, element) + + def assert_effects(self, clip, *effects): + # Make sure there are no other effects. + self.assertEqual(set(clip.get_top_effects()), set(effects)) + + # Make sure their order is correct. + indexes = [clip.get_top_effect_index(effect) + for effect in effects] + self.assertEqual(indexes, list(range(len(effects)))) + + def assertGESError(self, error, code, message=""): + if error is None: + raise AssertionError( + "{}{}Received no error".format(message, message and ": ")) + if error.domain != "GES_ERROR": + raise AssertionError( + "{}{}Received error ({}) in domain {} rather than " + "GES_ERROR".format( + message, message and ": ", error.message, error.domain)) + err_code = GES.Error(error.code) + if err_code != code: + raise AssertionError( + "{}{}Received {} error ({}) rather than {}".format( + message, message and ": ", err_code.value_name, + error.message, code.value_name)) + +class GESSimpleTimelineTest(GESTest): + + def __init__(self, *args): + self.track_types = [GES.TrackType.AUDIO, GES.TrackType.VIDEO] + super(GESSimpleTimelineTest, self).__init__(*args) + + def timeline_as_str(self): + res = "====== %s =======\n" % self.timeline + for layer in self.timeline.get_layers(): + res += "Layer %04d: " % layer.get_priority() + for clip in layer.get_clips(): + res += "{ %s }" % clip + res += '\n------------------------\n' + + for group in self.timeline.get_groups(): + res += "GROUP %s :" % group + for clip in group.get_children(False): + res += " { %s }" % clip.props.name + res += '\n' + res += "================================\n" + return res + + def print_timeline(self): + print(self.timeline_as_str()) + + def setUp(self): + self.timeline = GES.Timeline.new() + for track_type in self.track_types: + self.assertIn( + track_type, [GES.TrackType.AUDIO, GES.TrackType.VIDEO]) + if track_type == GES.TrackType.AUDIO: + self.assertTrue(self.timeline.add_track(GES.AudioTrack.new())) + else: + self.assertTrue(self.timeline.add_track(GES.VideoTrack.new())) + + self.assertEqual(len(self.timeline.get_tracks()), + len(self.track_types)) + self.layer = self.timeline.append_layer() + + def add_clip(self, start, in_point, duration, asset_type=GES.TestClip): + clip = GES.Asset.request(asset_type, None).extract() + clip.props.start = start + clip.props.in_point = in_point + clip.props.duration = duration + self.assertTrue(self.layer.add_clip(clip)) + + return clip + + def append_clip(self, layer=0, asset_type=GES.TestClip, asset_id=None): + while len(self.timeline.get_layers()) < layer + 1: + self.timeline.append_layer() + layer = self.timeline.get_layers()[layer] + if asset_type == GES.UriClip: + asset = GES.UriClipAsset.request_sync(asset_id) + else: + asset = GES.Asset.request(asset_type, asset_id) + clip = asset.extract() + clip.props.start = layer.get_duration() + clip.props.duration = 10 + self.assertTrue(layer.add_clip(clip)) + + return clip + + def assertElementAreEqual(self, ref, element): + self.assertTrue(isinstance(element, type(ref)), "%s and %s do not have the same type!" % (ref, element)) + + props = [p for p in ref.list_properties() if p.name not in ['name'] + and not GObject.type_is_a(p.value_type, GObject.Object)] + for p in props: + pname = p.name + refval = GObject.Value() + refval.init(p.value_type) + refval.set_value(ref.get_property(pname)) + + value = GObject.Value() + value.init(p.value_type) + value.set_value(element.get_property(pname)) + + self.assertTrue(Gst.value_compare(refval, value) == Gst.VALUE_EQUAL, + "%s are not equal: %s != %s\n %s != %s" % (pname, value, refval, element, ref)) + + if isinstance(ref, GES.TrackElement): + self.assertElementAreEqual(ref.get_nleobject(), element.get_nleobject()) + return + + if not isinstance(ref, GES.Clip): + return + + ttypes = [track.type for track in self.timeline.get_tracks()] + for ttype in ttypes: + if ttypes.count(ttype) > 1: + self.warning("Can't deeply check %s and %s " + "(only one track per type supported %s %s found)" % (ref, + element, ttypes.count(ttype), ttype)) + return + + children = element.get_children(False) + for ref_child in ref.get_children(False): + ref_track = ref_child.get_track() + if not ref_track: + self.warning("Can't check %s as not in a track" % (ref_child)) + continue + + child = None + for tmpchild in children: + if not isinstance(tmpchild, type(ref_child)): + continue + + if ref_track.type != tmpchild.get_track().type: + continue + + if not isinstance(ref_child, GES.Effect): + child = tmpchild + break + elif ref_child.props.bin_description == tmpchild.props.bin_description: + child = tmpchild + break + + self.assertIsNotNone(child, "Could not find equivalent child %s in %s(%s)" % (ref_child, + element, children)) + + self.assertElementAreEqual(ref_child, child) + + def check_reload_timeline(self): + tmpf = tempfile.NamedTemporaryFile(suffix='.xges') + uri = Gst.filename_to_uri(tmpf.name) + self.assertTrue(self.timeline.save_to_uri(uri, None, True)) + project = GES.Project.new(uri) + mainloop = create_main_loop() + def loaded_cb(unused_project, unused_timeline): + mainloop.quit() + + project.connect("loaded", loaded_cb) + reloaded_timeline = project.extract() + + mainloop.run() + self.assertIsNotNone(reloaded_timeline) + + layers = self.timeline.get_layers() + reloaded_layers = reloaded_timeline.get_layers() + self.assertEqual(len(layers), len(reloaded_layers)) + for layer, reloaded_layer in zip(layers, reloaded_layers): + clips = layer.get_clips() + reloaded_clips = reloaded_layer.get_clips() + self.assertEqual(len(clips), len(reloaded_clips)) + for clip, reloaded_clip in zip(clips, reloaded_clips): + self.assertElementAreEqual(clip, reloaded_clip) + + return reloaded_timeline + + def assertTimelineTopology(self, topology, groups=[]): + res = [] + for layer in self.timeline.get_layers(): + layer_timings = [] + for clip in layer.get_clips(): + layer_timings.append( + (type(clip), clip.props.start, clip.props.duration)) + for child in clip.get_children(True): + self.assertEqual(child.props.start, clip.props.start) + self.assertEqual(child.props.duration, clip.props.duration) + + res.append(layer_timings) + if topology != res: + Gst.error(self.timeline_as_str()) + self.assertEqual(topology, res) + + timeline_groups = self.timeline.get_groups() + if groups and timeline_groups: + for i, group in enumerate(groups): + self.assertEqual(set(group), set(timeline_groups[i].get_children(False))) + self.assertEqual(len(timeline_groups), i + 1) + + return res + + +class GESTimelineConfigTest(GESTest): + """ + Tests where all the configuration changes, snapping positions and + auto-transitions are accounted for. + """ + + def setUp(self): + timeline = GES.Timeline.new() + self.timeline = timeline + timeline.set_auto_transition(True) + + self.snap_occured = False + self.snap = None + + def snap_started(tl, el1, el2, pos): + if self.snap_occured: + raise AssertionError( + "Previous snap {} not accounted for".format(self.snap)) + self.snap_occured = True + if self.snap is not None: + raise AssertionError( + "Previous snap {} not ended".format(self.snap)) + self.snap = (el1.get_parent(), el2.get_parent(), pos) + + def snap_ended(tl, el1, el2, pos): + self.assertEqual( + self.snap, (el1.get_parent(), el2.get_parent(), pos)) + self.snap = None + + timeline.connect("snapping-started", snap_started) + timeline.connect("snapping-ended", snap_ended) + + self.lost_clips = [] + + def unrecord_lost_clip(layer, clip): + if clip in self.lost_clips: + self.lost_clips.remove(clip) + + def record_lost_clip(layer, clip): + self.lost_clips.append(clip) + + def layer_added(tl, layer): + layer.connect("clip-added", unrecord_lost_clip) + layer.connect("clip-removed", record_lost_clip) + + timeline.connect("layer-added", layer_added) + + self.clips = [] + self.auto_transitions = {} + self.config = {} + + @staticmethod + def new_config(start, duration, inpoint, maxduration, layer): + return {"start": start, "duration": duration, "in-point": inpoint, + "max-duration": maxduration, "layer": layer} + + def add_clip(self, name, layer, tracks, start, duration, inpoint=0, + maxduration=Gst.CLOCK_TIME_NONE, clip_type=GES.TestClip, + asset_id=None, effects=None): + """ + Create a clip with the given @name and properties and add it to the + layer of priority @layer to the tracks in @tracks. Also registers + its expected configuration. + """ + if effects is None: + effects = [] + + lay = self.timeline.get_layer(layer) + while lay is None: + self.timeline.append_layer() + lay = self.timeline.get_layer(layer) + + asset = GES.Asset.request(clip_type, asset_id) + clip = asset.extract() + self.assertTrue(clip.set_name(name)) + # FIXME: would be better to use select-tracks-for-object + # hack around the fact that we cannot use select-tracks-for-object + # in python by setting start to large number to ensure no conflict + # when adding a clip + self.assertTrue(clip.set_start(10000)) + self.assertTrue(clip.set_duration(duration)) + self.assertTrue(clip.set_inpoint(inpoint)) + + for effect in effects: + self.assertTrue(clip.add(effect)) + + if lay.add_clip(clip) != True: + raise AssertionError( + "Failed to add clip {} to layer {}".format(name, layer)) + + # then remove the children not in the selected tracks, which may + # now allow some clips to fully/triple overlap because they do + # not share a track + for child in clip.get_children(False): + if child.get_track() not in tracks: + clip.remove(child) + + # then move to the desired start + prev_snap = self.timeline.get_snapping_distance() + self.timeline.set_snapping_distance(0) + self.assertTrue(clip.set_start(start)) + self.timeline.set_snapping_distance(prev_snap) + + self.assertTrue(clip.set_max_duration(maxduration)) + + self.config[clip] = self.new_config( + start, duration, inpoint, maxduration, layer) + self.clips.append(clip) + + return clip + + def add_group(self, name, to_group): + """ + Create a group with the given @name and the elements in @to_group. + Also registers its expected configuration. + """ + group = GES.Group.new() + self.assertTrue(group.set_name(name)) + start = None + end = None + layer = None + for element in to_group: + if start is None: + start = element.start + end = element.start + element.duration + layer = element.get_layer_priority() + else: + start = min(start, element.start) + end = max(end, element.start + element.duration) + layer = min(layer, element.get_layer_priority()) + self.assertTrue(group.add(element)) + + self.config[group] = self.new_config( + start, end - start, 0, Gst.CLOCK_TIME_NONE, layer) + return group + + def register_auto_transition(self, clip1, clip2, track): + """ + Register that we expect an auto-transition to exist between + @clip1 and @clip2 in @track. + """ + transition = self._find_transition(clip1, clip2, track) + if transition is None: + raise AssertionError( + "{} and {} have no auto-transition in track {}".format( + clip1, clip2, track)) + if transition in self.auto_transitions.values(): + raise AssertionError( + "Auto-transition between {} and {} in track {} already " + "registered".format(clip1, clip2, track)) + key = (clip1, clip2, track) + if key in self.auto_transitions: + raise AssertionError( + "Auto-transition already registered for {}".format(key)) + + self.auto_transitions[key] = transition + + def add_video_track(self): + track = GES.VideoTrack.new() + self.assertTrue(self.timeline.add_track(track)) + return track + + def add_audio_track(self): + track = GES.AudioTrack.new() + self.assertTrue(self.timeline.add_track(track)) + return track + + def assertElementConfig(self, element, config): + for prop in config: + if prop == "layer": + val = element.get_layer_priority() + else: + val = element.get_property(prop) + + if val != config[prop]: + raise AssertionError("{} property {}: {} != {}".format( + element, prop, val, config[prop])) + + @staticmethod + def _source_in_track(clip, track): + if clip.find_track_element(track, GES.Source): + return True + return False + + def _find_transition(self, clip1, clip2, track): + """find transition from earlier clip1 to later clip2""" + if not self._source_in_track(clip1, track) or \ + not self._source_in_track(clip2, track): + return None + + layer_prio = clip1.get_layer_priority() + if layer_prio != clip2.get_layer_priority(): + return None + + if clip1.start >= clip2.start: + return None + + start = clip2.start + end = clip1.start + clip1.duration + if start >= end: + return None + duration = end - start + + layer = self.timeline.get_layer(layer_prio) + self.assertIsNotNone(layer) + + for clip in layer.get_clips(): + children = clip.get_children(False) + if len(children) == 1: + child = children[0] + else: + continue + if isinstance(clip, GES.TransitionClip) and clip.start == start \ + and clip.duration == duration and child.get_track() == track: + return clip + + raise AssertionError( + "No auto-transition between {} and {} in track {}".format( + clip1, clip2, track)) + + def _transition_between(self, new, existing, clip1, clip2, track): + if clip1.start < clip2.start: + entry = (clip1, clip2, track) + else: + entry = (clip2, clip1, track) + trans = self._find_transition(*entry) + + if trans is None: + return + + if entry in new: + new.remove(entry) + self.auto_transitions[entry] = trans + elif entry in existing: + existing.remove(entry) + expect = self.auto_transitions[entry] + if trans != expect: + raise AssertionError( + "Auto-transition between {} and {} in track {} changed " + "from {} to {}".format( + clip1, clip2, track, expect, trans)) + else: + raise AssertionError( + "Unexpected transition found between {} and {} in track {}" + "".format(clip1, clip2, track)) + + def assertTimelineConfig( + self, new_props=None, snap_position=None, snap_froms=None, + snap_tos=None, new_transitions=None, lost_transitions=None): + """ + Check that the timeline configuration has only changed by the + differences present in @new_props. + Check that a snap occurred at @snap_position between one of the + clips in @snap_froms and one of the clips in @snap_tos. + Check that all new transitions in the timeline are present in + @new_transitions. + Checl that all the transitions that were lost are in + @lost_transitions. + """ + if new_props is None: + new_props = {} + if snap_froms is None: + snap_froms = [] + if snap_tos is None: + snap_tos = [] + if new_transitions is None: + new_transitions = [] + if lost_transitions is None: + lost_transitions = [] + + for element, config in new_props.items(): + if element not in self.config: + self.config[element] = {} + + for prop in config: + self.config[element][prop] = new_props[element][prop] + + for element, config in self.config.items(): + self.assertElementConfig(element, config) + + # check that snapping occurred + snaps = [] + for snap_from in snap_froms: + for snap_to in snap_tos: + snaps.append((snap_from, snap_to, snap_position)) + + if self.snap is None: + if snaps: + raise AssertionError( + "No snap occurred, but expected a snap in {}".format(snaps)) + elif not snaps: + if self.snap_occured: + raise AssertionError( + "Snap {} occurred, but expected no snap".format(self.snap)) + elif self.snap not in snaps: + raise AssertionError( + "Snap {} occurred, but expected a snap in {}".format( + self.snap, snaps)) + self.snap_occured = False + + # check that lost transitions are not part of the layer + for clip1, clip2, track in lost_transitions: + key = (clip1, clip2, track) + if key not in self.auto_transitions: + raise AssertionError( + "No such auto-transition between {} and {} in track {} " + "is registered".format(clip1, clip2, track)) + # make sure original transition was removed from the layer + trans = self.auto_transitions[key] + if trans not in self.lost_clips: + raise AssertionError( + "The auto-transition {} between {} and {} track {} was " + "not removed from the layers, but expect it to be lost" + "".format(trans, clip1, clip2, track)) + self.lost_clips.remove(trans) + # make sure a new one wasn't created + trans = self._find_transition(clip1, clip2, track) + if trans is not None: + raise AssertionError( + "Found auto-transition between {} and {} in track {} " + "is present, but expected it to be lost".format( + clip1, clip2, track)) + # since it was lost, remove it + del self.auto_transitions[key] + + # check that all lost clips are accounted for + if self.lost_clips: + raise AssertionError( + "Clips were lost that are not accounted for: {}".format( + self.lost_clips)) + + # check that all other transitions are either existing ones or + # new ones + new = set(new_transitions) + existing = set(self.auto_transitions.keys()) + for i, clip1 in enumerate(self.clips): + for clip2 in self.clips[i+1:]: + for track in self.timeline.get_tracks(): + self._transition_between( + new, existing, clip1, clip2, track) + + # make sure we are not missing any expected transitions + if new: + raise AssertionError( + "Did not find new transitions for {}".format(new)) + if existing: + raise AssertionError( + "Did not find existing transitions for {}".format(existing)) + + # make sure there aren't any clips we are unaware of + transitions = self.auto_transitions.values() + for layer in self.timeline.get_layers(): + for clip in layer.get_clips(): + if clip not in self.clips and clip not in transitions: + raise AssertionError("Unknown clip {}".format(clip)) + + def assertEdit(self, element, layer, mode, edge, position, snap, + snap_froms, snap_tos, new_props, new_transitions, + lost_transitions): + if not element.edit_full(layer, mode, edge, position): + raise AssertionError( + "Edit of {} to layer {}, mode {}, edge {}, at position {} " + "failed when a success was expected".format( + element, layer, mode, edge, position)) + self.assertTimelineConfig( + new_props=new_props, snap_position=snap, snap_froms=snap_froms, + snap_tos=snap_tos, new_transitions=new_transitions, + lost_transitions=lost_transitions) + + def assertFailEdit(self, element, layer, mode, edge, position, err_code): + res = None + error = None + try: + res = element.edit_full(layer, mode, edge, position) + except GLib.Error as exception: + error = exception + + if err_code is None: + if res is not False: + raise AssertionError( + "Edit of {} to layer {}, mode {}, edge {}, at " + "position {} succeeded when a failure was expected" + "".format( + element, layer, mode, edge, position)) + if error is not None: + raise AssertionError( + "Edit of {} to layer {}, mode {}, edge {}, at " + "position {} did produced an error when none was " + "expected".format( + element, layer, mode, edge, position)) + else: + self.assertGESError( + error, err_code, + "Edit of {} to layer {}, mode {}, edge {}, at " + "position {}".format(element, layer, mode, edge, position)) + # should be no change or snapping if edit fails + self.assertTimelineConfig() diff --git a/tests/check/python/overrides_hack.py b/tests/check/python/overrides_hack.py new file mode 100644 index 0000000000..114b94cdba --- /dev/null +++ b/tests/check/python/overrides_hack.py @@ -0,0 +1,25 @@ +import os +import gi.overrides + +LOCAL_OVERRIDE_PATH = "gst-editing-services/bindings/python/gi/overrides/" +FILE = os.path.realpath(__file__) +if not gi.overrides.__path__[0].endswith(LOCAL_OVERRIDE_PATH): + local_overrides = None + # our overrides don't take precedence, let's fix it + for i, path in enumerate(gi.overrides.__path__): + if path.endswith(LOCAL_OVERRIDE_PATH): + local_overrides = path + + if local_overrides: + gi.overrides.__path__.remove(local_overrides) + else: + local_overrides = os.path.abspath(os.path.join(FILE, "../../../../../", LOCAL_OVERRIDE_PATH)) + + gi.overrides.__path__.insert(0, local_overrides) + +# Execute previously set sitecustomize.py script if it existed +if os.environ.get("GST_ENV"): + old_sitecustomize = os.path.join(os.path.dirname(__file__), + "old.sitecustomize.gstuninstalled.py") + if os.path.exists(old_sitecustomize): + exec(compile(open(old_sitecustomize).read(), old_sitecustomize, 'exec')) diff --git a/tests/check/python/test_assets.py b/tests/check/python/test_assets.py new file mode 100644 index 0000000000..e33c623009 --- /dev/null +++ b/tests/check/python/test_assets.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2019 Thibault Saunier <tsaunier@igalia.com> +# +# This program 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 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +from . import overrides_hack + +import os +import gi +import tempfile + +gi.require_version("Gst", "1.0") +gi.require_version("GES", "1.0") + +from gi.repository import Gst # noqa +from gi.repository import GLib # noqa +from gi.repository import GES # noqa +import unittest # noqa +from unittest import mock + +from . import common +from .common import GESSimpleTimelineTest # noqa + +Gst.init(None) +GES.init() + + +class TestTimeline(GESSimpleTimelineTest): + + def test_request_relocated_assets_sync(self): + path = os.path.join(__file__, "../../../", "png.png") + with self.assertRaises(GLib.Error): + GES.UriClipAsset.request_sync(Gst.filename_to_uri(path)) + + GES.add_missing_uri_relocation_uri(Gst.filename_to_uri(os.path.join(__file__, "../../assets")), False) + path = os.path.join(__file__, "../../", "png.png") + self.assertEqual(GES.UriClipAsset.request_sync(Gst.filename_to_uri(path)).props.id, + Gst.filename_to_uri(os.path.join(__file__, "../../assets/png.png"))) + + def test_request_relocated_twice(self): + GES.add_missing_uri_relocation_uri(Gst.filename_to_uri(os.path.join(__file__, "../../")), True) + proj = GES.Project.new() + + asset = proj.create_asset_sync("file:///png.png", GES.UriClip) + self.assertIsNotNone(asset) + asset = proj.create_asset_sync("file:///png.png", GES.UriClip) + self.assertIsNotNone(asset) + + @unittest.skipUnless(*common.can_generate_assets()) + def test_reload_asset(self): + with common.created_video_asset() as uri: + asset0 = GES.UriClipAsset.request_sync(uri) + self.assertEqual(asset0.props.duration, Gst.SECOND) + + with common.created_video_asset(uri, 60) as uri: + GES.Asset.needs_reload(GES.UriClip, uri) + asset1 = GES.UriClipAsset.request_sync(uri) + self.assertEqual(asset1.props.duration, 2 * Gst.SECOND) + self.assertEqual(asset1, asset0) + + with common.created_video_asset(uri, 90) as uri: + mainloop = common.create_main_loop() + def asset_loaded_cb(_, res, mainloop): + asset2 = GES.Asset.request_finish(res) + self.assertEqual(asset2.props.duration, 3 * Gst.SECOND) + self.assertEqual(asset2, asset0) + mainloop.quit() + + GES.Asset.needs_reload(GES.UriClip, uri) + GES.Asset.request_async(GES.UriClip, uri, None, asset_loaded_cb, mainloop) + mainloop.run() + + def test_asset_metadata_on_reload(self): + mainloop = GLib.MainLoop() + + unused, xges_path = tempfile.mkstemp(suffix=".xges") + project_uri = Gst.filename_to_uri(os.path.abspath(xges_path)) + + asset_uri = Gst.filename_to_uri(os.path.join(__file__, "../../assets/audio_video.ogg")) + xges = """<ges version='0.3'> + <project properties='properties;' metadatas='metadatas;'> + <ressources> + <asset id='%(uri)s' extractable-type-name='GESUriClip' properties='properties, supported-formats=(int)6, duration=(guint64)2003000000;' metadatas='metadatas, container-format=(string)Matroska, language-code=(string)und, application-name=(string)Lavc56.60.100, encoder-version=(uint)0, audio-codec=(string)Vorbis, nominal-bitrate=(uint)80000, bitrate=(uint)80000, video-codec=(string)"On2\ VP8", file-size=(guint64)223340, foo=(string)bar;' > + </asset> + </ressources> + </project> + </ges>"""% {"uri": asset_uri} + with open(xges_path, "w") as xges_file: + xges_file.write(xges) + + + def loaded_cb(project, timeline): + asset = project.list_assets(GES.Extractable)[0] + self.assertEqual(asset.get_meta("foo"), "bar") + mainloop.quit() + + loaded_project = GES.Project(uri=project_uri, extractable_type=GES.Timeline) + loaded_project.connect("loaded", loaded_cb) + timeline = loaded_project.extract() + mainloop.run() diff --git a/tests/check/python/test_clip.py b/tests/check/python/test_clip.py new file mode 100644 index 0000000000..e0784bfec5 --- /dev/null +++ b/tests/check/python/test_clip.py @@ -0,0 +1,378 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, Thibault Saunier +# +# This program 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 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +from . import overrides_hack + +import os +import tempfile + +import gi +gi.require_version("Gst", "1.0") +gi.require_version("GES", "1.0") + +from gi.repository import Gst # noqa +Gst.init(None) # noqa +from gi.repository import GES # noqa +GES.init() + +from . import common # noqa + +import unittest # noqa + + +class TestCopyPaste(unittest.TestCase): + + def setUp(self): + self.timeline = GES.Timeline.new_audio_video() + self.assertEqual(len(self.timeline.get_tracks()), 2) + self.layer = self.timeline.append_layer() + + def testCopyClipRemoveAndPaste(self): + clip1 = GES.TestClip.new() + clip1.props.duration = 10 + + self.layer.add_clip(clip1) + + self.assertEqual(len(clip1.get_children(False)), 2) + + copy = clip1.copy(True) + self.assertEqual(len(self.layer.get_clips()), 1) + + self.layer.remove_clip(clip1) + + copy.paste(10) + self.assertEqual(len(self.layer.get_clips()), 1) + + def testCopyPasteTitleClip(self): + clip1 = GES.TitleClip.new() + clip1.props.duration = 10 + + self.layer.add_clip(clip1) + self.assertEqual(len(clip1.get_children(False)), 1) + + copy = clip1.copy(True) + self.assertEqual(len(self.layer.get_clips()), 1) + + copy.paste(10) + self.assertEqual(len(self.layer.get_clips()), 2) + + +class TestTransitionClip(unittest.TestCase): + + def test_serialize_invert(self): + timeline = GES.Timeline.new() + timeline.add_track(GES.VideoTrack.new()) + layer = timeline.append_layer() + + clip1 = GES.TransitionClip.new_for_nick("crossfade") + clip1.props.duration = Gst.SECOND + self.assertTrue(layer.add_clip(clip1)) + + vtransition, = clip1.children + vtransition.set_inverted(True) + self.assertEqual(vtransition.props.invert, True) + + with tempfile.NamedTemporaryFile() as tmpxges: + uri = Gst.filename_to_uri(tmpxges.name) + timeline.save_to_uri(uri, None, True) + + timeline = GES.Timeline.new_from_uri(uri) + project = timeline.get_asset() + mainloop = common.create_main_loop() + project.connect("loaded", lambda _, __: mainloop.quit()) + mainloop.run() + self.assertIsNotNone(timeline) + layer, = timeline.get_layers() + clip, = layer.get_clips() + vtransition, = clip.children + self.assertEqual(vtransition.props.invert, True) + +class TestTitleClip(unittest.TestCase): + + def testSetColor(self): + timeline = GES.Timeline.new_audio_video() + clip = GES.TitleClip.new() + timeline.append_layer().add_clip(clip ) + self.assertTrue(clip.set_child_property('color', 1)) + self.assertTrue(clip.set_child_property('color', 4294967295)) + + def testGetPropertyNotInTrack(self): + title_clip = GES.TitleClip.new() + self.assertEqual(title_clip.props.text, "") + self.assertEqual(title_clip.props.font_desc, "Serif 36") + + def test_split_effect(self): + timeline = GES.Timeline.new() + timeline.add_track(GES.VideoTrack.new()) + layer = timeline.append_layer() + + clip1 = GES.TitleClip.new() + clip1.props.duration = Gst.SECOND + self.assertTrue(layer.add_clip(clip1)) + + effect = GES.Effect.new("agingtv") + self.assertTrue(clip1.add(effect)) + + children1 = clip1.get_children(True) + self.assertNotEqual(children1[0].props.priority, + children1[1].props.priority) + + clip2 = clip1.split(Gst.SECOND / 2) + + children1 = clip1.get_children(True) + self.assertNotEqual(children1[0].props.priority, + children1[1].props.priority) + + children2 = clip2.get_children(True) + self.assertNotEqual(children2[0].props.priority, + children2[1].props.priority) + + +class TestUriClip(common.GESSimpleTimelineTest): + def test_max_duration_on_extract(self): + asset = GES.UriClipAsset.request_sync(common.get_asset_uri("audio_video.ogg")) + clip = asset.extract() + + self.assertEqual(clip.props.max_duration, Gst.SECOND) + + +class TestTrackElements(common.GESSimpleTimelineTest): + + def test_add_to_layer_with_effect_remove_add(self): + timeline = GES.Timeline.new_audio_video() + video_track, audio_track = timeline.get_tracks() + layer = timeline.append_layer() + + test_clip = GES.TestClip() + self.assertEqual(test_clip.get_children(True), []) + self.assertTrue(layer.add_clip(test_clip)) + audio_source = test_clip.find_track_element(None, GES.AudioSource) + video_source = test_clip.find_track_element(None, GES.VideoSource) + + self.assertTrue(test_clip.set_child_property("volume", 0.0)) + self.assertEqual(audio_source.get_child_property("volume")[1], 0.0) + + effect = GES.Effect.new("agingtv") + test_clip.add(effect) + self.assertEqual(audio_source.props.track, audio_track) + self.assertEqual(video_source.props.track, video_track) + self.assertEqual(effect.props.track, video_track) + + children = test_clip.get_children(True) + layer.remove_clip(test_clip) + self.assertEqual(test_clip.get_children(True), children) + self.assertEqual(audio_source.props.track, None) + self.assertEqual(video_source.props.track, None) + self.assertEqual(effect.props.track, None) + + self.assertTrue(layer.add_clip(test_clip)) + self.assertEqual(test_clip.get_children(True), children) + self.assertEqual(audio_source.props.track, audio_track) + self.assertEqual(video_source.props.track, video_track) + self.assertEqual(effect.props.track, video_track) + + audio_source = test_clip.find_track_element(None, GES.AudioSource) + self.assertFalse(audio_source is None) + self.assertEqual(audio_source.get_child_property("volume")[1], 0.0) + self.assertEqual(audio_source.props.track, audio_track) + self.assertEqual(video_source.props.track, video_track) + self.assertEqual(effect.props.track, video_track) + + def test_effects_priority(self): + timeline = GES.Timeline.new_audio_video() + layer = timeline.append_layer() + + test_clip = GES.TestClip.new() + layer.add_clip(test_clip) + self.assert_effects(test_clip) + + effect1 = GES.Effect.new("agingtv") + test_clip.add(effect1) + self.assert_effects(test_clip, effect1) + + test_clip.set_top_effect_index(effect1, 1) + self.assert_effects(test_clip, effect1) + test_clip.set_top_effect_index(effect1, 10) + self.assert_effects(test_clip, effect1) + + effect2 = GES.Effect.new("dicetv") + test_clip.add(effect2) + self.assert_effects(test_clip, effect1, effect2) + + test_clip.remove(effect1) + self.assert_effects(test_clip, effect2) + + def test_effects_index(self): + timeline = GES.Timeline.new_audio_video() + layer = timeline.append_layer() + + test_clip = GES.TestClip.new() + layer.add_clip(test_clip) + self.assert_effects(test_clip) + + ref_effects_list = [] + + def add_effect(effect): + test_clip.add(effect) + ref_effects_list.append(effect) + + self.assert_effects(test_clip, *ref_effects_list) + + def move_effect(old_index, new_index): + effect = ref_effects_list[old_index] + self.assertTrue(test_clip.set_top_effect_index(effect, new_index)) + + ref_effects_list.insert(new_index, ref_effects_list.pop(old_index)) + + self.assert_effects(test_clip, *ref_effects_list) + + effects = ["agingtv", "dicetv", "burn", "gamma", "edgetv", "alpha", + "exclusion", "chromahold", "coloreffects", "videobalance"] + + for effect in effects: + add_effect(GES.Effect.new(effect)) + + move_effect(3, 8) + move_effect(5, 6) + move_effect(0, 9) + + self.assert_effects(test_clip, *ref_effects_list) + + def test_signal_order_when_removing_effect(self): + timeline = GES.Timeline.new_audio_video() + layer = timeline.append_layer() + + test_clip = GES.TestClip.new() + layer.add_clip(test_clip) + self.assert_effects(test_clip) + + effect1 = GES.Effect.new("agingtv") + test_clip.add(effect1) + effect2 = GES.Effect.new("dicetv") + test_clip.add(effect2) + self.assert_effects(test_clip, effect1, effect2) + + mainloop = common.create_main_loop() + + signals = [] + + def handler_cb(*args): + signals.append(args[-1]) + + test_clip.connect("child-removed", handler_cb, "child-removed") + effect2.connect("notify::priority", handler_cb, "notify::priority") + test_clip.remove(effect1) + test_clip.disconnect_by_func(handler_cb) + effect2.disconnect_by_func(handler_cb) + self.assert_effects(test_clip, effect2) + + mainloop.run(until_empty=True) + + self.assertEqual(signals, ["child-removed", "notify::priority"]) + + def test_moving_core_track_elements(self): + clip = self.append_clip() + clip1 = self.append_clip() + title_clip = self.append_clip(asset_type=GES.TitleClip) + + track_element = clip.find_track_element(None, GES.VideoSource) + self.assertTrue(clip.remove(track_element)) + + track_element1 = clip1.find_track_element(None, GES.VideoSource) + self.assertTrue(clip1.remove(track_element1)) + + self.assertTrue(clip1.add(track_element)) + self.assertIsNotNone(track_element.get_track()) + # We can add another TestSource to the clip as it has the same parent + # asset + self.assertTrue(clip1.add(track_element1)) + # We already have a core TrackElement for the video track, not adding + # a second one. + self.assertIsNone(track_element1.get_track()) + + clip1.remove(track_element) + clip1.remove(track_element1) + title = title_clip.find_track_element(None, GES.VideoSource) + self.assertTrue(title_clip.remove(title)) + # But we can't add an element that has been created by a TitleClip + self.assertFalse(clip.add(title)) + self.assertFalse(title_clip.add(track_element)) + self.assertTrue(clip.add(track_element)) + self.assertTrue(clip1.add(track_element1)) + + def test_ungroup_regroup(self): + clip = self.append_clip() + children = clip.get_children(True) + + clip1, clip2 = GES.Container.ungroup(clip, True) + + self.assertEqual(clip, clip1) + clip1_child, = clip1.get_children(True) + clip2_child, = clip2.get_children(True) + self.assertCountEqual (children, [clip1_child, clip2_child]) + + # can freely move children between the ungrouped clips + self.assertTrue(clip1.remove(clip1_child)) + self.assertTrue(clip2.add(clip1_child)) + + self.assertTrue(clip2.remove(clip2_child)) + self.assertTrue(clip1.add(clip2_child)) + + grouped = GES.Container.group([clip1, clip2]) + self.assertEqual(grouped, clip1) + + self.assertCountEqual(clip1.get_children(True), + [clip1_child, clip2_child]) + self.assertEqual(clip2.get_children(True), []) + + # can freely move children between the grouped clips + self.assertTrue(clip1.remove(clip2_child)) + self.assertTrue(clip2.add(clip2_child)) + + self.assertTrue(clip1.remove(clip1_child)) + self.assertTrue(clip2.add(clip1_child)) + + self.assertTrue(clip2.remove(clip1_child)) + self.assertTrue(clip1.add(clip1_child)) + + self.assertTrue(clip2.remove(clip2_child)) + self.assertTrue(clip1.add(clip2_child)) + + # clip2 no longer part of the timeline + self.assertIsNone(clip2.props.layer) + self.assertEqual(clip1.props.layer, self.layer) + self.assertIsNone(clip2.props.timeline) + self.assertEqual(clip1.props.timeline, self.timeline) + + def test_image_source_asset(self): + asset = GES.UriClipAsset.request_sync(common.get_asset_uri("png.png")) + clip = self.layer.add_asset(asset, 0, 0, Gst.SECOND, GES.TrackType.UNKNOWN) + + image_src, = clip.get_children(True) + + self.assertTrue(image_src.get_asset().is_image()) + self.assertTrue(isinstance(image_src, GES.VideoUriSource)) + imagefreeze, = [e for e in image_src.get_nleobject().iterate_recurse() + if e.get_factory().get_name() == "imagefreeze"] + + asset = GES.UriClipAsset.request_sync(common.get_asset_uri("audio_video.ogg")) + clip = self.layer.add_asset(asset, Gst.SECOND, 0, Gst.SECOND, GES.TrackType.VIDEO) + video_src, = clip.get_children(True) + self.assertEqual([e for e in video_src.get_nleobject().iterate_recurse() + if e.get_factory().get_name() == "imagefreeze"], []) diff --git a/tests/check/python/test_group.py b/tests/check/python/test_group.py new file mode 100644 index 0000000000..ab026eb631 --- /dev/null +++ b/tests/check/python/test_group.py @@ -0,0 +1,407 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, Thibault Saunier +# +# This program 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 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +from . import overrides_hack + +import gi + +gi.require_version("Gst", "1.0") +gi.require_version("GES", "1.0") + +from gi.repository import Gst # noqa +from gi.repository import GES # noqa + +from . import common # noqa + +import unittest # noqa +from unittest import mock + +Gst.init(None) +GES.init() + + +class TestGroup(common.GESSimpleTimelineTest): + + def testCopyGroup(self): + clip1 = GES.TestClip.new() + clip1.props.duration = 10 + + self.layer.add_clip(clip1) + + self.assertEqual(len(clip1.get_children(False)), 2) + + group = GES.Group.new() + self.assertTrue(group.add(clip1)) + + self.assertEqual(len(group.get_children(False)), 1) + + group_copy = group.copy(True) + self.assertEqual(len(group_copy.get_children(False)), 0) + + self.assertTrue(group_copy.paste(10)) + clips = self.layer.get_clips() + self.assertEqual(len(clips), 2) + self.assertEqual(clips[1].props.start, 10) + + clips[1].edit([], 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10) + clips = self.layer.get_clips() + self.assertEqual(len(clips), 1) + + def testPasteChangedGroup(self): + clip1 = GES.TestClip.new() + clip1.props.duration = 10 + + clip2 = GES.TestClip.new() + clip2.props.start = 20 + clip2.props.duration = 10 + + self.layer.add_clip(clip1) + self.layer.add_clip(clip2) + + self.assertEqual(len(clip1.get_children(False)), 2) + + group = GES.Group.new() + self.assertTrue(group.add(clip1)) + + self.assertEqual(len(group.get_children(False)), 1) + + group_copy = group.copy(True) + self.assertEqual(len(group_copy.get_children(False)), 0) + + self.assertTrue(group.add(clip2)) + self.assertEqual(len(group.get_children(False)), 2) + self.assertEqual(len(group_copy.get_children(False)), 0) + + self.assertTrue(group_copy.paste(10)) + clips = self.layer.get_clips() + self.assertEqual(len(clips), 3) + self.assertEqual(clips[1].props.start, 10) + + def testPasteChangedGroup(self): + clip1 = GES.TestClip.new() + clip1.props.duration = 10 + + clip2 = GES.TestClip.new() + clip2.props.start = 20 + clip2.props.duration = 10 + + self.layer.add_clip(clip1) + self.layer.add_clip(clip2) + + self.assertEqual(len(clip1.get_children(False)), 2) + + group = GES.Group.new() + self.assertTrue(group.add(clip1)) + + self.assertEqual(len(group.get_children(False)), 1) + + group_copy = group.copy(True) + self.assertEqual(len(group_copy.get_children(False)), 0) + + self.assertTrue(group.add(clip2)) + self.assertEqual(len(group.get_children(False)), 2) + self.assertEqual(len(group_copy.get_children(False)), 0) + + self.assertTrue(group_copy.paste(10)) + clips = self.layer.get_clips() + self.assertEqual(len(clips), 3) + self.assertEqual(clips[1].props.start, 10) + + def test_move_clips_between_layers_with_auto_transition(self): + self.timeline.props.auto_transition = True + layer2 = self.timeline.append_layer() + clip1 = GES.TestClip.new() + clip1.props.start = 0 + clip1.props.duration = 30 + + clip2 = GES.TestClip.new() + clip2.props.start = 20 + clip2.props.duration = 20 + + self.layer.add_clip(clip1) + self.layer.add_clip(clip2) + + clips = self.layer.get_clips() + self.assertEqual(len(clips), 4) + self.assertEqual(layer2.get_clips(), []) + + group = GES.Container.group(clips) + self.assertIsNotNone(group) + + self.assertTrue(clip1.edit( + self.timeline.get_layers(), 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0)) + self.assertEqual(self.layer.get_clips(), []) + + clips = layer2.get_clips() + self.assertEqual(len(clips), 4) + + def test_remove_emits_signal(self): + clip1 = GES.TestClip.new() + self.layer.add_clip(clip1) + + group = GES.Group.new() + child_removed_cb = mock.Mock() + group.connect("child-removed", child_removed_cb) + + group.add(clip1) + group.remove(clip1) + child_removed_cb.assert_called_once_with(group, clip1) + + group.add(clip1) + child_removed_cb.reset_mock() + group.ungroup(recursive=False) + child_removed_cb.assert_called_once_with(group, clip1) + + def test_loaded_project_has_groups(self): + mainloop = common.create_main_loop() + timeline = common.create_project(with_group=True, saved=True) + layer, = timeline.get_layers() + group, = timeline.get_groups() + self.assertEqual(len(layer.get_clips()), 2) + for clip in layer.get_clips(): + self.assertEqual(clip.get_parent(), group) + + # Reload the project, check the group. + project = GES.Project.new(uri=timeline.get_asset().props.uri) + + loaded_called = False + def loaded(unused_project, unused_timeline): + nonlocal loaded_called + loaded_called = True + mainloop.quit() + project.connect("loaded", loaded) + + timeline = project.extract() + + mainloop.run() + self.assertTrue(loaded_called) + + layer, = timeline.get_layers() + group, = timeline.get_groups() + self.assertEqual(len(layer.get_clips()), 2) + for clip in layer.get_clips(): + self.assertEqual(clip.get_parent(), group) + + def test_moving_group_with_transition(self): + self.timeline.props.auto_transition = True + clip1 = GES.TestClip.new() + clip1.props.start = 0 + clip1.props.duration = 30 + + clip2 = GES.TestClip.new() + clip2.props.start = 20 + clip2.props.duration = 20 + + self.layer.add_clip(clip1) + self.layer.add_clip(clip2) + + clips = self.layer.get_clips() + self.assertEqual(len(clips), 4) + + video_transition = None + audio_transition = None + for clip in clips: + if isinstance(clip, GES.TransitionClip): + if isinstance(clip.get_children(False)[0], GES.VideoTransition): + video_transition = clip + else: + audio_transition = clip + self.assertIsNotNone(audio_transition) + self.assertIsNotNone(video_transition) + + self.assertEqual(video_transition.props.start, 20) + self.assertEqual(video_transition.props.duration, 10) + self.assertEqual(audio_transition.props.start, 20) + self.assertEqual(audio_transition.props.duration, 10) + + group = GES.Container.group(clips) + self.assertIsNotNone(group) + + self.assertTrue(clip2.edit( + self.timeline.get_layers(), 0, + GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 25)) + clip2.props.start = 25 + + clips = self.layer.get_clips() + self.assertEqual(len(clips), 4) + self.assertEqual(clip1.props.start, 5) + self.assertEqual(clip1.props.duration, 30) + self.assertEqual(clip2.props.start, 25) + self.assertEqual(clip2.props.duration, 20) + + self.assertEqual(video_transition.props.start, 25) + self.assertEqual(video_transition.props.duration, 10) + self.assertEqual(audio_transition.props.start, 25) + self.assertEqual(audio_transition.props.duration, 10) + + def test_moving_group_snapping_from_the_middle(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + snapped_positions = [] + def snapping_started_cb(timeline, first_element, second_element, + position, snapped_positions): + snapped_positions.append(position) + + self.timeline.props.snapping_distance = 5 + self.timeline.connect("snapping-started", snapping_started_cb, + snapped_positions) + + for start in range(0, 20, 5): + clip = GES.TestClip.new() + clip.props.start = start + clip.props.duration = 5 + self.layer.add_clip(clip) + + clips = self.layer.get_clips() + self.assertEqual(len(clips), 4) + + group = GES.Container.group(clips[1:3]) + self.assertIsNotNone(group) + + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 5), + (GES.TestClip, 5, 5), + (GES.TestClip, 10, 5), + (GES.TestClip, 15, 5), + ], + ], groups=[clips[1:3]]) + + self.assertEqual(clips[1].props.start, 5) + self.assertEqual(clips[2].props.start, 10) + clips[2].edit([], 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 11) + + self.assertEqual(snapped_positions[0], 5) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 5), + (GES.TestClip, 5, 5), + (GES.TestClip, 10, 5), + (GES.TestClip, 15, 5), + ], + ], groups=[clips[1:3]]) + + def test_rippling_with_group(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + for _ in range(4): + self.append_clip() + + snapped_positions = [] + def snapping_started_cb(timeline, first_element, second_element, + position, snapped_positions): + snapped_positions.append(position) + + self.timeline.props.snapping_distance = 5 + self.timeline.connect("snapping-started", snapping_started_cb, + snapped_positions) + + clips = self.layer.get_clips() + self.assertEqual(len(clips), 4) + + group_clips = clips[1:3] + GES.Container.group(group_clips) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ], + ], groups=[group_clips]) + + self.assertFalse(clips[2].edit([], 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 5)) + + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ], + ], groups=[group_clips]) + + # Negative start... + self.assertFalse(clips[2].edit([], 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 1)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ], + ], groups=[group_clips]) + + self.assertTrue(clips[2].edit([], 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 20)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + ], + [ + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ], + ], groups=[group_clips]) + + def test_group_priority(self): + self.track_types = [GES.TrackType.AUDIO] + self.setUp() + + clip0 = self.append_clip() + clip1 = self.append_clip(1) + clip1.props.start = 20 + + group = GES.Group.new() + group.add(clip0) + group.add(clip1) + self.assertEqual(group.get_layer_priority(), 0) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + ], + [ + (GES.TestClip, 20, 10), + ] + ], groups=[(clip0, clip1)]) + group.remove(clip0) + self.assertEqual(group.get_layer_priority(), 1) + + clip1.edit(self.timeline.get_layers(), 2, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, clip1.start) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + ], + [ ], + [ + (GES.TestClip, 20, 10), + ] + ], groups=[(clip1,)]) + + self.assertEqual(group.get_layer_priority(), 2) + self.assertTrue(clip1.edit(self.timeline.get_layers(), 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, clip1.start)) + + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 20, 10), + ], + [ ], + [ ] + ], groups=[(clip1,)]) diff --git a/tests/check/python/test_timeline.py b/tests/check/python/test_timeline.py new file mode 100644 index 0000000000..e28607bcf5 --- /dev/null +++ b/tests/check/python/test_timeline.py @@ -0,0 +1,3771 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016 Alexandru Băluț <alexandru.balut@gmail.com> +# +# This program 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 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +from . import overrides_hack + +import tempfile # noqa +import gi + +gi.require_version("Gst", "1.0") +gi.require_version("GES", "1.0") + +from gi.repository import Gst # noqa +from gi.repository import GES # noqa +from gi.repository import GLib # noqa +import unittest # noqa +from unittest import mock + +from . import common # noqa + +Gst.init(None) +GES.init() + + +class TestTimeline(common.GESSimpleTimelineTest): + + def test_signals_not_emitted_when_loading(self): + mainloop = common.create_main_loop() + timeline = common.create_project(with_group=True, saved=True) + + # Reload the project, check the group. + project = GES.Project.new(uri=timeline.get_asset().props.uri) + + loaded_called = False + def loaded(unused_project, unused_timeline): + nonlocal loaded_called + loaded_called = True + mainloop.quit() + project.connect("loaded", loaded) + + timeline = project.extract() + + signals = ["layer-added", "group-added", "track-added"] + handle = mock.Mock() + for signal in signals: + timeline.connect(signal, handle) + + mainloop.run() + self.assertTrue(loaded_called) + handle.assert_not_called() + + def test_deeply_nested_serialization(self): + deep_timeline = common.create_project(with_group=True, saved="deep") + deep_project = deep_timeline.get_asset() + + deep_asset = GES.UriClipAsset.request_sync(deep_project.props.id) + + nested_timeline = common.create_project(with_group=False, saved=False) + nested_project = nested_timeline.get_asset() + nested_project.add_asset(deep_project) + nested_timeline.append_layer().add_asset(deep_asset, 0, 0, 5 * Gst.SECOND, GES.TrackType.UNKNOWN) + + uri = "file://%s" % tempfile.NamedTemporaryFile(suffix="-nested.xges").name + nested_timeline.get_asset().save(nested_timeline, uri, None, overwrite=True) + + asset = GES.UriClipAsset.request_sync(nested_project.props.id) + project = self.timeline.get_asset() + project.add_asset(nested_project) + refclip = self.layer.add_asset(asset, 0, 0, 5 * Gst.SECOND, GES.TrackType.VIDEO) + + uri = "file://%s" % tempfile.NamedTemporaryFile(suffix=".xges").name + project.save(self.timeline, uri, None, overwrite=True) + self.assertEqual(len(project.list_assets(GES.Extractable)), 2) + + mainloop = common.create_main_loop() + def loaded_cb(unused_project, unused_timeline): + mainloop.quit() + project.connect("loaded", loaded_cb) + + # Extract again the timeline and compare with previous one. + timeline = project.extract() + mainloop.run() + layer, = timeline.get_layers() + clip, = layer.get_clips() + self.assertEqual(clip.props.uri, refclip.props.uri) + self.assertEqual(timeline.props.duration, self.timeline.props.duration) + + self.assertEqual(timeline.get_asset(), project) + self.assertEqual(len(project.list_assets(GES.Extractable)), 2) + + def test_iter_timeline(self): + all_clips = set() + for l in range(5): + self.timeline.append_layer() + for _ in range(5): + all_clips.add(self.append_clip(l)) + self.assertEqual(set(self.timeline.iter_clips()), all_clips) + + + def test_nested_serialization(self): + nested_timeline = common.create_project(with_group=True, saved=True) + nested_project = nested_timeline.get_asset() + layer = nested_timeline.append_layer() + + asset = GES.UriClipAsset.request_sync(nested_project.props.id) + refclip = self.layer.add_asset(asset, 0, 0, 110 * Gst.SECOND, GES.TrackType.UNKNOWN) + nested_project.save(nested_timeline, nested_project.props.id, None, True) + + project = self.timeline.get_asset() + project.add_asset(nested_project) + uri = "file://%s" % tempfile.NamedTemporaryFile(suffix=".xges").name + self.assertEqual(len(project.list_assets(GES.Extractable)), 2) + project.save(self.timeline, uri, None, overwrite=True) + self.assertEqual(len(project.list_assets(GES.Extractable)), 2) + + mainloop = common.create_main_loop() + def loaded(unused_project, unused_timeline): + mainloop.quit() + project.connect("loaded", loaded) + + # Extract again the timeline and compare with previous one. + timeline = project.extract() + mainloop.run() + layer, = timeline.get_layers() + clip, = layer.get_clips() + self.assertEqual(clip.props.uri, refclip.props.uri) + self.assertEqual(timeline.props.duration, self.timeline.props.duration) + + self.assertEqual(timeline.get_asset(), project) + self.assertEqual(len(project.list_assets(GES.Extractable)), 2) + + def test_timeline_duration(self): + self.append_clip() + self.append_clip() + clips = self.layer.get_clips() + + self.assertEqual(self.timeline.props.duration, 20) + self.layer.remove_clip(clips[1]) + self.assertEqual(self.timeline.props.duration, 10) + + self.append_clip() + self.append_clip() + clips = self.layer.get_clips() + self.assertEqual(self.timeline.props.duration, 30) + + group = GES.Container.group(clips[1:]) + self.assertEqual(self.timeline.props.duration, 30) + + group1 = GES.Container.group([]) + group1.add(group) + self.assertEqual(self.timeline.props.duration, 30) + + def test_spliting_with_auto_transition_on_the_left(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + + self.timeline.props.auto_transition = True + clip1 = self.add_clip(0, 0, 100) + clip2 = self.add_clip(50, 0, 100) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 100), + (GES.TransitionClip, 50, 50), + (GES.TestClip, 50, 100) + ] + ]) + + clip1.split(25) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 25), + (GES.TestClip, 25, 75), + (GES.TransitionClip, 50, 50), + (GES.TestClip, 50, 100), + ] + ]) + + clip2.split(125) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 25), + (GES.TestClip, 25, 75), + (GES.TransitionClip, 50, 50), + (GES.TestClip, 50, 75), + (GES.TestClip, 125, 25), + ] + ]) + + @unittest.skipUnless(*common.can_generate_assets()) + def test_auto_transition_type_after_setting_proxy_asset(self): + self.track_types = [GES.TrackType.VIDEO] + super().setUp() + + self.timeline.props.auto_transition = True + with common.created_video_asset() as uri: + self.append_clip(asset_type=GES.UriClip, asset_id=uri) + self.append_clip(asset_type=GES.UriClip, asset_id=uri).props.start = 5 + clip1, transition, clip2 = self.layer.get_clips() + video_transition, = transition.get_children(True) + video_transition.set_transition_type(GES.VideoStandardTransitionType.BAR_WIPE_LR) + self.assertEqual(video_transition.get_transition_type(), GES.VideoStandardTransitionType.BAR_WIPE_LR) + + with common.created_video_asset() as uri2: + proxy_asset = GES.UriClipAsset.request_sync(uri2) + clip1.set_asset(proxy_asset) + clip1, transition1, clip2 = self.layer.get_clips() + + video_transition1, = transition1.get_children(True) + self.assertEqual(video_transition, video_transition1) + self.assertEqual(video_transition.get_transition_type(), GES.VideoStandardTransitionType.BAR_WIPE_LR) + + def test_frame_info(self): + self.track_types = [GES.TrackType.VIDEO] + super().setUp() + + vtrack, = self.timeline.get_tracks() + vtrack.update_restriction_caps(Gst.Caps("video/x-raw,framerate=60/1")) + self.assertEqual(self.timeline.get_frame_time(60), Gst.SECOND) + + layer = self.timeline.append_layer() + asset = GES.Asset.request(GES.TestClip, "framerate=120/1,height=500,width=500,max-duration=f120") + clip = layer.add_asset( asset, 0, 0, Gst.SECOND, GES.TrackType.UNKNOWN) + self.assertEqual(clip.get_id(), "GESTestClip, framerate=(fraction)120/1, height=(int)500, width=(int)500, max-duration=(string)f120;") + + test_source, = clip.get_children(True) + self.assertEqual(test_source.get_natural_size(), (True, 500, 500)) + self.assertEqual(test_source.get_natural_framerate(), (True, 120, 1)) + self.assertEqual(test_source.props.max_duration, Gst.SECOND) + self.assertEqual(clip.get_natural_framerate(), (True, 120, 1)) + + self.assertEqual(self.timeline.get_frame_at(Gst.SECOND), 60) + self.assertEqual(clip.props.max_duration, Gst.SECOND) + + def test_layer_active(self): + def check_nle_object_activeness(clip, track_type, active=None, ref_clip=None): + assert ref_clip is not None or active is not None + + if ref_clip: + ref_elem, = ref_clip.find_track_elements(None, track_type, GES.Source) + active = ref_elem.get_nleobject().props.active + + elem, = clip.find_track_elements(None, track_type, GES.Source) + self.assertIsNotNone(elem) + self.assertEqual(elem.get_nleobject().props.active, active) + + def get_tracks(timeline): + for track in self.timeline.get_tracks(): + if track.props.track_type == GES.TrackType.VIDEO: + video_track = track + else: + audio_track = track + return video_track, audio_track + + + def check_set_active_for_tracks(layer, active, tracks, expected_changed_tracks): + callback_called = [] + def _check_active_changed_cb(layer, active, tracks, expected_tracks, expected_active): + self.assertEqual(set(tracks), set(expected_tracks)) + self.assertEqual(active, expected_active) + callback_called.append(True) + + layer.connect("active-changed", _check_active_changed_cb, expected_changed_tracks, active) + self.assertTrue(layer.set_active_for_tracks(active, tracks)) + self.layer.disconnect_by_func(_check_active_changed_cb) + self.assertEqual(callback_called, [True]) + + c0 = self.append_clip() + check_nle_object_activeness(c0, GES.TrackType.VIDEO, True) + check_nle_object_activeness(c0, GES.TrackType.AUDIO, True) + + elem, = c0.find_track_elements(None, GES.TrackType.AUDIO, GES.Source) + elem.props.active = False + check_nle_object_activeness(c0, GES.TrackType.VIDEO, True) + check_nle_object_activeness(c0, GES.TrackType.AUDIO, False) + self.check_reload_timeline() + elem.props.active = True + + # Muting audio track + video_track, audio_track = get_tracks(self.timeline) + + check_set_active_for_tracks(self.layer, False, [audio_track], [audio_track]) + + check_nle_object_activeness(c0, GES.TrackType.VIDEO, True) + check_nle_object_activeness(c0, GES.TrackType.AUDIO, False) + self.check_reload_timeline() + + c1 = self.append_clip() + check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) + check_nle_object_activeness(c1, GES.TrackType.AUDIO, False) + + l1 = self.timeline.append_layer() + c1.move_to_layer(l1) + check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) + check_nle_object_activeness(c1, GES.TrackType.AUDIO, True) + + self.assertTrue(c1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_NORMAL, + GES.Edge.EDGE_NONE, c1.props.start)) + check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) + check_nle_object_activeness(c1, GES.TrackType.AUDIO, False) + self.check_reload_timeline() + + self.assertTrue(self.layer.remove_clip(c1)) + check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) + check_nle_object_activeness(c1, GES.TrackType.AUDIO, True) + + self.assertTrue(self.layer.add_clip(c1)) + check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) + check_nle_object_activeness(c1, GES.TrackType.AUDIO, False) + + check_set_active_for_tracks(self.layer, True, None, [audio_track]) + check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) + check_nle_object_activeness(c1, GES.TrackType.AUDIO, True) + + elem, = c1.find_track_elements(None, GES.TrackType.AUDIO, GES.Source) + check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) + check_nle_object_activeness(c1, GES.TrackType.AUDIO, True) + + # Force deactivating a specific TrackElement + elem.props.active = False + check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) + check_nle_object_activeness(c1, GES.TrackType.AUDIO, False) + self.check_reload_timeline() + + # Try activating a specific TrackElement, that won't change the + # underlying nleobject activness + check_set_active_for_tracks(self.layer, False, None, [audio_track, video_track]) + check_nle_object_activeness(c1, GES.TrackType.VIDEO, False) + check_nle_object_activeness(c1, GES.TrackType.AUDIO, False) + + elem.props.active = True + check_nle_object_activeness(c1, GES.TrackType.VIDEO, False) + check_nle_object_activeness(c1, GES.TrackType.AUDIO, False) + self.check_reload_timeline() + + +class TestEditing(common.GESSimpleTimelineTest): + + def test_transition_disappears_when_moving_to_another_layer(self): + self.timeline.props.auto_transition = True + unused_clip1 = self.add_clip(0, 0, 100) + clip2 = self.add_clip(50, 0, 100) + self.assertEqual(len(self.layer.get_clips()), 4) + + layer2 = self.timeline.append_layer() + clip2.edit([], layer2.get_priority(), GES.EditMode.EDIT_NORMAL, + GES.Edge.EDGE_NONE, clip2.props.start) + self.assertEqual(len(self.layer.get_clips()), 1) + self.assertEqual(len(layer2.get_clips()), 1) + + def activate_snapping(self): + self.timeline.set_snapping_distance(5) + self.snapped_at = [] + + def _snapped_cb(timeline, elem1, elem2, position): + self.snapped_at.append(position) + + def _snapped_end_cb(timeline, elem1, elem2, position): + if self.snapped_at: # Ignoring first snap end. + self.snapped_at.append(Gst.CLOCK_TIME_NONE) + + self.timeline.connect("snapping-started", _snapped_cb) + self.timeline.connect("snapping-ended", _snapped_end_cb) + + def test_snap_start_snap_end(self): + clip = self.append_clip() + self.append_clip() + + self.activate_snapping() + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + # snap to 20 + clip.props.start = 18 + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + ] + ]) + self.assertEqual(self.snapped_at, [20]) + + # no snapping + clip.props.start = 30 + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 10, 10), + (GES.TestClip, 30, 10), + ] + ]) + self.assertEqual(self.snapped_at, [20, Gst.CLOCK_TIME_NONE]) + + # snap to 20 + clip.props.start = 18 + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + ] + ]) + self.assertEqual(self.snapped_at, [20, Gst.CLOCK_TIME_NONE, 20]) + # snap to 20 again + clip.props.start = 19 + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + ] + ]) + self.assertEqual( + self.snapped_at, + [20, Gst.CLOCK_TIME_NONE, 20, Gst.CLOCK_TIME_NONE, 20]) + + def test_rippling_snaps(self): + self.timeline.props.auto_transition = True + self.append_clip() + clip = self.append_clip() + + self.activate_snapping() + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + clip.edit([], 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 15) + self.assertEqual(self.snapped_at, [10]) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + clip.edit([], 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 20) + self.assertEqual(self.snapped_at, [10, Gst.CLOCK_TIME_NONE]) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 20, 10), + ] + ]) + + def test_transition_moves_when_rippling_to_another_layer(self): + self.timeline.props.auto_transition = True + clip1 = self.add_clip(0, 0, 100) + clip2 = self.add_clip(50, 0, 100) + all_clips = self.layer.get_clips() + self.assertEqual(len(all_clips), 4) + + layer2 = self.timeline.append_layer() + clip1.edit([], layer2.get_priority(), GES.EditMode.EDIT_RIPPLE, + GES.Edge.EDGE_NONE, clip1.props.start) + self.assertEqual(self.layer.get_clips(), []) + self.assertEqual(set(layer2.get_clips()), set(all_clips)) + + def test_transition_rippling_after_next_clip_stays(self): + self.timeline.props.auto_transition = True + clip1 = self.add_clip(0, 0, 100) + clip2 = self.add_clip(50, 0, 100) + all_clips = self.layer.get_clips() + self.assertEqual(len(all_clips), 4) + + clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_RIPPLE, + GES.Edge.EDGE_NONE, clip2.props.start + 1) + self.assertEqual(set(self.layer.get_clips()), set(all_clips)) + + def test_transition_rippling_over_does_not_create_another_transition(self): + self.timeline.props.auto_transition = True + + clip1 = self.add_clip(0, 0, 17 * Gst.SECOND) + clip2 = clip1.split(7.0 * Gst.SECOND) + # Make a transition between the two clips + clip1.edit([], self.layer.get_priority(), + GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 4.5 * Gst.SECOND) + + # Rippl clip1 and check that transitions ar always the sames + all_clips = self.layer.get_clips() + self.assertEqual(len(all_clips), 4) + clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_RIPPLE, + GES.Edge.EDGE_NONE, 41.5 * Gst.SECOND) + self.assertEqual(len(self.layer.get_clips()), 4) + clip1.edit([], self.layer.get_priority(), + GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 35 * Gst.SECOND) + self.assertEqual(len(self.layer.get_clips()), 4) + + def test_trim_transition(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + + self.timeline.props.auto_transition = True + self.add_clip(0, 0, 10) + self.add_clip(5, 0, 10) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TransitionClip, 5, 5), + (GES.TestClip, 5, 10), + ] + ]) + transition = self.layer.get_clips()[1] + self.assertTrue(transition.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 7)) + + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TransitionClip, 7, 3), + (GES.TestClip, 7, 8), + ] + ]) + + def test_trim_start(self): + clip = self.append_clip() + self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 10)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 10, 10), + ] + ]) + + self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 10, 10), + ] + ]) + + def test_trim_non_core(self): + clip = self.append_clip() + self.assertTrue(clip.set_inpoint(12)) + self.assertTrue(clip.set_max_duration(30)) + self.assertEqual(clip.get_duration_limit(), 18) + for child in clip.get_children(False): + self.assertEqual(child.get_inpoint(), 12) + self.assertEqual(child.get_max_duration(), 30) + + effect0 = GES.Effect.new("textoverlay") + effect0.set_has_internal_source(True) + self.assertTrue(effect0.set_inpoint(5)) + self.assertTrue(effect0.set_max_duration(20)) + self.assertTrue(clip.add(effect0)) + self.assertEqual(clip.get_duration_limit(), 15) + + effect1 = GES.Effect.new("agingtv") + effect1.set_has_internal_source(False) + self.assertTrue(clip.add(effect1)) + + effect2 = GES.Effect.new("textoverlay") + effect2.set_has_internal_source(True) + self.assertTrue(effect2.set_inpoint(8)) + self.assertTrue(effect2.set_max_duration(18)) + self.assertTrue(clip.add(effect2)) + self.assertEqual(clip.get_duration_limit(), 10) + + effect3 = GES.Effect.new("textoverlay") + effect3.set_has_internal_source(True) + self.assertTrue(effect3.set_inpoint(20)) + self.assertTrue(effect3.set_max_duration(22)) + self.assertTrue(effect3.set_active(False)) + self.assertTrue(clip.add(effect3)) + self.assertEqual(clip.get_duration_limit(), 10) + + self.assertTrue(clip.set_start(10)) + self.assertTrue(clip.set_duration(10)) + + # cannot trim to a 0 because effect0 would have a negative in-point + error = None + try: + clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0) + except GLib.Error as err: + error = err + self.assertGESError(error, GES.Error.NEGATIVE_TIME) + + self.assertEqual(clip.start, 10) + self.assertEqual(clip.inpoint, 12) + self.assertEqual(effect0.inpoint, 5) + self.assertEqual(effect1.inpoint, 0) + self.assertEqual(effect2.inpoint, 8) + self.assertEqual(effect3.inpoint, 20) + + self.assertTrue( + clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5)) + + self.assertEqual(clip.start, 5) + self.assertEqual(clip.duration, 15) + self.assertEqual(clip.get_duration_limit(), 15) + + for child in clip.get_children(False): + self.assertEqual(child.start, 5) + self.assertEqual(child.duration, 15) + + self.assertEqual(clip.inpoint, 7) + self.assertEqual(effect0.inpoint, 0) + self.assertEqual(effect1.inpoint, 0) + self.assertEqual(effect2.inpoint, 3) + self.assertEqual(effect3.inpoint, 20) + + self.assertTrue( + clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 15)) + + self.assertEqual(clip.start, 15) + self.assertEqual(clip.duration, 5) + self.assertEqual(clip.get_duration_limit(), 5) + + for child in clip.get_children(False): + self.assertEqual(child.start, 15) + self.assertEqual(child.duration, 5) + + self.assertEqual(clip.inpoint, 17) + self.assertEqual(effect0.inpoint, 10) + self.assertEqual(effect1.inpoint, 0) + self.assertEqual(effect2.inpoint, 13) + self.assertEqual(effect3.inpoint, 20) + + def test_trim_time_effects(self): + self.track_types = [GES.TrackType.VIDEO] + super().setUp() + clip = self.append_clip(asset_id="max-duration=30") + self.assertTrue(clip.set_inpoint(12)) + self.assertEqual(clip.get_duration_limit(), 18) + + children = clip.get_children(False) + self.assertTrue(children) + self.assertEqual(len(children), 1) + + source = children[0] + self.assertEqual(source.get_inpoint(), 12) + self.assertEqual(source.get_max_duration(), 30) + + rate0 = GES.Effect.new("videorate rate=0.25") + + overlay = GES.Effect.new("textoverlay") + overlay.set_has_internal_source(True) + self.assertTrue(overlay.set_inpoint(5)) + self.assertTrue(overlay.set_max_duration(16)) + + rate1 = GES.Effect.new("videorate rate=2.0") + + self.assertTrue(clip.add(rate0)) + self.assertTrue(clip.add(overlay)) + self.assertTrue(clip.add(rate1)) + + # source -> rate1 -> overlay -> rate0 + # in-point/max-dur 12-30 5-16 + # internal + # start/end 12-30 0-9 5-14 0-36 + self.assertEqual(clip.get_duration_limit(), 36) + self.assertTrue(clip.set_start(40)) + self.assertTrue(clip.set_duration(10)) + self.check_reload_timeline() + + # cannot trim to a 16 because overlay would have a negative in-point + error = None + try: + clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 16) + except GLib.Error as err: + error = err + self.assertGESError(error, GES.Error.NEGATIVE_TIME) + + self.assertEqual(clip.get_start(), 40) + self.assertEqual(clip.get_duration(), 10) + self.assertEqual(source.get_inpoint(), 12) + self.assertEqual(source.get_max_duration(), 30) + self.assertEqual(overlay.get_inpoint(), 5) + self.assertEqual(overlay.get_max_duration(), 16) + + self.check_reload_timeline() + + # trim backwards to 20 + self.assertTrue( + clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 20)) + + self.assertEqual(clip.get_start(), 20) + self.assertEqual(clip.get_duration(), 30) + # reduced by 10 + self.assertEqual(source.get_inpoint(), 2) + self.assertEqual(source.get_max_duration(), 30) + # reduced by 5 + self.assertEqual(overlay.get_inpoint(), 0) + self.assertEqual(overlay.get_max_duration(), 16) + + # trim forwards to 28 + self.assertTrue( + clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 28)) + self.assertEqual(clip.get_start(), 28) + self.assertEqual(clip.get_duration(), 22) + # increased by 4 + self.assertEqual(source.get_inpoint(), 6) + self.assertEqual(source.get_max_duration(), 30) + # increased by 2 + self.assertEqual(overlay.get_inpoint(), 2) + self.assertEqual(overlay.get_max_duration(), 16) + self.check_reload_timeline() + + def test_ripple_end(self): + clip = self.append_clip() + clip.set_max_duration(20) + self.append_clip().set_max_duration(10) + self.append_clip().set_max_duration(10) + self.print_timeline() + self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 20)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 20), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ] + ]) + + self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 15)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 15), + (GES.TestClip, 15, 10), + (GES.TestClip, 25, 10), + ] + ]) + + def test_move_group_full_overlap(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + + for _ in range(4): + self.append_clip() + clips = self.layer.get_clips() + + self.assertTrue(clips[0].ripple(20)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + (GES.TestClip, 40, 10), + (GES.TestClip, 50, 10), + ] + ]) + group = GES.Container.group(clips[1:]) + self.print_timeline() + self.assertFalse(group.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0)) + self.print_timeline() + self.assertTimelineTopology([ + [ + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + (GES.TestClip, 40, 10), + (GES.TestClip, 50, 10), + ] + ]) + + self.assertFalse(clips[1].edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0)) + self.print_timeline() + self.assertTimelineTopology([ + [ + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + (GES.TestClip, 40, 10), + (GES.TestClip, 50, 10), + ] + ]) + + def test_trim_inside_group(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + + for _ in range(2): + self.append_clip() + clips = self.layer.get_clips() + group = GES.Container.group(clips) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + self.assertEqual(group.props.start, 0) + self.assertEqual(group.props.duration, 20) + + clips[0].trim(5) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 5, 5), + (GES.TestClip, 10, 10), + ] + ]) + self.assertEqual(group.props.start, 5) + self.assertEqual(group.props.duration, 15) + + group1 = GES.Group.new () + group1.add(group) + clips[0].trim(0) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + self.assertEqual(group.props.start, 0) + self.assertEqual(group.props.duration, 20) + self.assertEqual(group1.props.start, 0) + self.assertEqual(group1.props.duration, 20) + + self.assertTrue(clips[1].edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 5), + ] + ]) + self.assertEqual(group.props.start, 0) + self.assertEqual(group.props.duration, 15) + self.assertEqual(group1.props.start, 0) + self.assertEqual(group1.props.duration, 15) + + def test_trim_end_past_max_duration(self): + clip = self.append_clip() + max_duration = clip.props.duration + clip.set_max_duration(max_duration) + self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 5, 5), + ] + ]) + + self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 5, 5), + ] + ]) + + def test_illegal_effect_move(self): + c0 = self.append_clip() + self.append_clip() + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + effect = GES.Effect.new("agingtv") + c0.add(effect) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + self.assertFalse(effect.set_start(10)) + self.assertEqual(effect.props.start, 0) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + + self.assertFalse(effect.set_duration(20)) + self.assertEqual(effect.props.duration, 10) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + def test_moving_overlay_clip_in_group(self): + c0 = self.append_clip() + overlay = self.append_clip(asset_type=GES.TextOverlayClip) + group = GES.Group.new() + group.add(c0) + group.add(overlay) + + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TextOverlayClip, 10, 10), + ] + ], groups=[(c0, overlay)]) + + self.assertTrue(overlay.set_start(20)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 10, 10), + (GES.TextOverlayClip, 20, 10), + ] + ], groups=[(c0, overlay)]) + + def test_moving_group_in_group(self): + c0 = self.append_clip() + overlay = self.append_clip(asset_type=GES.TextOverlayClip) + group0 = GES.Group.new() + group0.add(c0) + group0.add(overlay) + + c1 = self.append_clip() + group1 = GES.Group.new() + group1.add(group0) + group1.add(c1) + + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TextOverlayClip, 10, 10), + (GES.TestClip, 20, 10), + ] + ], groups=[(c1, group0), (c0, overlay)]) + + self.assertTrue(group0.set_start(10)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 10, 10), + (GES.TextOverlayClip, 20, 10), + (GES.TestClip, 30, 10), + ] + ], groups=[(c1, group0), (c0, overlay)]) + self.check_element_values(group0, 10, 0, 20) + self.check_element_values(group1, 10, 0, 30) + + def test_illegal_group_child_move(self): + clip0 = self.append_clip() + _clip1 = self.add_clip(20, 0, 10) + overlay = self.add_clip(20, 0, 10, asset_type=GES.TextOverlayClip) + + group = GES.Group.new() + group.add(clip0) + group.add(overlay) + + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TextOverlayClip, 20, 10), + (GES.TestClip, 20, 10), + ] + ], groups=[(clip0, overlay),]) + + # Can't move as clip0 and clip1 would fully overlap + self.assertFalse(overlay.set_start(40)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TextOverlayClip, 20, 10), + (GES.TestClip, 20, 10), + ] + ], groups=[(clip0, overlay)]) + + def test_child_duration_change(self): + c0 = self.append_clip() + + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + ] + ]) + self.assertTrue(c0.set_duration(40)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 40), + ] + ]) + + c0.children[0].set_duration(10) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + ] + ]) + + self.assertTrue(c0.set_start(40)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 40, 10), + ] + ]) + + c0.children[0].set_start(10) + self.assertTimelineTopology([ + [ + (GES.TestClip, 10, 10), + ] + ]) + + +class TestInvalidOverlaps(common.GESSimpleTimelineTest): + + def test_adding_or_moving(self): + clip1 = self.add_clip(start=10, in_point=0, duration=3) + self.assertIsNotNone(clip1) + + def check_add_move_clip(start, duration): + self.timeline.props.auto_transition = True + self.layer.props.auto_transition = True + clip2 = GES.TestClip() + clip2.props.start = start + clip2.props.duration = duration + self.assertFalse(self.layer.add_clip(clip2)) + self.assertEqual(len(self.layer.get_clips()), 1) + + # Add the clip at a different position. + clip2.props.start = 25 + self.assertTrue(self.layer.add_clip(clip2)) + self.assertEqual(clip2.props.start, 25) + + # Try to move the second clip by editing it. + self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, start)) + self.assertEqual(clip2.props.start, 25) + + # Try to put it in a group and move the group. + clip3 = GES.TestClip() + clip3.props.start = 20 + clip3.props.duration = 1 + self.assertTrue(self.layer.add_clip(clip3)) + group = GES.Container.group([clip3, clip2]) + self.assertTrue(group.props.start, 20) + self.assertFalse(group.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, start - 5)) + self.assertEqual(group.props.start, 20) + self.assertEqual(clip3.props.start, 20) + self.assertEqual(clip2.props.start, 25) + + for clip in group.ungroup(False): + self.assertTrue(self.layer.remove_clip(clip)) + + # clip1 contains... + check_add_move_clip(start=10, duration=1) + check_add_move_clip(start=11, duration=1) + check_add_move_clip(start=12, duration=1) + + def test_splitting(self): + clip1 = self.add_clip(start=9, in_point=0, duration=3) + clip2 = self.add_clip(start=10, in_point=0, duration=4) + clip3 = self.add_clip(start=12, in_point=0, duration=3) + + self.assertIsNone(clip1.split_full(13)) + self.assertIsNone(clip1.split_full(8)) + self.assertIsNone(clip3.split_full(12)) + self.assertIsNone(clip3.split_full(15)) + + def _fail_split(self, clip, position): + split = None + error = None + try: + split = clip.split_full(position) + except GLib.Error as err: + error = err + self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK) + self.assertIsNone(split) + + def test_split_with_transition(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + self.timeline.set_auto_transition(True) + + clip0 = self.add_clip(start=0, in_point=0, duration=50) + clip1 = self.add_clip(start=20, in_point=0, duration=50) + clip2 = self.add_clip(start=60, in_point=0, duration=20) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 50), + (GES.TransitionClip, 20, 30), + (GES.TestClip, 20, 50), + (GES.TransitionClip, 60, 10), + (GES.TestClip, 60, 20), + ] + ]) + + # Split should fail as the first part of the split + # would be fully overlapping clip0 + self._fail_split(clip1, 40) + + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 50), + (GES.TransitionClip, 20, 30), + (GES.TestClip, 20, 50), + (GES.TransitionClip, 60, 10), + (GES.TestClip, 60, 20), + ] + ]) + + # same with end of the clip + self._fail_split(clip1, 65) + + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 50), + (GES.TransitionClip, 20, 30), + (GES.TestClip, 20, 50), + (GES.TransitionClip, 60, 10), + (GES.TestClip, 60, 20), + ] + ]) + + def test_changing_duration(self): + clip1 = self.add_clip(start=9, in_point=0, duration=2) + clip2 = self.add_clip(start=10, in_point=0, duration=2) + + self.assertFalse(clip1.set_start(10)) + self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, clip2.props.start + clip2.props.duration)) + self.assertFalse(clip1.ripple_end(clip2.props.start + clip2.props.duration)) + self.assertFalse(clip1.roll_end(clip2.props.start + clip2.props.duration)) + + # clip2's end edge to the left, to decrease its duration. + self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, clip1.props.start + clip1.props.duration)) + self.assertFalse(clip2.ripple_end(clip1.props.start + clip1.props.duration)) + self.assertFalse(clip2.roll_end(clip1.props.start + clip1.props.duration)) + + # clip2's start edge to the left, to increase its duration. + self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, clip1.props.start)) + self.assertFalse(clip2.trim(clip1.props.start)) + + # clip1's start edge to the right, to decrease its duration. + self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, clip2.props.start)) + self.assertFalse(clip1.trim(clip2.props.start)) + + def test_rippling_backward(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + self.maxDiff = None + for i in range(4): + self.append_clip() + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ] + ]) + + clip = self.layer.get_clips()[2] + self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip.props.start - 20)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ] + ]) + self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip.props.start + 10)) + + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + (GES.TestClip, 30, 10), + (GES.TestClip, 40, 10), + ] + ]) + + self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip.props.start -20)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + (GES.TestClip, 30, 10), + (GES.TestClip, 40, 10), + ] + ]) + + def test_rolling(self): + clip1 = self.add_clip(start=9, in_point=0, duration=2) + clip2 = self.add_clip(start=10, in_point=0, duration=2) + clip3 = self.add_clip(start=11, in_point=0, duration=2) + + # Rolling clip1's end -1 would lead to clip3 to overlap 100% with clip2. + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 9, 2), + (GES.TestClip, 10, 2), + (GES.TestClip, 11, 2) + ] + ]) + self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, clip1.props.start + clip1.props.duration - 1)) + self.assertFalse(clip1.roll_end(13)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 9, 2), + (GES.TestClip, 10, 2), + (GES.TestClip, 11, 2) + ] + ]) + + # Rolling clip3's start +1 would lead to clip1 to overlap 100% with clip2. + self.assertFalse(clip3.edit([], -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 12)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 9, 2), + (GES.TestClip, 10, 2), + (GES.TestClip, 11, 2) + ] + ]) + + def test_layers(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + self.maxDiff = None + self.timeline.append_layer() + + for i in range(2): + self.append_clip() + self.append_clip(1) + + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ], + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + clip = self.layer.get_clips()[0] + self.assertFalse(clip.edit([], 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ], + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + def test_rippling(self): + self.timeline.remove_track(self.timeline.get_tracks()[0]) + clip1 = self.add_clip(start=9, in_point=0, duration=2) + clip2 = self.add_clip(start=10, in_point=0, duration=2) + clip3 = self.add_clip(start=11, in_point=0, duration=2) + + # Rippling clip2's start -2 would bring clip3 exactly on top of clip1. + self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 8)) + self.assertFalse(clip2.ripple(8)) + + # Rippling clip1's end -1 would bring clip3 exactly on top of clip2. + self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 8)) + self.assertFalse(clip1.ripple_end(8)) + + def test_move_group_to_layer(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + self.append_clip() + self.append_clip() + self.append_clip() + + clips = self.layer.get_clips() + + clips[1].props.start += 2 + group = GES.Container.group(clips[1:]) + self.assertTrue(clips[1].edit([], 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, + group.props.start)) + + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + ], + [ + (GES.TestClip, 12, 10), + (GES.TestClip, 20, 10), + ] + ]) + + clips[0].props.start = 15 + self.assertTimelineTopology([ + [ + (GES.TestClip, 15, 10), + ], + [ + (GES.TestClip, 12, 10), + (GES.TestClip, 20, 10), + ] + ]) + + self.assertFalse(clips[1].edit([], 0, GES.EditMode.EDIT_NORMAL, + GES.Edge.EDGE_NONE, group.props.start)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 15, 10), + ], + [ + (GES.TestClip, 12, 10), + (GES.TestClip, 20, 10), + ] + ]) + + def test_copy_paste_overlapping(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + clip = self.append_clip() + + copy = clip.copy(True) + self.assertIsNone(copy.paste(copy.props.start)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + + ] + ]) + copy = clip.copy(True) + pasted = copy.paste(copy.props.start + 1) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 1, 10), + ] + ]) + + pasted.move_to_layer(self.timeline.append_layer()) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + ], + [ + (GES.TestClip, 1, 10), + ] + ]) + + copy = pasted.copy(True) + self.assertIsNotNone(copy.paste(pasted.props.start - 1)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + ], + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 1, 10), + ], + ]) + + group = GES.Group.new() + group.add(clip) + + copied_group = group.copy(True) + self.assertFalse(copied_group.paste(group.props.start)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + ], + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 1, 10), + ], + ]) + + def test_move_group_with_overlaping_clips(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + self.append_clip() + self.append_clip() + self.append_clip() + + self.timeline.props.auto_transition = True + clips = self.layer.get_clips() + + clips[1].props.start += 5 + group = GES.Container.group(clips[1:]) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 15, 10), + (GES.TransitionClip, 20, 5), + (GES.TestClip, 20, 10), + ] + ]) + + clips[0].props.start = 30 + self.assertTimelineTopology([ + [ + (GES.TestClip, 15, 10), + (GES.TransitionClip, 20, 5), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ] + ]) + + # the 3 clips would overlap + self.assertFalse(clips[1].edit([], 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 25)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 15, 10), + (GES.TransitionClip, 20, 5), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ] + ]) + + +class TestConfigurationRules(common.GESSimpleTimelineTest): + + def _try_add_clip(self, start, duration, layer=None, error=None): + if layer is None: + layer = self.layer + asset = GES.Asset.request(GES.TestClip, None) + found_err = None + clip = None + # large inpoint to allow trims + try: + clip = layer.add_asset_full( + asset, start, 1000, duration, GES.TrackType.UNKNOWN) + except GLib.Error as err: + found_err = err + if error is None: + self.assertIsNotNone(clip) + else: + self.assertIsNone(clip) + self.assertGESError(found_err, error) + return clip + + def test_full_overlap_add(self): + clip1 = self._try_add_clip(50, 50) + self._try_add_clip(50, 50, error=GES.Error.INVALID_OVERLAP_IN_TRACK) + self._try_add_clip(49, 51, error=GES.Error.INVALID_OVERLAP_IN_TRACK) + self._try_add_clip(51, 49, error=GES.Error.INVALID_OVERLAP_IN_TRACK) + + def test_triple_overlap_add(self): + clip1 = self._try_add_clip(0, 50) + clip2 = self._try_add_clip(40, 50) + self._try_add_clip(39, 12, error=GES.Error.INVALID_OVERLAP_IN_TRACK) + self._try_add_clip(30, 30, error=GES.Error.INVALID_OVERLAP_IN_TRACK) + self._try_add_clip(1, 88, error=GES.Error.INVALID_OVERLAP_IN_TRACK) + + def test_full_overlap_move(self): + clip1 = self._try_add_clip(0, 50) + clip2 = self._try_add_clip(50, 50) + self.assertFalse(clip2.set_start(0)) + + def test_triple_overlap_move(self): + clip1 = self._try_add_clip(0, 50) + clip2 = self._try_add_clip(40, 50) + clip3 = self._try_add_clip(100, 60) + self.assertFalse(clip3.set_start(30)) + + def test_full_overlap_move_into_layer(self): + clip1 = self._try_add_clip(0, 50) + layer2 = self.timeline.append_layer() + clip2 = self._try_add_clip(0, 50, layer2) + res = None + try: + res = clip2.move_to_layer_full(self.layer) + except GLib.Error as error: + self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK) + self.assertIsNone(res) + + def test_triple_overlap_move_into_layer(self): + clip1 = self._try_add_clip(0, 50) + clip2 = self._try_add_clip(40, 50) + layer2 = self.timeline.append_layer() + clip3 = self._try_add_clip(30, 30, layer2) + res = None + try: + res = clip3.move_to_layer_full(self.layer) + except GLib.Error as error: + self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK) + self.assertIsNone(res) + + def test_full_overlap_trim(self): + clip1 = self._try_add_clip(0, 50) + clip2 = self._try_add_clip(50, 50) + self.assertFalse(clip2.trim(0)) + self.assertFalse(clip1.set_duration(100)) + + def test_triple_overlap_trim(self): + clip1 = self._try_add_clip(0, 20) + clip2 = self._try_add_clip(10, 30) + clip3 = self._try_add_clip(30, 20) + self.assertFalse(clip3.trim(19)) + self.assertFalse(clip1.set_duration(31)) + +class TestSnapping(common.GESSimpleTimelineTest): + + def test_snapping(self): + self.timeline.props.auto_transition = True + self.timeline.set_snapping_distance(1) + clip1 = self.add_clip(0, 0, 100) + + # Split clip1. + split_position = 50 + clip2 = clip1.split(split_position) + self.assertEqual(len(self.layer.get_clips()), 2) + self.assertEqual(clip1.props.duration, split_position) + self.assertEqual(clip2.props.start, split_position) + + # Make sure snapping prevents clip2 to be moved to the left. + clip2.edit([], self.layer.get_priority(), GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, + clip2.props.start - 1) + self.assertEqual(clip2.props.start, split_position) + + def test_trim_snapps_inside_group(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + + self.timeline.props.auto_transition = True + self.timeline.set_snapping_distance(5) + + snaps = [] + def snapping_started_cb(timeline, element1, element2, dist, self): + snaps.append(set([element1, element2])) + + self.timeline.connect('snapping-started', snapping_started_cb, self) + clip = self.append_clip() + clip1 = self.append_clip() + + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 15) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + self.assertEqual(snaps[0], set([clip.get_children(False)[0], clip1.get_children(False)[0]])) + + clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 16) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 16, 4), + ] + ]) + + def test_trim_no_snapping_on_same_clip(self): + self.timeline.props.auto_transition = True + self.timeline.set_snapping_distance(1) + + not_called = [] + def snapping_started_cb(timeline, element1, element2, dist, self): + not_called.append("No snapping should happen") + + self.timeline.connect('snapping-started', snapping_started_cb, self) + clip = self.append_clip() + clip.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5) + self.assertEqual(not_called, []) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 5, 5), + ] + ]) + + clip.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 4) + self.assertEqual(not_called, []) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 4, 6), + ] + ]) + + def test_no_snapping_on_split(self): + self.timeline.props.auto_transition = True + self.timeline.set_snapping_distance(1) + + not_called = [] + def snapping_started_cb(timeline, element1, element2, dist, self): + not_called.append("No snapping should happen") + + self.timeline.connect('snapping-started', snapping_started_cb, self) + clip1 = self.add_clip(0, 0, 100) + + # Split clip1. + split_position = 50 + clip2 = clip1.split(split_position) + self.assertEqual(not_called, []) + self.assertEqual(len(self.layer.get_clips()), 2) + self.assertEqual(clip1.props.duration, split_position) + self.assertEqual(clip2.props.start, split_position) + +class TestComplexEditing(common.GESTimelineConfigTest): + + def test_normal_move(self): + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + .................g1.................. + : : + : *=========* + : | c0 | + : *=========* + ____________________:___________________________________:____ + layer1______________:___________________________________:____ + : : + ..............g0............... : + : : : + *=======================* : : + | c1 | : : + *=========*=============*=====* : + : | c2 | : + : *===================* : + :.............................: : + : : + :...................................: + _____________________________________________________________ + layer2_______________________________________________________ + + *=============================* + | c3 | + *=============================* + """ + track = self.add_video_track() + c0 = self.add_clip("c0", 0, [track], 23, 5) + c1 = self.add_clip("c1", 1, [track], 10, 12) + c2 = self.add_clip("c2", 1, [track], 15, 10) + self.register_auto_transition(c1, c2, track) + c3 = self.add_clip("c3", 2, [track], 2, 15) + g0 = self.add_group("g0", [c1, c2]) + g1 = self.add_group("g1", [c0, g0]) + + self.assertTimelineConfig() + + # test invalid edits + + # cannot move c0 up one layer because it would cause a triple + # overlap between c1, c2 and c3 when g0 moves + self.assertFailEdit( + c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 23, + GES.Error.INVALID_OVERLAP_IN_TRACK) + + # cannot move c0, without moving g1, to 21 layer 1 because it + # would be completely overlapped by c2 + self.assertFailEdit( + c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 20, + GES.Error.INVALID_OVERLAP_IN_TRACK) + + # cannot move c1, without moving g1, with end 25 because it + # would be completely overlapped by c2 + self.assertFailEdit( + c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 25, + GES.Error.INVALID_OVERLAP_IN_TRACK) + + # cannot move g0 to layer 0 because it would make c0 go to a + # negative layer + self.assertFailEdit( + g0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10, + GES.Error.NEGATIVE_LAYER) + + # cannot move c1 for same reason + error = None + try: + c1.move_to_layer_full(self.timeline.get_layer(0)) + except GLib.Error as err: + error = err + self.assertGESError(error, GES.Error.NEGATIVE_LAYER) + self.assertTimelineConfig({}, []) + + # failure with snapping + self.timeline.set_snapping_distance(1) + + # cannot move to 0 because end edge of c0 would snap with end of + # c3, making the new start become negative + self.assertFailEdit( + g0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0, + GES.Error.NEGATIVE_TIME) + + # cannot move start of c1 to 14 because snapping causes a full + # overlap with c0 + self.assertFailEdit( + c1, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 14, + GES.Error.INVALID_OVERLAP_IN_TRACK) + + # cannot move end of c2 to 21 because snapping causes a full + # overlap with c0 + self.assertFailEdit( + c2, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 21, + GES.Error.INVALID_OVERLAP_IN_TRACK) + + # successes + self.timeline.set_snapping_distance(3) + # moving c0 also moves g1, along with g0 + # with no snapping, this would result in a triple overlap between + # c1, c2 and c3, but c2's start edge will snap to the end of c3 + # at a distance of 3 allowing the edit to succeed + # + # c1 and c3 have a new transition + # transition between c1 and c2 is not lost + # + # NOTE: there is no snapping between c0, c1 or c2 even though + # their edges are within distance 2 of each other because they are + # all moving + self.assertEdit( + c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 22, 17, + [c2], [c3], + { + c0 : {"start": 25, "layer": 1}, + c1 : {"start": 12, "layer": 2}, + c2 : {"start": 17, "layer": 2}, + g0 : {"start": 12, "layer": 2}, + g1 : {"start": 12, "layer": 1} + }, [(c3, c1, track)], []) + + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + _____________________________________________________________ + layer1_______________________________________________________ + + .................g1.................. + : : + : *=========* + : | c0 | + : *=========* + ________________________:___________________________________: + layer2__________________:___________________________________: + : : + ..............g0............... : + : : : + *=======================* : : + | c1 | : : + *===================*=========*=============*=====* : + | c3 | c2 | : + *=============================*===================* : + :.............................: : + : : + :...................................: + """ + # using EDGE_START we can move without moving parent + # snap at same position 17 but with c1's end edge to c3's end + # edge + # loose transition between c1 and c3 + self.assertEdit( + g0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 5, 17, + [c1], [c3], + { + c1 : {"start": 5, "layer": 0}, + c2 : {"start": 10, "layer": 0}, + g0 : {"start": 5, "layer": 0}, + g1 : {"start": 5, "duration": 25, "layer": 0}, + }, [], [(c3, c1, track)]) + + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + .........................g1........................ + : : + ...............g0.............. : + : : : + *=======================* : : + | c1 | : : + *=========*=============*=====* : + : | c2 | : + : *===================* : + :.............................: : + __________:_________________________________________________: + layer1____:_________________________________________________: + : : + : *=========* + : | c0 | + : *=========* + :.................................................: + _____________________________________________________________ + layer2_______________________________________________________ + + *=============================* + | c3 | + *=============================* + """ + # using EDGE_END we can move without moving parent + # no snap + # loose transition between c1 and c2 + self.assertEdit( + c2, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 21, None, + [], [], + { + c2 : {"duration": 11, "layer": 1}, + g0 : {"duration": 16}, + }, [], [(c1, c2, track)]) + + # no snapping when we use move layer + self.timeline.set_snapping_distance(10) + self.assertTrue( + c3.move_to_layer(self.timeline.get_layer(1))) + self.assertTimelineConfig( + new_props={c3 : {"layer": 1}}, new_transitions=[(c3, c2, track)]) + + def test_ripple(self): + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + ......................g0................. + : : + *=========* : + | c0 | : + *=========* : + ______________:_______________________________________:______ + layer1________:_______________________________________:______ + : : + : *=============* + : | c3 | + : *=============* + :.......................................: + *===================* + | c2 | + *=========*=========*=========* + | c1 | + *===================* + """ + track = self.add_video_track() + c0 = self.add_clip("c0", 0, [track], 7, 5) + c1 = self.add_clip("c1", 1, [track], 5, 10) + c2 = self.add_clip("c2", 1, [track], 10, 10) + c3 = self.add_clip("c3", 1, [track], 20, 7) + self.register_auto_transition(c1, c2, track) + g0 = self.add_group("g0", [c0, c3]) + + self.assertTimelineConfig() + + # test failures + + self.timeline.set_snapping_distance(2) + + # would cause negative layer priority for c0 + self.assertFailEdit( + c1, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 5, + GES.Error.NEGATIVE_LAYER) + + # would lead to c2 fully overlapping c3 since c2 does ripple + # but c3 does not(c3 shares a toplevel with c0, and + # GES_EDGE_START, same as NORMAL mode, does not move the + # toplevel + self.assertFailEdit( + c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 25, + GES.Error.INVALID_OVERLAP_IN_TRACK) + + # would lead to c2 fully overlapping c3 since c2 does not + # ripple but c3 does + self.assertFailEdit( + c0, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 13, + GES.Error.INVALID_OVERLAP_IN_TRACK) + + # add two more clips + + c4 = self.add_clip("c4", 2, [track], 17, 8) + c5 = self.add_clip("c5", 2, [track], 21, 8) + self.register_auto_transition(c4, c5, track) + + self.assertTimelineConfig() + + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + ......................g0................. + : : + *=========* : + | c0 | : + *=========* : + ______________:_______________________________________:______ + layer1________:_______________________________________:______ + : : + : *=============* + : | c3 | + : *=============* + :.......................................: + *===================* + | c2 | + *=========*=========*=========* + | c1 | + *===================* + _____________________________________________________________ + layer2_______________________________________________________ + + *===============* + | c4 | + *=======*=======*=======* + | c5 | + *===============* + """ + + # rippling start of c2 only moves c4 and c5 because c3 is part + # of a toplevel with an earlier start + # NOTE: snapping only occurs for the edges of c2, in particular + # start of c4 does not snap to end of c1 + self.assertEdit( + c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 8, 7, + [c2], [c0], + { + c2 : {"start": 7}, + c4 : {"start": 14}, + c5 : {"start": 18}, + }, [], []) + + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + ......................g0................. + : : + *=========* : + | c0 | : + *=========* : + ______________:_______________________________________:______ + layer1________:_______________________________________:______ + : : + : *=============* + : | c3 | + : *=============* + :.......................................: + *===================* + | c2 | + *===*===============*===* + | c1 | + *===================* + _____________________________________________________________ + layer2_______________________________________________________ + + *===============* + | c4 | + *=======*=======*=======* + | c5 | + *===============* + """ + + # rippling end of c2, only c5 moves + # NOTE: start edge of c2 does not snap! + self.assertEdit( + c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 19, 20, + [c2], [c3], + { + c2 : {"duration": 13}, + c5 : {"start": 21}, + }, [], []) + + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + ......................g0................. + : : + *=========* : + | c0 | : + *=========* : + ______________:_______________________________________:______ + layer1________:_______________________________________:______ + : : + : *=============* + : | c3 | + : *=============* + :.......................................: + *=========================* + | c2 | + *===*===============*=========* + | c1 | + *===================* + _____________________________________________________________ + layer2_______________________________________________________ + + *===============* + | c4 | + *=============*=*=============* + | c5 | + *===============* + """ + + # everything except c1 moves, and to the next layer + # end edge of c2 snaps to end of c1 + # NOTE: does not snap to edges of rippled clips + # NOTE: c4 and c5 do not loose their transition when moving + # to the new layer + self.assertEdit( + c2, 2, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 0, 15, + [c2], [c1], + { + c0 : {"start": 2, "layer": 1}, + c2 : {"start": 2, "layer": 2}, + c3 : {"start": 15, "layer": 2}, + c4 : {"start": 9, "layer": 3}, + c5 : {"start": 16, "layer": 3}, + g0 : {"start": 2, "layer": 1}, + }, [(c0, c1, track)], [(c1, c2, track)]) + + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + _____________________________________________________________ + layer1_______________________________________________________ + + *===================* + | c1 | + *===================* + ...................g0.................... + *=========* : + | c0 | : + *=========* : + ____:_______________________________________:________________ + layer2______________________________________:________________ + : : + : *=============* + : | c3 | + : *=============* + :.......................................: + *=========================* + | c2 | + *=========================* + _____________________________________________________________ + layer3_______________________________________________________ + + *===============* + | c4 | + *=============*=*=============* + | c5 | + *===============* + """ + + # group c1 and c5, and g0 and c2 + g1 = self.add_group("g1", [c1, c5]) + g2 = self.add_group("g2", [g0, c2]) + self.assertTimelineConfig() + + # moving end edge of c0 does not move anything else in the same + # toplevel g2 + # c5 does not move because it is grouped with c1, which starts + # earlier than the end edge of c0 + # only c4 moves + # c0 does not snap to c4's start edge + self.assertEdit( + c0, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 10, None, + [], [], + { + c0 : {"duration": 8}, + c4 : {"start": 12}, + }, [], []) + + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + _____________________________________________________________ + layer1_______________________________________________________ + + ..................g1..................... + *===================* : + | c1 | : + *===================* : + :...................................... : + ...................g2.................... : : + :..................g0...................: : : + *===============* : : : + | c0 | : : : + *===============* : : : + ____:_______________________________________:___:_:__________ + layer2______________________________________:___:_:__________ + : : : : + : *=============* : : + : | c3 | : : + : *=============* : : + :.......................................: : : + *=========================* : : : + | c2 | : : : + *=========================* : : : + :.......................................: : : + ________________________________________________:_:__________ + layer3__________________________________________:_:__________ + ................: : + *===============* : + | c5 | : + *===============* : + :.................: + *===============* + | c4 | + *===============* + """ + + # rippling start of c5 does not move anything else + # end edge snaps to start of c4 + self.timeline.set_snapping_distance(1) + self.assertEdit( + c5, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 18, None, + [], [], + { + c5 : {"start": 18, "layer": 0}, + g1 : {"layer": 0, "duration": 21}, + }, [], [(c4, c5, track)]) + + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + ....................g1..................... + : *===============* + : | c5 | + : *===============* + __________:_________________________________________:________ + layer1____:_________________________________________:________ + : : + *===================* : + | c1 | : + *===================* : + :.........................................: + ...................g2.................... + :..................g0...................: + *===============* : + | c0 | : + *===============* : + ____:_______________________________________:________________ + layer2______________________________________:________________ + : : + : *=============* + : | c3 | + : *=============* + :.......................................: + *=========================* : + | c2 | : + *=========================* : + :.......................................: + _____________________________________________________________ + layer3_______________________________________________________ + + *===============* + | c4 | + *===============* + """ + + # rippling g1 using c5 + # initial position would make c1 go negative, but end edge of c1 + # will snap to end of c0, allowing the edit to succeed + # c4 also moves because it is after the start of g1 + self.timeline.set_snapping_distance(3) + self.assertEdit( + c5, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 12, 10, + [c1], [c0], + { + c5 : {"start": 13, "layer": 1}, + c1 : {"start": 0, "layer": 2}, + g1 : {"start": 0, "layer": 1}, + c4 : {"start": 7, "layer": 4}, + }, [(c1, c2, track)], [(c0, c1, track)]) + + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + _____________________________________________________________ + layer1_______________________________________________________ + + ....................g1..................... + : *===============* + : | c5 | + : *===============* + : ...................g2.................:.. + : :..................g0.................:.: + : *===============* : : + : | c0 | : : + : *===============* : : + :___:_____________________________________:_:________________ + layer2____________________________________:_:________________ + : : : : + *===================* : : + | c1 | : : + *===================* : : + :...:.....................................: : + : *=============* + : | c3 | + : *=============* + :.......................................: + *=========================* : + | c2 | : + *=========================* : + :.......................................: + _____________________________________________________________ + layer3_______________________________________________________ + _____________________________________________________________ + layer4_______________________________________________________ + + *===============* + | c4 | + *===============* + """ + # moving start of c1 will move everything expect c5 because they + # can snap to c5 since it is not moving + # c1 and c2 keep transition + self.assertEdit( + c1, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 20, 21, + [c1], [c5], + { + c1 : {"start": 21, "layer": 1}, + g1 : {"start": 13, "duration": 18}, + c0 : {"start": 23, "layer": 0}, + c2 : {"start": 23, "layer": 1}, + c3 : {"start": 36, "layer": 1}, + g0 : {"start": 23, "layer": 0}, + g2 : {"start": 23, "layer": 0}, + c4 : {"start": 28, "layer": 3}, + }, [], []) + + def test_trim(self): + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + ..................g2................... + : : + :...............g0............. : + : *===============* : + : | a0 | : + : *===*=====*=========* : + : | a1 | : : + : *=========* : : + *===================* : : + | v0 | : : + *===================* : : + :.............................: : + __________:_____________________________________:____________ + layer1____:_____________________________________:____________ + : : + : ............g1..............: + : : *=================* + : : | a2 | + : : *=================* + : *===========================* + : | v1 | + : *===========================* + __________:_________:___________________________:____________ + layer2____:_________:___________________________:____________ + *=============* *=================* + | a3 | | a4 | + *=============* *=================* + : :...........................: + *=========================* : + | v2 | : + *=============*===========*===========* + : | v3 | + : *=======================* + :.....................................: + _____________________________________________________________ + layer3_______________________________________________________ + + *===========================* *===============* + | v4 | | v5 | + *===========================* *===============* + """ + audio_track = self.add_audio_track() + video_track = self.add_video_track() + a0 = self.add_clip("a0", 0, [audio_track], 12, 8, 5, 15) + a1 = self.add_clip("a1", 0, [audio_track], 10, 5) + self.register_auto_transition(a1, a0, audio_track) + a2 = self.add_clip("a2", 1, [audio_track], 15, 9, 7, 19) + a3 = self.add_clip("a3", 2, [audio_track], 5, 7, 10) + a4 = self.add_clip("a4", 2, [audio_track], 15, 9) + + v0 = self.add_clip("v0", 0, [video_track], 5, 10, 5) + v1 = self.add_clip("v1", 1, [video_track], 10, 14) + v2 = self.add_clip("v2", 2, [video_track], 5, 13, 4) + v3 = self.add_clip("v3", 2, [video_track], 12, 12) + self.register_auto_transition(v2, v3, video_track) + v4 = self.add_clip("v4", 3, [video_track], 0, 13) + v5 = self.add_clip("v5", 3, [video_track], 18, 8) + + g0 = self.add_group("g0", [a0, a1, v0]) + g1 = self.add_group("g1", [v1, a2, a4]) + g2 = self.add_group("g2", [a3, v2, v3, g0, g1]) + + self.assertTimelineConfig() + + # edit failures + + # cannot trim end of g0 to 16 because a0 and a1 would fully + # overlap + self.assertFailEdit( + g0, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15, + GES.Error.INVALID_OVERLAP_IN_TRACK) + + # cannot edit to new layer because there would be triple overlaps + # between v2, v3, v4 and v5 + self.assertFailEdit( + g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 20, + GES.Error.INVALID_OVERLAP_IN_TRACK) + + # cannot trim g1 end to 14 because it would result in a negative + # duration for a2 and a4 + self.assertFailEdit( + g1, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 14, + GES.Error.NEGATIVE_TIME) + + # cannot trim end of v2 below its start + self.assertFailEdit( + v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 2, + GES.Error.NEGATIVE_TIME) + + # cannot trim end of g0 because a0's duration-limit would be + # exceeded + self.assertFailEdit( + g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 23, + GES.Error.NOT_ENOUGH_INTERNAL_CONTENT) + + # cannot trim g0 to 12 because a0 and a1 would fully overlap + self.assertFailEdit( + g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 12, + GES.Error.INVALID_OVERLAP_IN_TRACK) + + # cannot trim start of v2 beyond its end point + self.assertFailEdit( + v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 20, + GES.Error.NEGATIVE_TIME) + + # with snapping + self.timeline.set_snapping_distance(4) + + # cannot trim end of g2 to 19 because v1 and v2 would fully + # overlap after snapping to v5 start edge(18) + self.assertFailEdit( + g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 19, + GES.Error.INVALID_OVERLAP_IN_TRACK) + + # cannot trim g2 to 3 because it would snap to start edge of + # v4(0), causing v2's in-point to be negative + self.assertFailEdit( + g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 3, + GES.Error.NEGATIVE_TIME) + + # success + + self.timeline.set_snapping_distance(2) + + # first trim v4 start + self.assertEdit( + v4, 3, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 1, None, [], [], + { + v4 : {"start": 1, "in-point": 1, "duration": 12}, + }, [], []) + + # and trim v5 end + self.assertEdit( + v5, 3, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 25, 24, + [v5], [a2, a4, v1, v3], + { + v5 : {"duration": 6}, + }, [], []) + + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + ..................g2................... + : : + :...............g0............. : + : *===============* : + : | a0 | : + : *===*=====*=========* : + : | a1 | : : + : *=========* : : + *===================* : : + | v0 | : : + *===================* : : + :.............................: : + __________:_____________________________________:____________ + layer1____:_____________________________________:____________ + : : + : ............g1..............: + : : *=================* + : : | a2 | + : : *=================* + : *===========================* + : | v1 | + : *===========================* + __________:_________:___________________________:____________ + layer2____:_________:___________________________:____________ + *=============* *=================* + | a3 | | a4 | + *=============* *=================* + : :...........................: + *=========================* : + | v2 | : + *=============*===========*===========* + : | v3 | + : *=======================* + :.....................................: + _____________________________________________________________ + layer3_______________________________________________________ + + *=========================* *===========* + | v4 | | v5 | + *=========================* *===========* + """ + + # can trim g2 to 0 even though in-point of v2 is 4 because it will + # snap to 1. Note, there is only snapping on the start edge + # everything at the start edge is stretched back + self.assertEdit( + g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0, 1, + [v0, v2, a3], [v4], + { + v0 : {"start": 1, "in-point": 1, "duration": 14}, + a3 : {"start": 1, "in-point": 6, "duration": 11}, + v2 : {"start": 1, "in-point": 0, "duration": 17}, + g0 : {"start": 1, "duration": 19}, + g2 : {"start": 1, "duration": 23}, + }, [], []) + + self.timeline.set_snapping_distance(0) + + # trim end to use as a snapping point + self.assertEdit( + v4, 3, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 11, None, [], [], + { + v4 : {"duration": 10}, + }, [], []) + + self.timeline.set_snapping_distance(2) + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + ......................g2....................... + : : + :................g0.................... : + : *===============* : + : | a0 | : + : *===*=====*=========* : + : | a1 | : : + : *=========* : : + *===========================* : : + | v0 | : : + *===========================* : : + :.....................................: : + __:_____________________________________________:____________ + layer1__________________________________________:____________ + : : + : ............g1..............: + : : *=================* + : : | a2 | + : : *=================* + : *===========================* + : | v1 | + : *===========================* + __:_________________:___________________________:____________ + layer2______________:___________________________:____________ + *=====================* *=================* + | a3 | | a4 | + *=====================* *=================* + : :...........................: + *=================================* : + | v2 | : + *=====================*===========*===========* + : | v3 | + : *=======================* + :.............................................: + _____________________________________________________________ + layer3_______________________________________________________ + + *===================* *===========* + | v4 | | v5 | + *===================* *===========* + """ + + # can trim g2 to 12 even though it would cause a0 and a1 to fully + # overlap because the snapping allows it to succeed + self.assertEdit( + g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 12, 11, + [a3, v0, v2], [v4], + { + v0 : {"start": 11, "in-point": 11, "duration": 4}, + a1 : {"start": 11, "in-point": 1, "duration": 4}, + v1 : {"start": 11, "in-point": 1, "duration": 13}, + a3 : {"start": 11, "in-point": 16, "duration": 1}, + v2 : {"start": 11, "in-point": 10, "duration": 7}, + g0 : {"start": 11, "duration": 9}, + g1 : {"start": 11, "duration": 13}, + g2 : {"start": 11, "duration": 13}, + }, [], []) + + # trim end to use as a snapping point + self.assertEdit( + v5, 4, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 27, None, [], [], + { + v5 : {"duration": 9, "layer": 4}, + }, [], []) + + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + .............g2............ + : : + :.......g0......... : + : *===============* : + : | a0 | : + *=*=====*=========* : + | a1 | : : + *=======* : : + *=======* : : + | v0 | : : + *=======* : : + :.................: : + ______________________:_________________________:____________ + layer1________________:_________________________:____________ + : : + :.........g1..............: + : *=================* + : | a2 | + : *=================* + *=========================* + | v1 | + *=========================* + ______________________:_________________________:____________ + layer2________________:_________________________:____________ + *=* *=================* + a3| | a4 | + *=* *=================* + :.........................: + *=============* : + | v2 | : + *=*===========*===========* + : | v3 | + : *=======================* + :.........................: + _____________________________________________________________ + layer3_______________________________________________________ + + *===================* + | v4 | + *===================* + _____________________________________________________________ + layer4_______________________________________________________ + + *=================* + | v5 | + *=================* + """ + + # trim end of g2 and move layer. Without the snap, would fail since + # a2's duration-limit is 12. + # Even elements not being trimmed will still move layer + # a0 and a1 keep transition + # v2 and v3 keep transition + self.assertEdit( + g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 29, 27, + [a2, a4, v1, v3], [v5], + { + a2 : {"duration": 12, "layer": 2}, + v1 : {"duration": 16, "layer": 2}, + a4 : {"duration": 12, "layer": 3}, + v3 : {"duration": 15, "layer": 3}, + g1 : {"duration": 16, "layer": 2}, + g2 : {"duration": 16, "layer": 1}, + a0 : {"layer": 1}, + a1 : {"layer": 1}, + v0 : {"layer": 1}, + a3 : {"layer": 3}, + v2 : {"layer": 3}, + g0 : {"layer": 1}, + }, [], []) + + # trim start to use as a snapping point + self.timeline.set_snapping_distance(0) + self.assertEdit( + v5, 4, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 19, None, + [], [], + { + v5 : {"start": 19, "in-point": 1, "duration": 8}, + }, [], []) + + self.timeline.set_snapping_distance(2) + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + _____________________________________________________________ + layer1_______________________________________________________ + + .............g2.................. + : : + :.......g0......... : + : *===============* : + : | a0 | : + *=*=====*=========* : + | a1 | : : + *=======* : : + *=======* : : + | v0 | : : + *=======* : : + :.................: : + ______________________:_______________________________:______ + layer2________________:_______________________________:______ + : : + :.........g1....................: + : *=======================* + : | a2 | + : *=======================* + *===============================* + | v1 | + *===============================* + ______________________:_______________________________:______ + layer3________________:_______________________________:______ + *=* *=======================* + a3| | a4 | + *=* *=======================* + :...............................: + *===================*=============* : + | v4 | v2 | : + *===================*=*===========*=================* + : | v3 | + : *=============================* + :...............................: + _____________________________________________________________ + layer4_______________________________________________________ + + *===============* + | v5 | + *===============* + """ + + # trim end of g2 and move layer. Trim at 17 would lead to + # v3 being fully overlapped by v2, but snap to 19 makes it work + self.assertEdit( + g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 17, 19, + [a2, a4, v1, v3], [v5], + { + a0 : {"duration": 7}, + a2 : {"duration": 4}, + v1 : {"duration": 8}, + a4 : {"duration": 4}, + v3 : {"duration": 7}, + g0 : {"duration": 8}, + g1 : {"duration": 8}, + g2 : {"duration": 8}, + }, [], []) + + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + _____________________________________________________________ + layer1_______________________________________________________ + + ........g2....... + : : + :.......g0......: + : *=============* + : | a0 | + *=*=====*=======* + | a1 | : + *=======* : + *=======* : + | v0 | : + *=======* : + :...............: + ______________________:_______________:______________________ + layer2________________:_______________:______________________ + : : + :.......g1......: + : *=======* + : | a2 | + : *=======* + *===============* + | v1 | + *===============* + ______________________:_______________:______________________ + layer3________________:_______________:______________________ + *=* *=======* + a3| | a4 | + *=* *=======* + :...............: + *===================*=============* : + | v4 | v2 | : + *===================*=*===========*=* + : | v3 | + : *=============* + :...............: + _____________________________________________________________ + layer4_______________________________________________________ + + *===============* + | v5 | + *===============* + """ + + # can trim without trimming parent + self.assertEdit( + v0, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5, None, [], [], + { + v0 : {"start": 5, "in-point": 5, "duration": 10}, + g0 : {"start": 5, "duration": 14}, + g2 : {"start": 5, "duration": 14}, + }, [], []) + + self.assertEdit( + a2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 23, None, [], [], + { + a2 : {"duration": 8}, + g1 : {"duration": 12}, + g2 : {"duration": 18}, + }, [], []) + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + _____________________________________________________________ + layer1_______________________________________________________ + + ...................g2................ + : : + :...........g0............... : + : *=============* : + : | a0 | : + : *=*=====*=======* : + : | a1 | : : + : *=======* : : + *===================* : : + | v0 | : : + *===================* : : + :...........................: : + __________:___________________________________:______________ + layer2____:___________________________________:______________ + : : + : ............g1..........: + : : *===============* + : : | a2 | + : : *===============* + : *===============* : + : | v1 | : + : *===============* : + __________:___________:_______________________:______________ + layer3____:___________:_______________________:______________ + : *=* *=======* : + : a3| | a4 | : + : *=* *=======* : + : :.......................: + *===================*=============* : + | v4 | v2 | : + *===================*=*===========*=* : + : | v3 | : + : *=============* : + :...................................: + _____________________________________________________________ + layer4_______________________________________________________ + + *===============* + | v5 | + *===============* + """ + # same with group within a group + self.assertEdit( + g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 9, 11, + [v0], [v1, v2, v4, a3], + { + v0 : {"start": 11, "in-point": 11, "duration": 4, "layer": 0}, + a0 : {"layer": 0}, + a1 : {"layer": 0}, + g0 : {"start": 11, "duration": 8, "layer": 0}, + g2 : {"start": 11, "duration": 12, "layer": 0}, + }, [], []) + + self.assertEdit( + g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 17, 18, + [a0], [v2], + { + a0 : {"duration": 6}, + g0 : {"duration": 7}, + }, [], []) + + + def test_roll(self): + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + *===============================* ] + | c0 | ] + *=============*=====*===================*=====*=============* ]>video + | c1 | | c2 | ] + *===================* *===================* ] + ..........g0......... + *===========* : ] + | c3 | : ]>audio0 + *===*=======*===* : ] + : | c4 | : ] ] + : *===*=======*===* ] + : | c5 | ]>audio1 + : *===========* ] + ____________________:___________________:____________________ + layer1______________:___________________:____________________ + : : + .............g3.....:.... : + : *=================* : ] + : | c12 | ....:...g4............... ] + : *=================* : : : ] + :........g1.........: : : :.........g2........: ]>audio0 + *=============* : : : *=============* : ] + | c8 | : : : | c10 | : ] + *=============* : : *=========*=============* : ] + : : : | c7 | : ] ] + : *=========*=========* : ]>video + : | c6 | : : : ] ] + : *=============*=========* : : *=============* ] + : | c9 |...:...........:...: | c11 | ] + : *=============* : : : *=============* ] + :...................: : : :...................: ]>audio1 + :.......................: *=================* : ] + | c13 | : ] + *=================* : ] + :.......................: + """ + video = self.add_video_track() + audio0 = self.add_audio_track() + audio1 = self.add_audio_track() + + c0 = self.add_clip("c0", 0, [video], 7, 16) + c1 = self.add_clip("c1", 0, [video], 0, 10) + c2 = self.add_clip("c2", 0, [video], 20, 10, 20) + self.register_auto_transition(c1, c0, video) + self.register_auto_transition(c0, c2, video) + + c3 = self.add_clip("c3", 0, [audio0], 10, 6, 2, 38) + c4 = self.add_clip("c4", 0, [audio0, audio1], 12, 6, 15) + self.register_auto_transition(c3, c4, audio0) + c5 = self.add_clip("c5", 0, [audio1], 14, 6, 30, 38) + self.register_auto_transition(c4, c5, audio1) + c6 = self.add_clip("c6", 1, [audio1, video], 10, 5, 7) + c7 = self.add_clip("c7", 1, [audio0, video], 15, 5, 1, 15) + g0 = self.add_group("g0", [c3, c4, c5, c6, c7]) + + c8 = self.add_clip("c8", 1, [audio0], 0, 7, 3, 13) + c9 = self.add_clip("c9", 1, [audio1], 3, 7) + g1 = self.add_group("g1", [c8, c9]) + c10 = self.add_clip("c10", 1, [audio0], 20, 7, 1) + c11 = self.add_clip("c11", 1, [audio1], 23, 7, 3, 10) + g2 = self.add_group("g2", [c10, c11]) + + c12 = self.add_clip("c12", 1, [audio0], 3, 9) + self.register_auto_transition(c8, c12, audio0) + g3 = self.add_group("g3", [g1, c12]) + c13 = self.add_clip("c13", 1, [audio1], 18, 9) + self.register_auto_transition(c13, c11, audio1) + g4 = self.add_group("g4", [g2, c13]) + + self.assertTimelineConfig() + + # edit failures + self.timeline.set_snapping_distance(2) + + # cannot roll c10 to 22, which snaps to 23, because it will + # extend c5 beyond its duration limit of 8 + self.assertFailEdit( + c10, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22, + GES.Error.NOT_ENOUGH_INTERNAL_CONTENT) + + # same with g2 + self.assertFailEdit( + g2, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22, + GES.Error.NOT_ENOUGH_INTERNAL_CONTENT) + + # cannot roll end c9 to 8, which snaps to 7, because it would + # cause c3's in-point to become negative + self.assertFailEdit( + c9, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8, + GES.Error.NEGATIVE_TIME) + + # same with g1 + self.assertFailEdit( + g1, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8, + GES.Error.NEGATIVE_TIME) + + # cannot roll c13 to 19, snap to 20, because it would cause + # c4 to fully overlap c5 + self.assertFailEdit( + c13, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 19, + GES.Error.INVALID_OVERLAP_IN_TRACK) + + # cannot roll c12 to 11, snap to 10, because it would cause + # c3 to fully overlap c4 + self.assertFailEdit( + c12, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 11, + GES.Error.INVALID_OVERLAP_IN_TRACK) + + # give c6 a bit more allowed duration so we can focus on c9 + self.assertTrue(c6.set_inpoint(10)) + self.assertTimelineConfig({ c6 : {"in-point": 10}}) + # cannot roll c6 to 0 because it would cause c9 to be trimmed + # below its start + self.assertFailEdit( + c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 0, + GES.Error.NEGATIVE_TIME) + # set back + self.assertTrue(c6.set_inpoint(7)) + self.assertTimelineConfig({ c6 : {"in-point": 7}}) + + # give c7 a bit more allowed duration so we can focus on c10 + self.assertTrue(c7.set_inpoint(0)) + self.assertTimelineConfig({ c7 : {"in-point": 0}}) + # cannot roll end c7 to 30 because it would cause c10 to be + # trimmed beyond its end + self.assertFailEdit( + c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 30, + GES.Error.NEGATIVE_TIME) + # set back + self.assertTrue(c7.set_inpoint(1)) + self.assertTimelineConfig({ c7 : {"in-point": 1}}) + + # moving layer is not supported + self.assertFailEdit( + c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 7, None) + self.assertFailEdit( + c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 23, None) + + # successes + self.timeline.set_snapping_distance(0) + + # c1 and g1 are trimmed at their end + # NOTE: c12 is not trimmed even though it shares a group g3 + # with g1 because g3 does not share the same edge + # trim forward + self.assertEdit( + c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 11, None, + [], [], + { + c6 : {"start": 11, "in-point": 8, "duration": 4}, + c1 : {"duration": 11}, + c9 : {"duration": 8}, + g1 : {"duration": 11}, + }, [], []) + # and reset + self.assertEdit( + c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 10, None, + [], [], + { + c6 : {"start": 10, "in-point": 7, "duration": 5}, + c1 : {"duration": 10}, + c9 : {"duration": 7}, + g1 : {"duration": 10}, + }, [], []) + + # same with g0 + self.assertEdit( + g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 11, None, + [], [], + { + c6 : {"start": 11, "in-point": 8, "duration": 4}, + c3 : {"start": 11, "in-point": 3, "duration": 5}, + g0 : {"start": 11, "duration": 9}, + c1 : {"duration": 11}, + c9 : {"duration": 8}, + g1 : {"duration": 11}, + }, [], []) + self.assertEdit( + g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 10, None, + [], [], + { + c6 : {"start": 10, "in-point": 7, "duration": 5}, + c3 : {"start": 10, "in-point": 2, "duration": 6}, + g0 : {"start": 10, "duration": 10}, + c1 : {"duration": 10}, + c9 : {"duration": 7}, + g1 : {"duration": 10}, + }, [], []) + + self.timeline.set_snapping_distance(1) + # trim backward + # NOTE: c9 has zero width, not considered overlapping with c6 + # snapping allows the edit to succeed (in-point of c6 no longer + # negative) + # NOTE: c12 does not move, but c8 does because it is in the same + # group as g1 + # loose transitions + self.assertEdit( + c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 2, 3, + [c6], [c12], + { + c6 : {"start": 3, "in-point": 0, "duration": 12}, + g0 : {"start": 3, "duration": 17}, + c1 : {"duration": 3}, + c8 : {"duration": 3}, + c9 : {"duration": 0}, + g1 : {"duration": 3}, + }, [], [(c1, c0, video), (c8, c12, audio0)]) + + # bring back + # NOTE: no snapping to c3 start edge because it is part of the + # element being edited, g0, even though it doesn't end up changing + # gain back new transitions + self.assertEdit( + g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 10, None, + [], [], + { + c6 : {"start": 10, "in-point": 7, "duration": 5}, + g0 : {"start": 10, "duration": 10}, + c1 : {"duration": 10}, + c8 : {"duration": 10}, + c9 : {"duration": 7}, + g1 : {"duration": 10}, + }, [(c1, c0, video), (c8, c12, audio0)], []) + + + # same but with the end edge of g0 + self.timeline.set_snapping_distance(0) + self.assertEdit( + c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 19, None, [], [], + { + c7 : {"duration": 4}, + c2 : {"start": 19, "in-point": 19, "duration": 11}, + c10 : {"start": 19, "in-point": 0, "duration": 8}, + g2 : {"start": 19, "duration": 11}, + }, [], []) + self.assertEdit( + c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 20, None, [], [], + { + c7 : {"duration": 5}, + c2 : {"start": 20, "in-point": 20, "duration": 10}, + c10 : {"start": 20, "in-point": 1, "duration": 7}, + g2 : {"start": 20, "duration": 10}, + }, [], []) + # do same with g0 + self.assertEdit( + g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 19, None, [], [], + { + c7 : {"duration": 4}, + c5 : {"duration": 5}, + g0 : {"duration": 9}, + c2 : {"start": 19, "in-point": 19, "duration": 11}, + c10 : {"start": 19, "in-point": 0, "duration": 8}, + g2 : {"start": 19, "duration": 11}, + }, [], []) + self.assertEdit( + g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 20, None, [], [], + { + c7 : {"duration": 5}, + c5 : {"duration": 6}, + g0 : {"duration": 10}, + c2 : {"start": 20, "in-point": 20, "duration": 10}, + c10 : {"start": 20, "in-point": 1, "duration": 7}, + g2 : {"start": 20, "duration": 10}, + }, [], []) + + self.timeline.set_snapping_distance(1) + # trim forwards + # NOTE: c10 has zero width, not considered overlapping with c7 + # snapping allows the edit to succeed (duration of c7 no longer + # above its limit) + # NOTE: c12 does not move, but c11 does because it is in the same + # group as g2 + self.assertEdit( + c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 28, 27, + [c7], [c13], + { + c7 : {"duration": 12}, + g0 : {"duration": 17}, + c2 : {"start": 27, "in-point": 27, "duration": 3}, + c10 : {"start": 27, "in-point": 8, "duration": 0}, + c11 : {"start": 27, "in-point": 7, "duration": 3}, + g2 : {"start": 27, "duration": 3}, + }, [], [(c0, c2, video), (c13, c11, audio1)]) + # bring back using g0 + # NOTE: no snapping to c5 end edge because it is part of the + # element being edited, g0, even though it doesn't end up changing + self.assertEdit( + g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 20, None, [], [], + { + c7 : {"duration": 5}, + g0 : {"duration": 10}, + c2 : {"start": 20, "in-point": 20, "duration": 10}, + c10 : {"start": 20, "in-point": 1, "duration": 7}, + c11 : {"start": 20, "in-point": 0, "duration": 10}, + g2 : {"start": 20, "duration": 10}, + }, [(c0, c2, video), (c13, c11, audio1)], []) + + # adjust c0 for snapping + # doesn't move anything else + self.assertEdit( + c0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 8, None, + [], [], + { + c0 : {"start": 8, "in-point": 1, "duration": 15}, + }, [], []) + self.assertEdit( + c0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 22, None, [], [], + { + c0 : {"duration": 14}, + }, [], []) + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + *===========================* ] + | c0 | ] + *===============*===*===================*===*===============* ]>video + | c1 | | c2 | ] + *===================* *===================* ] + ..........g0......... + *===========* : ] + | c3 | : ]>audio0 + *===*=======*===* : ] + : | c4 | : ] ] + : *===*=======*===* ] + : | c5 | ]>audio1 + : *===========* ] + ____________________:___________________:____________________ + layer1______________:___________________:____________________ + : : + .............g3.....:.... : + : *=================* : ] + : | c12 | ....:...g4............... ] + : *=================* : : : ] + :........g1.........: : : :.........g2........: ]>audio0 + *===================* : : *=============* : ] + | c8 | : : | c10 | : ] + *===================* : *=========*=============* : ] + : : : | c7 | : ] ] + : *=========*=========* : ]>video + : | c6 | : : : ] ] + : *=============*=========* : *===================* ] + : | c9 |...:...........:...| c11 | ] + : *=============* : : *===================* ] + :...................: : : :...................: ]>audio1 + :.......................: *=================* : ] + | c13 | : ] + *=================* : ] + :.......................: + """ + # rolling only moves an element if it contains a source that + # touches the rolling edge. For a group, any source below it + # at the corresponding edge counts, we also prefer trimming the + # whole group over just one of its childrens. + # As such, when rolling the end of c5, c11 shares the audio1 + # track and starts when c5 ends, so is set to be trimmed. But it + # is also at the start edge of its parent g2, so g2 is set to be + # trimmed. However, it is not at the start of g4, so g4 is not + # set to be trimmed. As such, c10 will also move, even though it + # does not share a track. c2, on the other hand, will not move + # NOTE: snapping helps keep c5's duration below its limit (8) + self.assertEdit( + c5, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 23, 22, + [c5], [c0], + { + c5 : {"duration": 8}, + g0 : {"duration": 12}, + c11 : {"start": 22, "in-point": 2, "duration": 8}, + c10 : {"start": 22, "in-point": 3, "duration": 5}, + g2 : {"start": 22, "duration": 8}, + }, [], []) + + # same with c3 at its start edge + self.assertEdit( + c3, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 7, 8, + [c3], [c0], + { + c3 : {"start": 8, "in-point": 0, "duration": 8}, + g0 : {"start": 8, "duration": 14}, + c8 : {"duration": 8}, + c9 : {"duration": 5}, + g1 : {"duration": 8}, + }, [], []) + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + *===========================* ] + | c0 | ] + *===============*===*===================*===*===============* ]>video + | c1 | | c2 | ] + *===================* *===================* ] + ..............g0............. + *===============* : ] + | c3 | : ]>audio0 + *=======*=======*===* : ] + : | c4 | : ] ] + : *===*=======*=======* ] + : | c5 | ]>audio1 + : *===============* ] + ________________:___________________________:________________ + layer1__________:___________________________:________________ + : : + .............g3.:........ : + : *=================* : ] + : | c12 | ........:.g4............. ] + : *=================* : : : ] + :........g1.....: : : :.....g2........: ]>audio0 + *===============* : : *=========* : ] + | c8 | : : | c10 | : ] + *===============* : *=========* *=========* : ] + : : : | c7 | : : ] ] + : : *=========*=========* : : ]>video + : : | c6 | : : : ] ] + : *=========* *=========* : *===============* ] + : | c9 |.......:...........:.......| c11 | ] + : *=========* : : *===============* ] + :...............: : : :...............: ]>audio1 + :.......................: *=================* : ] + | c13 | : ] + *=================* : ] + :.......................: + """ + # rolling end of c1 only moves c6, similarly with c2 and c7 + self.assertEdit( + c1, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8, 8, + [c1], [c9, c8, c3, c0], + { + c1 : {"duration": 8}, + c6 : {"start": 8, "in-point": 5, "duration": 7}, + }, [], [(c1, c0, video)]) + self.assertEdit( + c2, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22, 22, + [c2], [c0, c5, c10, c11], + { + c2 : {"start": 22, "in-point": 22, "duration": 8}, + c7 : {"duration": 7}, + }, [], [(c0, c2, video)]) + + # move c3 end edge out the way + self.timeline.set_snapping_distance(0) + self.assertEdit( + c3, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 17, None, [], [], + { + c3: {"duration": 9}, + }, [], []) + + self.timeline.set_snapping_distance(2) + + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + *===========================* ] + | c0 | ] + *===============*===========================*===============* ]>video + | c1 | | c2 | ] + *===============* *===============* ] + ..............g0............. + *=================* : ] + | c3 | : ]>audio0 + *=======*=========*=* : ] + : | c4 | : ] ] + : *===*=======*=======* ] + : | c5 | ]>audio1 + : *===============* ] + ________________:___________________________:________________ + layer1__________:___________________________:________________ + : : + .............g3.:........ : + : *=================* : ] + : | c12 | ........:.g4............. ] + : *=================* : : : ] + :........g1.....: : : :.....g2........: ]>audio0 + *===============* : : *=========* : ] + | c8 | : : | c10 | : ] + *===============* : *=============*=========* : ] + : : : | c7 | : ] ] + : *=============*=============* : ]>video + : | c6 | : : : ] ] + : *=========*=============* : *===============* ] + : | c9 |.......:...........:.......| c11 | ] + : *=========* : : *===============* ] + :...............: : : :...............: ]>audio1 + :.......................: *=================* : ] + | c13 | : ] + *=================* : ] + :.......................: + """ + + # can safely roll within a group + # NOTE: we do not snap to an edge used in the edit + self.assertEdit( + c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 15, 14, + [c6], [c5], + { + c6: {"duration": 6}, + c7: {"start": 14, "in-point": 0, "duration": 8}, + }, [], []) + self.assertEdit( + c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 16, 17, + [c7], [c3], + { + c6: {"duration": 9}, + c7: {"start": 17, "in-point": 3, "duration": 5}, + }, [], []) + + def test_snap_from_negative(self): + track = self.add_video_track() + c0 = self.add_clip("c0", 0, [track], 0, 20) + c1 = self.add_clip("c1", 0, [track], 100, 10) + g1 = self.add_group("g0", [c0, c1]) + snap_to = self.add_clip("snap-to", 2, [track], 4, 50) + + self.assertTimelineConfig() + + self.timeline.set_snapping_distance(9) + # move without snap would make start edge of c0 go to -5, but this + # edge snaps to the start edge of snap_to, allowing the edit to + # succeed + self.assertEdit( + c1, 1, GES.EditMode.NORMAL, GES.Edge.NONE, 95, 4, [c0], [snap_to], + { + c0 : {"start": 4, "layer": 1}, + c1 : {"start": 104, "layer": 1}, + g1 : {"start": 4, "layer": 1}, + }, [], []) + + def test_move_layer(self): + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + *===================* + | c1 | + *===================* + ..............g2............... + : : + :.............g0..............: + *=============================* + | c0 | + *=============================* + : : + : : + __________:_____________________________:____________________ + layer1____:_____________________________:____________________ + : : + *===================* : + | c2 | : + *===================* : + :.............................: + : ...............g1...:.......... + : *=============================* + : | c3 | + : *=============================* + __________:_________:_____________________________:__________ + layer2____:_________:_____________________________:__________ + : : : + : : *===================* + : : | c5 | + : :.........*===================* + *===================* : + | c4 | : + *===================* : + :.............................: + *===================* + | c6 | + *===================* + """ + track = self.add_video_track() + c0 = self.add_clip("c0", 0, [track], 5, 15) + c1 = self.add_clip("c1", 0, [track], 15, 10) + self.register_auto_transition(c0, c1, track) + c2 = self.add_clip("c2", 1, [track], 5, 10) + c3 = self.add_clip("c3", 1, [track], 10, 15) + self.register_auto_transition(c2, c3, track) + c4 = self.add_clip("c4", 2, [track], 5, 10) + c5 = self.add_clip("c5", 2, [track], 15, 10) + c6 = self.add_clip("c6", 2, [track], 10, 10) + self.register_auto_transition(c4, c6, track) + self.register_auto_transition(c6, c5, track) + + g0 = self.add_group("g0", [c0, c2]) + g1 = self.add_group("g1", [c3, c5]) + g2 = self.add_group("g2", [g0, c4]) + + self.assertTimelineConfig() + + layer = self.timeline.get_layer(0) + self.assertIsNotNone(layer) + + # don't loose auto-transitions + # clips stay in their layer (groups do not move them) + self.timeline.move_layer(layer, 2) + self.assertTimelineConfig( + { + c0 : {"layer": 2}, + c1 : {"layer": 2}, + c2 : {"layer": 0}, + c3 : {"layer": 0}, + c4 : {"layer": 1}, + c5 : {"layer": 1}, + c6 : {"layer": 1}, + g1 : {"layer": 0}, + }) + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + ..............g2............... + : : + :.............g0..............: + *===================* : + | c2 | : + *===================* : + : ..........................: + : : ...............g1.............. + : : *=============================* + : : | c3 | + : : *=============================* + __________:___:_____:_____________________________:__________ + layer1____:___:_____:_____________________________:__________ + : : : : + : : : *===================* + : : : | c5 | + : : : *===================* + : : :.............................: + : :..........g2..... + : g0 : + : : : + *===================*: + | c4 |: + *===================*: + : :................: + : : *===================* + : : | c6 | + : : *===================* + __________:___:______________________________________________ + layer2____:___:______________________________________________ + : :.......................... + *=============================* + | c0 | + *=============================* + :.............................: + :.............................: + *===================* + | c1 | + *===================* + """ + layer = self.timeline.get_layer(1) + self.assertIsNotNone(layer) + self.timeline.move_layer(layer, 0) + self.assertTimelineConfig( + { + c2 : {"layer": 1}, + c3 : {"layer": 1}, + c4 : {"layer": 0}, + c5 : {"layer": 0}, + c6 : {"layer": 0}, + g0 : {"layer": 1}, + }) + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + *===================* + | c6 | + *===================* + + ..............g2............... + *===================* : + | c4 | : + *===================* : + : ...............g1...:.......... + : : *===================* + : : | c5 | + : : *===================* + __________:_________:___________________:_________:__________ + layer1____:_________:___________________:_________:__________ + : : : : + : *=============================* + : | c3 | + : *=============================* + : :...................:.........: + :.............g0..............: + *===================* : + | c2 | : + *===================* : + :.............................: + __________:_____________________________:____________________ + layer2____:_____________________________:____________________ + : : + *=============================* + | c0 | + *=============================* + :.............................: + :.............................: + *===================* + | c1 | + *===================* + """ + self.timeline.append_layer() + layer = self.timeline.get_layer(3) + self.assertIsNotNone(layer) + self.timeline.move_layer(layer, 1) + self.assertTimelineConfig( + { + c0 : {"layer": 3}, + c1 : {"layer": 3}, + c2 : {"layer": 2}, + c3 : {"layer": 2}, + g0 : {"layer": 2}, + }) + layer = self.timeline.get_layer(3) + self.assertIsNotNone(layer) + self.timeline.move_layer(layer, 0) + self.assertTimelineConfig( + { + c0 : {"layer": 0}, + c1 : {"layer": 0}, + c2 : {"layer": 3}, + c3 : {"layer": 3}, + c4 : {"layer": 1}, + c5 : {"layer": 1}, + c6 : {"layer": 1}, + g0 : {"layer": 0}, + g1 : {"layer": 1}, + }) + """ + , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , + 0 5 10 15 20 25 30 + _____________________________________________________________ + layer0_______________________________________________________ + + *===================* + | c1 | + *===================* + ..............g2............... + :.............g0..............: + *=============================* + | c0 | + *=============================* + : ..........................: + __________:___:______________________________________________ + layer1____:___:______________________________________________ + : : + : :.......g2....... + : g0 : + : : : + *===================* + | c4 | + *===================* + : :...............: + : : *===================* + : : | c6 | + : : *===================* + : : ...............g1.............. + : : : *===================* + : : : | c5 | + : : : *===================* + __________:___:_____:_____________________________:__________ + layer2____:___:_____:_____________________________:__________ + __________:___:_____:_____________________________:__________ + layer3____:___:_____:_____________________________:__________ + : : : : + : : *=============================* + : : | c3 | + : : *=============================* + : : :.............................: + : :.......................... + *===================* : + | c2 | : + *===================* : + :.............................: + :.............................: + """ + layer = self.timeline.get_layer(1) + self.assertTrue(self.timeline.remove_layer(layer)) + + # TODO: add tests when removing layers: + # FIXME: groups should probably loose their children when they + # are removed from the timeline, which would change g1's + # priority, but currently c5 remains in the group with priority + # of the removed layer + + def test_not_snappable(self): + track = self.add_video_track() + c0 = self.add_clip("c0", 0, [track], 0, 10) + no_source = self.add_clip( + "no-source", 0, [], 5, 10, effects=[GES.Effect.new("agingtv")]) + effect_clip = self.add_clip( + "effect-clip", 0, [track], 5, 10, clip_type=GES.EffectClip, + asset_id="agingtv || audioecho") + text = self.add_clip( + "text-clip", 0, [track], 5, 10, clip_type=GES.TextOverlayClip) + + self.assertTimelineConfig() + + self.timeline.set_snapping_distance(20) + + self.assertEdit( + c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 8, None, + [], [], {c0 : {"start": 8}}, [], []) + self.assertEdit( + c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 5, None, + [], [], {c0 : {"start": 5}}, [], []) + self.assertEdit( + c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 8, None, + [], [], {c0 : {"duration": 3}}, [], []) + + c1 = self.add_clip("c1", 0, [track], 30, 3) + self.assertTimelineConfig() + + # end edge snaps to start of c1 + self.assertEdit( + c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10, 30, + [c0], [c1], {c0 : {"start": 27}}, [], []) + + +class TestTransitions(common.GESSimpleTimelineTest): + + def test_emission_order_for_transition_clip_added_signal(self): + self.timeline.props.auto_transition = True + unused_clip1 = self.add_clip(0, 0, 100) + clip2 = self.add_clip(100, 0, 100) + + # Connect to signals to track in which order they are emitted. + signals = [] + + def clip_added_cb(layer, clip): + self.assertIsInstance(clip, GES.TransitionClip) + signals.append("clip-added") + self.layer.connect("clip-added", clip_added_cb) + + def property_changed_cb(clip, pspec): + self.assertEqual(clip, clip2) + self.assertEqual(pspec.name, "start") + signals.append("notify::start") + clip2.connect("notify::start", property_changed_cb) + + # Move clip2 to create a transition with clip1. + clip2.edit([], self.layer.get_priority(), + GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 50) + # The clip-added signal is emitted twice, once for the video + # transition and once for the audio transition. + self.assertEqual( + signals, ["notify::start", "clip-added", "clip-added"]) + + def create_xges(self): + uri = common.get_asset_uri("png.png") + return """<ges version='0.4'> + <project properties='properties;' metadatas='metadatas, author=(string)"", render-scale=(double)100, format-version=(string)0.4;'> + <ressources> + <asset id='GESTitleClip' extractable-type-name='GESTitleClip' properties='properties;' metadatas='metadatas;' /> + <asset id='bar-wipe-lr' extractable-type-name='GESTransitionClip' properties='properties;' metadatas='metadatas, description=(string)GES_VIDEO_STANDARD_TRANSITION_TYPE_BAR_WIPE_LR;' /> + <asset id='%(uri)s' extractable-type-name='GESUriClip' properties='properties, supported-formats=(int)4, duration=(guint64)18446744073709551615;' metadatas='metadatas, video-codec=(string)PNG, file-size=(guint64)73294;' /> + <asset id='crossfade' extractable-type-name='GESTransitionClip' properties='properties;' metadatas='metadatas, description=(string)GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE;' /> + </ressources> + <timeline properties='properties, auto-transition=(boolean)true, snapping-distance=(guint64)31710871;' metadatas='metadatas, duration=(guint64)13929667032;'> + <track caps='video/x-raw(ANY)' track-type='4' track-id='0' properties='properties, async-handling=(boolean)false, message-forward=(boolean)true, caps=(string)"video/x-raw\(ANY\)", restriction-caps=(string)"video/x-raw\,\ width\=\(int\)1920\,\ height\=\(int\)1080\,\ framerate\=\(fraction\)30/1", mixing=(boolean)true;' metadatas='metadatas;'/> + <track caps='audio/x-raw(ANY)' track-type='2' track-id='1' properties='properties, async-handling=(boolean)false, message-forward=(boolean)true, caps=(string)"audio/x-raw\(ANY\)", restriction-caps=(string)"audio/x-raw\,\ format\=\(string\)S32LE\,\ channels\=\(int\)2\,\ rate\=\(int\)44100\,\ layout\=\(string\)interleaved", mixing=(boolean)true;' metadatas='metadatas;'/> + <layer priority='0' properties='properties, auto-transition=(boolean)true;' metadatas='metadatas, volume=(float)1;'> + <clip id='0' asset-id='%(uri)s' type-name='GESUriClip' layer-priority='0' track-types='6' start='0' duration='4558919302' inpoint='0' rate='0' properties='properties, name=(string)uriclip25263, mute=(boolean)false, is-image=(boolean)false;' /> + <clip id='1' asset-id='bar-wipe-lr' type-name='GESTransitionClip' layer-priority='0' track-types='4' start='3225722559' duration='1333196743' inpoint='0' rate='0' properties='properties, name=(string)transitionclip84;' children-properties='properties, GESVideoTransition::border=(uint)0, GESVideoTransition::invert=(boolean)false;'/> + <clip id='2' asset-id='%(uri)s' type-name='GESUriClip' layer-priority='0' track-types='6' start='3225722559' duration='3479110239' inpoint='4558919302' rate='0' properties='properties, name=(string)uriclip25265, mute=(boolean)false, is-image=(boolean)false;' /> + </layer> + <groups> + </groups> + </timeline> +</project> +</ges>""" % {"uri": uri} + + def test_auto_transition(self): + xges = self.create_xges() + with common.created_project_file(xges) as proj_uri: + project = GES.Project.new(proj_uri) + timeline = project.extract() + + mainloop = common.create_main_loop() + def loaded_cb(unused_project, unused_timeline): + mainloop.quit() + project.connect("loaded", loaded_cb) + + mainloop.run() + + layers = timeline.get_layers() + self.assertEqual(len(layers), 1) + + self.assertTrue(layers[0].props.auto_transition) + + def test_transition_type(self): + xges = self.create_xges() + with common.created_project_file(xges) as proj_uri: + project = GES.Project.new(proj_uri) + timeline = project.extract() + + mainloop = common.create_main_loop() + def loaded_cb(unused_project, unused_timeline): + mainloop.quit() + project.connect("loaded", loaded_cb) + mainloop.run() + + layers = timeline.get_layers() + self.assertEqual(len(layers), 1) + + clips = layers[0].get_clips() + clip1 = clips[0] + clip2 = clips[-1] + # There should be a transition because clip1 intersects clip2 + self.assertLess(clip1.props.start, clip2.props.start) + self.assertLess(clip2.props.start, clip1.props.start + clip1.props.duration) + self.assertLess(clip1.props.start + clip1.props.duration, clip2.props.start + clip2.props.duration) + self.assertEqual(len(clips), 3) + + +class TestPriorities(common.GESSimpleTimelineTest): + + def test_clips_priorities(self): + clip = self.add_clip(0, 0, 100) + clip1 = self.add_clip(100, 0, 100) + self.timeline.commit() + + self.assertLess(clip.props.priority, clip1.props.priority) + + clip.props.start = 101 + self.timeline.commit() + self.assertGreater(clip.props.priority, clip1.props.priority) + + +class TestTimelineElement(common.GESSimpleTimelineTest): + + def test_set_child_property(self): + clip = self.add_clip(0, 0, 100) + source = clip.find_track_element(None, GES.VideoSource) + self.assertTrue(source.set_child_property("height", 5)) + self.assertEqual(clip.get_child_property("height"), (True, 5)) diff --git a/tests/check/scenarios/check-clip-positioning.validatetest b/tests/check/scenarios/check-clip-positioning.validatetest new file mode 100644 index 0000000000..2d19769c90 --- /dev/null +++ b/tests/check/scenarios/check-clip-positioning.validatetest @@ -0,0 +1,12 @@ +meta, + tool = "ges-launch-$(gst_api_version)", + handles-states=true, + args = { + +test-clip, blue, "d=0.5", "asset-id=time-overlay, width=640, height=360, max-duration=5.0", "name=clip", + --track-types, video, + --videosink, "$(videosink) name=videosink sync=true", + --video-caps, "video/x-raw,format=I420,width=640,height=360,framerate=10/1,chroma-site=mpeg2,colorimetry=bt709;", + } + +check-child-properties, element-name=clip, width=0, height=0 +stop \ No newline at end of file diff --git a/tests/check/scenarios/check_edit_in_frames.scenario b/tests/check/scenarios/check_edit_in_frames.scenario new file mode 100644 index 0000000000..39a1eb596d --- /dev/null +++ b/tests/check/scenarios/check_edit_in_frames.scenario @@ -0,0 +1,31 @@ +description, handles-states=true, + ges-options={\ + --track-types, video\ + } + +add-clip, name=clip, asset-id=GESTestClip, layer-priority=0, type=GESTestClip, start=f0, inpoint=f30, duration=f60 + +check-ges-properties, element-name=clip, start=0, in-point=1.0, duration=2.0 +edit, element-name=clip, position=f30 + +check-ges-properties, element-name=clip, start=1.0, in-point=1.0, duration=2.0 + +# Getting the 60th frame in the input media file, means inpoint=f30 + f30 = f60 +edit, element-name=clip, position=f60, edit-mode=edit_trim, edge=edge_end +check-ges-properties, element-name=clip, start=1.0, in-point=1.0, duration=1.0 + +set-track-restriction-caps, track-type=video, caps="video/x-raw,width=1280,height=720,framerate=60/1" + +# 60 frames in media time, meaning 90 - inpoint (30) / 30 = 2 seconds +edit, element-name=clip, source-frame=90, edit-mode=edit_trim, edge=edge_end +check-ges-properties, element-name=clip, start=1.0, in-point=1.0, duration=2.0 + +# 60 frames in timeline time, meaning 60/60 = 1 second +edit, element-name=clip, position=f60 +check-ges-properties, element-name=clip, start=1.0, in-point=1.0, duration=2.0 + +# 60 frames in timeline time, meaning 60/60 = 1 second +edit, element-name=clip, source-frame=75, edit-mode=trim, edge=start +check-ges-properties, element-name=clip, start=2.5, in-point=2.5, duration=0.5 + +stop \ No newline at end of file diff --git a/tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario b/tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario new file mode 100644 index 0000000000..e5e37ed6e9 --- /dev/null +++ b/tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario @@ -0,0 +1,23 @@ +description, handles-states=true, + ges-options={\ + --track-types, video, + --disable-mixing, + "--videosink=fakevideosink"\ + } + +add-clip, name=clip, asset-id="time-overlay,framerate=120/1", layer-priority=0, type=GESSourceClip, pattern=blue, duration=f240, inpoint=f100 +set-child-properties, element-name=clip, time-mode=time-code +pause + +check-last-sample, sinkpad-caps="video/x-raw", timecode-frame-number=100 + +edit, element-name=clip, edit-mode=normal, position=1.0 + +edit, element-name=clip, edit-mode=edit_trim, edge=start, source-frame=60 +edit, element-name=clip, position=0 +commit; +check-last-sample, sinkpad-caps="video/x-raw", timecode-frame-number=60 + +edit, element-name=clip, edit-mode=edit_trim, edge=start, source-frame=120 +check-ges-properties, element-name=clip, start=0.5 +stop diff --git a/tests/check/scenarios/check_keyframes_in_compositor_two_sources.validatetest b/tests/check/scenarios/check_keyframes_in_compositor_two_sources.validatetest new file mode 100644 index 0000000000..cbe1062f2f --- /dev/null +++ b/tests/check/scenarios/check_keyframes_in_compositor_two_sources.validatetest @@ -0,0 +1,111 @@ +meta, + tool = "ges-launch-$(gst_api_version)", + args = { + -t, video, + --videosink, "$(videosink) name=videosink sync=true", + --video-caps, "video/x-raw,width=500,height=500,framerate=10/1,format=I420,colorimetry=(string)bt601,chroma-site=(string)jpeg", + }, + handles-states=true, + ignore-eos=true, + configs = { + "$(validateflow), pad=videosink:sink, buffers-checksum=as-id, ignored-fields=\"stream-start={stream-id,group-id, stream}\"", + }, + expected-issues = { + # Blending is not 100% reproducible when seeking/changing values for + # some reason. It has been manually checked and the contents are very + # slightly off and ssim would detect no difference at all between the + # images + "expected-issue, issue-id=validateflow::mismatch, details=\".*content-id=10.*\\\\n.*\\\\n.*content-id=12.*\", sometimes=true", + "expected-issue, issue-id=validateflow::mismatch, details=\".*content-id=12.*\\\\n.*\\\\n.*content-id=10.*\", sometimes=true", + "expected-issue, issue-id=validateflow::mismatch, details=\".*content-id=0.*\\\\n.*\\\\n.*content-id=12.*\", sometimes=true", + "expected-issue, issue-id=validateflow::mismatch, details=\".*content-id=0.*\\\\n.*\\\\n.*content-id=13.*\", sometimes=true", + } + +remove-feature, name=queue + +add-clip, name=c0, asset-id="pattern=blue", layer-priority=0, type=GESTestClip, start=0, duration=2.0, inpoint=2.0 +add-clip, name=c1, asset-id="pattern=green", layer-priority=1, type=GESTestClip, start=0, duration=2.0 +set-child-properties, element-name=c0, width=250, height=250, pattern=blue +set-child-properties, element-name=c1, width=250, height=250, pattern=green + +set-control-source, element-name=c0, property-name=posx, binding-type=direct-absolute +set-control-source, element-name=c0, property-name=posy, binding-type=direct-absolute +set-control-source, element-name=c1, property-name=posx, binding-type=direct-absolute +set-control-source, element-name=c1, property-name=posy, binding-type=direct-absolute + +# c0 starts in the top left corner going to the bottom right at 1sec and going back to the top right at 2s +# Keyframes are in 'source stream time' so we take the inpoint into account for the timestamp +add-keyframe, element-name=c0, timestamp=2.0, property-name=posx, value=0 +add-keyframe, element-name=c0, timestamp=2.0, property-name=posy, value=0 +add-keyframe, element-name=c1, timestamp=0.0, property-name=posx, value=250 +add-keyframe, element-name=c1, timestamp=0.0, property-name=posy, value=250 + +add-keyframe, element-name=c0, timestamp=3.0, property-name=posx, value=250 +add-keyframe, element-name=c0, timestamp=3.0, property-name=posy, value=250 +add-keyframe, element-name=c1, timestamp=1.0, property-name=posx, value=0 +add-keyframe, element-name=c1, timestamp=1.0, property-name=posy, value=0 + +add-keyframe, element-name=c0, timestamp=4.0, property-name=posx, value=0 +add-keyframe, element-name=c0, timestamp=4.0, property-name=posy, value=0 +add-keyframe, element-name=c1, timestamp=2.0, property-name=posx, value=250 +add-keyframe, element-name=c1, timestamp=2.0, property-name=posy, value=250 + +play + +check-properties, + gessmartmixer0-compositor.sink_0::xpos=0, gessmartmixer0-compositor.sink_0::ypos=0, + gessmartmixer0-compositor.sink_1::xpos=250, gessmartmixer0-compositor.sink_1::ypos=250 + +crank-clock +wait, on-clock=true + +check-properties, + gessmartmixer0-compositor.sink_0::xpos=25, gessmartmixer0-compositor.sink_0::ypos=25, + gessmartmixer0-compositor.sink_1::xpos=225, gessmartmixer0-compositor.sink_1::ypos=225 + +# Check the 5th buffer +crank-clock, repeat=4 +wait, on-clock=true +check-properties, + gessmartmixer0-compositor.sink_0::xpos=125, gessmartmixer0-compositor.sink_0::ypos=125, + gessmartmixer0-compositor.sink_1::xpos=125, gessmartmixer0-compositor.sink_1::ypos=125 + +crank-clock +check-position, expected-position=0.5 + +# Check the 10th buffer +crank-clock, repeat=4 +wait, on-clock=true +check-properties, + gessmartmixer0-compositor.sink_0::xpos=250, gessmartmixer0-compositor.sink_0::ypos=250, + gessmartmixer0-compositor.sink_1::xpos=0, gessmartmixer0-compositor.sink_1::ypos=0 + +crank-clock +check-position, expected-position=1.0 + +crank-clock, repeat=11 +check-position, on-message=eos, expected-position=2000000001 + +seek, start=1.0, flags=accurate+flush +wait, on-clock=true + +# 10th buffer +check-properties, + gessmartmixer0-compositor.sink_3::xpos=250, gessmartmixer0-compositor.sink_3::ypos=250, + gessmartmixer0-compositor.sink_4::xpos=0, gessmartmixer0-compositor.sink_4::ypos=0 + +set-ges-properties, element-name=c0, start=1000000000 +set-ges-properties, element-name=c1, start=1000000000 +commit; +wait, on-clock=true + +check-position, expected-position=1.0 + +# First buffer +check-properties, + gessmartmixer0-compositor.sink_3::xpos=0, + gessmartmixer0-compositor.sink_3::ypos=0, + gessmartmixer0-compositor.sink_4::xpos=250, + gessmartmixer0-compositor.sink_4::ypos=250 + +stop diff --git a/tests/check/scenarios/check_keyframes_in_compositor_two_sources/flow-expectations/log-videosink-sink-expected b/tests/check/scenarios/check_keyframes_in_compositor_two_sources/flow-expectations/log-videosink-sink-expected new file mode 100644 index 0000000000..707a669b22 --- /dev/null +++ b/tests/check/scenarios/check_keyframes_in_compositor_two_sources/flow-expectations/log-videosink-sink-expected @@ -0,0 +1,34 @@ +event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE; +event caps: video/x-raw, chroma-site=(string)jpeg, colorimetry=(string)bt601, format=(string)I420, framerate=(fraction)10/1, height=(int)500, width=(int)500; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:02.000000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000, position=none +buffer: content-id=0, pts=0:00:00.000000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=1, pts=0:00:00.100000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=2, pts=0:00:00.200000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=3, pts=0:00:00.300000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=4, pts=0:00:00.400000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=5, pts=0:00:00.500000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=6, pts=0:00:00.600000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=7, pts=0:00:00.700000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=8, pts=0:00:00.800000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=9, pts=0:00:00.900000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=10, pts=0:00:01.000000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=9, pts=0:00:01.100000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=8, pts=0:00:01.200000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=7, pts=0:00:01.300000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=6, pts=0:00:01.400000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=5, pts=0:00:01.500000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=4, pts=0:00:01.600000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=3, pts=0:00:01.700000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=2, pts=0:00:01.800000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: content-id=1, pts=0:00:01.900000000, dur=0:00:00.100000000, meta=GstVideoMeta +event segment: format=TIME, start=0:00:02.000000000, offset=0:00:00.000000000, stop=0:00:02.000000001, flags=0x01, time=0:00:02.000000000, base=0:00:02.000000000, position=none +buffer: content-id=11, pts=0:00:02.000000000, dur=0:00:00.000000001, meta=GstVideoMeta +event eos: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=0:00:02.000000000, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000, position=none +buffer: content-id=10, pts=0:00:01.000000000, dur=0:00:00.100000000, meta=GstVideoMeta +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=0:00:03.000000000, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000, position=none +buffer: content-id=0, pts=0:00:01.000000000, dur=0:00:00.100000000, meta=GstVideoMeta diff --git a/tests/check/scenarios/check_layer_activness_gaps.scenario b/tests/check/scenarios/check_layer_activness_gaps.scenario new file mode 100644 index 0000000000..382f51677a --- /dev/null +++ b/tests/check/scenarios/check_layer_activness_gaps.scenario @@ -0,0 +1,24 @@ +description, handles-states=true, + ges-options={\ + "--disable-mixing", + "--videosink=fakevideosink", + "--audiosink=fakeaudiosink"\ + } + +add-clip, name=clip, asset-id="framerate=30/1", layer-priority=0, type=GESTestClip, pattern=blue, duration=5000.0 +set-layer-active, tracks={gesvideotrack0}, active=false, layer-priority=0 + +pause; + +# Make sure the video test src is a gap test src. +check-property, target-element-factory-name=videotestsrc, property-name=pattern, property-value="100% Black" +check-property, target-element-factory-name=audiotestsrc, property-name=wave, property-value="Sine" + +set-layer-active, tracks={gesvideotrack0}, active=true, layer-priority=0 +set-layer-active, tracks={gesaudiotrack0}, active=false, layer-priority=0 +commit; +# Make sure the video test src is the GESVideoTestSource and the audio test source is a gap +check-property, target-element-factory-name=videotestsrc, property-name=pattern, property-value="Blue" +check-property, target-element-factory-name=audiotestsrc, property-name=wave, property-value="Silence" + +stop; \ No newline at end of file diff --git a/tests/check/scenarios/check_video_track_restriction_scale.scenario b/tests/check/scenarios/check_video_track_restriction_scale.scenario new file mode 100644 index 0000000000..9ec1722667 --- /dev/null +++ b/tests/check/scenarios/check_video_track_restriction_scale.scenario @@ -0,0 +1,58 @@ +description, handles-states=true, + ges-options={\ + --track-types, video, + --video-caps, "video/x-raw,width=1200,height=1000" \ + } + +add-clip, name=clip, asset-id=GESTestClip, layer-priority=0, type=GESTestClip, duration=1.0 + +# VideoTestSource natural size is 1280x720, so keeping aspect ratio, mean +# that it will be scaled to 1200x675 (placed at x=0, y=163) +check-child-properties, element-name=clip, width=1200, height=675, posx=0, posy=163 + +set-child-properties, element-name=clip, width=1024, height=768 +check-child-properties, element-name=clip, width=1024, height=768 + +set-track-restriction-caps, track-type=video, caps="video/x-raw,width=1400,height=1200" +check-child-properties, element-name=clip, width=1024, height=768 + +set-child-properties, element-name=clip, width=0 +check-child-properties, element-name=clip, width=0, height=768 + +set-child-properties, element-name=clip, height=0 +check-child-properties, element-name=clip, width=0, height=0 + +set-child-properties, element-name=clip, width=1400, height=1200 +check-child-properties, element-name=clip, width=1400, height=1200 + +# Changing track size, keeping aspect ratio should scale the video source +set-track-restriction-caps, track-type=video, caps="video/x-raw,width=700,height=600" +check-child-properties, element-name=clip, width=700, height=600 + +# The video source has the same size as the track restriction caps but we +# are changing the aspect ratio, the video should thus not be rescaled. */ +set-track-restriction-caps, track-type=video, caps="video/x-raw,width=1920,height=1080" +check-child-properties, element-name=clip, width=700, height=600 + +set-child-properties, element-name=clip, width=1280, height=720, posx=320, posy=240 +check-child-properties, element-name=clip, width=1280, height=720, posx=320, posy=240 +set-track-restriction-caps, track-type=video, caps="video/x-raw,width=960,height=540" +check-child-properties, element-name=clip, width=640, height=360, posx=160, posy=120 + +set-track-restriction-caps, track-type=video, caps="video/x-raw,width=1280,height=720" +set-child-properties, element-name=clip, width=128, height=72, posx=-100, posy=-100 +check-child-properties, element-name=clip, width=128, height=72, posx=-100, posy=-100 + +set-track-restriction-caps, track-type=video, caps="video/x-raw,width=1920,height=1080" +check-child-properties, element-name=clip, width=192, height=108, posx=-150, posy=-150 + +set-track-restriction-caps, track-type=video, caps="video/x-raw,width=192,height=108" +check-child-properties, element-name=clip, width=19, height=11, posx=-15, posy=-15 + +set-child-properties, element-name=clip, posx=10, posy=-10 + +# Make sure we do not lose precision when going back to previous size +set-track-restriction-caps, track-type=video, caps="video/x-raw,width=1920,height=1080" +check-child-properties, element-name=clip, width=192, height=108, posx=100, posy=-100 + +stop \ No newline at end of file diff --git a/tests/check/scenarios/check_video_track_restriction_scale_with_keyframes.scenario b/tests/check/scenarios/check_video_track_restriction_scale_with_keyframes.scenario new file mode 100644 index 0000000000..0c0b95ee1f --- /dev/null +++ b/tests/check/scenarios/check_video_track_restriction_scale_with_keyframes.scenario @@ -0,0 +1,32 @@ +description, handles-states=true, seek=true, + ges-options={\ + --track-types, video, + --video-caps, "video/x-raw,width=1280,height=720,framerate=30/1" \ + } + +add-clip, name=clip, asset-id=GESTestClip, layer-priority=0, type=GESTestClip, start=0.0, duration=1.0, pattern=blue + +set-control-source, element-name=videotestsource0, property-name=width, binding-type=direct-absolute, source-type=interpolation +set-control-source, element-name=videotestsource0, property-name=height, binding-type=direct, source-type=interpolation + + +# Goes from 1280x720 at 0, to 640x360 at 0.5 then back to 1280x720 ar 1.0 +add-keyframe, element-name=videotestsource0, property-name="width", timestamp=0.0, value=(gint)1280 +add-keyframe, element-name=videotestsource0, property-name="height", timestamp=0.0, value=0.0072 + +add-keyframe, element-name=videotestsource0, property-name="width", timestamp=0.5, value=(gint)640 +add-keyframe, element-name=videotestsource0, property-name="height", timestamp=0.5, value=0.0036 + +add-keyframe, element-name=videotestsource0, property-name="width", timestamp=1.0, value=(gint)1280 +add-keyframe, element-name=videotestsource0, property-name="height", timestamp=1.0, value=0.0072 + +check-child-properties, element-name=videotestsource0, width=1280, height=720, posx=0, posy=0, at-time=0.0 +check-child-properties, element-name=videotestsource0, width=640, height=360, posx=0, posy=0, at-time=0.5 +check-child-properties, element-name=videotestsource0, width=1280, height=720, posx=0, posy=0, at-time=1.0 + +set-track-restriction-caps, track-type=video, caps="video/x-raw,width=1920,height=1080" +check-child-properties, element-name=videotestsource0, width=1920, height=1080, posx=0, posy=0, at-time=0.0 +check-child-properties, element-name=videotestsource0, width=960, height=540, posx=0, posy=0, at-time=0.5 +check-child-properties, element-name=videotestsource0, width=1920, height=1080, posx=0, posy=0, at-time=1.0 + +stop; \ No newline at end of file diff --git a/tests/check/scenarios/complex_effect_bin_desc.validatetest b/tests/check/scenarios/complex_effect_bin_desc.validatetest new file mode 100644 index 0000000000..d9585445ce --- /dev/null +++ b/tests/check/scenarios/complex_effect_bin_desc.validatetest @@ -0,0 +1,24 @@ +# Check that we can have effect with sources integrated where GES will request a pad on some elements +# In that example, we are blending a green rectangle on top of a blue GESVideoTestSource using an effect +meta, + tool = "ges-launch-$(gst_api_version)", + handles-states=true, + args = { + "--track-types", "video", + "--videosink", "$(videosink) name=videosink", + "--video-caps", "video/x-raw, format=I420, width=1280, height=720, framerate=30/1, chroma-site=jpeg, colorimetry=bt601", + }, + configs = { + "$(validateflow), pad=videosink:sink, buffers-checksum=true, ignored-fields=\"stream-start={stream-id,group-id,stream}, segment={position,}\", ignored-event-types={gap}", + } + + +add-clip, name=c0, asset-id=GESTestClip, layer-priority=0, type=GESTestClip, start=0, duration=0.1 +set-child-properties, element-name=c0, pattern=blue + +container-add-child, + container-name=c0, + asset-id="videotestsrc pattern=green ! video/x-raw,width=640,height=360 ! compositor sink_0::xpos=320 sink_0::ypos=180 sink_0::zorder=500", + child-type=GESEffect, + child-name=effect +play \ No newline at end of file diff --git a/tests/check/scenarios/complex_effect_bin_desc/flow-expectations/log-videosink-sink-expected b/tests/check/scenarios/complex_effect_bin_desc/flow-expectations/log-videosink-sink-expected new file mode 100644 index 0000000000..b4b70bc6e5 --- /dev/null +++ b/tests/check/scenarios/complex_effect_bin_desc/flow-expectations/log-videosink-sink-expected @@ -0,0 +1,9 @@ +event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE; +event caps: video/x-raw, chroma-site=(string)jpeg, colorimetry=(string)bt601, format=(string)I420, framerate=(fraction)30/1, height=(int)720, width=(int)1280; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.100000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000 +buffer: checksum=369888c2612267760fcfaa74e52fc53bd73e4d15, pts=0:00:00.000000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: checksum=369888c2612267760fcfaa74e52fc53bd73e4d15, pts=0:00:00.033333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: checksum=369888c2612267760fcfaa74e52fc53bd73e4d15, pts=0:00:00.066666667, dur=0:00:00.033333333, meta=GstVideoMeta +event segment: format=TIME, start=0:00:00.100000000, offset=0:00:00.000000000, stop=0:00:00.100000001, flags=0x01, time=0:00:00.100000000, base=0:00:00.100000000 +buffer: checksum=b4a126ab26f314a74ef860a9af457327a28d680b, pts=0:00:00.100000000, dur=0:00:00.000000001, meta=GstVideoMeta +event eos: (no structure) diff --git a/tests/check/scenarios/edit_while_seeked_with_stop.validatetest b/tests/check/scenarios/edit_while_seeked_with_stop.validatetest new file mode 100644 index 0000000000..0a96beefed --- /dev/null +++ b/tests/check/scenarios/edit_while_seeked_with_stop.validatetest @@ -0,0 +1,58 @@ +meta, + tool = "ges-launch-$(gst_api_version)", + args = { + --track-types, video, + --videosink, "$(videosink) name=videosink", + --video-caps, "video/x-raw,format=I420,width=1280,height=720,framerate=10/1,chroma-site=mpeg2,colorimetry=bt709", + }, + handles-states=true, + ignore-eos=true, + configs = { + # Ideally we should be able to record checksums... but they are not reproducible + "$(validateflow), pad=videosink:sink, record-buffers=true, ignored-fields=\"stream-start={stream-id,group-id,stream}\"", + } + +add-clip, name=c0, asset-id=time-overlay, layer-priority=0, type=GESSourceClip, start=0, duration=1.0 +set-child-properties, element-name=c0, pattern=blue, valignment=center, halignment=center, time-mode=time-code + +add-clip, name=c1, asset-id=time-overlay, layer-priority=0, type=GESSourceClip, start=1.0, duration=1.0 +set-child-properties, element-name=c1, pattern=red, valignment=center, halignment=center, time-mode=time-code +commit; +play + +seek, start=0.0, stop=0.5, flags=accurate+flush + +edit, element-name=c0, position=0.5, edge=end, edit-mode=trim +commit; + +crank-clock, expected-elapsed-time=0.0 +crank-clock, repeat=5, expected-elapsed-time=0.1 +check-position, on-message=eos, expected-position=0.5 + +seek, start=0.5, stop=1.0, flags=accurate+flush + +edit, element-name=c1, position=5.0, edge=end, edit-mode=trim +commit; + +crank-clock, expected-elapsed-time=0.0 +crank-clock, repeat=5, expected-elapsed-time=0.1 +check-position, on-message=eos, expected-position=1.0 + +edit, element-name=c1, position=3.0, edge=end, edit-mode=trim +commit; +check-position, on-message=eos, expected-position=1.0 + +seek, start=1.0, stop=2.0, flags=accurate+flush +check-position, expected-position=1.0 + +edit, element-name=c1, position=1.5, edge=end, edit-mode=trim +commit; + +crank-clock, expected-elapsed-time=0.0 +crank-clock, repeat=5, expected-elapsed-time=0.1 + +# Last 1ns clip added by GES +crank-clock, repeat=1, expected-elapsed-time=(guint64)1 +check-position, on-message=eos, expected-position=1500000001 + +stop; \ No newline at end of file diff --git a/tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected b/tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected new file mode 100644 index 0000000000..57d0771454 --- /dev/null +++ b/tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected @@ -0,0 +1,49 @@ +event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE; +event caps: video/x-raw, chroma-site=(string)mpeg2, colorimetry=(string)bt709, format=(string)I420, framerate=(fraction)10/1, height=(int)720, width=(int)1280; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000, position=none +buffer: pts=0:00:00.000000000, dur=0:00:00.100000000, meta=GstVideoMeta +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.500000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000, position=none +buffer: pts=0:00:00.000000000, dur=0:00:00.100000000, meta=GstVideoMeta +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.500000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000, position=none +buffer: pts=0:00:00.000000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:00.100000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:00.200000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:00.300000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:00.400000000, dur=0:00:00.100000000, meta=GstVideoMeta +event eos: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.500000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.500000000, base=0:00:00.000000000, position=none +buffer: pts=0:00:00.500000000, dur=0:00:00.100000000, meta=GstVideoMeta +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.500000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.500000000, base=0:00:00.000000000, position=none +buffer: pts=0:00:00.500000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:00.600000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:00.700000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:00.800000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:00.900000000, dur=0:00:00.100000000, meta=GstVideoMeta +event eos: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000, position=none +event eos: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=0:00:02.000000000, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000, position=none +buffer: pts=0:00:01.000000000, dur=0:00:00.100000000, meta=GstVideoMeta +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=0:00:01.500000000, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000, position=none +buffer: pts=0:00:01.000000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:01.100000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:01.200000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:01.300000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:01.400000000, dur=0:00:00.100000000, meta=GstVideoMeta +event segment: format=TIME, start=0:00:01.500000000, offset=0:00:00.000000000, stop=0:00:01.500000001, flags=0x01, time=0:00:01.500000000, base=0:00:00.500000000, position=none +buffer: pts=0:00:01.500000000, dur=0:00:00.000000001, meta=GstVideoMeta +event eos: (no structure) diff --git a/tests/check/scenarios/seek_with_stop.check_clock_sync.validatetest b/tests/check/scenarios/seek_with_stop.check_clock_sync.validatetest new file mode 100644 index 0000000000..0c679364b9 --- /dev/null +++ b/tests/check/scenarios/seek_with_stop.check_clock_sync.validatetest @@ -0,0 +1,30 @@ +meta, + tool = "ges-launch-$(gst_api_version)", + args = { + --track-types, video, + --videosink, "$(videosink) name=videosink", + --video-caps, "video/x-raw,format=I420,width=1280,height=720,framerate=10/1,chroma-site=mpeg2,colorimetry=bt709;", + }, + handles-states=true, + ignore-eos=true, + configs = { + # Ideally we should be able to record checksums... but they are not reproducible + "$(validateflow), pad=videosink:sink, record-buffers=true, ignored-fields=\"stream-start={stream-id,group-id,stream}\"", + } + +add-clip, name=c0, asset-id=time-overlay, layer-priority=0, type=GESSourceClip, start=0, duration=1.0 +set-child-properties, element-name=c0, pattern=blue, valignment=center, halignment=center, time-mode=time-code + +add-clip, name=c1, asset-id=time-overlay, layer-priority=0, type=GESSourceClip, start=1.0, duration=1.0 +set-child-properties, element-name=c1, pattern=red, valignment=center, halignment=center, time-mode=time-code +commit; +play + +seek, start=0.0, stop=0.5, flags=accurate+flush +crank-clock, expected-elapsed-time=0.0 + +# 4 remaining buffers + eos. +crank-clock, repeat=5, expected-elapsed-time=0.1 + +check-position, on-message=eos, expected-position=0.5 +stop; \ No newline at end of file diff --git a/tests/check/scenarios/seek_with_stop.check_clock_sync/flow-expectations/log-videosink-sink-expected b/tests/check/scenarios/seek_with_stop.check_clock_sync/flow-expectations/log-videosink-sink-expected new file mode 100644 index 0000000000..75364c15d2 --- /dev/null +++ b/tests/check/scenarios/seek_with_stop.check_clock_sync/flow-expectations/log-videosink-sink-expected @@ -0,0 +1,13 @@ +event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE; +event caps: video/x-raw, chroma-site=(string)mpeg2, colorimetry=(string)bt709, format=(string)I420, framerate=(fraction)10/1, height=(int)720, width=(int)1280; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000, position=none +buffer: pts=0:00:00.000000000, dur=0:00:00.100000000, meta=GstVideoMeta +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.500000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000, position=none +buffer: pts=0:00:00.000000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:00.100000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:00.200000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:00.300000000, dur=0:00:00.100000000, meta=GstVideoMeta +buffer: pts=0:00:00.400000000, dur=0:00:00.100000000, meta=GstVideoMeta +event eos: (no structure) diff --git a/tests/check/scenarios/seek_with_stop.validatetest b/tests/check/scenarios/seek_with_stop.validatetest new file mode 100644 index 0000000000..582d0a47e3 --- /dev/null +++ b/tests/check/scenarios/seek_with_stop.validatetest @@ -0,0 +1,28 @@ +meta, + tool = "ges-launch-$(gst_api_version)", + args = { + --videosink, "$(videosink) sync=false name=videosink", + --audiosink, "$(audiosink) sync=false name=audiosink", + --video-caps, "video/x-raw,format=I420,width=1280,height=720,framerate=30/1,chroma-site=mpeg2,colorimetry=bt709", + }, + handles-states=true, + ignore-eos=true, + configs = { + "$(validateflow), pad=videosink:sink, record-buffers=true, ignored-fields=\"stream-start={stream-id,group-id,stream}, segment={position,}\", ignored-event-types={gap}", + "$(validateflow), pad=audiosink:sink, record-buffers=true, ignored-fields=\"stream-start={stream-id,group-id,stream}, segment={position,}\", ignored-event-types={gap}", + } + +add-clip, name=c0, asset-id=time-overlay, layer-priority=0, type=GESSourceClip, start=0, duration=1.0 +set-child-properties, element-name=c0, pattern=blue, valignment=center, halignment=center, time-mode=time-code + +add-clip, name=c1, asset-id=time-overlay, layer-priority=0, type=GESSourceClip, start=1.0, duration=1.0 +set-child-properties, element-name=c1, pattern=red, valignment=center, halignment=center, time-mode=time-code +pause + +seek, start=0.0, stop=0.5, flags=accurate+flush +play + +seek, on-message=eos, start=0.0, stop=0.5, flags=accurate+flush +seek, on-message=eos, start=0.0, stop=1.0, flags=accurate+flush +seek, on-message=eos, start=1.0, stop=1.5, flags=accurate+flush +stop, on-message=eos \ No newline at end of file diff --git a/tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected b/tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected new file mode 100644 index 0000000000..17df41565b --- /dev/null +++ b/tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected @@ -0,0 +1,270 @@ +event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE; +event caps: audio/x-raw, channel-mask=(bitmask)0x0000000000000003, channels=(int)2, format=(string)S32LE, layout=(string)interleaved, rate=(int)44100; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000 +buffer: pts=0:00:00.000000000, dur=0:00:00.010000000 +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.500000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000 +buffer: pts=0:00:00.000000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.010000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.020000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.030000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.040000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.050000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.060000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.070000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.080000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.090000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.100000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.110000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.120000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.130000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.140000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.150000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.160000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.170000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.180000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.190000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.200000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.210000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.220000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.230000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.240000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.250000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.260000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.270000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.280000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.290000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.300000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.310000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.320000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.330000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.340000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.350000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.360000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.370000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.380000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.390000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.400000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.410000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.420000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.430000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.440000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.450000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.460000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.470000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.480000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.490000000, dur=0:00:00.010000000 +event eos: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.500000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000 +buffer: pts=0:00:00.000000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.010000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.020000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.030000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.040000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.050000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.060000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.070000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.080000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.090000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.100000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.110000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.120000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.130000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.140000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.150000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.160000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.170000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.180000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.190000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.200000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.210000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.220000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.230000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.240000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.250000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.260000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.270000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.280000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.290000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.300000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.310000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.320000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.330000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.340000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.350000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.360000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.370000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.380000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.390000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.400000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.410000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.420000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.430000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.440000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.450000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.460000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.470000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.480000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.490000000, dur=0:00:00.010000000 +event eos: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000 +buffer: pts=0:00:00.000000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.010000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.020000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.030000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.040000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.050000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.060000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.070000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.080000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.090000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.100000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.110000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.120000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.130000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.140000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.150000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.160000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.170000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.180000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.190000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.200000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.210000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.220000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.230000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.240000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.250000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.260000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.270000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.280000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.290000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.300000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.310000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.320000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.330000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.340000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.350000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.360000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.370000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.380000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.390000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.400000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.410000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.420000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.430000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.440000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.450000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.460000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.470000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.480000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.490000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.500000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.510000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.520000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.530000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.540000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.550000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.560000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.570000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.580000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.590000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.600000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.610000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.620000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.630000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.640000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.650000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.660000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.670000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.680000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.690000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.700000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.710000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.720000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.730000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.740000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.750000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.760000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.770000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.780000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.790000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.800000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.810000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.820000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.830000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.840000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.850000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.860000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.870000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.880000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.890000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.900000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.910000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.920000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.930000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.940000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.950000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.960000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.970000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.980000000, dur=0:00:00.010000000 +buffer: pts=0:00:00.990000000, dur=0:00:00.010000000 +event eos: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=0:00:01.500000000, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000 +buffer: pts=0:00:01.000000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.010000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.020000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.030000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.040000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.050000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.060000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.070000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.080000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.090000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.100000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.110000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.120000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.130000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.140000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.150000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.160000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.170000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.180000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.190000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.200000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.210000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.220000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.230000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.240000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.250000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.260000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.270000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.280000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.290000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.300000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.310000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.320000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.330000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.340000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.350000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.360000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.370000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.380000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.390000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.400000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.410000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.420000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.430000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.440000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.450000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.460000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.470000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.480000000, dur=0:00:00.010000000 +buffer: pts=0:00:01.490000000, dur=0:00:00.010000000 +event eos: (no structure) diff --git a/tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected b/tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected new file mode 100644 index 0000000000..626460fa6a --- /dev/null +++ b/tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected @@ -0,0 +1,95 @@ +event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE; +event caps: video/x-raw, chroma-site=(string)mpeg2, colorimetry=(string)bt709, format=(string)I420, framerate=(fraction)30/1, height=(int)720, width=(int)1280; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000 +buffer: pts=0:00:00.000000000, dur=0:00:00.033333333, meta=GstVideoMeta +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.500000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000 +buffer: pts=0:00:00.000000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.033333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.066666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.100000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.133333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.166666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.200000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.233333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.266666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.300000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.333333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.366666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.400000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.433333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.466666667, dur=0:00:00.033333333, meta=GstVideoMeta +event eos: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.500000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000 +buffer: pts=0:00:00.000000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.033333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.066666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.100000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.133333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.166666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.200000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.233333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.266666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.300000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.333333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.366666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.400000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.433333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.466666667, dur=0:00:00.033333333, meta=GstVideoMeta +event eos: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000 +buffer: pts=0:00:00.000000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.033333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.066666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.100000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.133333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.166666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.200000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.233333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.266666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.300000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.333333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.366666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.400000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.433333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.466666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.500000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.533333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.566666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.600000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.633333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.666666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.700000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.733333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.766666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.800000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.833333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.866666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.900000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:00.933333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:00.966666667, dur=0:00:00.033333333, meta=GstVideoMeta +event eos: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=0:00:01.500000000, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000 +buffer: pts=0:00:01.000000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:01.033333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:01.066666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:01.100000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:01.133333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:01.166666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:01.200000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:01.233333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:01.266666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:01.300000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:01.333333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:01.366666667, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:01.400000000, dur=0:00:00.033333333, meta=GstVideoMeta +buffer: pts=0:00:01.433333333, dur=0:00:00.033333334, meta=GstVideoMeta +buffer: pts=0:00:01.466666667, dur=0:00:00.033333333, meta=GstVideoMeta +event eos: (no structure) diff --git a/tests/check/scenarios/set-layer-on-command-line.validatetest b/tests/check/scenarios/set-layer-on-command-line.validatetest new file mode 100644 index 0000000000..d590bb8184 --- /dev/null +++ b/tests/check/scenarios/set-layer-on-command-line.validatetest @@ -0,0 +1,11 @@ +meta, handles-states=true, + tool = "ges-launch-$(gst_api_version)", + handles-states=true, + args = { + +test-clip, blue, "d=0.5", "layer=0", "name=blue", + +test-clip, green, "d=0.5", "layer=1", "name=green", + } + +check-ges-properties, element-name=blue, layer::priority=0 +check-ges-properties, element-name=green, layer::priority=1 +stop \ No newline at end of file diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000000..0ef30b3d30 --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,7 @@ +# FIXME: make check work on windows +if host_machine.system() != 'windows' and gstcheck_dep.found() + subdir('check') +endif + +subdir('validate') +subdir('benchmarks') \ No newline at end of file diff --git a/tests/validate/geslaunch.py b/tests/validate/geslaunch.py new file mode 100644 index 0000000000..289e0d02a1 --- /dev/null +++ b/tests/validate/geslaunch.py @@ -0,0 +1,413 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2013, Thibault Saunier <thibault.saunier@collabora.com> +# Copyright (c) 2020, Igalia S.L +# Author: Thibault Saunier <tsaunier@igalia.com> +# +# This program 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 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +import os +import sys +import tempfile +import urllib.parse +import subprocess +from launcher import utils +from urllib.parse import unquote +import xml.etree.ElementTree as ET +from launcher.baseclasses import GstValidateTest, TestsManager, ScenarioManager, MediaFormatCombination, \ + MediaDescriptor, GstValidateEncodingTestInterface + +GES_DURATION_TOLERANCE = utils.GST_SECOND / 2 + +GES_LAUNCH_COMMAND = "ges-launch-1.0" +if "win32" in sys.platform: + GES_LAUNCH_COMMAND += ".exe" + + +GES_ENCODING_TARGET_COMBINATIONS = [ + MediaFormatCombination("ogg", "vorbis", "theora"), + MediaFormatCombination("ogg", "opus", "theora"), + MediaFormatCombination("webm", "vorbis", "vp8"), + MediaFormatCombination("webm", "opus", "vp8"), + MediaFormatCombination("mp4", "aac", "h264"), + MediaFormatCombination("mp4", "ac3", "h264"), + MediaFormatCombination("quicktime", "aac", "jpeg"), + MediaFormatCombination("mkv", "opus", "h264"), + MediaFormatCombination("mkv", "vorbis", "h264"), + MediaFormatCombination("mkv", "opus", "jpeg"), + MediaFormatCombination("mkv", "vorbis", "jpeg") +] + + +def quote_uri(uri): + """ + Encode a URI/path according to RFC 2396, without touching the file:/// part. + """ + # Split off the "file:///" part, if present. + parts = urllib.parse.urlsplit(uri, allow_fragments=False) + # Make absolutely sure the string is unquoted before quoting again! + raw_path = unquote(parts.path) + return utils.path2url(raw_path) + + +class XgesProjectDescriptor(MediaDescriptor): + def __init__(self, uri): + super(XgesProjectDescriptor, self).__init__() + + self._uri = uri + self._xml_path = utils.url2path(uri) + self._root = ET.parse(self._xml_path) + self._duration = None + + def get_media_filepath(self): + return self._xml_path + + def get_path(self): + return self._xml_path + + def get_caps(self): + raise NotImplemented + + def get_uri(self): + return self._uri + + def get_duration(self): + if self._duration: + return self._duration + + for l in self._root.iter(): + if l.tag == "timeline": + self._duration=int(l.attrib['metadatas'].split("duration=(guint64)")[1].split(" ")[0].split(";")[0]) + break + + if not self._duration: + self.error("%s does not have duration! (setting 2mins)" % self._uri) + self._duration = 2 * 60 + + return self._duration + + def get_protocol(self): + return Protocols.FILE + + def is_seekable(self): + return True + + def is_image(self): + return False + + def get_num_tracks(self, track_type): + num_tracks = 0 + for l in self._root.iter(): + if l.tag == "track": + if track_type in l.attrib["caps"]: + num_tracks += 1 + return num_tracks + + +class GESTest(GstValidateTest): + def __init__(self, classname, options, reporter, project, scenario=None, + combination=None, expected_failures=None, nest=False, testfile=None): + + super(GESTest, self).__init__(GES_LAUNCH_COMMAND, classname, options, reporter, + scenario=scenario) + + self.project = project + self.nested = nest + self.testfile = testfile + + def set_sample_paths(self): + if self.testfile: + # testfile should be self contained + return + + if not self.options.paths: + if self.options.disable_recurse: + return + if self.project: + paths = [os.path.dirname(self.project.get_media_filepath())] + else: + paths = [] + else: + paths = self.options.paths + + if not isinstance(paths, list): + paths = [paths] + + for path in paths: + # We always want paths separator to be cut with '/' for ges-launch + path = path.replace("\\", "/") + if not self.options.disable_recurse: + self.add_arguments("--ges-sample-path-recurse", quote_uri(path)) + self.add_arguments("--ges-sample-path-recurse", quote_uri(self.options.projects_paths)) + else: + self.add_arguments("--ges-sample-paths", quote_uri(path)) + + def set_sink_args(self): + if self.testfile: + # testfile should be self contained and --mute should give required infos. + if self.options.mute: + self.add_arguments("--mute") + return + + if self.options.mute: + needs_clock = self.scenario.needs_clock_sync() \ + if self.scenario else False + audiosink = utils.get_fakesink_for_media_type("audio", needs_clock) + videosink = utils.get_fakesink_for_media_type("video", needs_clock) + else: + audiosink = 'autoaudiosink' + videosink = 'autovideosink' + self.add_arguments("--videosink", videosink + " name=videosink") + self.add_arguments("--audiosink", audiosink + " name=audiosink") + + def build_arguments(self): + GstValidateTest.build_arguments(self) + + self.set_sink_args() + self.set_sample_paths() + + if self.project: + assert self.testfile is None + if self.nested: + self.add_arguments("+clip", self.project.get_uri()) + else: + self.add_arguments("-l", self.project.get_uri()) + elif self.testfile: + self.add_arguments("--set-test-file", self.testfile) + +class GESPlaybackTest(GESTest): + def __init__(self, classname, options, reporter, project, scenario,nest): + super(GESPlaybackTest, self).__init__(classname, options, reporter, + project, scenario=scenario, nest=nest) + + def get_current_value(self): + return self.get_current_position() + +class GESScenarioTest(GESTest): + def __init__(self, classname, options, reporter, scenario): + super().__init__(classname, options, reporter, None, scenario=scenario) + + def build_arguments(self): + super().build_arguments() + self.add_arguments("--set-scenario", self.scenario.path) + + def get_subproc_env(self): + scenario = self.scenario + self.scenario = None + res = super().get_subproc_env() + self.scenario = scenario + + return res + + def get_current_value(self): + return self.get_current_position() + + +class GESRenderTest(GESTest, GstValidateEncodingTestInterface): + def __init__(self, classname, options, reporter, project, combination): + GESTest.__init__(self, classname, options, reporter, project) + + GstValidateEncodingTestInterface.__init__(self, combination, self.project) + + def build_arguments(self): + GESTest.build_arguments(self) + self._set_rendering_info() + + def run_external_checks(self): + reference_file_path = urllib.parse.urlsplit(self.media_descriptor.get_uri()).path + ".expected_result" + if os.path.exists(reference_file_path): + self.run_iqa_test(utils.path2url(reference_file_path)) + + def _set_rendering_info(self): + self.dest_file = path = os.path.join(self.options.dest, + self.classname.replace(".render.", os.sep). + replace(".", os.sep)) + utils.mkdir(os.path.dirname(urllib.parse.urlsplit(self.dest_file).path)) + if not utils.isuri(self.dest_file): + self.dest_file = utils.path2url(self.dest_file) + + profile = self.get_profile() + self.add_arguments("-f", profile, "-o", self.dest_file) + + def check_results(self): + self.check_encoded_file() + return GstValidateTest.check_results(self) + + def get_current_value(self): + size = self.get_current_size() + if size is None: + return self.get_current_position() + + return size + + +class GESTestsManager(TestsManager): + name = "ges" + + _scenarios = ScenarioManager() + + def __init__(self): + super(GESTestsManager, self).__init__() + + def init(self): + try: + with tempfile.NamedTemporaryFile() as f: + if "--set-scenario=" in subprocess.check_output([GES_LAUNCH_COMMAND, "--help"], stderr=f).decode(): + return True + else: + self.warning("Can not use ges-launch, it seems not to be compiled against" + " gst-validate") + except subprocess.CalledProcessError as e: + self.warning("Can not use ges-launch: %s" % e) + except OSError as e: + self.warning("Can not use ges-launch: %s" % e) + + def add_options(self, parser): + group = parser.add_argument_group("GStreamer Editing Services specific option" + " and behaviours", + description=""" +The GStreamer Editing Services launcher will be usable only if GES has been compiled against GstValidate +You can simply run scenarios specifying project as args. For example the following will run all available +and activated scenarios on project.xges: + + $gst-validate-launcher ges /some/ges/project.xges + + +Available options:""") + group.add_argument("-P", "--projects-paths", dest="projects_paths", + default=os.path.join(utils.DEFAULT_GST_QA_ASSETS, + "ges", + "ges-projects"), + help="Paths in which to look for moved medias") + group.add_argument("--ges-scenario-paths", dest="scenarios_path", + default=None, + help="Paths in which to look for moved medias") + group.add_argument("-r", "--disable-recurse-paths", dest="disable_recurse", + default=False, action="store_true", + help="Whether to recurse into paths to find medias") + + def set_settings(self, options, args, reporter): + TestsManager.set_settings(self, options, args, reporter) + self._scenarios.config = self.options + + try: + os.makedirs(utils.url2path(options.dest)[0]) + except OSError: + pass + + def list_tests(self): + return self.tests + + def register_defaults(self, project_paths=None, scenarios_path=None): + projects = list() + all_scenarios = {} + if not self.args: + if project_paths == None: + path = self.options.projects_paths + else: + path = project_paths + + for root, dirs, files in os.walk(path): + for f in files: + if not f.endswith(".xges"): + continue + projects.append(utils.path2url(os.path.join(path, root, f))) + + if self.options.scenarios_path: + scenarios_path = self.options.scenarios_path + + if scenarios_path: + for root, dirs, files in os.walk(scenarios_path): + for f in files: + name, ext = os.path.splitext(f) + f = os.path.join(root, f) + if ext == ".validatetest": + fpath = os.path.abspath(os.path.join(root, f)) + pathname = os.path.abspath(os.path.join(root, name)) + name = pathname.replace(os.path.commonpath([scenarios_path, root]), '').replace('/', '.') + self.add_test(GESTest('test' + name, + self.options, + self.reporter, + None, + testfile=fpath)) + continue + elif ext != ".scenario": + continue + config = f + ".config" + if not os.path.exists(config): + config = None + all_scenarios[f] = config + else: + for proj_uri in self.args: + if not utils.isuri(proj_uri): + proj_uri = utils.path2url(proj_uri) + + if os.path.exists(proj_uri): + projects.append(proj_uri) + + if self.options.long_limit != 0: + scenarios = ["none", + "scrub_forward_seeking", + "scrub_backward_seeking"] + else: + scenarios = ["play_15s", + "scrub_forward_seeking_full", + "scrub_backward_seeking_full"] + for proj_uri in projects: + # First playback casses + project = XgesProjectDescriptor(proj_uri) + for scenario_name in scenarios: + scenario = self._scenarios.get_scenario(scenario_name) + if scenario is None: + continue + + if scenario.get_min_media_duration() >= (project.get_duration() / utils.GST_SECOND): + continue + + classname = "playback.%s.%s" % (scenario.name, + os.path.basename(proj_uri).replace(".xges", "")) + self.add_test(GESPlaybackTest(classname, + self.options, + self.reporter, + project, + scenario=scenario, + nest=False)) + #For nested timelines + classname = "playback.nested.%s.%s" % (scenario.name, + os.path.basename(proj_uri).replace(".xges", "")) + self.add_test(GESPlaybackTest(classname, + self.options, + self.reporter, + project, + scenario=scenario, + nest=True)) + + # And now rendering casses + for comb in GES_ENCODING_TARGET_COMBINATIONS: + classname = "render.%s.%s" % (str(comb).replace(' ', '_'), + os.path.splitext(os.path.basename(proj_uri))[0]) + self.add_test(GESRenderTest(classname, self.options, + self.reporter, project, + combination=comb) + ) + if all_scenarios: + for scenario in self._scenarios.discover_scenarios(list(all_scenarios.keys())): + config = all_scenarios[scenario.path] + classname = "scenario.%s" % scenario.name + test = GESScenarioTest(classname, self.options, self.reporter, scenario=scenario) + if config: + test.add_validate_config(config) + self.add_test(test) \ No newline at end of file diff --git a/tests/validate/meson.build b/tests/validate/meson.build new file mode 100644 index 0000000000..9437f5224a --- /dev/null +++ b/tests/validate/meson.build @@ -0,0 +1,5 @@ +subdir ('scenarios') + +install_data (['geslaunch.py'], + install_dir : join_paths(get_option('libdir'), + 'gst-validate-launcher', 'python', 'launcher', 'apps')) diff --git a/tests/validate/scenarios/ges-edit-clip-while-paused.scenario b/tests/validate/scenarios/ges-edit-clip-while-paused.scenario new file mode 100644 index 0000000000..3da1bf65e8 --- /dev/null +++ b/tests/validate/scenarios/ges-edit-clip-while-paused.scenario @@ -0,0 +1,5 @@ +description, duration=4.0 +pause, playback_time=0.0 +wait, duration=1.0 +edit-clip, playback_time=0.0, clip-name="uriclip0", position=3.0, edit-mode="edit_trim", edge="edge_end" +play, playback_time=0.0 diff --git a/tests/validate/scenarios/meson.build b/tests/validate/scenarios/meson.build new file mode 100644 index 0000000000..abf7cbbdae --- /dev/null +++ b/tests/validate/scenarios/meson.build @@ -0,0 +1,3 @@ +install_data (['ges-edit-clip-while-paused.scenario'], + install_dir : join_paths(get_option('datadir'), + 'gstreamer-1.0', 'validate', 'scenarios')) diff --git a/tools/ges-launch-1.0.1 b/tools/ges-launch-1.0.1 new file mode 100644 index 0000000000..238db50d6e --- /dev/null +++ b/tools/ges-launch-1.0.1 @@ -0,0 +1,48 @@ +.TH GES\-LAUNCH\-1.0 "1" "December 2016" "GStreamer Editing Services API version 1.0" "User Commands" +.SH NAME +ges\-launch\-1.0 \- create and render multimedia timelines +.SH "SYNOPSIS" +\fBges\-launch\-1.0\fR \fI[OPTION...]\fR +.SH DESCRIPTION +ges\-launch\-1.0 creates a multimedia timeline and plays it back, +or renders it to the specified format. +.PP +It can load a timeline from an existing project, or create one +from the specified commands. +.PP +You can learn more about individual ges\-launch\-1.0 commands with +"ges\-launch\-1.0 help command". +.PP +By default, ges\-launch\-1.0 is in "playback\-mode". +.SH "OPTIONS" +For the full list of \fIges\-launch\-1.0\fP options see the command line help: +.TP 8 +\fB\-h\fR, \fB\-\-help\fR +Show help options +.TP 8 +\fB\-\-help\-all\fR +Show all help options +.TP 8 +\fB\-\-help\-gst\fR +Show GStreamer Options +.TP 8 +\fB\-\-help\-GES\fR +Show GES Options +.TP 8 +\fB\-\-help\-project\fR +Show project\-related options +.TP 8 +\fB\-\-help\-rendering\fR +Show rendering options +.TP 8 +\fB\-\-help\-playback\fR +Show playback options +.TP 8 +\fB\-\-help\-informative\fR +Show informative options +.SH "SEE ALSO" +For more detailed info and some examples of use, also check out the on-line documentation: +.br +https://gstreamer.freedesktop.org/documentation/tools/ges-launch.html +.SH "AUTHOR" +Antonio Ospite https://ao2.it diff --git a/tools/ges-launch.c b/tools/ges-launch.c new file mode 100644 index 0000000000..66eeb239a5 --- /dev/null +++ b/tools/ges-launch.c @@ -0,0 +1,45 @@ +/* GStreamer Editing Services + * Copyright (C) 2010 Edward Hervey <bilboed@bilboed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <stdlib.h> +#include <locale.h> /* for LC_ALL */ +#include "ges-launcher.h" + + +int +main (int argc, gchar ** argv) +{ + GESLauncher *launcher; + gint ret; + + setlocale (LC_ALL, ""); + + launcher = ges_launcher_new (); + + ret = g_application_run (G_APPLICATION (launcher), argc, argv); + + if (!ret) + ret = ges_launcher_get_exit_status (launcher); + + g_object_unref (launcher); + ges_deinit (); + gst_deinit (); + + return ret; +} diff --git a/tools/ges-launcher-kb.c b/tools/ges-launcher-kb.c new file mode 100644 index 0000000000..ffaafde777 --- /dev/null +++ b/tools/ges-launcher-kb.c @@ -0,0 +1,293 @@ +/* GStreamer command line playback testing utility - keyboard handling helpers + * + * Copyright (C) 2013 Tim-Philipp Müller <tim centricular net> + * Copyright (C) 2013 Centricular Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-launcher-kb.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#ifdef G_OS_UNIX +#include <unistd.h> +#include <termios.h> +#endif + +#ifdef G_OS_WIN32 +#include <windows.h> +#include <io.h> +#endif + +#include <gst/gst.h> + +/* This is all not thread-safe, but doesn't have to be really */ +static GstPlayKbFunc kb_callback; +static gpointer kb_callback_data; + +#ifdef G_OS_UNIX +static struct termios term_settings; +static gboolean term_settings_saved = FALSE; +static gulong io_watch_id; + +static gboolean +gst_play_kb_io_cb (GIOChannel * ioc, GIOCondition cond, gpointer user_data) +{ + GIOStatus status; + + if (cond & G_IO_IN) { + gchar buf[16] = { 0, }; + gsize read; + + status = g_io_channel_read_chars (ioc, buf, sizeof (buf) - 1, &read, NULL); + if (status == G_IO_STATUS_ERROR) + return FALSE; + if (status == G_IO_STATUS_NORMAL) { + if (kb_callback) + kb_callback (buf, kb_callback_data); + } + } + + return TRUE; /* call us again */ +} + +gboolean +gst_play_kb_set_key_handler (GstPlayKbFunc kb_func, gpointer user_data) +{ + GIOChannel *ioc; + + if (!isatty (STDIN_FILENO)) { + GST_INFO ("stdin is not connected to a terminal"); + return FALSE; + } + + if (io_watch_id > 0) { + g_source_remove (io_watch_id); + io_watch_id = 0; + } + + if (kb_func == NULL && term_settings_saved) { + /* restore terminal settings */ + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &term_settings) == 0) + term_settings_saved = FALSE; + else + g_warning ("could not restore terminal attributes"); + + setvbuf (stdin, NULL, _IOLBF, 0); + } + + if (kb_func != NULL) { + struct termios new_settings; + + if (!term_settings_saved) { + if (tcgetattr (STDIN_FILENO, &term_settings) != 0) { + g_warning ("could not save terminal attributes"); + return FALSE; + } + term_settings_saved = TRUE; + + /* Echo off, canonical mode off, extended input processing off */ + new_settings = term_settings; + new_settings.c_lflag &= ~(ECHO | ICANON | IEXTEN); + new_settings.c_cc[VMIN] = 0; + new_settings.c_cc[VTIME] = 0; + + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &new_settings) != 0) { + g_warning ("Could not set terminal state"); + return FALSE; + } + setvbuf (stdin, NULL, _IONBF, 0); + } + } + + ioc = g_io_channel_unix_new (STDIN_FILENO); + + io_watch_id = g_io_add_watch_full (ioc, G_PRIORITY_DEFAULT, G_IO_IN, + (GIOFunc) gst_play_kb_io_cb, user_data, NULL); + g_io_channel_unref (ioc); + + kb_callback = kb_func; + kb_callback_data = user_data; + + return TRUE; +} + +#elif defined(G_OS_WIN32) + +typedef struct +{ + GThread *thread; + HANDLE event_handle; + HANDLE console_handle; + gboolean closing; + GMutex lock; +} Win32KeyHandler; + +static Win32KeyHandler *win32_handler = NULL; + +static gboolean +gst_play_kb_source_cb (Win32KeyHandler * handler) +{ + HANDLE h_input = handler->console_handle; + INPUT_RECORD buffer; + DWORD n; + + if (PeekConsoleInput (h_input, &buffer, 1, &n) && n == 1) { + ReadConsoleInput (h_input, &buffer, 1, &n); + + if (buffer.EventType == KEY_EVENT && buffer.Event.KeyEvent.bKeyDown) { + gchar key_val[2] = { 0 }; + + switch (buffer.Event.KeyEvent.wVirtualKeyCode) { + case VK_RIGHT: + kb_callback (GST_PLAY_KB_ARROW_RIGHT, kb_callback_data); + break; + case VK_LEFT: + kb_callback (GST_PLAY_KB_ARROW_LEFT, kb_callback_data); + break; + case VK_UP: + kb_callback (GST_PLAY_KB_ARROW_UP, kb_callback_data); + break; + case VK_DOWN: + kb_callback (GST_PLAY_KB_ARROW_DOWN, kb_callback_data); + break; + default: + key_val[0] = buffer.Event.KeyEvent.uChar.AsciiChar; + kb_callback (key_val, kb_callback_data); + break; + } + } + } + + return G_SOURCE_REMOVE; +} + +static gpointer +gst_play_kb_win32_thread (gpointer user_data) +{ + Win32KeyHandler *handler = (Win32KeyHandler *) user_data; + HANDLE handles[2]; + + handles[0] = handler->event_handle; + handles[1] = handler->console_handle; + + if (!kb_callback) + return NULL; + + while (TRUE) { + DWORD ret = WaitForMultipleObjects (2, handles, FALSE, INFINITE); + + if (ret == WAIT_FAILED) { + GST_WARNING ("WaitForMultipleObject Failed"); + return NULL; + } + + g_mutex_lock (&handler->lock); + if (handler->closing) { + g_mutex_unlock (&handler->lock); + + return NULL; + } + g_mutex_unlock (&handler->lock); + + g_idle_add ((GSourceFunc) gst_play_kb_source_cb, handler); + } + + return NULL; +} + +gboolean +gst_play_kb_set_key_handler (GstPlayKbFunc kb_func, gpointer user_data) +{ + gint fd = _fileno (stdin); + + if (!_isatty (fd)) { + GST_INFO ("stdin is not connected to a terminal"); + return FALSE; + } + + if (win32_handler) { + g_mutex_lock (&win32_handler->lock); + win32_handler->closing = TRUE; + g_mutex_unlock (&win32_handler->lock); + + SetEvent (win32_handler->event_handle); + g_thread_join (win32_handler->thread); + CloseHandle (win32_handler->event_handle); + + g_mutex_clear (&win32_handler->lock); + g_free (win32_handler); + win32_handler = NULL; + } + + if (kb_func) { + SECURITY_ATTRIBUTES sec_attrs; + + sec_attrs.nLength = sizeof (SECURITY_ATTRIBUTES); + sec_attrs.lpSecurityDescriptor = NULL; + sec_attrs.bInheritHandle = FALSE; + + win32_handler = g_new0 (Win32KeyHandler, 1); + + /* create cancellable event handle */ + win32_handler->event_handle = CreateEvent (&sec_attrs, TRUE, FALSE, NULL); + + if (!win32_handler->event_handle) { + GST_WARNING ("Couldn't create event handle"); + g_free (win32_handler); + win32_handler = NULL; + + return FALSE; + } + + win32_handler->console_handle = GetStdHandle (STD_INPUT_HANDLE); + if (!win32_handler->console_handle) { + GST_WARNING ("Couldn't get console handle"); + CloseHandle (win32_handler->event_handle); + g_free (win32_handler); + win32_handler = NULL; + + return FALSE; + } + + g_mutex_init (&win32_handler->lock); + win32_handler->thread = + g_thread_new ("gst-play-kb", gst_play_kb_win32_thread, win32_handler); + } + + kb_callback = kb_func; + kb_callback_data = user_data; + + return TRUE; +} + +#else + +gboolean +gst_play_kb_set_key_handler (GstPlayKbFunc key_func, gpointer user_data) +{ + GST_FIXME ("Keyboard handling for this OS needs to be implemented"); + return FALSE; +} + +#endif /* !G_OS_UNIX */ diff --git a/tools/ges-launcher-kb.h b/tools/ges-launcher-kb.h new file mode 100644 index 0000000000..7dab0edd77 --- /dev/null +++ b/tools/ges-launcher-kb.h @@ -0,0 +1,35 @@ +/* GStreamer command line playback testing utility - keyboard handling helpers + * + * Copyright (C) 2013 Tim-Philipp Müller <tim centricular net> + * Copyright (C) 2013 Centricular Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef __GST_PLAY_KB_INCLUDED__ +#define __GST_PLAY_KB_INCLUDED__ + +#include <glib.h> + +#define GST_PLAY_KB_ARROW_UP "\033[A" +#define GST_PLAY_KB_ARROW_DOWN "\033[B" +#define GST_PLAY_KB_ARROW_RIGHT "\033[C" +#define GST_PLAY_KB_ARROW_LEFT "\033[D" + +typedef void (*GstPlayKbFunc) (const gchar * kb_input, gpointer user_data); + +gboolean gst_play_kb_set_key_handler (GstPlayKbFunc kb_func, gpointer user_data); + +#endif /* __GST_PLAY_KB_INCLUDED__ */ diff --git a/tools/ges-launcher.c b/tools/ges-launcher.c new file mode 100644 index 0000000000..a13238e816 --- /dev/null +++ b/tools/ges-launcher.c @@ -0,0 +1,1716 @@ +/* GStreamer Editing Services + * Copyright (C) 2015 Mathieu Duponchelle <mathieu.duponchelle@opencreed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <glib.h> +#include <glib/gprintf.h> +#include <stdlib.h> +#include <string.h> +#ifdef G_OS_UNIX +#include <glib-unix.h> +#endif +#include "ges-launcher.h" +#include "ges-validate.h" +#include "utils.h" +#include "ges-launcher-kb.h" + +typedef enum +{ + GST_PLAY_TRICK_MODE_NONE = 0, + GST_PLAY_TRICK_MODE_DEFAULT, + GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO, + GST_PLAY_TRICK_MODE_KEY_UNITS, + GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO, + GST_PLAY_TRICK_MODE_INSTANT_RATE, + GST_PLAY_TRICK_MODE_LAST +} GstPlayTrickMode; + +struct _GESLauncherPrivate +{ + GESTimeline *timeline; + GESPipeline *pipeline; + gboolean seenerrors; +#ifdef G_OS_UNIX + guint signal_watch_id; +#endif + GESLauncherParsedOptions parsed_options; + + GstPlayTrickMode trick_mode; + gdouble rate; + + GstState desired_state; /* as per user interaction, PAUSED or PLAYING */ +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GESLauncher, ges_launcher, G_TYPE_APPLICATION); + +static const gchar *HELP_SUMMARY = + " `ges-launch-1.0` creates a multimedia timeline and plays it back,\n" + " or renders it to the specified format.\n\n" + " It can load a timeline from an existing project, or create one\n" + " using the 'Timeline description format', specified in the section\n" + " of the same name.\n\n" + " Updating an existing project can be done through `--set-scenario`\n" + " if ges-launch-1.0 has been compiled with gst-validate, see\n" + " `ges-launch-1.0 --inspect-action-type` for the available commands.\n\n" + " By default, ges-launch-1.0 is in \"playback-mode\"."; + +static gboolean +play_do_seek (GESLauncher * self, gint64 pos, gdouble rate, + GstPlayTrickMode mode) +{ + GstSeekFlags seek_flags; + GstEvent *seek; + + seek_flags = 0; + + switch (mode) { + case GST_PLAY_TRICK_MODE_DEFAULT: + seek_flags |= GST_SEEK_FLAG_TRICKMODE; + break; + case GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO: + seek_flags |= GST_SEEK_FLAG_TRICKMODE | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO; + break; + case GST_PLAY_TRICK_MODE_KEY_UNITS: + seek_flags |= GST_SEEK_FLAG_TRICKMODE_KEY_UNITS; + break; + case GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO: + seek_flags |= + GST_SEEK_FLAG_TRICKMODE_KEY_UNITS | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO; + break; + case GST_PLAY_TRICK_MODE_NONE: + default: + break; + } + + /* See if we can do an instant rate change (not changing dir) */ + if (mode & GST_PLAY_TRICK_MODE_INSTANT_RATE && rate * self->priv->rate > 0) { + seek = gst_event_new_seek (rate, GST_FORMAT_TIME, + seek_flags | GST_SEEK_FLAG_INSTANT_RATE_CHANGE, + GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE, + GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); + if (gst_element_send_event (GST_ELEMENT (self->priv->pipeline), seek)) { + goto done; + } + } + + /* No instant rate change, need to do a flushing seek */ + seek_flags |= GST_SEEK_FLAG_FLUSH; + if (rate >= 0) + seek = gst_event_new_seek (rate, GST_FORMAT_TIME, + seek_flags | GST_SEEK_FLAG_ACCURATE, + /* start */ GST_SEEK_TYPE_SET, pos, + /* stop */ GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE); + else + seek = gst_event_new_seek (rate, GST_FORMAT_TIME, + seek_flags | GST_SEEK_FLAG_ACCURATE, + /* start */ GST_SEEK_TYPE_SET, 0, + /* stop */ GST_SEEK_TYPE_SET, pos); + + if (!gst_element_send_event (GST_ELEMENT (self->priv->pipeline), seek)) + return FALSE; + +done: + self->priv->rate = rate; + self->priv->trick_mode = mode & ~GST_PLAY_TRICK_MODE_INSTANT_RATE; + return TRUE; +} + +static void +restore_terminal (void) +{ + gst_play_kb_set_key_handler (NULL, NULL); +} + +static void +toggle_paused (GESLauncher * self) +{ + if (self->priv->desired_state == GST_STATE_PLAYING) + self->priv->desired_state = GST_STATE_PAUSED; + else + self->priv->desired_state = GST_STATE_PLAYING; + + gst_element_set_state (GST_ELEMENT (self->priv->pipeline), + self->priv->desired_state); +} + +static void +relative_seek (GESLauncher * self, gdouble percent) +{ + gint64 pos = -1, step, dur; + + g_return_if_fail (percent >= -1.0 && percent <= 1.0); + + if (!gst_element_query_position (GST_ELEMENT (self->priv->pipeline), + GST_FORMAT_TIME, &pos)) + goto seek_failed; + + if (!gst_element_query_duration (GST_ELEMENT (self->priv->pipeline), + GST_FORMAT_TIME, &dur)) { + goto seek_failed; + } + + step = dur * percent; + if (ABS (step) < GST_SECOND) + step = (percent < 0) ? -GST_SECOND : GST_SECOND; + + pos = pos + step; + if (pos > dur) { + gst_print ("\n%s\n", "Reached end of self list."); + g_application_quit (G_APPLICATION (self)); + } else { + if (pos < 0) + pos = 0; + + play_do_seek (self, pos, self->priv->rate, self->priv->trick_mode); + } + + return; + +seek_failed: + { + gst_print ("\nCould not seek.\n"); + } +} + +static gboolean +play_set_rate_and_trick_mode (GESLauncher * self, gdouble rate, + GstPlayTrickMode mode) +{ + gint64 pos = -1; + + g_return_val_if_fail (rate != 0, FALSE); + + if (!gst_element_query_position (GST_ELEMENT (self->priv->pipeline), + GST_FORMAT_TIME, &pos)) + return FALSE; + + return play_do_seek (self, pos, rate, mode); +} + +static void +play_set_playback_rate (GESLauncher * self, gdouble rate) +{ + GstPlayTrickMode mode = self->priv->trick_mode; + + if (play_set_rate_and_trick_mode (self, rate, mode)) { + gst_print ("Playback rate: %.2f", rate); + gst_print (" \n"); + } else { + gst_print ("\n"); + gst_print ("Could not change playback rate to %.2f", rate); + gst_print (".\n"); + } +} + +static void +play_set_relative_playback_rate (GESLauncher * self, gdouble rate_step, + gboolean reverse_direction) +{ + gdouble new_rate = self->priv->rate + rate_step; + + play_set_playback_rate (self, new_rate); +} + +static const gchar * +trick_mode_get_description (GstPlayTrickMode mode) +{ + switch (mode) { + case GST_PLAY_TRICK_MODE_NONE: + return "normal playback, trick modes disabled"; + case GST_PLAY_TRICK_MODE_DEFAULT: + return "trick mode: default"; + case GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO: + return "trick mode: default, no audio"; + case GST_PLAY_TRICK_MODE_KEY_UNITS: + return "trick mode: key frames only"; + case GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO: + return "trick mode: key frames only, no audio"; + default: + break; + } + return "unknown trick mode"; +} + +static void +play_switch_trick_mode (GESLauncher * self) +{ + GstPlayTrickMode new_mode = ++self->priv->trick_mode; + const gchar *mode_desc; + + if (new_mode == GST_PLAY_TRICK_MODE_LAST) + new_mode = GST_PLAY_TRICK_MODE_NONE; + + mode_desc = trick_mode_get_description (new_mode); + + if (play_set_rate_and_trick_mode (self, self->priv->rate, new_mode)) { + gst_print ("Rate: %.2f (%s) \n", self->priv->rate, + mode_desc); + } else { + gst_print ("\nCould not change trick mode to %s.\n", mode_desc); + } +} + +static void +print_keyboard_help (void) +{ + static struct + { + const gchar *key_desc; + const gchar *key_help; + } key_controls[] = { + { + "space", "pause/unpause"}, { + "q or ESC", "quit"}, { + "\342\206\222", "seek forward"}, { + "\342\206\220", "seek backward"}, { + "+", "increase playback rate"}, { + "-", "decrease playback rate"}, { + "t", "enable/disable trick modes"}, { + "s", "change subtitle track"}, { + "0", "seek to beginning"}, { + "k", "show keyboard shortcuts"},}; + guint i, chars_to_pad, desc_len, max_desc_len = 0; + + gst_print ("\n\n%s\n\n", "Interactive mode - keyboard controls:"); + + for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) { + desc_len = g_utf8_strlen (key_controls[i].key_desc, -1); + max_desc_len = MAX (max_desc_len, desc_len); + } + ++max_desc_len; + + for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) { + chars_to_pad = max_desc_len - g_utf8_strlen (key_controls[i].key_desc, -1); + gst_print ("\t%s", key_controls[i].key_desc); + gst_print ("%-*s: ", chars_to_pad, ""); + gst_print ("%s\n", key_controls[i].key_help); + } + gst_print ("\n"); +} + +static gboolean +_parse_track_type (const gchar * option_name, const gchar * value, + GESLauncherParsedOptions * opts, GError ** error) +{ + if (!get_flags_from_string (GES_TYPE_TRACK_TYPE, value, &opts->track_types)) + return FALSE; + + return TRUE; +} + +static gboolean +_set_track_restriction_caps (GESTrack * track, const gchar * caps_str) +{ + GstCaps *caps; + + if (!caps_str) + return TRUE; + + caps = gst_caps_from_string (caps_str); + + if (!caps) { + g_error ("Could not create caps for %s from: %s", + G_OBJECT_TYPE_NAME (track), caps_str); + + return FALSE; + } + + ges_track_set_restriction_caps (track, caps); + + gst_caps_unref (caps); + return TRUE; +} + +static void +_set_restriction_caps (GESTimeline * timeline, GESLauncherParsedOptions * opts) +{ + GList *tmp, *tracks = ges_timeline_get_tracks (timeline); + + for (tmp = tracks; tmp; tmp = tmp->next) { + if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_VIDEO) + _set_track_restriction_caps (tmp->data, opts->video_track_caps); + else if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_AUDIO) + _set_track_restriction_caps (tmp->data, opts->audio_track_caps); + } + + g_list_free_full (tracks, gst_object_unref); + +} + +static void +_set_track_forward_tags (const GValue * item, gpointer unused) +{ + GstElement *comp = g_value_get_object (item); + + g_object_set (comp, "drop-tags", FALSE, NULL); +} + +static void +_set_tracks_forward_tags (GESTimeline * timeline, + GESLauncherParsedOptions * opts) +{ + GList *tmp, *tracks; + + if (!opts->forward_tags) + return; + + tracks = ges_timeline_get_tracks (timeline); + + for (tmp = tracks; tmp; tmp = tmp->next) { + GstIterator *it = + gst_bin_iterate_all_by_element_factory_name (GST_BIN (tmp->data), + "nlecomposition"); + + gst_iterator_foreach (it, + (GstIteratorForeachFunction) _set_track_forward_tags, NULL); + gst_iterator_free (it); + } + + g_list_free_full (tracks, gst_object_unref); + +} + +static void +_check_has_audio_video (GESLauncher * self, gint * n_audio, gint * n_video) +{ + GList *tmp, *tracks = ges_timeline_get_tracks (self->priv->timeline); + + *n_video = *n_audio = 0; + for (tmp = tracks; tmp; tmp = tmp->next) { + if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_VIDEO) + *n_video = *n_video + 1; + else if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_AUDIO) + *n_audio = *n_audio + 1; + } +} + +#define N_INSTANCES "__n_instances" +static gint +sort_encoding_profiles (gconstpointer a, gconstpointer b) +{ + const gint acount = + GPOINTER_TO_INT (g_object_get_data ((GObject *) a, N_INSTANCES)); + const gint bcount = + GPOINTER_TO_INT (g_object_get_data ((GObject *) b, N_INSTANCES)); + + if (acount < bcount) + return -1; + + if (acount == bcount) + return 0; + + return 1; +} + +static GList * +_timeline_assets (GESLauncher * self) +{ + GList *tmp, *assets = NULL; + + for (tmp = self->priv->timeline->layers; tmp; tmp = tmp->next) { + GList *tclip, *clips = ges_layer_get_clips (tmp->data); + + for (tclip = clips; tclip; tclip = tclip->next) { + if (GES_IS_URI_CLIP (tclip->data)) { + assets = + g_list_append (assets, ges_extractable_get_asset (tclip->data)); + } + } + g_list_free_full (clips, gst_object_unref); + } + + return assets; +} + +static GESAsset * +_asset_for_named_clip (GESLauncher * self, const gchar * name) +{ + GList *tmp; + GESAsset *ret = NULL; + + for (tmp = self->priv->timeline->layers; tmp; tmp = tmp->next) { + GList *tclip, *clips = ges_layer_get_clips (tmp->data); + + for (tclip = clips; tclip; tclip = tclip->next) { + if (GES_IS_URI_CLIP (tclip->data) && + !g_strcmp0 (name, ges_timeline_element_get_name (tclip->data))) { + ret = ges_extractable_get_asset (tclip->data); + break; + } + } + + g_list_free_full (clips, gst_object_unref); + + if (ret) + break; + } + + return ret; +} + +static GstEncodingProfile * +_get_profile_from (GESLauncher * self) +{ + GESAsset *asset = + _asset_for_named_clip (self, self->priv->parsed_options.profile_from); + GstDiscovererInfo *info; + GstEncodingProfile *prof; + + g_assert (asset); + + info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset)); + prof = gst_encoding_profile_from_discoverer (info); + + return prof; +} + +static GstEncodingProfile * +get_smart_profile (GESLauncher * self) +{ + gint n_audio, n_video; + GList *tmp, *assets, *possible_profiles = NULL; + GstEncodingProfile *res = NULL; + + if (self->priv->parsed_options.profile_from) { + GESAsset *asset = + _asset_for_named_clip (self, self->priv->parsed_options.profile_from); + GstDiscovererInfo *info; + GstEncodingProfile *prof; + + g_assert (asset); + + info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset)); + prof = gst_encoding_profile_from_discoverer (info); + + return prof; + } + + _check_has_audio_video (self, &n_audio, &n_video); + + assets = _timeline_assets (self); + + for (tmp = assets; tmp; tmp = tmp->next) { + GESAsset *asset = tmp->data; + GList *audio_streams, *video_streams; + GstDiscovererInfo *info; + + if (!GES_IS_URI_CLIP_ASSET (asset)) + continue; + + info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset)); + audio_streams = gst_discoverer_info_get_audio_streams (info); + video_streams = gst_discoverer_info_get_video_streams (info); + if (g_list_length (audio_streams) >= n_audio + && g_list_length (video_streams) >= n_video) { + GstEncodingProfile *prof = gst_encoding_profile_from_discoverer (info); + GList *prevprof; + + prevprof = + g_list_find_custom (possible_profiles, prof, + (GCompareFunc) gst_encoding_profile_is_equal); + if (prevprof) { + g_object_unref (prof); + prof = prevprof->data; + } else { + possible_profiles = g_list_prepend (possible_profiles, prof); + } + + g_object_set_data ((GObject *) prof, N_INSTANCES, + GINT_TO_POINTER (GPOINTER_TO_INT (g_object_get_data ((GObject *) prof, + N_INSTANCES)) + 1)); + } + gst_discoverer_stream_info_list_free (audio_streams); + gst_discoverer_stream_info_list_free (video_streams); + } + + g_list_free (assets); + + if (possible_profiles) { + possible_profiles = g_list_sort (possible_profiles, sort_encoding_profiles); + res = gst_object_ref (possible_profiles->data); + g_list_free_full (possible_profiles, gst_object_unref); + } + + return res; +} + +static void +disable_bframe_for_smart_rendering_cb (GstBin * bin, GstBin * sub_bin, + GstElement * child) +{ + GstElementFactory *factory = gst_element_get_factory (child); + + if (factory && !g_strcmp0 (GST_OBJECT_NAME (factory), "x264enc")) { + g_object_set (child, "b-adapt", FALSE, "b-pyramid", FALSE, "bframes", 0, + NULL); + } +} + +static gboolean +_set_rendering_details (GESLauncher * self) +{ + GESLauncherParsedOptions *opts = &self->priv->parsed_options; + gboolean smart_profile = FALSE; + GESPipelineFlags cmode = ges_pipeline_get_mode (self->priv->pipeline); + GESProject *proj; + + if (cmode & GES_PIPELINE_MODE_RENDER + || cmode & GES_PIPELINE_MODE_SMART_RENDER) { + GST_INFO_OBJECT (self, "Rendering settings already set"); + return TRUE; + } + + proj = + GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (self->priv-> + timeline))); + + /* Setup profile/encoding if needed */ + if (opts->outputuri) { + GstEncodingProfile *prof = NULL; + if (!opts->format) { + const GList *profiles = ges_project_list_encoding_profiles (proj); + + if (profiles) { + prof = profiles->data; + if (opts->encoding_profile) + for (; profiles; profiles = profiles->next) + if (g_strcmp0 (opts->encoding_profile, + gst_encoding_profile_get_name (profiles->data)) == 0) + prof = profiles->data; + } + + if (prof) + prof = gst_object_ref (prof); + } + + if (!prof) { + if (opts->format == NULL) { + if (opts->profile_from) + prof = _get_profile_from (self); + else if (opts->smartrender) + prof = get_smart_profile (self); + if (prof) + smart_profile = TRUE; + else { + opts->format = get_file_extension (opts->outputuri); + prof = parse_encoding_profile (opts->format); + } + } else { + prof = parse_encoding_profile (opts->format); + if (!prof) + g_error ("Invalid format specified: %s", opts->format); + } + + if (!prof) { + ges_warn + ("No format specified and couldn't find one from output file extension, " + "falling back to theora+vorbis in ogg."); + g_free (opts->format); + + opts->format = + g_strdup ("application/ogg:video/x-theora:audio/x-vorbis"); + prof = parse_encoding_profile (opts->format); + } + + if (!prof) { + ges_printerr ("Could not find any encoding format for %s\n", + opts->format); + return FALSE; + } + + gst_print ("\nEncoding details:\n"); + gst_print ("================\n"); + + gst_print (" -> Output file: %s\n", opts->outputuri); + gst_print (" -> Profile:%s\n", + smart_profile ? + " (selected from input files format for efficient smart rendering" : + ""); + describe_encoding_profile (prof); + gst_print ("\n"); + + ges_project_add_encoding_profile (proj, prof); + } + + opts->outputuri = ensure_uri (opts->outputuri); + if (opts->smartrender) { + g_signal_connect (self->priv->pipeline, "deep-element-added", + G_CALLBACK (disable_bframe_for_smart_rendering_cb), NULL); + } + if (!prof + || !ges_pipeline_set_render_settings (self->priv->pipeline, + opts->outputuri, prof) + || !ges_pipeline_set_mode (self->priv->pipeline, + opts->smartrender ? GES_PIPELINE_MODE_SMART_RENDER : + GES_PIPELINE_MODE_RENDER)) { + return FALSE; + } + + gst_encoding_profile_unref (prof); + } else { + ges_pipeline_set_mode (self->priv->pipeline, GES_PIPELINE_MODE_PREVIEW); + } + return TRUE; +} + +static void +_track_set_mixing (GESTrack * track, GESLauncherParsedOptions * opts) +{ + static gboolean printed_mixing_disabled = FALSE; + + if (opts->disable_mixing || opts->smartrender) + ges_track_set_mixing (track, FALSE); + if (!opts->disable_mixing && opts->smartrender && !printed_mixing_disabled) { + gst_print ("**Mixing is disabled for smart rendering to work**\n"); + printed_mixing_disabled = TRUE; + } +} + +static gboolean +_timeline_set_user_options (GESLauncher * self, GESTimeline * timeline, + const gchar * load_path) +{ + GList *tmp; + GESTrack *tracka, *trackv; + gboolean has_audio = FALSE, has_video = FALSE; + GESLauncherParsedOptions *opts = &self->priv->parsed_options; + + if (self->priv->parsed_options.profile_from) { + GList *tmp, *tracks; + GList *audio_streams, *video_streams; + GESAsset *asset = + _asset_for_named_clip (self, self->priv->parsed_options.profile_from); + GstDiscovererInfo *info; + guint i; + + if (!asset) { + ges_printerr + ("\nERROR: can't create profile from named clip, no such clip %s\n\n", + self->priv->parsed_options.profile_from); + return FALSE; + } + + tracks = ges_timeline_get_tracks (self->priv->timeline); + + for (tmp = tracks; tmp; tmp = tmp->next) { + ges_timeline_remove_track (timeline, tmp->data); + } + + g_list_free_full (tracks, gst_object_unref); + + info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset)); + + audio_streams = gst_discoverer_info_get_audio_streams (info); + video_streams = gst_discoverer_info_get_video_streams (info); + + for (i = 0; i < g_list_length (audio_streams); i++) { + ges_timeline_add_track (timeline, GES_TRACK (ges_audio_track_new ())); + } + + for (i = 0; i < g_list_length (video_streams); i++) { + ges_timeline_add_track (timeline, GES_TRACK (ges_video_track_new ())); + } + + gst_discoverer_stream_info_list_free (audio_streams); + gst_discoverer_stream_info_list_free (video_streams); + } + +retry: + for (tmp = timeline->tracks; tmp; tmp = tmp->next) { + + if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_VIDEO) + has_video = TRUE; + else if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_AUDIO) + has_audio = TRUE; + + _track_set_mixing (tmp->data, opts); + + if (!self->priv->parsed_options.profile_from) { + if (!(GES_TRACK (tmp->data)->type & opts->track_types)) { + ges_timeline_remove_track (timeline, tmp->data); + goto retry; + } + } + } + + if ((opts->scenario || opts->testfile) && !load_path + && !self->priv->parsed_options.profile_from) { + if (!has_video && opts->track_types & GES_TRACK_TYPE_VIDEO) { + trackv = GES_TRACK (ges_video_track_new ()); + + if (!_set_track_restriction_caps (trackv, opts->video_track_caps)) + return FALSE; + + _track_set_mixing (trackv, opts); + + if (!(ges_timeline_add_track (timeline, trackv))) + return FALSE; + } + + if (!has_audio && opts->track_types & GES_TRACK_TYPE_AUDIO) { + tracka = GES_TRACK (ges_audio_track_new ()); + + if (!_set_track_restriction_caps (tracka, opts->audio_track_caps)) + return FALSE; + + _track_set_mixing (tracka, opts); + + if (!(ges_timeline_add_track (timeline, tracka))) + return FALSE; + } + } else { + _set_restriction_caps (timeline, opts); + } + + _set_tracks_forward_tags (timeline, opts); + + return TRUE; +} + +static void +_project_loading_error_cb (GESProject * project, GESTimeline * timeline, + GError * error, GESLauncher * self) +{ + ges_printerr ("Error loading timeline: '%s'\n", error->message); + self->priv->seenerrors = TRUE; + + g_application_quit (G_APPLICATION (self)); +} + +static void +_project_loaded_cb (GESProject * project, GESTimeline * timeline, + GESLauncher * self) +{ + gchar *project_uri = NULL; + GESLauncherParsedOptions *opts = &self->priv->parsed_options; + GST_INFO ("Project loaded, playing it"); + + if (opts->save_path) { + gchar *uri; + GError *error = NULL; + + if (g_strcmp0 (opts->save_path, "+r") == 0) { + uri = ges_project_get_uri (project); + } else if (!(uri = ensure_uri (opts->save_path))) { + g_error ("couldn't create uri for '%s", opts->save_path); + + self->priv->seenerrors = TRUE; + g_application_quit (G_APPLICATION (self)); + } + + gst_print ("\nSaving project to %s\n", uri); + ges_project_save (project, timeline, uri, NULL, TRUE, &error); + g_free (uri); + + g_assert_no_error (error); + if (error) { + self->priv->seenerrors = TRUE; + g_error_free (error); + g_application_quit (G_APPLICATION (self)); + } + } + + project_uri = ges_project_get_uri (project); + + if (self->priv->parsed_options.load_path && project_uri + && ges_validate_activate (GST_PIPELINE (self->priv->pipeline), + self, opts) == FALSE) { + if (opts->scenario) + g_error ("Could not activate scenario %s", opts->scenario); + else + g_error ("Could not activate testfile %s", opts->testfile); + self->priv->seenerrors = TRUE; + g_application_quit (G_APPLICATION (self)); + } + + if (!_timeline_set_user_options (self, timeline, project_uri)) { + g_error ("Failed to set user options on timeline\n"); + } else if (project_uri) { + if (!_set_rendering_details (self)) + g_error ("Failed to setup rendering details\n"); + } + + print_timeline (self->priv->timeline); + + g_free (project_uri); + + if (!self->priv->seenerrors && opts->needs_set_state && + gst_element_set_state (GST_ELEMENT (self->priv->pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { + g_error ("Failed to start the pipeline\n"); + } +} + +static void +_error_loading_asset_cb (GESProject * project, GError * error, + const gchar * failed_id, GType extractable_type, GESLauncher * self) +{ + ges_printerr ("Error loading asset %s: %s\n", failed_id, error->message); + self->priv->seenerrors = TRUE; + + g_application_quit (G_APPLICATION (self)); +} + +static gboolean +_create_timeline (GESLauncher * self, const gchar * serialized_timeline, + const gchar * proj_uri, gboolean validate) +{ + GESProject *project; + + GError *error = NULL; + + if (proj_uri != NULL) { + project = ges_project_new (proj_uri); + } else if (!validate) { + project = ges_project_new (serialized_timeline); + } else { + project = ges_project_new (NULL); + } + + g_signal_connect (project, "error-loading-asset", + G_CALLBACK (_error_loading_asset_cb), self); + g_signal_connect (project, "loaded", G_CALLBACK (_project_loaded_cb), self); + g_signal_connect (project, "error-loading", + G_CALLBACK (_project_loading_error_cb), self); + + self->priv->timeline = + GES_TIMELINE (ges_asset_extract (GES_ASSET (project), &error)); + gst_object_unref (project); + + if (error) { + ges_printerr ("\nERROR: Could not create timeline because: %s\n\n", + error->message); + g_error_free (error); + return FALSE; + } + + return TRUE; +} + +typedef void (*SetSinkFunc) (GESPipeline * pipeline, GstElement * element); + +static gboolean +_set_sink (GESLauncher * self, const gchar * sink_desc, SetSinkFunc set_func) +{ + if (sink_desc != NULL) { + GError *err = NULL; + GstElement *sink = gst_parse_bin_from_description_full (sink_desc, TRUE, + NULL, GST_PARSE_FLAG_NO_SINGLE_ELEMENT_BINS, &err); + if (sink == NULL) { + GST_ERROR ("could not create the requested videosink %s (err: %s), " + "exiting", err ? err->message : "", sink_desc); + if (err) + g_error_free (err); + return FALSE; + } + set_func (self->priv->pipeline, sink); + } + return TRUE; +} + +static gboolean +_set_playback_details (GESLauncher * self) +{ + GESLauncherParsedOptions *opts = &self->priv->parsed_options; + + if (!_set_sink (self, opts->videosink, ges_pipeline_preview_set_video_sink) || + !_set_sink (self, opts->audiosink, ges_pipeline_preview_set_audio_sink)) + return FALSE; + + return TRUE; +} + +static void +bus_message_cb (GstBus * bus, GstMessage * message, GESLauncher * self) +{ + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_WARNING:{ + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->priv->pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "ges-launch.warning"); + break; + } + case GST_MESSAGE_ERROR:{ + GError *err = NULL; + gchar *dbg_info = NULL; + + gst_message_parse_error (message, &err, &dbg_info); + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->priv->pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "ges-launch-error"); + ges_printerr ("ERROR from element %s: %s\n", + GST_OBJECT_NAME (message->src), err->message); + ges_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none"); + g_clear_error (&err); + g_free (dbg_info); + self->priv->seenerrors = TRUE; + g_application_quit (G_APPLICATION (self)); + break; + } + case GST_MESSAGE_EOS: + if (!self->priv->parsed_options.ignore_eos) { + ges_ok ("\nDone\n"); + g_application_quit (G_APPLICATION (self)); + } + break; + case GST_MESSAGE_STATE_CHANGED: + if (GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (self->priv->pipeline)) { + gchar *dump_name; + GstState old, new, pending; + gchar *state_transition_name; + + gst_message_parse_state_changed (message, &old, &new, &pending); + state_transition_name = g_strdup_printf ("%s_%s", + gst_element_state_get_name (old), gst_element_state_get_name (new)); + dump_name = g_strconcat ("ges-launch.", state_transition_name, NULL); + + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->priv->pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, dump_name); + + g_free (dump_name); + g_free (state_transition_name); + } + break; + case GST_MESSAGE_REQUEST_STATE: + ges_validate_handle_request_state_change (message, G_APPLICATION (self)); + break; + default: + break; + } +} + +#ifdef G_OS_UNIX +static gboolean +intr_handler (GESLauncher * self) +{ + gst_print ("interrupt received.\n"); + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->priv->pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "ges-launch.interrupted"); + + g_application_quit (G_APPLICATION (self)); + + /* remove signal handler */ + return TRUE; +} +#endif /* G_OS_UNIX */ + +static gboolean +_save_timeline (GESLauncher * self) +{ + GESLauncherParsedOptions *opts = &self->priv->parsed_options; + + + if (opts->embed_nesteds) { + GList *tmp, *assets; + GESProject *proj = + GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (self-> + priv->timeline))); + + assets = ges_project_list_assets (proj, GES_TYPE_URI_CLIP); + for (tmp = assets; tmp; tmp = tmp->next) { + gboolean is_nested; + + g_object_get (tmp->data, "is-nested-timeline", &is_nested, NULL); + if (is_nested) { + GESAsset *subproj = + ges_asset_request (GES_TYPE_TIMELINE, ges_asset_get_id (tmp->data), + NULL); + + ges_project_add_asset (proj, subproj); + } + } + g_list_free_full (assets, gst_object_unref); + } + + if (opts->save_only_path) { + gchar *uri; + + if (!(uri = ensure_uri (opts->save_only_path))) { + g_error ("couldn't create uri for '%s", opts->save_only_path); + return FALSE; + } + + return ges_timeline_save_to_uri (self->priv->timeline, uri, NULL, TRUE, + NULL); + } + + if (opts->save_path && !opts->load_path) { + gchar *uri; + if (!(uri = ensure_uri (opts->save_path))) { + g_error ("couldn't create uri for '%s", opts->save_path); + return FALSE; + } + + return ges_timeline_save_to_uri (self->priv->timeline, uri, NULL, TRUE, + NULL); + } + + return TRUE; +} + +static gboolean +_run_pipeline (GESLauncher * self) +{ + GstBus *bus; + GESLauncherParsedOptions *opts = &self->priv->parsed_options; + + if (!opts->load_path) { + g_clear_pointer (&opts->sanitized_timeline, &g_free); + if (ges_validate_activate (GST_PIPELINE (self->priv->pipeline), + self, opts) == FALSE) { + g_error ("Could not activate scenario %s", opts->scenario); + return FALSE; + } + + if (opts->sanitized_timeline) { + GESProject *project = ges_project_new (opts->sanitized_timeline); + + if (!ges_project_load (project, self->priv->timeline, NULL)) { + ges_printerr ("Could not load timeline: %s\n", + opts->sanitized_timeline); + return FALSE; + } + } + + if (!_timeline_set_user_options (self, self->priv->timeline, NULL)) { + ges_printerr ("Could not properly set tracks\n"); + return FALSE; + } + + if (!_set_rendering_details (self)) { + g_error ("Failed to setup rendering details\n"); + return FALSE; + } + } + + bus = gst_pipeline_get_bus (GST_PIPELINE (self->priv->pipeline)); + gst_bus_add_signal_watch (bus); + g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), self); + + g_application_hold (G_APPLICATION (self)); + + return TRUE; +} + +static gboolean +_create_pipeline (GESLauncher * self, const gchar * serialized_timeline) +{ + gchar *uri = NULL; + gboolean res = TRUE; + GESLauncherParsedOptions *opts = &self->priv->parsed_options; + + /* Timeline creation */ + if (opts->load_path) { + gst_print ("Loading project from : %s\n", opts->load_path); + + if (!(uri = ensure_uri (opts->load_path))) { + g_error ("couldn't create uri for '%s'", opts->load_path); + goto failure; + } + } + + self->priv->pipeline = ges_pipeline_new (); + + if (opts->outputuri) + ges_pipeline_set_mode (self->priv->pipeline, 0); + + if (!_create_timeline (self, serialized_timeline, uri, opts->scenario + || opts->testfile)) { + GST_ERROR ("Could not create the timeline"); + goto failure; + } + + if (!opts->load_path) + ges_timeline_commit (self->priv->timeline); + + /* save project if path is given. we do this now in case GES crashes or + * hangs during playback. */ + if (!_save_timeline (self)) + goto failure; + + if (opts->save_only_path) + goto done; + + /* In order to view our timeline, let's grab a convenience pipeline to put + * our timeline in. */ + + if (opts->mute) { + GstElement *sink = gst_element_factory_make ("fakeaudiosink", NULL); + ges_pipeline_preview_set_audio_sink (self->priv->pipeline, sink); + + sink = gst_element_factory_make ("fakevideosink", NULL); + ges_pipeline_preview_set_video_sink (self->priv->pipeline, sink); + } + + /* Add the timeline to that pipeline */ + if (!ges_pipeline_set_timeline (self->priv->pipeline, self->priv->timeline)) + goto failure; + +done: + if (uri) + g_free (uri); + + return res; + +failure: + { + if (self->priv->timeline) + gst_object_unref (self->priv->timeline); + if (self->priv->pipeline) + gst_object_unref (self->priv->pipeline); + self->priv->pipeline = NULL; + self->priv->timeline = NULL; + + res = FALSE; + goto done; + } +} + +static void +_print_transition_list (void) +{ + print_enum (GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE); +} + +static GOptionGroup * +ges_launcher_get_project_option_group (GESLauncherParsedOptions * opts) +{ + GOptionGroup *group; + + GOptionEntry options[] = { + {"load", 'l', 0, G_OPTION_ARG_STRING, &opts->load_path, + "Load project from file. The project can be saved " + "again with the --save option.", + "<path>"}, + {"save", 's', 0, G_OPTION_ARG_STRING, &opts->save_path, + "Save project to file before rendering. " + "It can then be loaded with the --load option", + "<path>"}, + {"save-only", 0, 0, G_OPTION_ARG_STRING, &opts->save_only_path, + "Same as save project, except exit as soon as the timeline " + "is saved instead of playing it back", + "<path>"}, + {NULL} + }; + group = g_option_group_new ("project", "Project Options", + "Show project-related options", NULL, NULL); + + g_option_group_add_entries (group, options); + + return group; +} + +static GOptionGroup * +ges_launcher_get_info_option_group (GESLauncherParsedOptions * opts) +{ + GOptionGroup *group; + + GOptionEntry options[] = { +#ifdef HAVE_GST_VALIDATE + {"inspect-action-type", 0, 0, G_OPTION_ARG_NONE, &opts->inspect_action_type, + "Inspect the available action types that can be defined in a scenario " + "set with --set-scenario. " + "Will list all action-types if action-type is empty.", + "<[action-type]>"}, +#endif + {"list-transitions", 0, 0, G_OPTION_ARG_NONE, &opts->list_transitions, + "List all valid transition types and exit. " + "See ges-launch-1.0 help transition for more information.", + NULL}, + {NULL} + }; + + group = g_option_group_new ("informative", "Informative Options", + "Show informative options", NULL, NULL); + + g_option_group_add_entries (group, options); + + return group; +} + +static GOptionGroup * +ges_launcher_get_rendering_option_group (GESLauncherParsedOptions * opts) +{ + GOptionGroup *group; + + GOptionEntry options[] = { + {"outputuri", 'o', 0, G_OPTION_ARG_STRING, &opts->outputuri, + "If set, ges-launch-1.0 will render the timeline instead of playing " + "it back. If no format `--format` is specified, the outputuri extension" + " will be used to determine an encoding format, or default to theora+vorbis" + " in ogg if that doesn't work out.", + "<URI>"}, + {"format", 'f', 0, G_OPTION_ARG_STRING, &opts->format, + "Set an encoding profile on the command line. " + "See ges-launch-1.0 help profile for more information. " + "This will have no effect if no outputuri has been specified.", + "<profile>"}, + {"encoding-profile", 'e', 0, G_OPTION_ARG_STRING, &opts->encoding_profile, + "Set an encoding profile from a preset file. " + "See ges-launch-1.0 help profile for more information. " + "This will have no effect if no outputuri has been specified.", + "<profile-name>"}, + {"profile-from", 0, 0, G_OPTION_ARG_STRING, &opts->profile_from, + "Use clip with name <clip-name> to determine the topology and profile " + "of the rendered output. This will have no effect if no outputuri " + "has been specified.", + "<clip-name>"}, + {"forward-tags", 0, 0, G_OPTION_ARG_NONE, &opts->forward_tags, + "Forward tags from input files to the output", + NULL}, + {"smart-rendering", 0, 0, G_OPTION_ARG_NONE, &opts->smartrender, + "Avoid reencoding when rendering. This option implies --disable-mixing.", + NULL}, + {NULL} + }; + + group = g_option_group_new ("rendering", "Rendering Options", + "Show rendering options", NULL, NULL); + + g_option_group_add_entries (group, options); + + return group; +} + +static GOptionGroup * +ges_launcher_get_playback_option_group (GESLauncherParsedOptions * opts) +{ + GOptionGroup *group; + + GOptionEntry options[] = { + {"videosink", 'v', 0, G_OPTION_ARG_STRING, &opts->videosink, + "Set the videosink used for playback.", "<videosink>"}, + {"audiosink", 'a', 0, G_OPTION_ARG_STRING, &opts->audiosink, + "Set the audiosink used for playback.", "<audiosink>"}, + {"mute", 'm', 0, G_OPTION_ARG_NONE, &opts->mute, + "Mute playback output. This has no effect when rendering.", NULL}, + {NULL} + }; + + group = g_option_group_new ("playback", "Playback Options", + "Show playback options", NULL, NULL); + + g_option_group_add_entries (group, options); + + return group; +} + +gboolean +ges_launcher_parse_options (GESLauncher * self, + gchar ** arguments[], gint * argc, GOptionContext * ctx, GError ** error) +{ + gboolean res; + GOptionGroup *main_group; + gint nargs = 0, tmpargc; + gchar **commands = NULL, *help, *tmp; + GError *err = NULL; + gboolean owns_ctx = ctx == NULL; + GESLauncherParsedOptions *opts = &self->priv->parsed_options; + gchar *prev_videosink = opts->videosink, *prev_audiosink = opts->audiosink; +/* *INDENT-OFF* */ + GOptionEntry options[] = { + {"disable-mixing", 0, 0, G_OPTION_ARG_NONE, &opts->disable_mixing, + "Do not use mixing elements to mix layers together.", NULL + }, + {"track-types", 't', 0, G_OPTION_ARG_CALLBACK, &_parse_track_type, + "Specify the track types to be created. " + "When loading a project, only relevant tracks will be added to the " + "timeline.", + "<track-types>" + }, + { + "video-caps", + 0, + 0, + G_OPTION_ARG_STRING, + &opts->video_track_caps, + "Specify the track restriction caps of the video track.", + }, + { + "audio-caps", + 0, + 0, + G_OPTION_ARG_STRING, + &opts->audio_track_caps, + "Specify the track restriction caps of the audio track.", + }, +#ifdef HAVE_GST_VALIDATE + {"set-test-file", 0, 0, G_OPTION_ARG_STRING, &opts->testfile, + "ges-launch-1.0 exposes gst-validate functionalities, such as test files and scenarios." + " Scenarios describe actions to execute, such as seeks or setting of " + "properties. " + "GES implements editing-specific actions such as adding or removing " + "clips. " + "See gst-validate-1.0 --help for more info about validate and " + "scenarios, " "and --inspect-action-type.", + "</test/file/path>" + }, + {"set-scenario", 0, 0, G_OPTION_ARG_STRING, &opts->scenario, + "ges-launch-1.0 exposes gst-validate functionalities, such as scenarios." + " Scenarios describe actions to execute, such as seeks or setting of " + "properties. " + "GES implements editing-specific actions such as adding or removing " + "clips. " + "See gst-validate-1.0 --help for more info about validate and " + "scenarios, " "and --inspect-action-type.", + "<scenario_name>" + }, + {"enable-validate", 0, 0, G_OPTION_ARG_NONE, &opts->enable_validate, + "Run inside GstValidate.", NULL, + }, +#endif + { + "embed-nesteds", + 0, + 0, + G_OPTION_ARG_NONE, + &opts->embed_nesteds, + "Embed nested timelines when saving.", + NULL, + }, + {"no-interactive", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, + &opts->interactive, + "Disable interactive control via the keyboard", NULL + }, + {NULL} + }; +/* *INDENT-ON* */ + + if (owns_ctx) { + opts->videosink = opts->audiosink = NULL; + ctx = g_option_context_new ("- plays or renders a timeline."); + } + tmpargc = argc ? *argc : g_strv_length (*arguments); + + if (tmpargc > 2) { + nargs = tmpargc - 2; + commands = &(*arguments)[2]; + } + + tmp = ges_command_line_formatter_get_help (nargs, commands); + help = + g_strdup_printf ("%s\n\nTimeline description format:\n\n%s", HELP_SUMMARY, + tmp); + g_free (tmp); + g_option_context_set_summary (ctx, help); + g_free (help); + + main_group = + g_option_group_new ("launcher", "launcher options", + "Main launcher options", opts, NULL); + g_option_group_add_entries (main_group, options); + g_option_context_set_main_group (ctx, main_group); + g_option_context_add_group (ctx, gst_init_get_option_group ()); + g_option_context_add_group (ctx, ges_init_get_option_group ()); + g_option_context_add_group (ctx, + ges_launcher_get_project_option_group (opts)); + g_option_context_add_group (ctx, + ges_launcher_get_rendering_option_group (opts)); + g_option_context_add_group (ctx, + ges_launcher_get_playback_option_group (opts)); + g_option_context_add_group (ctx, ges_launcher_get_info_option_group (opts)); + g_option_context_set_ignore_unknown_options (ctx, TRUE); + + res = g_option_context_parse_strv (ctx, arguments, &err); + if (argc) + *argc = tmpargc; + + if (err) + g_propagate_error (error, err); + + opts->enable_validate |= opts->testfile || opts->scenario + || g_getenv ("GST_VALIDATE_SCENARIO"); + + if (owns_ctx) { + g_option_context_free (ctx); + /* sinks passed in the command line are preferred. */ + if (prev_videosink) { + g_free (opts->videosink); + opts->videosink = prev_videosink; + } + + if (prev_audiosink) { + g_free (opts->audiosink); + opts->audiosink = prev_audiosink; + } + _set_playback_details (self); + } + + return res; +} + +static gboolean +_local_command_line (GApplication * application, gchar ** arguments[], + gint * exit_status) +{ + gboolean res = TRUE; + gint argc; + GError *error = NULL; + GESLauncher *self = GES_LAUNCHER (application); + GESLauncherParsedOptions *opts = &self->priv->parsed_options; + GOptionContext *ctx = g_option_context_new ("- plays or renders a timeline."); + + *exit_status = 0; + argc = g_strv_length (*arguments); + + gst_init (&argc, arguments); + if (!ges_launcher_parse_options (self, arguments, &argc, ctx, &error)) { + gst_init (NULL, NULL); + g_option_context_free (ctx); + if (error) { + ges_printerr ("Error initializing: %s\n", error->message); + g_error_free (error); + } else { + ges_printerr ("Error parsing command line arguments\n"); + } + *exit_status = 1; + goto done; + } + + if (opts->inspect_action_type) { + ges_validate_print_action_types ((const gchar **) &((*arguments)[1]), + argc - 1); + goto done; + } + + if (!opts->load_path && !opts->scenario && !opts->testfile + && !opts->list_transitions && (argc <= 1)) { + gst_print ("%s", g_option_context_get_help (ctx, TRUE, NULL)); + g_option_context_free (ctx); + *exit_status = 1; + goto done; + } + + g_option_context_free (ctx); + + opts->sanitized_timeline = sanitize_timeline_description (*arguments, opts); + + if (!g_application_register (application, NULL, &error)) { + *exit_status = 1; + g_clear_error (&error); + res = FALSE; + } + +done: + return res; +} + +static void +keyboard_cb (const gchar * key_input, gpointer user_data) +{ + GESLauncher *self = (GESLauncher *) user_data; + gchar key = '\0'; + + /* only want to switch/case on single char, not first char of string */ + if (key_input[0] != '\0' && key_input[1] == '\0') + key = g_ascii_tolower (key_input[0]); + + switch (key) { + case 'k': + print_keyboard_help (); + break; + case ' ': + toggle_paused (self); + break; + case 'q': + case 'Q': + g_application_quit (G_APPLICATION (self)); + break; + case '+': + if (ABS (self->priv->rate) < 2.0) + play_set_relative_playback_rate (self, 0.1, FALSE); + else if (ABS (self->priv->rate) < 4.0) + play_set_relative_playback_rate (self, 0.5, FALSE); + else + play_set_relative_playback_rate (self, 1.0, FALSE); + break; + case '-': + if (ABS (self->priv->rate) <= 2.0) + play_set_relative_playback_rate (self, -0.1, FALSE); + else if (ABS (self->priv->rate) <= 4.0) + play_set_relative_playback_rate (self, -0.5, FALSE); + else + play_set_relative_playback_rate (self, -1.0, FALSE); + break; + case 't': + play_switch_trick_mode (self); + break; + case 27: /* ESC */ + if (key_input[1] == '\0') { + g_application_quit (G_APPLICATION (self)); + break; + } + case '0': + play_do_seek (self, 0, self->priv->rate, self->priv->trick_mode); + break; + default: + if (strcmp (key_input, GST_PLAY_KB_ARROW_RIGHT) == 0) { + relative_seek (self, +0.08); + } else if (strcmp (key_input, GST_PLAY_KB_ARROW_LEFT) == 0) { + relative_seek (self, -0.01); + } else { + GST_INFO ("keyboard input:"); + for (; *key_input != '\0'; ++key_input) + GST_INFO (" code %3d", *key_input); + } + break; + } +} + +static void +_startup (GApplication * application) +{ + GESLauncher *self = GES_LAUNCHER (application); + GESLauncherParsedOptions *opts = &self->priv->parsed_options; + +#ifdef G_OS_UNIX + self->priv->signal_watch_id = + g_unix_signal_add (SIGINT, (GSourceFunc) intr_handler, self); +#endif + + /* Initialize the GStreamer Editing Services */ + if (!ges_init ()) { + ges_printerr ("Error initializing GES\n"); + goto done; + } + + if (opts->interactive && !opts->outputuri) { + if (gst_play_kb_set_key_handler (keyboard_cb, self)) { + gst_print ("Press 'k' to see a list of keyboard shortcuts.\n"); + atexit (restore_terminal); + } else { + gst_print ("Interactive keyboard handling in terminal not available.\n"); + } + } + + if (opts->list_transitions) { + _print_transition_list (); + goto done; + } + + if (!_create_pipeline (self, opts->sanitized_timeline)) + goto failure; + + if (opts->save_only_path) + goto done; + + if (!_set_playback_details (self)) + goto failure; + + if (!_run_pipeline (self)) + goto failure; + +done: + G_APPLICATION_CLASS (ges_launcher_parent_class)->startup (application); + + return; + +failure: + self->priv->seenerrors = TRUE; + + goto done; +} + +static void +_shutdown (GApplication * application) +{ + gint validate_res = 0; + GESLauncher *self = GES_LAUNCHER (application); + GESLauncherParsedOptions *opts = &self->priv->parsed_options; + + _save_timeline (self); + + if (self->priv->pipeline) { + gst_element_set_state (GST_ELEMENT (self->priv->pipeline), GST_STATE_NULL); + validate_res = ges_validate_clean (GST_PIPELINE (self->priv->pipeline)); + } + + if (self->priv->seenerrors == FALSE) + self->priv->seenerrors = validate_res; + +#ifdef G_OS_UNIX + g_source_remove (self->priv->signal_watch_id); +#endif + + g_free (opts->sanitized_timeline); + + G_APPLICATION_CLASS (ges_launcher_parent_class)->shutdown (application); +} + +static void +_finalize (GObject * object) +{ + GESLauncher *self = GES_LAUNCHER (object); + GESLauncherParsedOptions *opts = &self->priv->parsed_options; + + g_free (opts->load_path); + g_free (opts->save_path); + g_free (opts->save_only_path); + g_free (opts->outputuri); + g_free (opts->format); + g_free (opts->encoding_profile); + g_free (opts->profile_from); + g_free (opts->videosink); + g_free (opts->audiosink); + g_free (opts->video_track_caps); + g_free (opts->audio_track_caps); + g_free (opts->scenario); + g_free (opts->testfile); + + G_OBJECT_CLASS (ges_launcher_parent_class)->finalize (object); +} + +static void +ges_launcher_class_init (GESLauncherClass * klass) +{ + G_APPLICATION_CLASS (klass)->local_command_line = _local_command_line; + G_APPLICATION_CLASS (klass)->startup = _startup; + G_APPLICATION_CLASS (klass)->shutdown = _shutdown; + + G_OBJECT_CLASS (klass)->finalize = _finalize; +} + +static void +ges_launcher_init (GESLauncher * self) +{ + self->priv = ges_launcher_get_instance_private (self); + self->priv->parsed_options.track_types = + GES_TRACK_TYPE_AUDIO | GES_TRACK_TYPE_VIDEO; + self->priv->parsed_options.interactive = TRUE; + self->priv->desired_state = GST_STATE_PLAYING; + self->priv->rate = 1.0; + self->priv->trick_mode = GST_PLAY_TRICK_MODE_NONE; +} + +gint +ges_launcher_get_exit_status (GESLauncher * self) +{ + return self->priv->seenerrors; +} + +GESLauncher * +ges_launcher_new (void) +{ + return GES_LAUNCHER (g_object_new (ges_launcher_get_type (), "application-id", + "org.gstreamer.geslaunch", "flags", + G_APPLICATION_NON_UNIQUE | G_APPLICATION_HANDLES_COMMAND_LINE, NULL)); +} diff --git a/tools/ges-launcher.h b/tools/ges-launcher.h new file mode 100644 index 0000000000..108ca8446c --- /dev/null +++ b/tools/ges-launcher.h @@ -0,0 +1,47 @@ +/* GStreamer Editing Services + * Copyright (C) 2015 Mathieu Duponchelle <mathieu.duponchelle@opencreed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <ges/ges.h> + +#include "utils.h" + +G_BEGIN_DECLS + +#define GES_TYPE_LAUNCHER ges_launcher_get_type() + +typedef struct _GESLauncherPrivate GESLauncherPrivate; +G_DECLARE_FINAL_TYPE(GESLauncher, ges_launcher, GES, LAUNCHER, GApplication); + +struct _GESLauncher { + GApplication parent; + + /*< private >*/ + GESLauncherPrivate *priv; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GESLauncher* ges_launcher_new (void); +gint ges_launcher_get_exit_status (GESLauncher *self); +gboolean ges_launcher_parse_options(GESLauncher* self, gchar*** arguments, gint *argc, GOptionContext* ctx, GError** error); + +G_END_DECLS diff --git a/tools/ges-validate.c b/tools/ges-validate.c new file mode 100644 index 0000000000..5ea786ed01 --- /dev/null +++ b/tools/ges-validate.c @@ -0,0 +1,306 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2013> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "utils.h" +#include "ges-validate.h" + +#include <string.h> + +static gboolean +_print_position (GstElement * pipeline) +{ + gint64 position = 0, duration = -1; + + if (pipeline) { + gst_element_query_position (GST_ELEMENT (pipeline), GST_FORMAT_TIME, + &position); + gst_element_query_duration (GST_ELEMENT (pipeline), GST_FORMAT_TIME, + &duration); + + gst_print ("<position: %" GST_TIME_FORMAT " duration: %" GST_TIME_FORMAT + "/>\r", GST_TIME_ARGS (position), GST_TIME_ARGS (duration)); + } + + return TRUE; +} + +#ifdef HAVE_GST_VALIDATE +#include <gst/validate/gst-validate-scenario.h> +#include <gst/validate/validate.h> +#include <gst/validate/gst-validate-utils.h> +#include <gst/validate/gst-validate-element-monitor.h> +#include <gst/validate/gst-validate-bin-monitor.h> + +#define MONITOR_ON_PIPELINE "validate-monitor" +#define RUNNER_ON_PIPELINE "runner-monitor" +#define WRONG_DECODER_ADDED g_quark_from_static_string ("ges::wrong-decoder-added") + +static void +_validate_report_added_cb (GstValidateRunner * runner, + GstValidateReport * report, GstPipeline * pipeline) +{ + if (report->level == GST_VALIDATE_REPORT_LEVEL_CRITICAL) { + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "ges-launch--validate-error"); + } +} + +static void +bin_element_added (GstTracer * runner, GstClockTime ts, + GstBin * bin, GstElement * element, gboolean result) +{ + GstObject *parent; + GstValidateElementMonitor *monitor = + g_object_get_data (G_OBJECT (element), "validate-monitor"); + + if (!monitor) + return; + + if (!monitor->is_decoder) + return; + + parent = gst_object_get_parent (GST_OBJECT (element)); + do { + if (GES_IS_TRACK (parent)) { + GstElementClass *klass = GST_ELEMENT_CLASS (G_OBJECT_GET_CLASS (element)); + const gchar *klassname = + gst_element_class_get_metadata (klass, GST_ELEMENT_METADATA_KLASS); + + if (GES_IS_AUDIO_TRACK (parent) && strstr (klassname, "Audio") == NULL) { + GST_VALIDATE_REPORT (monitor, WRONG_DECODER_ADDED, + "Adding non audio decoder %s in audio track %s.", + GST_OBJECT_NAME (element), GST_OBJECT_NAME (parent)); + } else if (GES_IS_VIDEO_TRACK (parent) + && strstr (klassname, "Video") == NULL + && strstr (klassname, "Image") == NULL) { + GST_VALIDATE_REPORT (monitor, WRONG_DECODER_ADDED, + "Adding non video decoder %s in video track %s.", + GST_OBJECT_NAME (element), GST_OBJECT_NAME (parent)); + + } + gst_object_unref (parent); + break; + } + + gst_object_unref (parent); + parent = gst_object_get_parent (parent); + } while (parent); +} + +static void +ges_validate_register_issues (void) +{ + gst_validate_issue_register (gst_validate_issue_new (WRONG_DECODER_ADDED, + "Wrong decoder type added to track.", + "In a specific track type we should never create decoders" + " for some other types (No audio decoder should be added" + " in a Video track).", GST_VALIDATE_REPORT_LEVEL_CRITICAL)); +} + +gboolean +ges_validate_activate (GstPipeline * pipeline, GESLauncher * launcher, + GESLauncherParsedOptions * opts) +{ + GstValidateRunner *runner = NULL; + GstValidateMonitor *monitor = NULL; + + if (!opts->enable_validate) { + opts->needs_set_state = TRUE; + g_object_set_data (G_OBJECT (pipeline), "pposition-id", + GUINT_TO_POINTER (g_timeout_add (200, + (GSourceFunc) _print_position, pipeline))); + return TRUE; + } + + gst_validate_init_debug (); + + if (opts->testfile) { + if (opts->scenario) + g_error ("Can not specify scenario and testfile at the same time"); + gst_validate_setup_test_file (opts->testfile, opts->mute); + } else if (opts->scenario) { + if (g_strcmp0 (opts->scenario, "none")) { + gchar *scenario_name = + g_strconcat (opts->scenario, "->gespipeline*", NULL); + g_setenv ("GST_VALIDATE_SCENARIO", scenario_name, TRUE); + g_free (scenario_name); + } + } + + ges_validate_register_action_types (); + ges_validate_register_issues (); + + runner = gst_validate_runner_new (); + gst_tracing_register_hook (GST_TRACER (runner), "bin-add-post", + G_CALLBACK (bin_element_added)); + g_signal_connect (runner, "report-added", + G_CALLBACK (_validate_report_added_cb), pipeline); + monitor = + gst_validate_monitor_factory_create (GST_OBJECT_CAST (pipeline), runner, + NULL); + if (GST_VALIDATE_BIN_MONITOR (monitor)->scenario) { + GstStructure *metas = + GST_VALIDATE_BIN_MONITOR (monitor)->scenario->description; + + if (metas) { + gchar **ges_options = gst_validate_utils_get_strv (metas, "ges-options"); + if (!ges_options) + ges_options = gst_validate_utils_get_strv (metas, "args"); + + gst_structure_get_boolean (metas, "ignore-eos", &opts->ignore_eos); + if (ges_options) { + gint i; + gchar **ges_options_full = + g_new0 (gchar *, g_strv_length (ges_options) + 2); + + ges_options_full[0] = g_strdup ("something"); + for (i = 0; ges_options[i]; i++) + ges_options_full[i + 1] = g_strdup (ges_options[i]); + + ges_launcher_parse_options (launcher, &ges_options_full, NULL, NULL, + NULL); + opts->sanitized_timeline = + sanitize_timeline_description (ges_options_full, opts); + g_strfreev (ges_options_full); + g_strfreev (ges_options); + } + } + } + + gst_validate_reporter_set_handle_g_logs (GST_VALIDATE_REPORTER (monitor)); + + g_object_get (monitor, "handles-states", &opts->needs_set_state, NULL); + opts->needs_set_state = !opts->needs_set_state; + g_object_set_data (G_OBJECT (pipeline), MONITOR_ON_PIPELINE, monitor); + g_object_set_data (G_OBJECT (pipeline), RUNNER_ON_PIPELINE, runner); + + return TRUE; +} + +gint +ges_validate_clean (GstPipeline * pipeline) +{ + gint res = 0; + GstValidateMonitor *monitor = + g_object_get_data (G_OBJECT (pipeline), MONITOR_ON_PIPELINE); + GstValidateRunner *runner = + g_object_get_data (G_OBJECT (pipeline), RUNNER_ON_PIPELINE); + + if (runner) + res = gst_validate_runner_exit (runner, TRUE); + else + g_source_remove (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pipeline), + "pposition-id"))); + + gst_object_unref (pipeline); + if (runner) + gst_object_unref (runner); + if (monitor) + gst_object_unref (monitor); + + return res; +} + +void +ges_validate_handle_request_state_change (GstMessage * message, + GApplication * application) +{ + GstState state; + + gst_message_parse_request_state (message, &state); + + if (GST_IS_VALIDATE_SCENARIO (GST_MESSAGE_SRC (message)) + && state == GST_STATE_NULL) { + gst_validate_printf (GST_MESSAGE_SRC (message), + "State change request NULL, " "quitting application\n"); + g_application_quit (application); + } +} + +gint +ges_validate_print_action_types (const gchar ** types, gint num_types) +{ + ges_validate_register_action_types (); + + if (!gst_validate_print_action_types (types, num_types)) { + GST_ERROR ("Could not print all wanted types"); + return 1; + } + + return 0; +} + +#else +gboolean +ges_validate_activate (GstPipeline * pipeline, GESLauncher * launcher, + GESLauncherParsedOptions * opts) +{ + if (opts->testfile) { + GST_WARNING ("Trying to run testfile %s, but gst-validate not supported", + opts->testfile); + + return FALSE; + } + + if (opts->scenario) { + GST_WARNING ("Trying to run scenario %s, but gst-validate not supported", + opts->scenario); + + return FALSE; + } + + g_object_set_data (G_OBJECT (pipeline), "pposition-id", + GUINT_TO_POINTER (g_timeout_add (200, + (GSourceFunc) _print_position, pipeline))); + + opts->needs_set_state = TRUE; + + return TRUE; +} + +gint +ges_validate_clean (GstPipeline * pipeline) +{ + g_source_remove (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pipeline), + "pposition-id"))); + + gst_object_unref (pipeline); + + return 0; +} + +void +ges_validate_handle_request_state_change (GstMessage * message, + GApplication * application) +{ + return; +} + +gint +ges_validate_print_action_types (const gchar ** types, gint num_types) +{ + return 0; +} + +#endif diff --git a/tools/ges-validate.h b/tools/ges-validate.h new file mode 100644 index 0000000000..1bdc16aaab --- /dev/null +++ b/tools/ges-validate.h @@ -0,0 +1,42 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2013> Thibault Saunier <thibault.saunier@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#pragma once + +#include <glib.h> +#include <gio/gio.h> +#include <gst/gst.h> +#include <ges/ges.h> + +#include "utils.h" +#include "ges-launcher.h" + +G_BEGIN_DECLS + +gboolean +ges_validate_activate(GstPipeline* pipeline, GESLauncher *launcher, GESLauncherParsedOptions* opts); +void ges_launch_validate_uri(const gchar* nid); + +gint +ges_validate_clean (GstPipeline *pipeline); + +void ges_validate_handle_request_state_change (GstMessage *message, GApplication *application); +gint ges_validate_print_action_types (const gchar **types, gint num_types); + +G_END_DECLS diff --git a/tools/meson.build b/tools/meson.build new file mode 100644 index 0000000000..f0726abd68 --- /dev/null +++ b/tools/meson.build @@ -0,0 +1,53 @@ +deps = [ges_dep, gstpbutils_dep, gio_dep, gstvideo_dep, gstaudio_dep] + +ges_tool_args = [ges_c_args] +if gstvalidate_dep.found() + deps = deps + [gstvalidate_dep] + ges_tool_args += ['-DGST_USE_UNSTABLE_API'] +endif + +ges_launch = executable('ges-launch-@0@'.format(apiversion), + 'ges-validate.c', 'ges-launch.c', 'ges-launcher.c', 'utils.c', 'ges-launcher-kb.c', + c_args : [ges_tool_args], + dependencies : deps, + install: true +) + +install_man('ges-launch-1.0.1') + +# bash completion +bashcomp_option = get_option('bash-completion') +bashcomp_dep = dependency('bash-completion', version : '>= 2.0', required : bashcomp_option) +bash_completions_dir = '' +bash_helpers_dir = '' + +bashcomp_found = false +if bashcomp_dep.found() + bashcomp_found = true + bashcomp_dir_override = bashcomp_dep.version().version_compare('>= 2.10') ? ['datadir', datadir] : ['prefix', prefix] + bash_completions_dir = bashcomp_dep.get_pkgconfig_variable('completionsdir', define_variable: bashcomp_dir_override) + if bash_completions_dir == '' + msg = 'Found bash-completion but the .pc file did not set \'completionsdir\'.' + if bashcomp_option.enabled() + error(msg) + else + message(msg) + endif + bashcomp_found = false + endif + + bash_helpers_dir = bashcomp_dep.get_pkgconfig_variable('helpersdir', define_variable: bashcomp_dir_override) + if bash_helpers_dir == '' + msg = 'Found bash-completion, but the .pc file did not set \'helpersdir\'.' + if bashcomp_option.enabled() + error(msg) + else + message(msg) + endif + bashcomp_found = false + endif + + if bashcomp_found + install_data('../data/completions/ges-launch-1.0', install_dir : bash_completions_dir) + endif +endif diff --git a/tools/utils.c b/tools/utils.c new file mode 100644 index 0000000000..9b368fe81e --- /dev/null +++ b/tools/utils.c @@ -0,0 +1,494 @@ +/* GStreamer Editing Services + * Copyright (C) 2015 Mathieu Duponchelle <mathieu.duponchelle@opencreed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <stdlib.h> +#include <glib/gprintf.h> +#include <string.h> +#include <gst/gst.h> +#include "utils.h" +#include "../ges/ges-internal.h" + +#undef GST_CAT_DEFAULT + +/* Copy of GST_ASCII_IS_STRING */ +#define ASCII_IS_STRING(c) (g_ascii_isalnum((c)) || ((c) == '_') || \ + ((c) == '-') || ((c) == '+') || ((c) == '/') || ((c) == ':') || \ + ((c) == '.')) + +/* g_free after usage */ +static gchar * +_sanitize_argument (gchar * arg, const gchar * prev_arg) +{ + gboolean expect_equal = !(arg[0] == '+' || g_str_has_prefix (arg, "set-") + || prev_arg == NULL || prev_arg[0] == '+' + || g_str_has_prefix (prev_arg, "set-")); + gboolean need_wrap = FALSE; + gchar *first_equal = NULL; + gchar *wrap_start; + gchar *new_string, *tmp_string; + gsize num_escape; + + for (tmp_string = arg; *tmp_string != '\0'; tmp_string++) { + if (expect_equal && first_equal == NULL && *tmp_string == '=') { + first_equal = tmp_string; + /* if this is the first equal, then don't count it as necessarily + * needing a wrap */ + } else if (!ASCII_IS_STRING (*tmp_string)) { + need_wrap = TRUE; + break; + } + } + + if (!need_wrap) + return g_strdup (arg); + + if (first_equal) + wrap_start = first_equal + 1; + else + wrap_start = arg; + + /* need to escape any '"' or '\\' to correctly parse in as a structure */ + num_escape = 0; + for (tmp_string = wrap_start; *tmp_string != '\0'; tmp_string++) { + if (*tmp_string == '"' || *tmp_string == '\\') + num_escape++; + } + + tmp_string = new_string = + g_malloc (sizeof (gchar) * (strlen (arg) + num_escape + 3)); + + while (arg != wrap_start) + *(tmp_string++) = *(arg++); + (*tmp_string++) = '"'; + + while (*arg != '\0') { + if (*arg == '"' || *arg == '\\') + (*tmp_string++) = '\\'; + *(tmp_string++) = *(arg++); + } + *(tmp_string++) = '"'; + *tmp_string = '\0'; + + return new_string; +} + +gchar * +sanitize_timeline_description (gchar ** args, GESLauncherParsedOptions * opts) +{ + gint i; + gchar *prev_arg = NULL; + GString *track_def; + GString *timeline_str; + + gchar *string = g_strdup (" "); + + for (i = 1; args[i]; i++) { + gchar *new_string; + gchar *sanitized = _sanitize_argument (args[i], prev_arg); + + new_string = g_strconcat (string, " ", sanitized, NULL); + + g_free (sanitized); + g_free (string); + string = new_string; + prev_arg = args[i]; + } + + if (i == 1) { + g_free (string); + + return NULL; + } + + if (strstr (string, "+track")) { + gchar *res = g_strconcat ("ges:", string, NULL); + g_free (string); + + return res; + } + + timeline_str = g_string_new (string); + g_free (string); + + if (opts->track_types & GES_TRACK_TYPE_VIDEO) { + track_def = g_string_new (" +track video "); + + if (opts->video_track_caps) + g_string_append_printf (track_def, " restrictions=[%s] ", + opts->video_track_caps); + + g_string_prepend (timeline_str, track_def->str); + g_string_free (track_def, TRUE); + } + + if (opts->track_types & GES_TRACK_TYPE_AUDIO) { + track_def = g_string_new (" +track audio "); + + if (opts->audio_track_caps) + g_string_append_printf (track_def, " restrictions=[%s] ", + opts->audio_track_caps); + + g_string_prepend (timeline_str, track_def->str); + g_string_free (track_def, TRUE); + } + + g_string_prepend (timeline_str, "ges:"); + + return g_string_free (timeline_str, FALSE); +} + +gboolean +get_flags_from_string (GType type, const gchar * str_flags, guint * flags) +{ + GValue value = G_VALUE_INIT; + g_value_init (&value, type); + + if (!gst_value_deserialize (&value, str_flags)) { + g_value_unset (&value); + + return FALSE; + } + + *flags = g_value_get_flags (&value); + g_value_unset (&value); + + return TRUE; +} + +gchar * +ensure_uri (const gchar * location) +{ + if (gst_uri_is_valid (location)) + return g_strdup (location); + else + return gst_filename_to_uri (location, NULL); +} + +GstEncodingProfile * +parse_encoding_profile (const gchar * format) +{ + GstEncodingProfile *profile; + GValue value = G_VALUE_INIT; + + g_value_init (&value, GST_TYPE_ENCODING_PROFILE); + + if (!gst_value_deserialize (&value, format)) { + g_value_reset (&value); + + return NULL; + } + + profile = g_value_dup_object (&value); + g_value_reset (&value); + + return profile; +} + +void +print_enum (GType enum_type) +{ + GEnumClass *enum_class = G_ENUM_CLASS (g_type_class_ref (enum_type)); + guint i; + + for (i = 0; i < enum_class->n_values; i++) { + gst_print ("%s\n", enum_class->values[i].value_nick); + } + + g_type_class_unref (enum_class); +} + +void +ges_print (GstDebugColorFlags c, gboolean err, gboolean nline, + const gchar * format, va_list var_args) +{ + GString *str = g_string_new (NULL); + GstDebugColorMode color_mode; + gchar *color = NULL; + const gchar *clear = NULL; + + color_mode = gst_debug_get_color_mode (); +#ifdef G_OS_WIN32 + if (color_mode == GST_DEBUG_COLOR_MODE_UNIX) { +#else + if (color_mode != GST_DEBUG_COLOR_MODE_OFF) { +#endif + clear = "\033[00m"; + color = gst_debug_construct_term_color (c); + } + + if (color) { + g_string_append (str, color); + g_free (color); + } + + g_string_append_vprintf (str, format, var_args); + + if (nline) + g_string_append_c (str, '\n'); + + if (clear) + g_string_append (str, clear); + + if (err) + gst_printerr ("%s", str->str); + else + gst_print ("%s", str->str); + + g_string_free (str, TRUE); +} + +void +ges_ok (const gchar * format, ...) +{ + va_list var_args; + + va_start (var_args, format); + ges_print (GST_DEBUG_FG_GREEN, FALSE, TRUE, format, var_args); + va_end (var_args); +} + +void +ges_warn (const gchar * format, ...) +{ + va_list var_args; + + va_start (var_args, format); + ges_print (GST_DEBUG_FG_YELLOW, TRUE, TRUE, format, var_args); + va_end (var_args); +} + +void +ges_printerr (const gchar * format, ...) +{ + va_list var_args; + + va_start (var_args, format); + ges_print (GST_DEBUG_FG_RED, TRUE, TRUE, format, var_args); + va_end (var_args); +} + +gchar * +get_file_extension (gchar * uri) +{ + size_t len; + gint find; + + len = strlen (uri); + find = len - 1; + + while (find >= 0) { + if (uri[find] == '.') + break; + find--; + } + + if (find <= 0) + return NULL; + + return g_strdup (&uri[find + 1]); +} + +static const gchar * +get_type_icon (gpointer obj) +{ + if (GST_IS_ENCODING_AUDIO_PROFILE (obj) || GST_IS_DISCOVERER_AUDIO_INFO (obj)) + return "♫"; + else if (GST_IS_ENCODING_VIDEO_PROFILE (obj) + || GST_IS_DISCOVERER_VIDEO_INFO (obj)) + return "▶"; + else if (GST_IS_ENCODING_CONTAINER_PROFILE (obj) + || GST_IS_DISCOVERER_CONTAINER_INFO (obj)) + return "∋"; + else + return ""; +} + +static void +print_profile (GstEncodingProfile * profile, const gchar * prefix) +{ + const gchar *name = gst_encoding_profile_get_name (profile); + const gchar *desc = gst_encoding_profile_get_description (profile); + GstCaps *format = gst_encoding_profile_get_format (profile); + gchar *capsdesc = NULL; + + if (gst_caps_is_fixed (format)) + capsdesc = gst_pb_utils_get_codec_description (format); + if (!capsdesc) + capsdesc = gst_caps_to_string (format); + + if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) { + gst_print ("%s> %s %s: %s%s%s%s\n", prefix, + get_type_icon (profile), + capsdesc, name ? name : "", + desc ? " (" : "", desc ? desc : "", desc ? ")" : ""); + + } else { + gst_print ("%s%s %s%s%s%s%s%s", prefix, get_type_icon (profile), + name ? name : capsdesc, desc ? ": " : "", desc ? desc : "", + name ? " (" : "", name ? capsdesc : "", name ? ")" : ""); + + if (GST_IS_ENCODING_VIDEO_PROFILE (profile)) { + GstCaps *caps = gst_encoding_profile_get_restriction (profile); + + if (!caps && gst_caps_is_fixed (format)) + caps = gst_caps_ref (format); + + if (caps) { + GstVideoInfo info; + + if (gst_video_info_from_caps (&info, caps)) { + gst_print (" (%dx%d", info.width, info.height); + if (info.fps_n) + gst_print ("@%d/%dfps", info.fps_n, info.fps_d); + gst_print (")"); + } + gst_caps_unref (caps); + } + } else if (GST_IS_ENCODING_AUDIO_PROFILE (profile)) { + GstCaps *caps = gst_encoding_profile_get_restriction (profile); + + if (!caps && gst_caps_is_fixed (format)) + caps = gst_caps_ref (format); + + if (caps) { + GstAudioInfo info; + + if (gst_caps_is_fixed (caps) && gst_audio_info_from_caps (&info, caps)) + gst_print (" (%d channels @ %dhz)", info.channels, info.rate); + gst_caps_unref (caps); + } + } + + + gst_print ("\n"); + } + + gst_caps_unref (format); + + g_free (capsdesc); +} + +void +describe_encoding_profile (GstEncodingProfile * profile) +{ + g_return_if_fail (GST_IS_ENCODING_PROFILE (profile)); + + print_profile (profile, " "); + if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) { + const GList *tmp; + + for (tmp = + gst_encoding_container_profile_get_profiles + (GST_ENCODING_CONTAINER_PROFILE (profile)); tmp; tmp = tmp->next) + print_profile (tmp->data, " - "); + } +} + +static void +describe_stream_info (GstDiscovererStreamInfo * sinfo, GString * desc) +{ + gchar *capsdesc; + GstCaps *caps; + + caps = gst_discoverer_stream_info_get_caps (sinfo); + capsdesc = gst_pb_utils_get_codec_description (caps); + if (!capsdesc) + capsdesc = gst_caps_to_string (caps); + gst_caps_unref (caps); + + g_string_append_printf (desc, "%s%s%s", desc->len ? ", " : "", + get_type_icon (sinfo), capsdesc); + + if (GST_IS_DISCOVERER_CONTAINER_INFO (sinfo)) { + GList *tmp, *streams; + + streams = + gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO + (sinfo)); + for (tmp = streams; tmp; tmp = tmp->next) + describe_stream_info (tmp->data, desc); + gst_discoverer_stream_info_list_free (streams); + } +} + +static gchar * +describe_discoverer (GstDiscovererInfo * info) +{ + GString *desc = g_string_new (NULL); + GstDiscovererStreamInfo *sinfo = gst_discoverer_info_get_stream_info (info); + + describe_stream_info (sinfo, desc); + gst_discoverer_stream_info_unref (sinfo); + + return g_string_free (desc, FALSE); +} + +void +print_timeline (GESTimeline * timeline) +{ + gchar *uri; + GList *layer, *clip, *clips; + + if (!timeline->layers) + return; + + uri = ges_command_line_formatter_get_timeline_uri (timeline); + gst_print ("\nTimeline description: `%s`\n", &uri[5]); + g_free (uri); + gst_print ("====================\n\n"); + for (layer = timeline->layers; layer; layer = layer->next) { + clips = ges_layer_get_clips (layer->data); + + if (!clips) + continue; + + gst_printerr (" layer %d: \n", ges_layer_get_priority (layer->data)); + gst_printerr (" --------\n"); + for (clip = clips; clip; clip = clip->next) { + gchar *name; + + if (GES_IS_URI_CLIP (clip->data)) { + GESUriClipAsset *asset = + GES_URI_CLIP_ASSET (ges_extractable_get_asset (clip->data)); + gchar *asset_desc = + describe_discoverer (ges_uri_clip_asset_get_info (asset)); + + name = g_strdup_printf ("Clip from: '%s' [%s]", + ges_asset_get_id (GES_ASSET (asset)), asset_desc); + g_free (asset_desc); + } else { + name = g_strdup (GES_TIMELINE_ELEMENT_NAME (clip->data)); + } + gst_print (" - %s\n start=%" GST_TIME_FORMAT, + name, GST_TIME_ARGS (GES_TIMELINE_ELEMENT_START (clip->data))); + g_free (name); + if (GES_TIMELINE_ELEMENT_INPOINT (clip->data)) + gst_print (" inpoint=%" GST_TIME_FORMAT, + GST_TIME_ARGS (GES_TIMELINE_ELEMENT_INPOINT (clip->data))); + gst_print (" duration=%" GST_TIME_FORMAT "\n", + GST_TIME_ARGS (GES_TIMELINE_ELEMENT_END (clip->data))); + } + if (layer->next) + gst_printerr ("\n"); + + g_list_free_full (clips, gst_object_unref); + } + + gst_print ("\n"); +} diff --git a/tools/utils.h b/tools/utils.h new file mode 100644 index 0000000000..09ba6f2781 --- /dev/null +++ b/tools/utils.h @@ -0,0 +1,70 @@ +/* GStreamer Editing Services + * Copyright (C) 2015 Mathieu Duponchelle <mathieu.duponchelle@opencreed.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <ges/ges.h> +#include <gst/pbutils/pbutils.h> +#include <gst/pbutils/encoding-profile.h> + +#pragma once + +typedef struct +{ + gboolean mute; + gboolean disable_mixing; + gchar *save_path; + gchar *save_only_path; + gchar *load_path; + GESTrackType track_types; + gboolean needs_set_state; + gboolean smartrender; + gchar *scenario; + gchar *testfile; + gchar *format; + gchar *outputuri; + gchar *encoding_profile; + gchar *profile_from; + gchar *videosink; + gchar *audiosink; + gboolean list_transitions; + gboolean inspect_action_type; + gchar *sanitized_timeline; + gchar *video_track_caps; + gchar *audio_track_caps; + gboolean embed_nesteds; + gboolean enable_validate; + + gboolean ignore_eos; + gboolean interactive; + gboolean forward_tags; +} GESLauncherParsedOptions; + +gchar * sanitize_timeline_description (gchar **args, GESLauncherParsedOptions *opts); +gboolean get_flags_from_string (GType type, const gchar * str_flags, guint *val); +gchar * ensure_uri (const gchar * location); +GstEncodingProfile * parse_encoding_profile (const gchar * format); +void print_enum (GType enum_type); + +void ges_print (GstDebugColorFlags c, gboolean err, gboolean nline, const gchar * format, va_list var_args); +void ges_ok (const gchar * format, ...); +void ges_warn (const gchar * format, ...); +void ges_printerr (const gchar * format, ...); + +gchar * get_file_extension (gchar * uri); +void describe_encoding_profile (GstEncodingProfile *profile); +void print_timeline(GESTimeline *timeline);